diff --git a/packages/build/src/config/redact-config.spec.ts b/packages/build/src/config/redact-config.spec.ts new file mode 100644 index 0000000000..75f9a1164e --- /dev/null +++ b/packages/build/src/config/redact-config.spec.ts @@ -0,0 +1,28 @@ +import { expect } from 'chai'; +import { Config } from './config'; +import { redactConfig } from './redact-config'; + +describe('config redact-config', () => { + const redactedKeys: Array = [ + 'evgAwsKey', + 'evgAwsSecret', + 'downloadCenterAwsKey', + 'downloadCenterAwsSecret', + 'githubToken' + ]; + + const config: Config = {} as Config; + + beforeEach(() => { + redactedKeys.forEach(k => { + (config as any)[k] = 'asecret'; + }); + }); + + redactedKeys.forEach(k => { + it(`removes ${k} from the config`, () => { + const redacted = redactConfig(config); + expect(redacted[k]).to.be.undefined; + }); + }); +}); diff --git a/packages/build/src/npm-packages.spec.ts b/packages/build/src/npm-packages.spec.ts deleted file mode 100644 index 4e33789f68..0000000000 --- a/packages/build/src/npm-packages.spec.ts +++ /dev/null @@ -1,222 +0,0 @@ -import { expect } from 'chai'; -import path from 'path'; -import sinon, { SinonStub } from 'sinon'; -import { - bumpNpmPackages, - listNpmPackages, - markBumpedFilesAsAssumeUnchanged, - publishNpmPackages, - spawnSync, - LernaPackageDescription -} from './npm-packages'; - - -describe('npm-packages', () => { - describe('spawnSync', () => { - it('works for a valid command', () => { - const result = spawnSync('bash', ['-c', 'echo -n works'], { encoding: 'utf8' }); - expect(result.status).to.equal(0); - expect(result.stdout).to.equal('works'); - }); - - it('throws on ENOENT error', () => { - try { - spawnSync('notaprogram', [], { encoding: 'utf8' }); - } catch (e) { - return expect(e).to.not.be.undefined; - } - expect.fail('Expected error'); - }); - - it('throws on non-zero exit code', () => { - try { - spawnSync('bash', ['-c', 'exit 1'], { encoding: 'utf8' }); - } catch (e) { - return expect(e).to.not.be.undefined; - } - expect.fail('Expected error'); - }); - - it('ignores errors when asked to for ENOENT', () => { - const result = spawnSync('notaprogram', [], { encoding: 'utf8' }, true); - expect(result).to.not.be.undefined; - }); - - it('ignores errors when asked to for non-zero exit code', () => { - const result = spawnSync('bash', ['-c', 'exit 1'], { encoding: 'utf8' }, true); - expect(result).to.not.be.undefined; - expect(result?.status).to.equal(1); - }); - }); - - describe('bumpNpmPackages', () => { - let spawnSync: SinonStub; - - beforeEach(() => { - spawnSync = sinon.stub(); - }); - - it('does not do anything if no version or placeholder version is specified', () => { - bumpNpmPackages('', spawnSync); - bumpNpmPackages('0.0.0-dev.0', spawnSync); - expect(spawnSync).to.not.have.been.called; - }); - - it('calls lerna to bump package version', () => { - const lernaBin = path.resolve(__dirname, '..', '..', '..', 'node_modules', '.bin', 'lerna'); - bumpNpmPackages('0.7.0', spawnSync); - expect(spawnSync).to.have.been.calledWith( - lernaBin, - ['version', '0.7.0', '--no-changelog', '--no-push', '--exact', '--no-git-tag-version', '--force-publish', '--yes'], - sinon.match.any - ); - expect(spawnSync).to.have.been.calledWith( - 'git', - ['status', '--porcelain'], - sinon.match.any - ); - }); - }); - - describe('publishNpmPackages', () => { - let listNpmPackages: SinonStub; - let markBumpedFilesAsAssumeUnchanged: SinonStub; - let spawnSync: SinonStub; - - beforeEach(() => { - listNpmPackages = sinon.stub(); - markBumpedFilesAsAssumeUnchanged = sinon.stub(); - spawnSync = sinon.stub(); - }); - - it('fails if packages have different versions', () => { - listNpmPackages.returns([ - { name: 'packageA', version: '0.0.1' }, - { name: 'packageB', version: '0.0.2' } - ]); - try { - publishNpmPackages( - listNpmPackages, - markBumpedFilesAsAssumeUnchanged, - spawnSync - ); - } catch (e) { - expect(markBumpedFilesAsAssumeUnchanged).to.not.have.been.called; - expect(spawnSync).to.not.have.been.called; - return; - } - expect.fail('Expected error'); - }); - - it('fails if packages have placeholder versions', () => { - listNpmPackages.returns([ - { name: 'packageA', version: '0.0.0-dev.0' }, - { name: 'packageB', version: '0.0.0-dev.0' } - ]); - try { - publishNpmPackages( - listNpmPackages, - markBumpedFilesAsAssumeUnchanged, - spawnSync - ); - } catch (e) { - expect(markBumpedFilesAsAssumeUnchanged).to.not.have.been.called; - expect(spawnSync).to.not.have.been.called; - return; - } - expect.fail('Expected error'); - }); - - it('calls lerna to publish packages for a real version', () => { - const lernaBin = path.resolve(__dirname, '..', '..', '..', 'node_modules', '.bin', 'lerna'); - const packages = [ - { name: 'packageA', version: '0.7.0' } - ]; - listNpmPackages.returns(packages); - - publishNpmPackages( - listNpmPackages, - markBumpedFilesAsAssumeUnchanged, - spawnSync - ); - - expect(markBumpedFilesAsAssumeUnchanged).to.have.been.calledWith(packages, true); - expect(spawnSync).to.have.been.calledWith( - lernaBin, - ['publish', 'from-package', '--no-changelog', '--no-push', '--exact', '--no-git-tag-version', '--force-publish', '--yes'], - sinon.match.any - ); - expect(markBumpedFilesAsAssumeUnchanged).to.have.been.calledWith(packages, false); - }); - - it('reverts the assume unchanged even on spawn failure', () => { - const packages = [ - { name: 'packageA', version: '0.7.0' } - ]; - listNpmPackages.returns(packages); - spawnSync.throws(new Error('meeep')); - - try { - publishNpmPackages( - listNpmPackages, - markBumpedFilesAsAssumeUnchanged, - spawnSync - ); - } catch (e) { - expect(markBumpedFilesAsAssumeUnchanged).to.have.been.calledWith(packages, true); - expect(spawnSync).to.have.been.called; - expect(markBumpedFilesAsAssumeUnchanged).to.have.been.calledWith(packages, false); - return; - } - expect.fail('Expected error'); - }); - }); - - describe('listNpmPackages', () => { - it('lists packages', () => { - const packages = listNpmPackages(); - expect(packages.length).to.be.greaterThan(1); - for (const { name, version } of packages) { - expect(name).to.be.a('string'); - expect(version).to.be.a('string'); - } - }); - }); - - describe('markBumpedFilesAsAssumeUnchanged', () => { - let packages: LernaPackageDescription[]; - let expectedFiles: string[]; - let spawnSync: SinonStub; - - beforeEach(() => { - expectedFiles = [ - path.resolve(__dirname, '..', '..', '..', 'lerna.json') - ]; - packages = listNpmPackages(); - packages.forEach(({ location }) => { - expectedFiles.push(path.resolve(location, 'package.json')); - expectedFiles.push(path.resolve(location, 'package-lock.json')); - }); - - spawnSync = sinon.stub(); - }); - - it('marks files with --assume-unchanged', () => { - markBumpedFilesAsAssumeUnchanged(packages, true, spawnSync); - expectedFiles.forEach(f => { - expect(spawnSync).to.have.been.calledWith( - 'git', ['update-index', '--assume-unchanged', f], sinon.match.any - ); - }); - }); - - it('marks files with --no-assume-unchanged', () => { - markBumpedFilesAsAssumeUnchanged(packages, false, spawnSync); - expectedFiles.forEach(f => { - expect(spawnSync).to.have.been.calledWith( - 'git', ['update-index', '--no-assume-unchanged', f], sinon.match.any - ); - }); - }); - }); -}); diff --git a/packages/build/src/npm-packages.ts b/packages/build/src/npm-packages.ts deleted file mode 100644 index f8746bc986..0000000000 --- a/packages/build/src/npm-packages.ts +++ /dev/null @@ -1,156 +0,0 @@ -import { SpawnSyncOptionsWithStringEncoding, SpawnSyncReturns } from 'child_process'; -import * as spawn from 'cross-spawn'; -import path from 'path'; - -const PLACEHOLDER_VERSION = '0.0.0-dev.0'; -const PROJECT_ROOT = path.resolve(__dirname, '..', '..', '..'); -const LERNA_BIN = path.resolve(PROJECT_ROOT, 'node_modules', '.bin', 'lerna'); - -export interface LernaPackageDescription { - name: string; - version: string; - private: boolean; - location: string; -} - -export function spawnSync(command: string, args: string[], options: SpawnSyncOptionsWithStringEncoding): SpawnSyncReturns; -export function spawnSync(command: string, args: string[], options: SpawnSyncOptionsWithStringEncoding, ignoreErrors: false): SpawnSyncReturns; -export function spawnSync(command: string, args: string[], options: SpawnSyncOptionsWithStringEncoding, ignoreErrors: true): SpawnSyncReturns | undefined; -export function spawnSync(command: string, args: string[], options: SpawnSyncOptionsWithStringEncoding, ignoreErrors = false): SpawnSyncReturns | undefined { - const result = spawn.sync(command, args, options); - if (result.error) { - console.error('spawn.sync returned error', result.error); - console.error(result.stdout); - console.error(result.stderr); - - if (!ignoreErrors) { - throw new Error(`Failed to spawn ${command}, args: ${args.join(',')}: ${result.error}`); - } else { - console.warn('Ignoring error and continuing...'); - } - } else if (result.status !== 0) { - console.error('spawn.sync exited with non-zero', result.status); - console.error(result.stdout); - console.error(result.stderr); - if (!ignoreErrors) { - throw new Error(`Spawn exited non-zero for ${command}, args: ${args.join(',')}: ${result.status}`); - } else { - console.warn('Ignoring error and continuing...'); - } - } - return result; -} - -export function bumpNpmPackages( - version: string, - spawnSyncFn: typeof spawnSync = spawnSync -): void { - if (!version || version === PLACEHOLDER_VERSION) { - console.info('mongosh: Not bumping package version, keeping at placeholder'); - return; - } - - console.info(`mongosh: Bumping package versions to ${version}`); - spawnSyncFn(LERNA_BIN, [ - 'version', - version, - '--no-changelog', - '--no-push', - '--exact', - '--no-git-tag-version', - '--force-publish', - '--yes' - ], { - stdio: 'inherit', - cwd: PROJECT_ROOT, - encoding: 'utf8' - }); - spawnSyncFn('git', ['status', '--porcelain'], { - stdio: 'inherit', - cwd: PROJECT_ROOT, - encoding: 'utf8' - }); -} - -export function publishNpmPackages( - listNpmPackagesFn: typeof listNpmPackages = listNpmPackages, - markBumpedFilesAsAssumeUnchangedFn: typeof markBumpedFilesAsAssumeUnchanged = markBumpedFilesAsAssumeUnchanged, - spawnSyncFn: typeof spawnSync = spawnSync -): void { - const packages = listNpmPackagesFn(); - - const versions = Array.from(new Set(packages.map(({ version }) => version))); - - if (versions.length !== 1) { - throw new Error(`Refusing to publish packages with multiple versions: ${versions}`); - } - - if (versions[0] === PLACEHOLDER_VERSION) { - throw new Error('Refusing to publish packages with placeholder version'); - } - - // Lerna requires a clean repository for a publish from-package (--force-publish does not have any effect here) - // we use git update-index --assume-unchanged on files we know have been bumped - markBumpedFilesAsAssumeUnchangedFn(packages, true); - try { - spawnSyncFn(LERNA_BIN, [ - 'publish', - 'from-package', - '--no-changelog', - '--no-push', - '--exact', - '--no-git-tag-version', - '--force-publish', - '--yes' - ], { - stdio: 'inherit', - cwd: PROJECT_ROOT, - encoding: 'utf8' - }); - } finally { - markBumpedFilesAsAssumeUnchangedFn(packages, false); - } -} - -export function listNpmPackages(): LernaPackageDescription[] { - const lernaListOutput = spawnSync( - LERNA_BIN, [ - 'list', - '--json', - '--all' - ], - { - cwd: PROJECT_ROOT, - encoding: 'utf8' - } - ); - - return JSON.parse(lernaListOutput.stdout); -} - -export function markBumpedFilesAsAssumeUnchanged( - packages: LernaPackageDescription[], - assumeUnchanged: boolean, - spawnSyncFn: typeof spawnSync = spawnSync -): void { - const filesToAssume = [ - path.resolve(PROJECT_ROOT, 'lerna.json') - ]; - packages.forEach(({ location }) => { - filesToAssume.push(path.resolve(location, 'package.json')); - filesToAssume.push(path.resolve(location, 'package-lock.json')); - }); - - filesToAssume.forEach(f => { - spawnSyncFn('git', [ - 'update-index', - assumeUnchanged ? '--assume-unchanged' : '--no-assume-unchanged', - f - ], { - stdio: 'inherit', - cwd: PROJECT_ROOT, - encoding: 'utf8' - }, true); - console.info(`File ${f} is now ${assumeUnchanged ? '' : 'NOT '}assumed to be unchanged`); - }); -} diff --git a/packages/build/src/npm-packages/bump.spec.ts b/packages/build/src/npm-packages/bump.spec.ts new file mode 100644 index 0000000000..906a4063a1 --- /dev/null +++ b/packages/build/src/npm-packages/bump.spec.ts @@ -0,0 +1,33 @@ +import { expect } from 'chai'; +import path from 'path'; +import sinon, { SinonStub } from 'sinon'; +import { bumpNpmPackages } from './bump'; + +describe('npm-packages bumpNpmPackages', () => { + let spawnSync: SinonStub; + + beforeEach(() => { + spawnSync = sinon.stub(); + }); + + it('does not do anything if no version or placeholder version is specified', () => { + bumpNpmPackages('', spawnSync); + bumpNpmPackages('0.0.0-dev.0', spawnSync); + expect(spawnSync).to.not.have.been.called; + }); + + it('calls lerna to bump package version', () => { + const lernaBin = path.resolve(__dirname, '..', '..', '..', '..', 'node_modules', '.bin', 'lerna'); + bumpNpmPackages('0.7.0', spawnSync); + expect(spawnSync).to.have.been.calledWith( + lernaBin, + ['version', '0.7.0', '--no-changelog', '--no-push', '--exact', '--no-git-tag-version', '--force-publish', '--yes'], + sinon.match.any + ); + expect(spawnSync).to.have.been.calledWith( + 'git', + ['status', '--porcelain'], + sinon.match.any + ); + }); +}); diff --git a/packages/build/src/npm-packages/bump.ts b/packages/build/src/npm-packages/bump.ts new file mode 100644 index 0000000000..6964425424 --- /dev/null +++ b/packages/build/src/npm-packages/bump.ts @@ -0,0 +1,33 @@ +import { LERNA_BIN, PLACEHOLDER_VERSION, PROJECT_ROOT } from './constants'; +import { spawnSync } from './spawn-sync'; + +export function bumpNpmPackages( + version: string, + spawnSyncFn: typeof spawnSync = spawnSync +): void { + if (!version || version === PLACEHOLDER_VERSION) { + console.info('mongosh: Not bumping package version, keeping at placeholder'); + return; + } + + console.info(`mongosh: Bumping package versions to ${version}`); + spawnSyncFn(LERNA_BIN, [ + 'version', + version, + '--no-changelog', + '--no-push', + '--exact', + '--no-git-tag-version', + '--force-publish', + '--yes' + ], { + stdio: 'inherit', + cwd: PROJECT_ROOT, + encoding: 'utf8' + }); + spawnSyncFn('git', ['status', '--porcelain'], { + stdio: 'inherit', + cwd: PROJECT_ROOT, + encoding: 'utf8' + }); +} diff --git a/packages/build/src/npm-packages/constants.ts b/packages/build/src/npm-packages/constants.ts new file mode 100644 index 0000000000..bc5f651ea5 --- /dev/null +++ b/packages/build/src/npm-packages/constants.ts @@ -0,0 +1,5 @@ +import path from 'path'; + +export const PLACEHOLDER_VERSION = '0.0.0-dev.0'; +export const PROJECT_ROOT = path.resolve(__dirname, '..', '..', '..', '..'); +export const LERNA_BIN = path.resolve(PROJECT_ROOT, 'node_modules', '.bin', 'lerna'); diff --git a/packages/build/src/npm-packages/index.ts b/packages/build/src/npm-packages/index.ts new file mode 100644 index 0000000000..5096a4ef4a --- /dev/null +++ b/packages/build/src/npm-packages/index.ts @@ -0,0 +1,3 @@ + +export { bumpNpmPackages } from './bump'; +export { publishNpmPackages } from './publish'; diff --git a/packages/build/src/npm-packages/list.spec.ts b/packages/build/src/npm-packages/list.spec.ts new file mode 100644 index 0000000000..414825ac04 --- /dev/null +++ b/packages/build/src/npm-packages/list.spec.ts @@ -0,0 +1,56 @@ +import { expect } from 'chai'; +import path from 'path'; +import sinon, { SinonStub } from 'sinon'; +import { LernaPackageDescription, listNpmPackages } from './list'; +import { markBumpedFilesAsAssumeUnchanged } from './publish'; + +describe('npm-packages list', () => { + describe('listNpmPackages', () => { + it('lists packages', () => { + const packages = listNpmPackages(); + expect(packages.length).to.be.greaterThan(1); + for (const { name, version } of packages) { + expect(name).to.be.a('string'); + expect(version).to.be.a('string'); + } + }); + }); + + + describe('markBumpedFilesAsAssumeUnchanged', () => { + let packages: LernaPackageDescription[]; + let expectedFiles: string[]; + let spawnSync: SinonStub; + + beforeEach(() => { + expectedFiles = [ + path.resolve(__dirname, '..', '..', '..', '..', 'lerna.json') + ]; + packages = listNpmPackages(); + packages.forEach(({ location }) => { + expectedFiles.push(path.resolve(location, 'package.json')); + expectedFiles.push(path.resolve(location, 'package-lock.json')); + }); + + spawnSync = sinon.stub(); + }); + + it('marks files with --assume-unchanged', () => { + markBumpedFilesAsAssumeUnchanged(packages, true, spawnSync); + expectedFiles.forEach(f => { + expect(spawnSync).to.have.been.calledWith( + 'git', ['update-index', '--assume-unchanged', f], sinon.match.any + ); + }); + }); + + it('marks files with --no-assume-unchanged', () => { + markBumpedFilesAsAssumeUnchanged(packages, false, spawnSync); + expectedFiles.forEach(f => { + expect(spawnSync).to.have.been.calledWith( + 'git', ['update-index', '--no-assume-unchanged', f], sinon.match.any + ); + }); + }); + }); +}); diff --git a/packages/build/src/npm-packages/list.ts b/packages/build/src/npm-packages/list.ts new file mode 100644 index 0000000000..911240e389 --- /dev/null +++ b/packages/build/src/npm-packages/list.ts @@ -0,0 +1,25 @@ +import { LERNA_BIN, PROJECT_ROOT } from './constants'; +import { spawnSync } from './spawn-sync'; + +export interface LernaPackageDescription { + name: string; + version: string; + private: boolean; + location: string; +} + +export function listNpmPackages(): LernaPackageDescription[] { + const lernaListOutput = spawnSync( + LERNA_BIN, [ + 'list', + '--json', + '--all' + ], + { + cwd: PROJECT_ROOT, + encoding: 'utf8' + } + ); + + return JSON.parse(lernaListOutput.stdout); +} diff --git a/packages/build/src/npm-packages/publish.spec.ts b/packages/build/src/npm-packages/publish.spec.ts new file mode 100644 index 0000000000..44334c6523 --- /dev/null +++ b/packages/build/src/npm-packages/publish.spec.ts @@ -0,0 +1,98 @@ +import { expect } from 'chai'; +import path from 'path'; +import sinon, { SinonStub } from 'sinon'; +import { publishNpmPackages } from './publish'; + +describe('npm-packages publishNpmPackages', () => { + let listNpmPackages: SinonStub; + let markBumpedFilesAsAssumeUnchanged: SinonStub; + let spawnSync: SinonStub; + + beforeEach(() => { + listNpmPackages = sinon.stub(); + markBumpedFilesAsAssumeUnchanged = sinon.stub(); + spawnSync = sinon.stub(); + }); + + it('fails if packages have different versions', () => { + listNpmPackages.returns([ + { name: 'packageA', version: '0.0.1' }, + { name: 'packageB', version: '0.0.2' } + ]); + try { + publishNpmPackages( + listNpmPackages, + markBumpedFilesAsAssumeUnchanged, + spawnSync + ); + } catch (e) { + expect(markBumpedFilesAsAssumeUnchanged).to.not.have.been.called; + expect(spawnSync).to.not.have.been.called; + return; + } + expect.fail('Expected error'); + }); + + it('fails if packages have placeholder versions', () => { + listNpmPackages.returns([ + { name: 'packageA', version: '0.0.0-dev.0' }, + { name: 'packageB', version: '0.0.0-dev.0' } + ]); + try { + publishNpmPackages( + listNpmPackages, + markBumpedFilesAsAssumeUnchanged, + spawnSync + ); + } catch (e) { + expect(markBumpedFilesAsAssumeUnchanged).to.not.have.been.called; + expect(spawnSync).to.not.have.been.called; + return; + } + expect.fail('Expected error'); + }); + + it('calls lerna to publish packages for a real version', () => { + const lernaBin = path.resolve(__dirname, '..', '..', '..', '..', 'node_modules', '.bin', 'lerna'); + const packages = [ + { name: 'packageA', version: '0.7.0' } + ]; + listNpmPackages.returns(packages); + + publishNpmPackages( + listNpmPackages, + markBumpedFilesAsAssumeUnchanged, + spawnSync + ); + + expect(markBumpedFilesAsAssumeUnchanged).to.have.been.calledWith(packages, true); + expect(spawnSync).to.have.been.calledWith( + lernaBin, + ['publish', 'from-package', '--no-changelog', '--no-push', '--exact', '--no-git-tag-version', '--force-publish', '--yes'], + sinon.match.any + ); + expect(markBumpedFilesAsAssumeUnchanged).to.have.been.calledWith(packages, false); + }); + + it('reverts the assume unchanged even on spawn failure', () => { + const packages = [ + { name: 'packageA', version: '0.7.0' } + ]; + listNpmPackages.returns(packages); + spawnSync.throws(new Error('meeep')); + + try { + publishNpmPackages( + listNpmPackages, + markBumpedFilesAsAssumeUnchanged, + spawnSync + ); + } catch (e) { + expect(markBumpedFilesAsAssumeUnchanged).to.have.been.calledWith(packages, true); + expect(spawnSync).to.have.been.called; + expect(markBumpedFilesAsAssumeUnchanged).to.have.been.calledWith(packages, false); + return; + } + expect.fail('Expected error'); + }); +}); diff --git a/packages/build/src/npm-packages/publish.ts b/packages/build/src/npm-packages/publish.ts new file mode 100644 index 0000000000..2d9fcd0151 --- /dev/null +++ b/packages/build/src/npm-packages/publish.ts @@ -0,0 +1,71 @@ +import path from 'path'; +import { LERNA_BIN, PLACEHOLDER_VERSION, PROJECT_ROOT } from './constants'; +import { LernaPackageDescription, listNpmPackages as listNpmPackagesFn } from './list'; +import { spawnSync } from './spawn-sync'; + +export function publishNpmPackages( + listNpmPackages: typeof listNpmPackagesFn = listNpmPackagesFn, + markBumpedFilesAsAssumeUnchangedFn: typeof markBumpedFilesAsAssumeUnchanged = markBumpedFilesAsAssumeUnchanged, + spawnSyncFn: typeof spawnSync = spawnSync +): void { + const packages = listNpmPackages(); + + const versions = Array.from(new Set(packages.map(({ version }) => version))); + + if (versions.length !== 1) { + throw new Error(`Refusing to publish packages with multiple versions: ${versions}`); + } + + if (versions[0] === PLACEHOLDER_VERSION) { + throw new Error('Refusing to publish packages with placeholder version'); + } + + // Lerna requires a clean repository for a publish from-package (--force-publish does not have any effect here) + // we use git update-index --assume-unchanged on files we know have been bumped + markBumpedFilesAsAssumeUnchangedFn(packages, true); + try { + spawnSyncFn(LERNA_BIN, [ + 'publish', + 'from-package', + '--no-changelog', + '--no-push', + '--exact', + '--no-git-tag-version', + '--force-publish', + '--yes' + ], { + stdio: 'inherit', + cwd: PROJECT_ROOT, + encoding: 'utf8' + }); + } finally { + markBumpedFilesAsAssumeUnchangedFn(packages, false); + } +} + +export function markBumpedFilesAsAssumeUnchanged( + packages: LernaPackageDescription[], + assumeUnchanged: boolean, + spawnSyncFn: typeof spawnSync = spawnSync +): void { + const filesToAssume = [ + path.resolve(PROJECT_ROOT, 'lerna.json') + ]; + packages.forEach(({ location }) => { + filesToAssume.push(path.resolve(location, 'package.json')); + filesToAssume.push(path.resolve(location, 'package-lock.json')); + }); + + filesToAssume.forEach(f => { + spawnSyncFn('git', [ + 'update-index', + assumeUnchanged ? '--assume-unchanged' : '--no-assume-unchanged', + f + ], { + stdio: 'inherit', + cwd: PROJECT_ROOT, + encoding: 'utf8' + }, true); + console.info(`File ${f} is now ${assumeUnchanged ? '' : 'NOT '}assumed to be unchanged`); + }); +} diff --git a/packages/build/src/npm-packages/spawn-sync.spec.ts b/packages/build/src/npm-packages/spawn-sync.spec.ts new file mode 100644 index 0000000000..e77ae51c22 --- /dev/null +++ b/packages/build/src/npm-packages/spawn-sync.spec.ts @@ -0,0 +1,39 @@ +import { expect } from 'chai'; +import { spawnSync } from './spawn-sync'; + +describe('npm-packages spawnSync', () => { + it('works for a valid command', () => { + const result = spawnSync('bash', ['-c', 'echo -n works'], { encoding: 'utf8' }); + expect(result.status).to.equal(0); + expect(result.stdout).to.equal('works'); + }); + + it('throws on ENOENT error', () => { + try { + spawnSync('notaprogram', [], { encoding: 'utf8' }); + } catch (e) { + return expect(e).to.not.be.undefined; + } + expect.fail('Expected error'); + }); + + it('throws on non-zero exit code', () => { + try { + spawnSync('bash', ['-c', 'exit 1'], { encoding: 'utf8' }); + } catch (e) { + return expect(e).to.not.be.undefined; + } + expect.fail('Expected error'); + }); + + it('ignores errors when asked to for ENOENT', () => { + const result = spawnSync('notaprogram', [], { encoding: 'utf8' }, true); + expect(result).to.not.be.undefined; + }); + + it('ignores errors when asked to for non-zero exit code', () => { + const result = spawnSync('bash', ['-c', 'exit 1'], { encoding: 'utf8' }, true); + expect(result).to.not.be.undefined; + expect(result?.status).to.equal(1); + }); +}); diff --git a/packages/build/src/npm-packages/spawn-sync.ts b/packages/build/src/npm-packages/spawn-sync.ts new file mode 100644 index 0000000000..4249e6546e --- /dev/null +++ b/packages/build/src/npm-packages/spawn-sync.ts @@ -0,0 +1,30 @@ +import { SpawnSyncOptionsWithStringEncoding, SpawnSyncReturns } from 'child_process'; +import * as spawn from 'cross-spawn'; + +export function spawnSync(command: string, args: string[], options: SpawnSyncOptionsWithStringEncoding): SpawnSyncReturns; +export function spawnSync(command: string, args: string[], options: SpawnSyncOptionsWithStringEncoding, ignoreErrors: false): SpawnSyncReturns; +export function spawnSync(command: string, args: string[], options: SpawnSyncOptionsWithStringEncoding, ignoreErrors: true): SpawnSyncReturns | undefined; +export function spawnSync(command: string, args: string[], options: SpawnSyncOptionsWithStringEncoding, ignoreErrors = false): SpawnSyncReturns | undefined { + const result = spawn.sync(command, args, options); + if (result.error) { + console.error('spawn.sync returned error', result.error); + console.error(result.stdout); + console.error(result.stderr); + + if (!ignoreErrors) { + throw new Error(`Failed to spawn ${command}, args: ${args.join(',')}: ${result.error}`); + } else { + console.warn('Ignoring error and continuing...'); + } + } else if (result.status !== 0) { + console.error('spawn.sync exited with non-zero', result.status); + console.error(result.stdout); + console.error(result.stderr); + if (!ignoreErrors) { + throw new Error(`Spawn exited non-zero for ${command}, args: ${args.join(',')}: ${result.status}`); + } else { + console.warn('Ignoring error and continuing...'); + } + } + return result; +}