Skip to content

Commit c608686

Browse files
committed
feat: add support to ask customInstallTask
1 parent 321c466 commit c608686

5 files changed

Lines changed: 117 additions & 11 deletions

File tree

src/composed-store.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import crypto from 'node:crypto';
22
import { toNamespace } from '@yeoman/namespace';
33
import type { BaseGenerator, Logger } from '@yeoman/types';
44
import createdLogger from 'debug';
5-
import type { InstallTask } from './package-manager.ts';
5+
import type { PackageManagerInstallTaskOptions } from './package-manager.ts';
66

77
const debug = createdLogger('yeoman:environment:composed-store');
88

@@ -22,7 +22,7 @@ export class ComposedStore {
2222
return this.findUniqueFeature('customCommitTask');
2323
}
2424

25-
get customInstallTask(): InstallTask | undefined {
25+
get customInstallTask(): PackageManagerInstallTaskOptions['customInstallTask'] | undefined {
2626
return this.findUniqueFeature('customInstallTask');
2727
}
2828

src/package-manager.ts

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ export type PackageManagerInstallTaskOptions = {
1515
packageJsonLocation: string;
1616
adapter: InputOutputAdapter;
1717
nodePackageManager?: string;
18-
customInstallTask?: boolean | InstallTask;
18+
customInstallTask?: boolean | InstallTask | 'ask';
1919
skipInstall?: boolean;
2020
};
2121

@@ -46,7 +46,11 @@ export async function packageManagerInstallTask({
4646
* @return {Vinyl | undefined} a Vinyl file.
4747
*/
4848
function getDestinationPackageJson() {
49-
return memFs.get(join(packageJsonLocation, 'package.json'));
49+
const packageJsonFile = join(packageJsonLocation, 'package.json');
50+
51+
if (memFs.existsInMemory(packageJsonFile)) {
52+
return memFs.get(packageJsonFile);
53+
}
5054
}
5155

5256
/**
@@ -56,14 +60,14 @@ export async function packageManagerInstallTask({
5660
*/
5761
function isDestinationPackageJsonCommitted() {
5862
const file = getDestinationPackageJson();
59-
return file.committed;
63+
return file?.committed;
6064
}
6165

6266
if (!getDestinationPackageJson()) {
6367
return false;
6468
}
6569

66-
if (customInstallTask && typeof customInstallTask !== 'function') {
70+
if (customInstallTask && typeof customInstallTask !== 'function' && customInstallTask !== 'ask') {
6771
debug('Install disabled by customInstallTask');
6872
return false;
6973
}
@@ -102,9 +106,19 @@ Running ${packageManagerName} install for you to install the required dependenci
102106
return true;
103107
};
104108

105-
if (customInstallTask) {
109+
if (typeof customInstallTask === 'function') {
106110
return customInstallTask(packageManagerName, execPackageManager);
107111
}
112+
if (customInstallTask === 'ask') {
113+
const { runInstall } = await adapter.prompt({
114+
type: 'confirm',
115+
name: 'runInstall',
116+
message: `Do you want to run ${packageManagerName} install now?`,
117+
});
118+
if (!runInstall) {
119+
return false;
120+
}
121+
}
108122

109123
return execPackageManager();
110124
}

test/generator-features.js

Lines changed: 93 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
11
import assert from 'node:assert';
22
import { stub } from 'sinon';
33
import { after, afterEach, before, beforeEach, describe, esmocha, expect, it } from 'esmocha';
4-
import helpers, { getCreateEnv as getCreateEnvironment } from './helpers.js';
4+
import helpers, { getCreateEnv as getCreateEnvironment, result } from './helpers.js';
55
import { greaterThan5 } from './generator-versions.js';
66

7-
const { commitSharedFsTask } = await esmocha.mock('../src/commit.ts', import('../src/commit.ts'));
8-
const { packageManagerInstallTask } = await esmocha.mock('../src/package-manager.ts', import('../src/package-manager.ts'));
7+
const commitModule = await import('../src/commit.ts');
8+
const { commitSharedFsTask: originalCommitSharedFsTask } = commitModule;
9+
const { commitSharedFsTask } = await esmocha.mock('../src/commit.ts', Promise.resolve(commitModule));
910
const { execa } = await esmocha.mock('execa', import('execa'));
11+
const packageManagerModule = await import('../src/package-manager.ts');
12+
const { packageManagerInstallTask: originalPackageManagerInstallTask } = packageManagerModule;
13+
const { packageManagerInstallTask } = await esmocha.mock('../src/package-manager.ts', Promise.resolve(packageManagerModule));
1014
const { default: BasicEnvironment } = await import('../src/environment-base.ts');
1115

1216
for (const generatorVersion of greaterThan5) {
@@ -216,6 +220,92 @@ for (const generatorVersion of greaterThan5) {
216220
});
217221
});
218222

223+
describe('with ask customInstallTask', () => {
224+
describe('accepting to run install', () => {
225+
beforeEach(async () => {
226+
commitSharedFsTask.mockImplementation(originalCommitSharedFsTask);
227+
packageManagerInstallTask.mockImplementation(originalPackageManagerInstallTask);
228+
await helpers
229+
.run('custom-install', undefined, { createEnv: getCreateEnvironment(BasicEnvironment) })
230+
.withOptions({ skipInstall: false })
231+
.withAnswers({ runInstall: true })
232+
.withGenerators([
233+
[
234+
class extends FeaturesGenerator {
235+
constructor(arguments_, options, features) {
236+
super(arguments_, options, { ...features, customInstallTask: 'ask' });
237+
}
238+
239+
packageJsonTask() {
240+
this.packageJson.set({ name: 'foo' });
241+
}
242+
},
243+
{ namespace: 'custom-install:app' },
244+
],
245+
]);
246+
});
247+
248+
it('should write package.json', () => {
249+
result.assertFile('package.json');
250+
});
251+
252+
it('should call packageManagerInstallTask', () => {
253+
expect(packageManagerInstallTask).toHaveBeenCalledTimes(1);
254+
expect(packageManagerInstallTask).toHaveBeenCalledWith(
255+
expect.objectContaining({
256+
customInstallTask: 'ask',
257+
}),
258+
);
259+
});
260+
261+
it('should call execa', () => {
262+
expect(execa).toHaveBeenCalled();
263+
});
264+
});
265+
266+
describe('declining to run install', () => {
267+
beforeEach(async () => {
268+
commitSharedFsTask.mockImplementation(originalCommitSharedFsTask);
269+
packageManagerInstallTask.mockImplementation(originalPackageManagerInstallTask);
270+
await helpers
271+
.run('custom-install', undefined, { createEnv: getCreateEnvironment(BasicEnvironment) })
272+
.withOptions({ skipInstall: false })
273+
.withAnswers({ runInstall: false })
274+
.withGenerators([
275+
[
276+
class extends FeaturesGenerator {
277+
constructor(arguments_, options, features) {
278+
super(arguments_, options, { ...features, customInstallTask: 'ask' });
279+
}
280+
281+
packageJsonTask() {
282+
this.packageJson.set({ name: 'foo' });
283+
}
284+
},
285+
{ namespace: 'custom-install:app' },
286+
],
287+
]);
288+
});
289+
290+
it('should write package.json', () => {
291+
result.assertFile('package.json');
292+
});
293+
294+
it('should call packageManagerInstallTask', () => {
295+
expect(packageManagerInstallTask).toHaveBeenCalledTimes(1);
296+
expect(packageManagerInstallTask).toHaveBeenCalledWith(
297+
expect.objectContaining({
298+
customInstallTask: 'ask',
299+
}),
300+
);
301+
});
302+
303+
it('should not call execa', () => {
304+
expect(execa).not.toHaveBeenCalled();
305+
});
306+
});
307+
});
308+
219309
describe('with function customInstallTask and custom path', () => {
220310
let runContext;
221311
let customInstallTask;

test/helpers.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,5 @@ export const getCreateEnv =
99
export default createHelpers({
1010
createEnv: getCreateEnv(Environment),
1111
});
12+
13+
export { result } from 'yeoman-test';

test/package-manager.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ describe('environment (package-manager)', () => {
2626
beforeEach(() => {
2727
adapter = { log: esmocha.fn() };
2828
execa.mockReturnValue();
29-
memFs = { get: esmocha.fn() };
29+
memFs = { get: esmocha.fn(), existsInMemory: esmocha.fn().mockReturnValue(true) };
3030
packageJsonLocation = path.join(__dirname, 'fixtures', 'package-manager', 'npm');
3131
whichPackageManager.mockResolvedValue('npm');
3232
});

0 commit comments

Comments
 (0)