mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-01-17 01:25:36 -05:00
Prompts user for desired action when autorest is not installed (#17305)
* Giving user option to install autorest globally or run via npx * Adds message for when no files are generated (no models in spec) * Adding test * Reinstating the rest of the test suite * PR feedback * Fixing tests to stub new prompt when test runner doesn't have autorest installed * PR feedback * fix typo * fix typo
This commit is contained in:
@@ -423,9 +423,14 @@ export enum DatabaseProjectItemType {
|
||||
// AutoRest
|
||||
export const autorestPostDeploymentScriptName = 'PostDeploymentScript.sql';
|
||||
export const nodeButNotAutorestFound = localize('nodeButNotAutorestFound', "Autorest tool not found in system path, but found Node.js. Running via npx. Please execute 'npm install autorest -g' to install permanently.");
|
||||
export const nodeNotFound = localize('nodeNotFound', "Neither autorest nor Node.js (npx) found in system path. Please install Node.js for autorest generation to work.");
|
||||
export const nodeNotFound = localize('nodeNotFound', "Neither Autorest nor Node.js (npx) found in system path. Please install Node.js for Autorest generation to work.");
|
||||
export const nodeButNotAutorestFoundPrompt = localize('nodeButNotAutorestFoundPrompt', "Autorest is not installed. To proceed, choose whether to run Autorest from a temporary location via 'npx' or install Autorest globally then run.");
|
||||
export const installGlobally = localize('installGlobally', "Install globally");
|
||||
export const runViaNpx = localize('runViaNpx', "Run via npx");
|
||||
|
||||
export const selectSpecFile = localize('selectSpecFile', "Select OpenAPI/Swagger spec file");
|
||||
export function generatingProjectFailed(errorMessage: string) { return localize('generatingProjectFailed', "Generating project via AutoRest failed. Check output pane for more details. Error: {0}", errorMessage); }
|
||||
export const noSqlFilesGenerated = localize('noSqlFilesGenerated', "No .sql files were generated by Autorest. Please confirm that your spec contains model definitions, or check the output log for details.");
|
||||
export function multipleMostDeploymentScripts(count: number) { return localize('multipleMostDeploymentScripts', "Unexpected number of {0} files: {1}", autorestPostDeploymentScriptName, count); }
|
||||
export const specSelectionText = localize('specSelectionText', "OpenAPI/Swagger spec");
|
||||
|
||||
|
||||
@@ -913,8 +913,8 @@ export class ProjectsController {
|
||||
return { newProjectFolder, outputFolder, projectName };
|
||||
}
|
||||
|
||||
public async generateAutorestFiles(specPath: string, newProjectFolder: string): Promise<void> {
|
||||
await this.autorestHelper.generateAutorestFiles(specPath, newProjectFolder);
|
||||
public async generateAutorestFiles(specPath: string, newProjectFolder: string): Promise<string | undefined> {
|
||||
return this.autorestHelper.generateAutorestFiles(specPath, newProjectFolder);
|
||||
}
|
||||
|
||||
public async openProjectInWorkspace(projectFilePath: string): Promise<void> {
|
||||
@@ -940,7 +940,18 @@ export class ProjectsController {
|
||||
}
|
||||
|
||||
// 3. run AutoRest to generate .sql files
|
||||
await this.generateAutorestFiles(specPath, projectInfo.newProjectFolder);
|
||||
const result = await this.generateAutorestFiles(specPath, projectInfo.newProjectFolder);
|
||||
if (!result) { // user canceled operation when choosing how to run autorest
|
||||
return;
|
||||
}
|
||||
|
||||
const fileFolderList: vscode.Uri[] | undefined = await this.getSqlFileList(projectInfo.newProjectFolder);
|
||||
|
||||
if (!fileFolderList || fileFolderList.length === 0) {
|
||||
void vscode.window.showInformationMessage(constants.noSqlFilesGenerated);
|
||||
this._outputChannel.show();
|
||||
return;
|
||||
}
|
||||
|
||||
// 4. create new SQL project
|
||||
const newProjFilePath = await this.createNewProject({
|
||||
@@ -952,7 +963,6 @@ export class ProjectsController {
|
||||
const project = await Project.openProject(newProjFilePath);
|
||||
|
||||
// 5. add generated files to SQL project
|
||||
let fileFolderList: vscode.Uri[] = await this.getSqlFileList(project.projectFolderPath);
|
||||
await project.addToProject(fileFolderList.filter(f => !f.fsPath.endsWith(constants.autorestPostDeploymentScriptName))); // Add generated file structure to the project
|
||||
|
||||
const postDeploymentScript: vscode.Uri | undefined = this.findPostDeploymentScript(fileFolderList);
|
||||
@@ -987,17 +997,20 @@ export class ProjectsController {
|
||||
default:
|
||||
throw new Error(constants.multipleMostDeploymentScripts(results.length));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private async getSqlFileList(folder: string): Promise<vscode.Uri[]> {
|
||||
private async getSqlFileList(folder: string): Promise<vscode.Uri[] | undefined> {
|
||||
if (!(await utils.exists(folder))) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const entries = await fs.readdir(folder, { withFileTypes: true });
|
||||
|
||||
const folders = entries.filter(dir => dir.isDirectory()).map(dir => path.join(folder, dir.name));
|
||||
const files = entries.filter(file => !file.isDirectory() && path.extname(file.name) === '.sql').map(file => vscode.Uri.file(path.join(folder, file.name)));
|
||||
const files = entries.filter(file => !file.isDirectory() && path.extname(file.name) === constants.sqlFileExtension).map(file => vscode.Uri.file(path.join(folder, file.name)));
|
||||
|
||||
for (const folder of folders) {
|
||||
files.push(...await this.getSqlFileList(folder));
|
||||
files.push(...(await this.getSqlFileList(folder) ?? []));
|
||||
}
|
||||
|
||||
return files;
|
||||
|
||||
@@ -11,6 +11,8 @@ import * as path from 'path';
|
||||
import { TestContext, createContext } from './testContext';
|
||||
import { AutorestHelper } from '../tools/autorestHelper';
|
||||
import { promises as fs } from 'fs';
|
||||
import { window } from 'vscode';
|
||||
import { runViaNpx } from '../common/constants';
|
||||
|
||||
let testContext: TestContext;
|
||||
|
||||
@@ -24,12 +26,16 @@ describe('Autorest tests', function (): void {
|
||||
});
|
||||
|
||||
it('Should detect autorest', async function (): Promise<void> {
|
||||
sinon.stub(window, 'showInformationMessage').returns(<any>Promise.resolve(runViaNpx)); // stub a selection in case test runner doesn't have autorest installed
|
||||
|
||||
const autorestHelper = new AutorestHelper(testContext.outputChannel);
|
||||
const executable = await autorestHelper.detectInstallation();
|
||||
should(executable === 'autorest' || executable === 'npx autorest').equal(true, 'autorest command should be found in default path during unit tests');
|
||||
});
|
||||
|
||||
it('Should run an autorest command successfully', async function (): Promise<void> {
|
||||
sinon.stub(window, 'showInformationMessage').returns(<any>Promise.resolve(runViaNpx)); // stub a selection in case test runner doesn't have autorest installed
|
||||
|
||||
const autorestHelper = new AutorestHelper(testContext.outputChannel);
|
||||
const dummyFile = path.join(await testUtils.generateTestFolderPath(), 'testoutput.log');
|
||||
sinon.stub(autorestHelper, 'constructAutorestCommand').returns(`${await autorestHelper.detectInstallation()} --version > ${dummyFile}`);
|
||||
@@ -47,12 +53,27 @@ describe('Autorest tests', function (): void {
|
||||
});
|
||||
|
||||
it('Should construct a correct autorest command for project generation', async function (): Promise<void> {
|
||||
const autorestHelper = new AutorestHelper(testContext.outputChannel);
|
||||
sinon.stub(window, 'showInformationMessage').returns(<any>Promise.resolve(runViaNpx)); // stub a selection in case test runner doesn't have autorest installed
|
||||
sinon.stub(autorestHelper, 'detectInstallation').returns(Promise.resolve('autorest'));
|
||||
|
||||
const expectedOutput = 'autorest --use:autorest-sql-testing@latest --input-file="/some/path/test.yaml" --output-folder="/some/output/path" --clear-output-folder --verbose';
|
||||
|
||||
const autorestHelper = new AutorestHelper(testContext.outputChannel);
|
||||
const constructedCommand = autorestHelper.constructAutorestCommand((await autorestHelper.detectInstallation())!, '/some/path/test.yaml', '/some/output/path');
|
||||
|
||||
// depending on whether the machine running the test has autorest installed or just node, the expected output may differ by just the prefix, hence matching against two options
|
||||
should(constructedCommand === expectedOutput || constructedCommand === `npx ${expectedOutput}`).equal(true, `Constructed autorest command not formatting as expected:\nActual: ${constructedCommand}\nExpected: [npx ]${expectedOutput}`);
|
||||
should(constructedCommand === expectedOutput).equal(true, `Constructed autorest command not formatting as expected:\nActual:\n\t${constructedCommand}\nExpected:\n\t${expectedOutput}`);
|
||||
});
|
||||
|
||||
it('Should prompt user for action when autorest not found', async function (): Promise<void> {
|
||||
const promptStub = sinon.stub(window, 'showInformationMessage').returns(<any>Promise.resolve());
|
||||
const detectStub = sinon.stub(utils, 'detectCommandInstallation');
|
||||
detectStub.withArgs('autorest').returns(Promise.resolve(false));
|
||||
detectStub.withArgs('npx').returns(Promise.resolve(true));
|
||||
|
||||
const autorestHelper = new AutorestHelper(testContext.outputChannel);
|
||||
await autorestHelper.detectInstallation();
|
||||
|
||||
should(promptStub.calledOnce).be.true('User should have been prompted for how to run autorest because it wasn\'t found.');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -704,6 +704,7 @@ describe('ProjectsController', function (): void {
|
||||
projController.setup(x => x.generateAutorestFiles(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(async () => {
|
||||
await testUtils.createDummyFileStructure(true, fileList, newProjFolder);
|
||||
await testUtils.createTestFile('SELECT \'This is a post-deployment script\'', constants.autorestPostDeploymentScriptName, newProjFolder);
|
||||
return 'some dummy console output';
|
||||
});
|
||||
|
||||
projController.setup(x => x.openProjectInWorkspace(TypeMoq.It.isAny())).returns(async () => { });
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import { DoNotAskAgain, Install, nodeButNotAutorestFound, nodeNotFound } from '../common/constants';
|
||||
import { DoNotAskAgain, Install, nodeButNotAutorestFound, nodeNotFound, nodeButNotAutorestFoundPrompt, runViaNpx, installGlobally } from '../common/constants';
|
||||
import * as utils from '../common/utils';
|
||||
import * as semver from 'semver';
|
||||
import { DBProjectConfigurationKey } from './netcoreTool';
|
||||
@@ -48,7 +48,15 @@ export class AutorestHelper extends ShellExecutionHelper {
|
||||
|
||||
if (await utils.detectCommandInstallation(npxCommand)) {
|
||||
this._outputChannel.appendLine(nodeButNotAutorestFound);
|
||||
return `${npxCommand} ${autorestCommand}`;
|
||||
|
||||
const response = await vscode.window.showInformationMessage(nodeButNotAutorestFoundPrompt, installGlobally, runViaNpx);
|
||||
|
||||
if (response === installGlobally) {
|
||||
await this.runStreamedCommand('npm install autorest -g', this._outputChannel);
|
||||
return autorestCommand;
|
||||
} else if (response === runViaNpx) {
|
||||
return `${npxCommand} ${autorestCommand}`;
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
@@ -63,7 +71,7 @@ export class AutorestHelper extends ShellExecutionHelper {
|
||||
public async generateAutorestFiles(specPath: string, outputFolder: string): Promise<string | undefined> {
|
||||
const commandExecutable = await this.detectInstallation();
|
||||
|
||||
if (commandExecutable === undefined) {
|
||||
if (!commandExecutable) {
|
||||
// unable to find autorest or npx
|
||||
|
||||
if (vscode.workspace.getConfiguration(DBProjectConfigurationKey)[nodejsDoNotAskAgainKey] !== true) {
|
||||
|
||||
Reference in New Issue
Block a user