mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-01-14 01:25:37 -05:00
Adds autorest-based SQL Project generation to SQL Database Projects extension (#17078)
* Initial changes * checkpoint * Constructing project with post deployment script * Correcting to intentionally read from cached list of projects * Adding activation event, fixing fresh workspace bug * Convert netcoreTool and autorestHelper to share a helper class for streamed command * Include npm package version to force update * test checkpoint * Unit tests * Added contextual quickpicks for autorest dialogs * Adding projectController test * Added projectController test, some refactoring for testability * Merge branch 'main' into benjin/autorest * Fixing 'which' import * PR feedback * Fixing tests * Adding additional information for when project provider tests fail * Hopefully fixing failing tests (unable to repro locally) * Adding Generate Project item to workspace menu * PR feedback
This commit is contained in:
@@ -24,7 +24,8 @@ import { IDeploySettings } from '../models/IDeploySettings';
|
||||
import { BaseProjectTreeItem } from '../models/tree/baseTreeItem';
|
||||
import { ProjectRootTreeItem } from '../models/tree/projectTreeItem';
|
||||
import { ImportDataModel } from '../models/api/import';
|
||||
import { NetCoreTool, DotNetCommandOptions, DotNetError } from '../tools/netcoreTool';
|
||||
import { NetCoreTool, DotNetError } from '../tools/netcoreTool';
|
||||
import { ShellCommandOptions } from '../tools/shellExecutionHelper';
|
||||
import { BuildHelper } from '../tools/buildHelper';
|
||||
import { readPublishProfile } from '../models/publishProfile/publishProfile';
|
||||
import { AddDatabaseReferenceDialog } from '../dialogs/addDatabaseReferenceDialog';
|
||||
@@ -38,6 +39,7 @@ import { launchPublishDatabaseQuickpick } from '../dialogs/publishDatabaseQuickp
|
||||
import { launchPublishToDockerContainerQuickpick } from '../dialogs/deployDatabaseQuickpick';
|
||||
import { DeployService } from '../models/deploy/deployService';
|
||||
import { SqlTargetPlatform } from 'sqldbproj';
|
||||
import { AutorestHelper } from '../tools/autorestHelper';
|
||||
import { createNewProjectFromDatabaseWithQuickpick } from '../dialogs/createProjectFromDatabaseQuickpick';
|
||||
import { addDatabaseReferenceQuickpick } from '../dialogs/addDatabaseReferenceQuickpick';
|
||||
|
||||
@@ -67,6 +69,7 @@ export class ProjectsController {
|
||||
private buildInfo: DashboardData[] = [];
|
||||
private publishInfo: PublishData[] = [];
|
||||
private deployService: DeployService;
|
||||
private autorestHelper: AutorestHelper;
|
||||
|
||||
projFileWatchers = new Map<string, vscode.FileSystemWatcher>();
|
||||
|
||||
@@ -74,6 +77,7 @@ export class ProjectsController {
|
||||
this.netCoreTool = new NetCoreTool(outputChannel);
|
||||
this.buildHelper = new BuildHelper();
|
||||
this.deployService = new DeployService(outputChannel);
|
||||
this.autorestHelper = new AutorestHelper(outputChannel);
|
||||
}
|
||||
|
||||
public getDashboardPublishData(projectFile: string): (string | dataworkspace.IconCellValue)[][] {
|
||||
@@ -215,7 +219,7 @@ export class ProjectsController {
|
||||
// Check mssql extension for project dlls (tracking issue #10273)
|
||||
await this.buildHelper.createBuildDirFolder();
|
||||
|
||||
const options: DotNetCommandOptions = {
|
||||
const options: ShellCommandOptions = {
|
||||
commandTitle: 'Build',
|
||||
workingDirectory: project.projectFolderPath,
|
||||
argument: this.buildHelper.constructBuildArguments(project.projectFilePath, this.buildHelper.extensionBuildDirPath)
|
||||
@@ -827,6 +831,171 @@ export class ProjectsController {
|
||||
return result;
|
||||
}
|
||||
|
||||
public async selectAutorestSpecFile(): Promise<string | undefined> {
|
||||
let quickpickSelection = await vscode.window.showQuickPick(
|
||||
[constants.browseEllipsis],
|
||||
{ title: constants.selectSpecFile, ignoreFocusOut: true });
|
||||
if (!quickpickSelection) {
|
||||
return;
|
||||
}
|
||||
|
||||
const filters: { [name: string]: string[] } = {};
|
||||
filters['OpenAPI/Swagger spec'] = ['yaml'];
|
||||
|
||||
let uris = await vscode.window.showOpenDialog({
|
||||
canSelectFiles: true,
|
||||
canSelectFolders: false,
|
||||
canSelectMany: false,
|
||||
openLabel: constants.selectString,
|
||||
filters: filters,
|
||||
title: constants.selectSpecFile
|
||||
});
|
||||
|
||||
if (!uris) {
|
||||
return;
|
||||
}
|
||||
|
||||
return uris[0].fsPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns \{ newProjectFolder: 'C:\Source\MyProject',
|
||||
* outputFolder: 'C:\Source',
|
||||
* projectName: 'MyProject'}
|
||||
*/
|
||||
public async selectAutorestProjectLocation(specPath: string): Promise<{ newProjectFolder: string, outputFolder: string, projectName: string } | undefined> {
|
||||
let valid = false;
|
||||
let newProjectFolder: string = '';
|
||||
let outputFolder: string = '';
|
||||
let projectName: string = '';
|
||||
|
||||
let quickpickSelection = await vscode.window.showQuickPick(
|
||||
[constants.browseEllipsis],
|
||||
{ title: constants.selectProjectLocation, ignoreFocusOut: true });
|
||||
if (!quickpickSelection) {
|
||||
return;
|
||||
}
|
||||
|
||||
while (!valid) {
|
||||
const folders = await vscode.window.showOpenDialog({
|
||||
canSelectFiles: false,
|
||||
canSelectFolders: true,
|
||||
canSelectMany: false,
|
||||
openLabel: constants.selectString,
|
||||
defaultUri: vscode.workspace.workspaceFolders?.[0]?.uri,
|
||||
title: constants.selectProjectLocation
|
||||
});
|
||||
|
||||
if (!folders) {
|
||||
return;
|
||||
}
|
||||
|
||||
outputFolder = folders[0].fsPath;
|
||||
projectName = path.basename(specPath, constants.yamlFileExtension);
|
||||
newProjectFolder = path.join(outputFolder, projectName);
|
||||
|
||||
if (await utils.exists(newProjectFolder)) {
|
||||
|
||||
quickpickSelection = await vscode.window.showQuickPick(
|
||||
[constants.browseEllipsis],
|
||||
{ title: constants.folderAlreadyExistsChooseNewLocation(newProjectFolder), ignoreFocusOut: true });
|
||||
if (!quickpickSelection) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
valid = true;
|
||||
}
|
||||
}
|
||||
|
||||
await fs.mkdir(newProjectFolder);
|
||||
return { newProjectFolder, outputFolder, projectName };
|
||||
}
|
||||
|
||||
public async generateAutorestFiles(specPath: string, newProjectFolder: string): Promise<void> {
|
||||
await this.autorestHelper.generateAutorestFiles(specPath, newProjectFolder);
|
||||
}
|
||||
|
||||
public async openProjectInWorkspace(projectFilePath: string): Promise<void> {
|
||||
const workspaceApi = utils.getDataWorkspaceExtensionApi();
|
||||
await workspaceApi.validateWorkspace();
|
||||
await workspaceApi.addProjectsToWorkspace([vscode.Uri.file(projectFilePath)]);
|
||||
|
||||
workspaceApi.showProjectsView();
|
||||
}
|
||||
|
||||
public async generateProjectFromOpenApiSpec(): Promise<Project | undefined> {
|
||||
try {
|
||||
// 1. select spec file
|
||||
const specPath: string | undefined = await this.selectAutorestSpecFile();
|
||||
if (!specPath) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. select location, make new folder
|
||||
const projectInfo = await this.selectAutorestProjectLocation(specPath!);
|
||||
if (!projectInfo) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 3. run AutoRest to generate .sql files
|
||||
await this.generateAutorestFiles(specPath, projectInfo.newProjectFolder);
|
||||
|
||||
// 4. create new SQL project
|
||||
const newProjFilePath = await this.createNewProject({
|
||||
newProjName: projectInfo.projectName,
|
||||
folderUri: vscode.Uri.file(projectInfo.outputFolder),
|
||||
projectTypeId: constants.emptySqlDatabaseProjectTypeId
|
||||
});
|
||||
|
||||
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);
|
||||
|
||||
if (postDeploymentScript) {
|
||||
await project.addScriptItem(path.relative(project.projectFolderPath, postDeploymentScript.fsPath), undefined, templates.postDeployScript);
|
||||
}
|
||||
|
||||
// 6. add project to workspace and open
|
||||
await this.openProjectInWorkspace(newProjFilePath);
|
||||
|
||||
return project;
|
||||
} catch (err) {
|
||||
void vscode.window.showErrorMessage(`${constants.generatingProjectFailed}: ${utils.getErrorMessage(err)}`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private findPostDeploymentScript(files: vscode.Uri[]): vscode.Uri | undefined {
|
||||
const results = files.filter(f => f.fsPath.endsWith(constants.autorestPostDeploymentScriptName));
|
||||
|
||||
switch (results.length) {
|
||||
case 0:
|
||||
return undefined;
|
||||
case 1:
|
||||
return results[0];
|
||||
default:
|
||||
throw new Error(constants.multipleMostDeploymentScripts(results.length));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private async getSqlFileList(folder: string): Promise<vscode.Uri[]> {
|
||||
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)));
|
||||
|
||||
for (const folder of folders) {
|
||||
files.push(...await this.getSqlFileList(folder));
|
||||
}
|
||||
|
||||
return files;
|
||||
}
|
||||
|
||||
//#region Helper methods
|
||||
|
||||
public getPublishDialog(project: Project): PublishDatabaseDialog {
|
||||
|
||||
Reference in New Issue
Block a user