diff --git a/src/environment-base.ts b/src/environment-base.ts index 5ddedbd4..46783dd8 100644 --- a/src/environment-base.ts +++ b/src/environment-base.ts @@ -739,6 +739,59 @@ export default class EnvironmentBase extends EventEmitter implements BaseEnviron }, match); } + /** + * Watch for package.json and queue package manager install task. + */ + public watchForPackageManagerInstall({ + cwd, + queueTask, + installTask, + }: { + cwd?: string; + queueTask?: boolean; + installTask?: (nodePackageManager: string | undefined, defaultTask: () => Promise) => void | Promise; + } = {}) { + if (cwd && !installTask) { + throw new Error(`installTask is required when using a custom cwd`); + } + + const npmCwd = cwd ?? this.cwd; + + const queueInstallTask = () => { + this.queueTask( + 'install', + async () => { + if (this.compatibilityMode === 'v4') { + debug('Running in generator < 5 compatibility. Package manager install is done by the generator.'); + return; + } + + const { adapter, sharedFs: memFs } = this; + const { skipInstall, nodePackageManager } = this.options; + await packageManagerInstallTask({ + adapter, + memFs, + packageJsonLocation: npmCwd, + skipInstall, + nodePackageManager, + customInstallTask: installTask ?? this.composedStore.customInstallTask, + }); + }, + { once: `package manager install ${npmCwd}` }, + ); + }; + + this.sharedFs.on('change', file => { + if (file === join(npmCwd, 'package.json')) { + queueInstallTask(); + } + }); + + if (queueTask) { + queueInstallTask(); + } + } + /** * Start Environment queue * @param {Object} options - Conflicter options. @@ -751,7 +804,10 @@ export default class EnvironmentBase extends EventEmitter implements BaseEnviron this.conflicterOptions.cwd = this.logCwd; this.queueCommit(); - this.queuePackageManagerInstall(); + this.queueTask('install', () => { + // Postpone watchForPackageManagerInstall to install priority since env's cwd can be changed by generators + this.watchForPackageManagerInstall({ queueTask: true }); + }); /* * Listen to errors and reject if emmited. @@ -834,38 +890,6 @@ export default class EnvironmentBase extends EventEmitter implements BaseEnviron queueCommit(); } - /** - * Queue environment's package manager install task. - */ - protected queuePackageManagerInstall() { - this.queueTask( - 'install', - async () => { - if (this.compatibilityMode === 'v4') { - debug('Running in generator < 5 compatibility. Package manager install is done by the generator.'); - return; - } - - const { adapter, sharedFs: memFs } = this; - const { skipInstall, nodePackageManager } = this.options; - const { customInstallTask } = this.composedStore; - await packageManagerInstallTask({ - adapter, - memFs, - packageJsonLocation: this.cwd, - skipInstall, - nodePackageManager, - customInstallTask, - }); - - memFs.once('change', file => { - if (file === join(this.cwd, 'package.json')) this.queuePackageManagerInstall(); - }); - }, - { once: 'package manager install' }, - ); - } - /** * Registers a specific `generator` to this environment. This generator is stored under * provided namespace, or a default namespace format if none if available. diff --git a/test/generator-features.js b/test/generator-features.js index a1a1008f..96595cc2 100644 --- a/test/generator-features.js +++ b/test/generator-features.js @@ -201,7 +201,6 @@ describe('environment (generator-features)', () => { } packageJsonTask() { - console.log('foo'); this.packageJson.set({ name: 'foo' }); } }, @@ -223,5 +222,58 @@ describe('environment (generator-features)', () => { assert.equal(typeof customInstallTask.getCall(0).args[1], 'function'); }); }); + + describe('with function customInstallTask and custom path', () => { + let runContext; + let customInstallTask; + let installTask; + beforeEach(async () => { + customInstallTask = sinon.stub(); + installTask = (pm, defaultTask) => defaultTask(pm); + runContext = helpers + .create('custom-install', undefined, { createEnv: getCreateEnv(BasicEnvironment) }) + .withOptions({ skipInstall: false }) + .withGenerators([ + [ + class extends FeaturesGenerator { + constructor(args, options) { + super(args, options, { customInstallTask }); + this.destinationRoot(this.destinationPath('foo')); + this.env.watchForPackageManagerInstall({ + cwd: this.destinationPath(), + installTask, + }); + } + + packageJsonTask() { + this.packageJson.set({ name: 'foo' }); + } + }, + { namespace: 'custom-install:app' }, + ], + ]); + await runContext.run(); + }); + + it('should not call customInstallTask', () => { + assert.equal(customInstallTask.callCount, 0, 'should not have been called'); + }); + + it('should call packageManagerInstallTask twice', () => { + expect(packageManagerInstallTask).toHaveBeenCalledTimes(2); + expect(packageManagerInstallTask).toHaveBeenNthCalledWith( + 2, + expect.objectContaining({ + customInstallTask, + }), + ); + expect(packageManagerInstallTask).toHaveBeenNthCalledWith( + 1, + expect.objectContaining({ + customInstallTask: installTask, + }), + ); + }); + }); }); });