diff --git a/extensions/sql-database-projects/src/models/project.ts b/extensions/sql-database-projects/src/models/project.ts index 9ea7be6e43..4fbe4bb880 100644 --- a/extensions/sql-database-projects/src/models/project.ts +++ b/extensions/sql-database-projects/src/models/project.ts @@ -899,7 +899,7 @@ export class Project implements ISqlProject { itemGroup.appendChild(newFileNode); } - private removeFileFromProjFile(path: string): void { + private async removeFileFromProjFile(path: string): Promise { const fileNodes = this.projFileXmlDoc!.documentElement.getElementsByTagName(constants.Build); const preDeployNodes = this.projFileXmlDoc!.documentElement.getElementsByTagName(constants.PreDeploy); const postDeployNodes = this.projFileXmlDoc!.documentElement.getElementsByTagName(constants.PostDeploy); @@ -907,14 +907,40 @@ export class Project implements ISqlProject { const nodes = [fileNodes, preDeployNodes, postDeployNodes, noneNodes]; let deleted = false; + + // remove the entry if there is one for (let i = 0; i < nodes.length; i++) { deleted = this.removeNode(path, nodes[i]); if (deleted) { - return; + // still might need to add a node if this is an sdk style project + if (this.isSdkStyleProject) { + break; + } else { + return; + } } } + // if it's an sdk style project, we'll need to add a entry to remove this file if it's + // still included by a glob + if (this.isSdkStyleProject) { + // write any changes from removing an include node and get the current files included in the project + if (deleted) { + await this.serializeToProjFile(this.projFileXmlDoc); + } + const currentFiles = await this.readFilesInProject(); + + // only add a 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); + removeFileNode.setAttribute(constants.Remove, utils.convertSlashesForSqlProj(path)); + this.findOrCreateItemGroup(constants.Build).appendChild(removeFileNode); + } + + return; + } + throw new Error(constants.unableToFindObject(path, constants.fileObject)); } @@ -1233,7 +1259,7 @@ export class Project implements ISqlProject { for (const entry of entries) { switch (entry.type) { case EntryType.File: - this.removeFileFromProjFile((entry).relativePath); + await this.removeFileFromProjFile((entry).relativePath); break; case EntryType.Folder: this.removeFolderFromProjFile((entry).relativePath); @@ -1260,6 +1286,9 @@ export class Project implements ISqlProject { }); // TODO: replace await fs.writeFile(this._projectFilePath, xml); + + // update projFileXmlDoc since the file was updated + this.projFileXmlDoc = new xmldom.DOMParser().parseFromString(xml); } /** diff --git a/extensions/sql-database-projects/src/test/baselines/openSdkStyleSqlProjectWithGlobsSpecifiedBaseline.xml b/extensions/sql-database-projects/src/test/baselines/openSdkStyleSqlProjectWithGlobsSpecifiedBaseline.xml index 091f1c1781..e93de1e8c3 100644 --- a/extensions/sql-database-projects/src/test/baselines/openSdkStyleSqlProjectWithGlobsSpecifiedBaseline.xml +++ b/extensions/sql-database-projects/src/test/baselines/openSdkStyleSqlProjectWithGlobsSpecifiedBaseline.xml @@ -17,6 +17,7 @@ + diff --git a/extensions/sql-database-projects/src/test/project.test.ts b/extensions/sql-database-projects/src/test/project.test.ts index d8c4e32da1..4a78028c75 100644 --- a/extensions/sql-database-projects/src/test/project.test.ts +++ b/extensions/sql-database-projects/src/test/project.test.ts @@ -896,7 +896,7 @@ describe('Project: sdk style project content operations', function (): void { const project: Project = await Project.openProject(projFilePath); - should(project.files.filter(f => f.type === EntryType.File).length).equal(17); + should(project.files.filter(f => f.type === EntryType.File).length).equal(18); // make sure all the correct files from the globbing patterns were included // ..\other\folder1\test?.sql @@ -967,6 +967,48 @@ describe('Project: sdk style project content operations', function (): void { should(project.files.filter(f => f.relativePath === 'file1.sql').length).equal(0); }); + it('Should handle excluding files included by glob patterns', async function (): Promise { + const testFolderPath = await testUtils.generateTestFolderPath(); + const mainProjectPath = path.join(testFolderPath, 'project'); + const otherFolderPath = path.join(testFolderPath, 'other'); + projFilePath = await testUtils.createTestSqlProjFile(baselines.openSdkStyleSqlProjectWithGlobsSpecifiedBaseline, mainProjectPath); + await testUtils.createDummyFileStructure(false, undefined, path.dirname(projFilePath)); + + // create files outside of project folder that are included in the project file + await fs.mkdir(otherFolderPath); + await testUtils.createOtherDummyFiles(otherFolderPath); + + const project: Project = await Project.openProject(projFilePath); + + should(project.files.filter(f => f.type === EntryType.File).length).equal(18); + + // exclude a file in the project's folder + should(project.files.filter(f => f.relativePath === 'folder1\\file1.sql').length).equal(1); + await project.exclude(project.files.find(f => f.relativePath === 'folder1\\file1.sql')!); + should(project.files.filter(f => f.relativePath === 'folder1\\file1.sql').length).equal(0); + + // exclude explicitly included file from an outside folder + should(project.files.filter(f => f.relativePath === '..\\other\\file1.sql').length).equal(1); + await project.exclude(project.files.find(f => f.relativePath === '..\\other\\file1.sql')!); + should(project.files.filter(f => f.relativePath === '..\\other\\file1.sql').length).equal(0); + + // exclude glob included file from an outside folder + should(project.files.filter(f => f.relativePath === '..\\other\\folder1\\test2.sql').length).equal(1); + await project.exclude(project.files.find(f => f.relativePath === '..\\other\\folder1\\test2.sql')!); + should(project.files.filter(f => f.relativePath === '..\\other\\folder1\\test2.sql').length).equal(0); + + // make sure a was added + const projFileText = (await fs.readFile(projFilePath)).toString(); + should(projFileText.includes('')).equal(true, projFileText); + + // make sure was removed and no was added for it + should(projFileText.includes('')).equal(false, projFileText); + should(projFileText.includes('')).equal(false, projFileText); + + // make sure a was added + should(projFileText.includes('')).equal(true, projFileText); + }); + it('Should only add Build entries to sqlproj for files not included by project folder glob and external streaming jobs', async function (): Promise { projFilePath = await testUtils.createTestSqlProjFile(baselines.openSdkStyleSqlProjectBaseline); const project = await Project.openProject(projFilePath);