From 7f896c8f7b876f3e065d9cc2bdc0c8f9ac8c90ea Mon Sep 17 00:00:00 2001 From: Ralph Callaway Date: Tue, 15 Oct 2019 02:29:16 -0600 Subject: [PATCH 1/5] wip trying to generate a package.xmk for deleted files and then mv to destructiveChanges.xml in the new location --- src/commands/git/package.ts | 45 ++++++++++++++++++++++++------------- 1 file changed, 30 insertions(+), 15 deletions(-) diff --git a/src/commands/git/package.ts b/src/commands/git/package.ts index df93cf4..279b27e 100644 --- a/src/commands/git/package.ts +++ b/src/commands/git/package.ts @@ -88,16 +88,19 @@ export default class Package extends SfdxCommand { } const diff = await spawnPromise('git', diffArgs, {shell: true}); - const changes = await this.getChanged(diff); - if (!changes.changed.length) { + const diffResults = await this.getChanged(diff); + if (!diffResults.changed.length) { this.ux.warn('No changes found!'); this.exit(1); return; } - // create a temp project so we can leverage force:source:convert + // create a temp project so we can leverlage force:source:convert for destructiveChanges + const tmpDeletProj = !diffResults.removed.length ? null : await this.setupTmpProject(diffResults.removed, null, true); - const tmpProject = await this.setupTmpProject(changes, fromBranch); + + // create a temp project so we can leverage force:source:convert for primary deploy + const tmpProject = await this.setupTmpProject(diffResults.changed, fromBranch, false); const outDir = isAbsolute(this.flags.outputdir) ? this.flags.outputdir : join(this.projectPath, this.flags.outputdir); try { @@ -128,9 +131,12 @@ export default class Package extends SfdxCommand { } } } catch (e) {} - await spawnPromise('sfdx', ['force:source:convert', '-d', outDir], {shell: true, cwd: tmpProject}); - + if (diffResults.removed.length) { + const tempDeleteProjConverted = await this.mkTempDir(); + await spawnPromise('sfdx', ['force:source:convert', '-d', tempDeleteProjConverted], {shell: true, cwd: tmpDeletProj}); + await fsPromise.copyFile(`${tempDeleteProjConverted}/package.xml`,`${outDir}/destructiveChanges.xml`); + } } catch (e) { this.ux.error(e); this.exit(1); @@ -139,8 +145,7 @@ export default class Package extends SfdxCommand { return {}; } - private async setupTmpProject(diff: DiffResults, targetRef: string) { - + private async mkTempDir() { const tempDir = await new Promise((resolve, reject) => { tmp.dir((err, path) => { if (err) { @@ -149,15 +154,20 @@ export default class Package extends SfdxCommand { resolve(path); }); }); - // const tempDir = join(this.projectPath, TEMP); - await fs.mkdirp(tempDir); + fs.mkdirp(tempDir); + return tempDir; + } + + private async setupTmpProject(changed: string[], targetRef?: string, deletions?:boolean) { + + const tempDir = await this.mkTempDir(); for (const sourcePath of this.sourcePaths) { await fs.mkdirp(join(tempDir, sourcePath)); } await fsPromise.copyFile(join(this.projectPath, 'sfdx-project.json'), join(tempDir, 'sfdx-project.json')); - for (const path of diff.changed) { + for (const path of changed) { const metadataPaths = await resolveMetadata(path); if (!metadataPaths) { @@ -173,13 +183,18 @@ export default class Package extends SfdxCommand { const newPath = join(tempDir, mdPath); await fs.mkdirp(dirname(newPath)); - if (targetRef) { - await copyFileFromRef(mdPath, targetRef, newPath); + if (deletions) { + await fsPromise.writeFile(newPath, 'DELETE'); } else { - await fsPromise.copyFile(mdPath, newPath); + if (targetRef) { + await copyFileFromRef(mdPath, targetRef, newPath); + } else { + await fsPromise.copyFile(mdPath, newPath); + } } } } + return tempDir; } @@ -221,7 +236,7 @@ export default class Package extends SfdxCommand { continue; } - // [TODO] build a "distructivechanges.xml" + // [TODO] build a "destructivechanges.xml" if (status === 'D') { removed.push(path); } else { From c6df92e71cfa8ef8638f36a6fadd6e334993b94d Mon Sep 17 00:00:00 2001 From: Ralph Callaway Date: Wed, 16 Oct 2019 18:32:36 -0600 Subject: [PATCH 2/5] fix launch.json --- .vscode/launch.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index c5b0237..bea30c6 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -9,8 +9,7 @@ "request": "attach", "name": "Attach to Remote", "address": "127.0.0.1", - "port": 9229, - "localRoot": "${workspaceFolder}" + "port": 9229 }, { "name": "Unit Tests", From 727d516aa3237e93f78283614b21b283eb04fe0d Mon Sep 17 00:00:00 2001 From: Ralph Callaway Date: Fri, 18 Oct 2019 22:56:33 -0600 Subject: [PATCH 3/5] handle cases where directory isn't fully deleted --- src/commands/git/package.ts | 41 +++++++++++++++++++++++++++++-------- src/metadataResolvers.ts | 30 +++++++++++++++++++-------- 2 files changed, 53 insertions(+), 18 deletions(-) diff --git a/src/commands/git/package.ts b/src/commands/git/package.ts index 279b27e..e3bb76f 100644 --- a/src/commands/git/package.ts +++ b/src/commands/git/package.ts @@ -5,7 +5,7 @@ import * as jsdiff from 'diff'; import { promises as fsPromise } from 'fs'; import { dirname, isAbsolute, join, relative } from 'path'; import * as tmp from 'tmp'; -import { resolveMetadata } from '../../metadataResolvers'; +import { resolveMetadata, getResolver } from '../../metadataResolvers'; import { copyFileFromRef, getIgnore, purgeFolder, spawnPromise } from '../../util'; interface DiffResults { @@ -95,8 +95,15 @@ export default class Package extends SfdxCommand { return; } - // create a temp project so we can leverlage force:source:convert for destructiveChanges - const tmpDeletProj = !diffResults.removed.length ? null : await this.setupTmpProject(diffResults.removed, null, true); + // create a temp project so we can leverage force:source:convert for destructiveChanges + let hasDeletions = diffResults.removed.length > 0; + let tmpDeleteProj:string; + let tempDeleteProjConverted:string; + if (hasDeletions) { + tmpDeleteProj = await this.setupTmpProject(diffResults.removed, fromBranch, true); + tempDeleteProjConverted = await this.mkTempDir(); + await spawnPromise('sfdx', ['force:source:convert', '-d', tempDeleteProjConverted], {shell: true, cwd: tmpDeleteProj}); + } // create a temp project so we can leverage force:source:convert for primary deploy @@ -132,9 +139,7 @@ export default class Package extends SfdxCommand { } } catch (e) {} await spawnPromise('sfdx', ['force:source:convert', '-d', outDir], {shell: true, cwd: tmpProject}); - if (diffResults.removed.length) { - const tempDeleteProjConverted = await this.mkTempDir(); - await spawnPromise('sfdx', ['force:source:convert', '-d', tempDeleteProjConverted], {shell: true, cwd: tmpDeletProj}); + if (hasDeletions) { await fsPromise.copyFile(`${tempDeleteProjConverted}/package.xml`,`${outDir}/destructiveChanges.xml`); } } catch (e) { @@ -158,7 +163,7 @@ export default class Package extends SfdxCommand { return tempDir; } - private async setupTmpProject(changed: string[], targetRef?: string, deletions?:boolean) { + private async setupTmpProject(changed: string[], targetRef: string|undefined, deletions?:boolean) { const tempDir = await this.mkTempDir(); @@ -203,7 +208,7 @@ export default class Package extends SfdxCommand { const lines = diffOutput.split(/\r?\n/); // tuple of additions, deletions const changed = []; - const removed = []; + let removed = []; for (const line of lines) { const parts = line.split('\t'); const status = parts[0]; @@ -236,7 +241,6 @@ export default class Package extends SfdxCommand { continue; } - // [TODO] build a "destructivechanges.xml" if (status === 'D') { removed.push(path); } else { @@ -244,6 +248,25 @@ export default class Package extends SfdxCommand { } } + + // check for directory style resources that are full deletions (and are actually changes) + const notFullyRemoved = []; + for (const path of removed) { + const resolver = getResolver(path); + if (resolver.getIsDirectory()) { + const metadataPaths = await resolver.getMetadataPaths(path); + // current implementation will return meta file regardless of whether it exists in org or not + if (metadataPaths.length > 1) { + notFullyRemoved.push(path); + for (const mdPath of metadataPaths) { + if (!changed.includes(mdPath)) { + changed.push(mdPath); + } + } + } + } + } + removed = removed.filter(path => !notFullyRemoved.includes(path)); return { changed, removed diff --git a/src/metadataResolvers.ts b/src/metadataResolvers.ts index 79f1371..72dc450 100644 --- a/src/metadataResolvers.ts +++ b/src/metadataResolvers.ts @@ -10,6 +10,7 @@ const STATIC_RESOURCE_FILE_REGEX = /(.*\/staticresources\/\w*)\.\w*/; interface MetadataResolver { match: ((path: string) => boolean) | RegExp; getMetadataPaths: (path: string) => Promise; + getIsDirectory: () => boolean; } // order from most selective to least @@ -20,13 +21,15 @@ const metadataResolvers: MetadataResolver[] = [ }, getMetadataPaths: async (path: string) => { return [path, path + '-meta.xml']; - } + }, + getIsDirectory: () => { return false; } }, { // reverse of above rule match: COMP_META, getMetadataPaths: async (path: string) => { return [path, path.replace('-meta.xml', '')]; - } + }, + getIsDirectory: () => { return false; } }, { // other metadata match: path => { @@ -34,38 +37,47 @@ const metadataResolvers: MetadataResolver[] = [ }, getMetadataPaths: async (path: string) => { return [path]; - } + }, + getIsDirectory: () => { return false; } }, { // aura bundles match: AURA_REGEX, getMetadataPaths: async (path: string) => { const appDir = AURA_REGEX.exec(path)[1]; return await getFiles(appDir); - } + }, + getIsDirectory: () => { return true; } }, { // decompressed static resource (folders) match: STATIC_RESOURCE_FOLDER_REGEX, getMetadataPaths: async (path: string) => { const appDir = STATIC_RESOURCE_FOLDER_REGEX.exec(path)[1]; return [...await getFiles(appDir), `${appDir}.resource-meta.xml`]; - } + }, + getIsDirectory: () => { return true; } }, { // static resource (single files) match: STATIC_RESOURCE_FILE_REGEX, getMetadataPaths: async (path: string) => { const baseName = STATIC_RESOURCE_FILE_REGEX.exec(path)[1]; return [path, `${baseName}.resource-meta.xml` ]; - } + }, + getIsDirectory: () => { return false; } } ]; -// given a path, return all other paths that must be included along side for a valid deployment -export function resolveMetadata(path: string) { +export function getResolver(path: string) { for (const resolver of metadataResolvers) { const isMatch = resolver.match instanceof RegExp ? resolver.match.test(path) : resolver.match(path); if (isMatch) { - return resolver.getMetadataPaths(path); + return resolver; } } return null; } + +// given a path, return all other paths that must be included along side for a valid deployment +export function resolveMetadata(path: string) { + const resolver = getResolver(path); + return resolver && resolver.getMetadataPaths(path); +} From d011be0418ea7cacb2449bc8e9beba50afc2aab8 Mon Sep 17 00:00:00 2001 From: Ralph Callaway Date: Fri, 18 Oct 2019 23:12:36 -0600 Subject: [PATCH 4/5] add flag to skip generating destructiveChanges.xml --- messages/package.json | 3 ++- src/commands/git/package.ts | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/messages/package.json b/messages/package.json index bd52f6c..efe700e 100644 --- a/messages/package.json +++ b/messages/package.json @@ -5,5 +5,6 @@ "fromBranchDescription": "The git ref (branch or commit) which we are deploying from. If left blank, will use working copy", "force": "Continue even if source is behind target", "purgeDescription": "Delete output dir if it already exists (without prompt)", - "ignoreWhitespace": "Don't package changes that are whitespace only" + "ignoreWhitespace": "Don't package changes that are whitespace only", + "nodelete": "Don't generate destructiveChanges.xml for deletions" } diff --git a/src/commands/git/package.ts b/src/commands/git/package.ts index e3bb76f..be3a710 100644 --- a/src/commands/git/package.ts +++ b/src/commands/git/package.ts @@ -42,6 +42,7 @@ export default class Package extends SfdxCommand { outputdir: flags.string({ char: 'd', description: messages.getMessage('outputdirDescription'), required: true }), ignorewhitespace: flags.boolean({ char: 'w', description: messages.getMessage('ignoreWhitespace')}), purge: flags.boolean({ description: messages.getMessage('purgeDescription') }), + nodelete: flags.boolean({ description: messages.getMessage('nodelete') }), force: flags.boolean({ char: 'f', description: messages.getMessage('force')}) }; @@ -96,7 +97,7 @@ export default class Package extends SfdxCommand { } // create a temp project so we can leverage force:source:convert for destructiveChanges - let hasDeletions = diffResults.removed.length > 0; + let hasDeletions = diffResults.removed.length > 0 && !this.flags.nodelete; let tmpDeleteProj:string; let tempDeleteProjConverted:string; if (hasDeletions) { From d636d5821ec4428ce68a2b320465009a0723262f Mon Sep 17 00:00:00 2001 From: Charlie Jonas Date: Sat, 19 Oct 2019 18:37:26 -0600 Subject: [PATCH 5/5] - fixing bugs with the destructive changes - fixing bug with purge - fixing issue where files where not resolved from target branch --- package.json | 4 +- src/commands/git/package.ts | 83 ++++++++++++++++++++----------------- src/metadataResolvers.ts | 32 +++++++------- src/util.ts | 38 +++++------------ 4 files changed, 73 insertions(+), 84 deletions(-) diff --git a/package.json b/package.json index 367a7b9..f70e8d7 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "sfdx-git-packager", "description": "Generates a package.xml for difference between two branches", - "version": "0.0.1", + "version": "0.0.2", "author": "Charlie Jonas @ChuckJonas", "bugs": "https://github.com/ChuckJonas/sfdx-git-diff-to-pkg/issues", "dependencies": { @@ -13,7 +13,6 @@ "diff": "^4.0.1", "glob-regex": "^0.3.2", "ignore": "^5.1.4", - "rimraf": "^3.0.0", "tmp": "^0.1.0", "tslib": "^1", "xml2js": "^0.4.22" @@ -24,7 +23,6 @@ "@oclif/test": "^1", "@salesforce/dev-config": "1.4.1", "@types/chai": "^4", - "@types/rimraf": "^2.0.2", "@types/diff": "^4.0.2", "@types/mocha": "^5", "@types/node": "^10", diff --git a/src/commands/git/package.ts b/src/commands/git/package.ts index be3a710..6a453da 100644 --- a/src/commands/git/package.ts +++ b/src/commands/git/package.ts @@ -5,7 +5,7 @@ import * as jsdiff from 'diff'; import { promises as fsPromise } from 'fs'; import { dirname, isAbsolute, join, relative } from 'path'; import * as tmp from 'tmp'; -import { resolveMetadata, getResolver } from '../../metadataResolvers'; +import { getResolver, resolveMetadata } from '../../metadataResolvers'; import { copyFileFromRef, getIgnore, purgeFolder, spawnPromise } from '../../util'; interface DiffResults { @@ -40,10 +40,10 @@ export default class Package extends SfdxCommand { sourceref: flags.string({ char: 's', description: messages.getMessage('fromBranchDescription') }), targetref: flags.string({ char: 't', description: messages.getMessage('toBranchDescription'), default: 'master' }), outputdir: flags.string({ char: 'd', description: messages.getMessage('outputdirDescription'), required: true }), - ignorewhitespace: flags.boolean({ char: 'w', description: messages.getMessage('ignoreWhitespace')}), + ignorewhitespace: flags.boolean({ char: 'w', description: messages.getMessage('ignoreWhitespace') }), purge: flags.boolean({ description: messages.getMessage('purgeDescription') }), nodelete: flags.boolean({ description: messages.getMessage('nodelete') }), - force: flags.boolean({ char: 'f', description: messages.getMessage('force')}) + force: flags.boolean({ char: 'f', description: messages.getMessage('force') }) }; // Comment this out if your command does not require an org username @@ -74,7 +74,7 @@ export default class Package extends SfdxCommand { try { const diffRefs = `${toBranch}...` + (fromBranch ? fromBranch : ''); - const aheadBehind = await spawnPromise('git', ['rev-list', '--left-right', '--count', diffRefs], {shell: true}); + const aheadBehind = await spawnPromise('git', ['rev-list', '--left-right', '--count', diffRefs], { shell: true }); const behind = Number(aheadBehind.split(/(\s+)/)[0]); if (behind > 0) { const behindMessage = `${fromBranch ? fromBranch : '"working tree"'} is ${behind} commit(s) behind ${toBranch}! You probably want to rebase ${toBranch} into ${fromBranch} before deploying!`; @@ -88,29 +88,30 @@ export default class Package extends SfdxCommand { } } - const diff = await spawnPromise('git', diffArgs, {shell: true}); - const diffResults = await this.getChanged(diff); - if (!diffResults.changed.length) { + const diff = await spawnPromise('git', diffArgs, { shell: true }); + const diffResults = await this.getChanged(diff, fromBranch); + + const hasChanges = diffResults.changed.length > 0; + const hasDeletions = diffResults.removed.length > 0 && !this.flags.nodelete; + if (!hasChanges && !hasDeletions) { this.ux.warn('No changes found!'); this.exit(1); return; } // create a temp project so we can leverage force:source:convert for destructiveChanges - let hasDeletions = diffResults.removed.length > 0 && !this.flags.nodelete; - let tmpDeleteProj:string; - let tempDeleteProjConverted:string; + + let tmpDeleteProj: string; + let tempDeleteProjConverted: string; if (hasDeletions) { - tmpDeleteProj = await this.setupTmpProject(diffResults.removed, fromBranch, true); + tmpDeleteProj = await this.setupTmpProject(diffResults.removed, toBranch); tempDeleteProjConverted = await this.mkTempDir(); - await spawnPromise('sfdx', ['force:source:convert', '-d', tempDeleteProjConverted], {shell: true, cwd: tmpDeleteProj}); + await spawnPromise('sfdx', ['force:source:convert', '-d', tempDeleteProjConverted], { shell: true, cwd: tmpDeleteProj }); } - // create a temp project so we can leverage force:source:convert for primary deploy - const tmpProject = await this.setupTmpProject(diffResults.changed, fromBranch, false); + const tmpProject = await this.setupTmpProject(diffResults.changed, fromBranch); const outDir = isAbsolute(this.flags.outputdir) ? this.flags.outputdir : join(this.projectPath, this.flags.outputdir); - try { const stat = await fs.stat(outDir); if (stat.isDirectory()) { @@ -131,17 +132,22 @@ export default class Package extends SfdxCommand { try { await purgeFolder(outDir); } catch (e) { - this.ux.error('Failed to remove files'); + this.ux.error(e); this.exit(1); return; } } } - } catch (e) {} - await spawnPromise('sfdx', ['force:source:convert', '-d', outDir], {shell: true, cwd: tmpProject}); + } catch (e) { } + + await fs.mkdirp(outDir); + + if (hasChanges) { + await spawnPromise('sfdx', ['force:source:convert', '-d', outDir], { shell: true, cwd: tmpProject }); + } if (hasDeletions) { - await fsPromise.copyFile(`${tempDeleteProjConverted}/package.xml`,`${outDir}/destructiveChanges.xml`); + await fsPromise.copyFile(`${tempDeleteProjConverted}/package.xml`, `${outDir}/destructiveChanges.xml`); } } catch (e) { this.ux.error(e); @@ -160,21 +166,21 @@ export default class Package extends SfdxCommand { resolve(path); }); }); - fs.mkdirp(tempDir); + await fs.mkdirp(tempDir); return tempDir; } - private async setupTmpProject(changed: string[], targetRef: string|undefined, deletions?:boolean) { - + private async setupTmpProject(changed: string[], targetRef: string | undefined) { const tempDir = await this.mkTempDir(); for (const sourcePath of this.sourcePaths) { await fs.mkdirp(join(tempDir, sourcePath)); } + await fsPromise.copyFile(join(this.projectPath, 'sfdx-project.json'), join(tempDir, 'sfdx-project.json')); for (const path of changed) { - const metadataPaths = await resolveMetadata(path); + const metadataPaths = await resolveMetadata(path, targetRef); if (!metadataPaths) { this.ux.warn(`Could not resolve metadata for ${path}`); @@ -189,33 +195,31 @@ export default class Package extends SfdxCommand { const newPath = join(tempDir, mdPath); await fs.mkdirp(dirname(newPath)); - if (deletions) { - await fsPromise.writeFile(newPath, 'DELETE'); + + if (targetRef) { + await copyFileFromRef(mdPath, targetRef, newPath); } else { - if (targetRef) { - await copyFileFromRef(mdPath, targetRef, newPath); - } else { - await fsPromise.copyFile(mdPath, newPath); - } + await fsPromise.copyFile(mdPath, newPath); } } + } return tempDir; } - private async getChanged(diffOutput: string): Promise { + private async getChanged(diffOutput: string, targetRef: string): Promise { const ignore = await getIgnore(this.projectPath); const lines = diffOutput.split(/\r?\n/); // tuple of additions, deletions const changed = []; let removed = []; for (const line of lines) { - const parts = line.split('\t'); + const parts = line.split('\t'); const status = parts[0]; const path = parts[1]; - if (!path || path.startsWith('.') || ignore.ignores(path) ) { + if (!path || path.startsWith('.') || ignore.ignores(path)) { continue; } @@ -254,10 +258,12 @@ export default class Package extends SfdxCommand { const notFullyRemoved = []; for (const path of removed) { const resolver = getResolver(path); + if (resolver.getIsDirectory()) { - const metadataPaths = await resolver.getMetadataPaths(path); + const metadataPaths = await resolver.getMetadataPaths(path, targetRef); // current implementation will return meta file regardless of whether it exists in org or not if (metadataPaths.length > 1) { + notFullyRemoved.push(path); for (const mdPath of metadataPaths) { if (!changed.includes(mdPath)) { @@ -267,6 +273,7 @@ export default class Package extends SfdxCommand { } } } + removed = removed.filter(path => !notFullyRemoved.includes(path)); return { changed, @@ -278,11 +285,11 @@ export default class Package extends SfdxCommand { // checks two strings and returns true if they have "non-whitespace" changes (spaces or newlines) function hasNonWhitespaceChanges(a: string, b: string) { - const diffResults = jsdiff.diffLines(a, b, {ignoreWhitespace: true, newlineIsToken: true}); + const diffResults = jsdiff.diffLines(a, b, { ignoreWhitespace: true, newlineIsToken: true }); for (const result of diffResults) { - if (result.added || result.removed) { - return true; - } + if (result.added || result.removed) { + return true; + } } return false; } diff --git a/src/metadataResolvers.ts b/src/metadataResolvers.ts index 72dc450..2540a99 100644 --- a/src/metadataResolvers.ts +++ b/src/metadataResolvers.ts @@ -1,5 +1,5 @@ import { extname } from 'path'; -import { getFiles } from './util'; +import { getFilesFromRef } from './util'; // these need to be re-witten for windows... maybe use globs instead const AURA_REGEX = /(.*\/aura\/\w*)\/.*/; @@ -9,7 +9,7 @@ const STATIC_RESOURCE_FILE_REGEX = /(.*\/staticresources\/\w*)\.\w*/; interface MetadataResolver { match: ((path: string) => boolean) | RegExp; - getMetadataPaths: (path: string) => Promise; + getMetadataPaths: (path: string, ref: string) => Promise; getIsDirectory: () => boolean; } @@ -22,39 +22,39 @@ const metadataResolvers: MetadataResolver[] = [ getMetadataPaths: async (path: string) => { return [path, path + '-meta.xml']; }, - getIsDirectory: () => { return false; } + getIsDirectory: () => false }, { // reverse of above rule match: COMP_META, - getMetadataPaths: async (path: string) => { + getMetadataPaths: async (path: string, ref: string) => { return [path, path.replace('-meta.xml', '')]; }, - getIsDirectory: () => { return false; } + getIsDirectory: () => false }, { // other metadata match: path => { return path.endsWith('-meta.xml'); }, - getMetadataPaths: async (path: string) => { + getMetadataPaths: async (path: string, ref: string) => { return [path]; }, - getIsDirectory: () => { return false; } + getIsDirectory: () => false }, { // aura bundles match: AURA_REGEX, - getMetadataPaths: async (path: string) => { + getMetadataPaths: async (path: string, ref: string) => { const appDir = AURA_REGEX.exec(path)[1]; - return await getFiles(appDir); + return await getFilesFromRef(appDir, ref); }, - getIsDirectory: () => { return true; } + getIsDirectory: () => true }, { // decompressed static resource (folders) match: STATIC_RESOURCE_FOLDER_REGEX, - getMetadataPaths: async (path: string) => { + getMetadataPaths: async (path: string, ref: string) => { const appDir = STATIC_RESOURCE_FOLDER_REGEX.exec(path)[1]; - return [...await getFiles(appDir), `${appDir}.resource-meta.xml`]; + return [...await getFilesFromRef(appDir, ref), `${appDir}.resource-meta.xml`]; }, - getIsDirectory: () => { return true; } + getIsDirectory: () => true }, { // static resource (single files) match: STATIC_RESOURCE_FILE_REGEX, @@ -62,7 +62,7 @@ const metadataResolvers: MetadataResolver[] = [ const baseName = STATIC_RESOURCE_FILE_REGEX.exec(path)[1]; return [path, `${baseName}.resource-meta.xml` ]; }, - getIsDirectory: () => { return false; } + getIsDirectory: () => false } ]; @@ -77,7 +77,7 @@ export function getResolver(path: string) { } // given a path, return all other paths that must be included along side for a valid deployment -export function resolveMetadata(path: string) { +export function resolveMetadata(path: string, ref: string) { const resolver = getResolver(path); - return resolver && resolver.getMetadataPaths(path); + return resolver && resolver.getMetadataPaths(path, ref); } diff --git a/src/util.ts b/src/util.ts index bc2cd37..9dcd61f 100644 --- a/src/util.ts +++ b/src/util.ts @@ -2,7 +2,6 @@ import { spawn, SpawnOptions } from 'child_process'; import * as fs from 'fs'; import ignore from 'ignore'; import { join, resolve } from 'path'; -import * as rimraf from 'rimraf'; const PKG_IGNORE = '.packageIgnore'; @@ -40,21 +39,16 @@ export async function copyFileFromRef(path: string, ref: string, destination: st await fs.promises.writeFile(destination, source); } -export async function getFiles(dir: string): Promise { - const dirents = await fs.promises.readdir(dir, { withFileTypes: true }); - - const files = []; - for (const dirent of dirents) { - const res = resolve(dir, dirent.name); - if (dirent.isDirectory()) { - files.push(await getFiles(res)); - } else { - files.push(res); - } - +export async function getFilesFromRef(dir: string, ref: string): Promise { + // probably a better way to check if the file exists + try { + return (await spawnPromise('git', ['ls-tree', '-r', '--name-only', `${ref}:${dir}`])) + .split('\n') + .filter(f => f) + .map(fPath => join(dir, fPath)); + } catch (e) { + return []; } - - return Array.prototype.concat(...files); } export async function purgeFolder(targetDir: string): Promise { @@ -63,19 +57,9 @@ export async function purgeFolder(targetDir: string): Promise { const stat = await fs.promises.stat(join(targetDir, file)); if (stat.isDirectory()) { await purgeFolder(resolve(targetDir, file)); + await fs.promises.rmdir(resolve(targetDir, file)); } else { - if (file.startsWith('.')) { - continue; - } - const fPath = join(targetDir, file); - await new Promise((res, rej) => { - rimraf(fPath, err => { - if (err) { - rej(err); - } - res(); - }); - }); + await fs.promises.unlink(join(targetDir, file)); } } }