mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-01-25 09:35:37 -05:00
Add publish profile to sql proj and tree (#22008)
* Read publish profiles stored in sqlproj file and present it in the projects tree * Save publish profile and add it to sqlproj file, and present it in the tree * Fix context menu operations * Add tests * Address comments
This commit is contained in:
@@ -46,6 +46,7 @@ export class Project implements ISqlProject {
|
||||
private _isSdkStyleProject: boolean = false; // https://docs.microsoft.com/en-us/dotnet/core/project-sdk/overview
|
||||
private _outputPath: string = '';
|
||||
private _configuration: Configuration = Configuration.Debug;
|
||||
private _publishProfiles: FileProjectEntry[] = [];
|
||||
|
||||
public get dacpacOutputPath(): string {
|
||||
return path.join(this.outputPath, `${this._projectFileName}.dacpac`);
|
||||
@@ -111,6 +112,10 @@ export class Project implements ISqlProject {
|
||||
return this._configuration;
|
||||
}
|
||||
|
||||
public get publishProfiles(): FileProjectEntry[] {
|
||||
return this._publishProfiles;
|
||||
}
|
||||
|
||||
private projFileXmlDoc: Document | undefined = undefined;
|
||||
|
||||
constructor(projectFilePath: string) {
|
||||
@@ -153,6 +158,9 @@ export class Project implements ISqlProject {
|
||||
this._databaseReferences = this.readDatabaseReferences();
|
||||
this._importedTargets = this.readImportedTargets();
|
||||
|
||||
// get publish profiles specified in the sqlproj
|
||||
this._publishProfiles = this.readPublishProfiles();
|
||||
|
||||
// find all SQLCMD variables to include
|
||||
try {
|
||||
this._sqlCmdVariables = utils.readSqlCmdVariables(this.projFileXmlDoc, false);
|
||||
@@ -468,7 +476,7 @@ export class Project implements ISqlProject {
|
||||
const noneItems = itemGroup.getElementsByTagName(constants.None);
|
||||
for (let n = 0; n < noneItems.length; n++) {
|
||||
const includeAttribute = noneItems[n].getAttribute(constants.Include);
|
||||
if (includeAttribute) {
|
||||
if (includeAttribute && !utils.isPublishProfile(includeAttribute)) {
|
||||
noneDeployScripts.push(this.createFileProjectEntry(includeAttribute, EntryType.File));
|
||||
}
|
||||
}
|
||||
@@ -505,6 +513,34 @@ export class Project implements ISqlProject {
|
||||
return noneRemoveScripts;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @returns all the publish profiles (ending with *.publish.xml) specified as <None Include="file.publish.xml" /> in the sqlproj
|
||||
*/
|
||||
private readPublishProfiles(): FileProjectEntry[] {
|
||||
const publishProfiles: FileProjectEntry[] = [];
|
||||
|
||||
for (let ig = 0; ig < this.projFileXmlDoc!.documentElement.getElementsByTagName(constants.ItemGroup).length; ig++) {
|
||||
const itemGroup = this.projFileXmlDoc!.documentElement.getElementsByTagName(constants.ItemGroup)[ig];
|
||||
|
||||
// find all publish profile scripts to include
|
||||
try {
|
||||
const noneItems = itemGroup.getElementsByTagName(constants.None);
|
||||
for (let n = 0; n < noneItems.length; n++) {
|
||||
const includeAttribute = noneItems[n].getAttribute(constants.Include);
|
||||
if (includeAttribute && utils.isPublishProfile(includeAttribute)) {
|
||||
publishProfiles.push(this.createFileProjectEntry(includeAttribute, EntryType.File));
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
void window.showErrorMessage(constants.errorReadingProject(constants.PublishProfileElements, this.projectFilePath));
|
||||
console.error(utils.getErrorMessage(e));
|
||||
}
|
||||
}
|
||||
|
||||
return publishProfiles;
|
||||
}
|
||||
|
||||
private readDatabaseReferences(): IDatabaseReferenceProjectEntry[] {
|
||||
const databaseReferenceEntries: IDatabaseReferenceProjectEntry[] = [];
|
||||
|
||||
@@ -949,18 +985,19 @@ export class Project implements ISqlProject {
|
||||
}
|
||||
|
||||
public async exclude(entry: FileProjectEntry): Promise<void> {
|
||||
const toExclude: FileProjectEntry[] = this._files.concat(this._preDeployScripts).concat(this._postDeployScripts).concat(this._noneDeployScripts).filter(x => x.fsUri.fsPath.startsWith(entry.fsUri.fsPath));
|
||||
const toExclude: FileProjectEntry[] = this._files.concat(this._preDeployScripts).concat(this._postDeployScripts).concat(this._noneDeployScripts).concat(this._publishProfiles).filter(x => x.fsUri.fsPath.startsWith(entry.fsUri.fsPath));
|
||||
await this.removeFromProjFile(toExclude);
|
||||
|
||||
this._files = this._files.filter(x => !x.fsUri.fsPath.startsWith(entry.fsUri.fsPath));
|
||||
this._preDeployScripts = this._preDeployScripts.filter(x => !x.fsUri.fsPath.startsWith(entry.fsUri.fsPath));
|
||||
this._postDeployScripts = this._postDeployScripts.filter(x => !x.fsUri.fsPath.startsWith(entry.fsUri.fsPath));
|
||||
this._noneDeployScripts = this._noneDeployScripts.filter(x => !x.fsUri.fsPath.startsWith(entry.fsUri.fsPath));
|
||||
this._publishProfiles = this._publishProfiles.filter(x => !x.fsUri.fsPath.startsWith(entry.fsUri.fsPath));
|
||||
}
|
||||
|
||||
public async deleteFileFolder(entry: FileProjectEntry): Promise<void> {
|
||||
// compile a list of folder contents to delete; if entry is a file, contents will contain only itself
|
||||
const toDeleteFiles: FileProjectEntry[] = this._files.concat(this._preDeployScripts).concat(this._postDeployScripts).concat(this._noneDeployScripts).filter(x => x.fsUri.fsPath.startsWith(entry.fsUri.fsPath) && x.type === EntryType.File);
|
||||
const toDeleteFiles: FileProjectEntry[] = this._files.concat(this._preDeployScripts).concat(this._postDeployScripts).concat(this._noneDeployScripts).concat(this._publishProfiles).filter(x => x.fsUri.fsPath.startsWith(entry.fsUri.fsPath) && x.type === EntryType.File);
|
||||
const toDeleteFolders: FileProjectEntry[] = this._files.filter(x => x.fsUri.fsPath.startsWith(entry.fsUri.fsPath) && x.type === EntryType.Folder);
|
||||
|
||||
await Promise.all(toDeleteFiles.map(x => fs.unlink(x.fsUri.fsPath)));
|
||||
@@ -1165,6 +1202,24 @@ export class Project implements ISqlProject {
|
||||
return this.getCollectionProjectPropertyValue(constants.DatabaseSource);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds publish profile to the project
|
||||
*
|
||||
* @param relativeFilePath Relative path of the file
|
||||
*/
|
||||
public async addPublishProfileToProjFile(absolutePublishProfilePath: string): Promise<FileProjectEntry> {
|
||||
const relativePublishProfilePath = (utils.trimUri(Uri.file(this.projectFilePath), Uri.file(absolutePublishProfilePath)));
|
||||
|
||||
// Update sqlproj XML
|
||||
|
||||
const fileEntry = this.createFileProjectEntry(relativePublishProfilePath, EntryType.File);
|
||||
this._publishProfiles.push(fileEntry);
|
||||
|
||||
await this.addToProjFile(fileEntry, constants.None);
|
||||
|
||||
return fileEntry;
|
||||
}
|
||||
|
||||
public createFileProjectEntry(relativePath: string, entryType: EntryType, sqlObjectType?: string, containsCreateTableStatement?: boolean): FileProjectEntry {
|
||||
let platformSafeRelativePath = utils.getPlatformSafeFileEntryPath(relativePath);
|
||||
return new FileProjectEntry(
|
||||
@@ -1176,7 +1231,8 @@ export class Project implements ISqlProject {
|
||||
}
|
||||
|
||||
private findOrCreateItemGroup(containedTag?: string, prePostScriptExist?: { scriptExist: boolean; }): Element {
|
||||
let outputItemGroup = undefined;
|
||||
let outputItemGroup: Element[] = []; // "None" can have more than one ItemGroup, for "None Include" (for pre/post deploy scripts and publish profiles), "None Remove"
|
||||
let returnItemGroup;
|
||||
|
||||
// search for a particular item goup if a child type is provided
|
||||
if (containedTag) {
|
||||
@@ -1184,25 +1240,45 @@ export class Project implements ISqlProject {
|
||||
for (let ig = 0; ig < this.projFileXmlDoc!.documentElement.getElementsByTagName(constants.ItemGroup).length; ig++) {
|
||||
const currentItemGroup = this.projFileXmlDoc!.documentElement.getElementsByTagName(constants.ItemGroup)[ig];
|
||||
|
||||
// if we find the tag, use the ItemGroup
|
||||
if (currentItemGroup.getElementsByTagName(containedTag).length > 0) {
|
||||
outputItemGroup = currentItemGroup;
|
||||
break;
|
||||
outputItemGroup.push(currentItemGroup);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if none already exist, make a new ItemGroup for it
|
||||
if (!outputItemGroup) {
|
||||
outputItemGroup = this.projFileXmlDoc!.createElement(constants.ItemGroup);
|
||||
this.projFileXmlDoc!.documentElement.appendChild(outputItemGroup);
|
||||
if (outputItemGroup.length === 0) {
|
||||
returnItemGroup = this.projFileXmlDoc!.createElement(constants.ItemGroup);
|
||||
this.projFileXmlDoc!.documentElement.appendChild(returnItemGroup);
|
||||
|
||||
if (prePostScriptExist) {
|
||||
prePostScriptExist.scriptExist = false;
|
||||
}
|
||||
} else { // if item group exists and containedTag = None, read the content to find None Include with publish profile
|
||||
if (containedTag === constants.None) {
|
||||
for (let ig = 0; ig < outputItemGroup.length; ig++) {
|
||||
const itemGroup = outputItemGroup[ig];
|
||||
|
||||
// find all none include scripts specified in the sqlproj
|
||||
const noneItems = itemGroup.getElementsByTagName(constants.None);
|
||||
for (let n = 0; n < noneItems.length; n++) {
|
||||
let noneIncludeItem = noneItems[n].getAttribute(constants.Include);
|
||||
if (noneIncludeItem && utils.isPublishProfile(noneIncludeItem)) {
|
||||
returnItemGroup = itemGroup;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!returnItemGroup) {
|
||||
returnItemGroup = this.projFileXmlDoc!.createElement(constants.ItemGroup);
|
||||
this.projFileXmlDoc!.documentElement.appendChild(returnItemGroup);
|
||||
}
|
||||
} else {
|
||||
returnItemGroup = outputItemGroup[0]; // Return the first item group that was found, to match prior implementation
|
||||
}
|
||||
}
|
||||
|
||||
return outputItemGroup;
|
||||
return returnItemGroup;
|
||||
}
|
||||
|
||||
private async addFileToProjFile(filePath: string, xmlTag: string, attributes?: Map<string, string>): Promise<void> {
|
||||
@@ -1220,6 +1296,8 @@ export class Project implements ISqlProject {
|
||||
void window.showInformationMessage(constants.deployScriptExists(xmlTag));
|
||||
xmlTag = constants.None; // Add only one pre-deploy and post-deploy script. All additional ones get added in the same item group with None tag
|
||||
}
|
||||
} else if (xmlTag === constants.None) { // Add publish profiles with None tag
|
||||
itemGroup = this.findOrCreateItemGroup(xmlTag);
|
||||
}
|
||||
else {
|
||||
if (this.isSdkStyleProject) {
|
||||
@@ -1260,7 +1338,7 @@ export class Project implements ISqlProject {
|
||||
itemGroup.appendChild(newFileNode);
|
||||
}
|
||||
|
||||
private async removeFileFromProjFile(path: string): Promise<void> {
|
||||
private async removeFileFromProjFile(path: string): Promise<void> {//TODO: publish profile
|
||||
const fileNodes = this.projFileXmlDoc!.documentElement.getElementsByTagName(constants.Build);
|
||||
const preDeployNodes = this.projFileXmlDoc!.documentElement.getElementsByTagName(constants.PreDeploy);
|
||||
const postDeployNodes = this.projFileXmlDoc!.documentElement.getElementsByTagName(constants.PostDeploy);
|
||||
|
||||
@@ -65,7 +65,8 @@ export class ProjectRootTreeItem extends BaseProjectTreeItem {
|
||||
let treeItemList = this.project.files
|
||||
.concat(this.project.preDeployScripts)
|
||||
.concat(this.project.postDeployScripts)
|
||||
.concat(this.project.noneDeployScripts);
|
||||
.concat(this.project.noneDeployScripts)
|
||||
.concat(this.project.publishProfiles);
|
||||
|
||||
for (const entry of treeItemList) {
|
||||
if (entry.type !== EntryType.File && entry.relativePath.startsWith(RelativeOuterPath)) {
|
||||
|
||||
Reference in New Issue
Block a user