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');