From f0dd31c45703244df1143b427c8079f026d747e8 Mon Sep 17 00:00:00 2001
From: Kim Santiago <31145923+kisantia@users.noreply.github.com>
Date: Tue, 25 Jan 2022 11:49:52 -0800
Subject: [PATCH] fix excluding pre/post/none deploy scripts in sdk style
projects (#18117)
---
.../src/models/project.ts | 47 +++++++++++++++++--
.../src/test/project.test.ts | 39 +++++++++++++++
2 files changed, 83 insertions(+), 3 deletions(-)
diff --git a/extensions/sql-database-projects/src/models/project.ts b/extensions/sql-database-projects/src/models/project.ts
index 3a780bb31f..abd60d898b 100644
--- a/extensions/sql-database-projects/src/models/project.ts
+++ b/extensions/sql-database-projects/src/models/project.ts
@@ -238,6 +238,10 @@ export class Project implements ISqlProject {
this.preDeployScripts.forEach(f => filesSet.delete(f.relativePath));
this.postDeployScripts.forEach(f => filesSet.delete(f.relativePath));
this.noneDeployScripts.forEach(f => filesSet.delete(f.relativePath));
+
+ // remove any none remove scripts (these would be pre/post/none deploy scripts that were excluded)
+ const noneRemoveScripts = this.readNoneRemoveScripts();
+ noneRemoveScripts.forEach(f => filesSet.delete(f.relativePath));
}
// create a FileProjectEntry for each file
@@ -379,7 +383,10 @@ export class Project implements ISqlProject {
try {
const noneItems = itemGroup.getElementsByTagName(constants.None);
for (let n = 0; n < noneItems.length; n++) {
- noneDeployScripts.push(this.createFileProjectEntry(noneItems[n].getAttribute(constants.Include)!, EntryType.File));
+ const includeAttribute = noneItems[n].getAttribute(constants.Include);
+ if (includeAttribute) {
+ noneDeployScripts.push(this.createFileProjectEntry(includeAttribute, EntryType.File));
+ }
}
} catch (e) {
void window.showErrorMessage(constants.errorReadingProject(constants.NoneElements, this.projectFilePath));
@@ -390,6 +397,30 @@ export class Project implements ISqlProject {
return noneDeployScripts;
}
+ /**
+ * @returns all the files specified as in the sqlproj
+ */
+ private readNoneRemoveScripts(): FileProjectEntry[] {
+ const noneRemoveScripts: 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 none remove scripts to specified in the sqlproj
+ try {
+ const noneItems = itemGroup.getElementsByTagName(constants.None);
+ for (let n = 0; n < noneItems.length; n++) {
+ noneRemoveScripts.push(this.createFileProjectEntry(noneItems[n].getAttribute(constants.Remove)!, EntryType.File));
+ }
+ } catch (e) {
+ void window.showErrorMessage(constants.errorReadingProject(constants.NoneElements, this.projectFilePath));
+ console.error(utils.getErrorMessage(e));
+ }
+ }
+
+ return noneRemoveScripts;
+ }
+
private readDatabaseReferences(): IDatabaseReferenceProjectEntry[] {
const databaseReferenceEntries: IDatabaseReferenceProjectEntry[] = [];
@@ -969,6 +1000,8 @@ export class Project implements ISqlProject {
const noneNodes = this.projFileXmlDoc!.documentElement.getElementsByTagName(constants.None);
const nodes = [fileNodes, preDeployNodes, postDeployNodes, noneNodes];
+ const isBuildElement = this.files.find(f => f.relativePath === path);
+
let deleted = false;
// remove the entry if there is one
@@ -992,13 +1025,17 @@ export class Project implements ISqlProject {
if (deleted) {
await this.serializeToProjFile(this.projFileXmlDoc!);
}
+ this._preDeployScripts = this.readPreDeployScripts();
+ this._postDeployScripts = this.readPostDeployScripts();
+ this._noneDeployScripts = this.readNoneDeployScripts();
const currentFiles = await this.readFilesInProject();
- // only add a node to exclude the file if it's still included by a glob
+ // only add a Remove node to exclude the file if it's still included by a glob
if (currentFiles.find(f => f.relativePath === utils.convertSlashesForSqlProj(path))) {
- const removeFileNode = this.projFileXmlDoc!.createElement(constants.Build);
+ const removeFileNode = isBuildElement ? this.projFileXmlDoc!.createElement(constants.Build) : this.projFileXmlDoc!.createElement(constants.None);
removeFileNode.setAttribute(constants.Remove, utils.convertSlashesForSqlProj(path));
this.findOrCreateItemGroup(constants.Build).appendChild(removeFileNode);
+ return;
}
return;
@@ -1503,6 +1540,10 @@ export class Project implements ISqlProject {
* @returns Project entry for the last folder in the path, if path is under the project folder; otherwise `undefined`.
*/
private async ensureFolderItems(relativeFolderPath: string): Promise {
+ if (!relativeFolderPath) {
+ return;
+ }
+
const absoluteFolderPath = path.join(this.projectFolderPath, relativeFolderPath);
const normalizedProjectFolderPath = path.normalize(this.projectFolderPath);
diff --git a/extensions/sql-database-projects/src/test/project.test.ts b/extensions/sql-database-projects/src/test/project.test.ts
index 62dfa672bf..9692ade1d3 100644
--- a/extensions/sql-database-projects/src/test/project.test.ts
+++ b/extensions/sql-database-projects/src/test/project.test.ts
@@ -988,6 +988,45 @@ describe('Project: sdk style project content operations', function (): void {
should(project.files.filter(f => f.relativePath === 'file1.sql').length).equal(0);
});
+ it('Should exclude pre/post/none deploy scripts correctly', async function (): Promise {
+ const folderPath = await testUtils.generateTestFolderPath();
+ projFilePath = await testUtils.createTestSqlProjFile(baselines.newSdkStyleProjectSdkNodeBaseline, folderPath);
+
+ const project: Project = await Project.openProject(projFilePath);
+ await project.addScriptItem('Script.PreDeployment1.sql', 'fake contents', templates.preDeployScript);
+ await project.addScriptItem('Script.PreDeployment2.sql', 'fake contents', templates.preDeployScript);
+ await project.addScriptItem('Script.PostDeployment1.sql', 'fake contents', templates.postDeployScript);
+
+ // verify they were added to the sqlproj
+ let projFileText = (await fs.readFile(projFilePath)).toString();
+ should(projFileText.includes('')).equal(true);
+ should(projFileText.includes('')).equal(true);
+ should(projFileText.includes('')).equal(true);
+ should(project.preDeployScripts.length).equal(1, 'Script.PreDeployment1.sql should have been added');
+ should(project.noneDeployScripts.length).equal(1, 'Script.PreDeployment2.sql should have been added');
+ should(project.preDeployScripts.length).equal(1, 'Script.PostDeployment1.sql should have been added');
+ should(project.files.length).equal(0, 'There should not be any files');
+
+ // exclude the pre/post/none deploy script
+ await project.exclude(project.preDeployScripts.find(f => f.relativePath === 'Script.PreDeployment1.sql')!);
+ await project.exclude(project.noneDeployScripts.find(f => f.relativePath === 'Script.PreDeployment2.sql')!);
+ await project.exclude(project.postDeployScripts.find(f => f.relativePath === 'Script.PostDeployment1.sql')!);
+
+ // verify they are excluded in the sqlproj
+ projFileText = (await fs.readFile(projFilePath)).toString();
+ should(projFileText.includes('')).equal(false);
+ should(projFileText.includes('')).equal(false);
+ should(projFileText.includes('')).equal(false);
+ should(projFileText.includes('')).equal(true);
+ should(projFileText.includes('')).equal(true);
+ should(projFileText.includes('')).equal(true);
+
+ should(project.preDeployScripts.length).equal(0, 'Script.PreDeployment1.sql should have been removed');
+ should(project.noneDeployScripts.length).equal(0, 'Script.PreDeployment2.sql should have been removed');
+ should(project.postDeployScripts.length).equal(0, 'Script.PostDeployment1.sql should have been removed');
+ should(project.files.length).equal(0, 'There should not be any files after the excludes');
+ });
+
it('Should handle excluding files included by glob patterns', async function (): Promise {
const testFolderPath = await testUtils.generateTestFolderPath();
const mainProjectPath = path.join(testFolderPath, 'project');