Skip to content

Commit 707911d

Browse files
clydindgp1130
authored andcommitted
feat(@schematics/angular): support controlling addDependency utility rule install behavior
When using the `addDependency` rule from `@schematics/angular/utility` a new option is now available that supports controlling the rule's behavior when scheduling the `NodePackageInstallTask`. Previously, the behavior was automatic and the rule would only schedule the task if it was not already scheduled by a previous `addDependency` rule usage for the `package.json`. This behavior is still the default behavior. However, it can now be customized per rule invocation via the `install` rule option which can be either `InstallBehavior.None`, `InstallBehavior.Auto`, or `InstallBehavior.Always`.
1 parent c3821ca commit 707911d

File tree

3 files changed

+174
-5
lines changed

3 files changed

+174
-5
lines changed

packages/schematics/angular/utility/dependency.ts

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,30 @@ export enum DependencyType {
2828
Peer = 'peerDependencies',
2929
}
3030

31+
/**
32+
* An enum used to specify the dependency installation behavior for the {@link addDependency}
33+
* schematics rule. The installation behavior affects if and when {@link NodePackageInstallTask}
34+
* will be scheduled when using the rule.
35+
*/
36+
export enum InstallBehavior {
37+
/**
38+
* No installation will occur as a result of the rule when specified.
39+
*
40+
* NOTE: This does not prevent other rules from scheduling a {@link NodePackageInstallTask}
41+
* which may install the dependency.
42+
*/
43+
None,
44+
/**
45+
* Automatically determine the need to schedule a {@link NodePackageInstallTask} based on
46+
* previous usage of the {@link addDependency} within the schematic.
47+
*/
48+
Auto,
49+
/**
50+
* Always schedule a {@link NodePackageInstallTask} when the rule is executed.
51+
*/
52+
Always,
53+
}
54+
3155
/**
3256
* Adds a package as a dependency to a `package.json`. By default the `package.json` located
3357
* at the schematic's root will be used. The `manifestPath` option can be used to explicitly specify
@@ -58,9 +82,19 @@ export function addDependency(
5882
* Defaults to `/package.json`.
5983
*/
6084
packageJsonPath?: string;
85+
/**
86+
* The dependency installation behavior to use to determine whether a
87+
* {@link NodePackageInstallTask} should be scheduled after adding the dependency.
88+
* Defaults to {@link InstallBehavior.Auto}.
89+
*/
90+
install?: InstallBehavior;
6191
} = {},
6292
): Rule {
63-
const { type = DependencyType.Default, packageJsonPath = '/package.json' } = options;
93+
const {
94+
type = DependencyType.Default,
95+
packageJsonPath = '/package.json',
96+
install = InstallBehavior.Auto,
97+
} = options;
6498

6599
return (tree, context) => {
66100
const manifest = tree.readJson(packageJsonPath) as MinimalPackageManifest;
@@ -86,7 +120,10 @@ export function addDependency(
86120
tree.overwrite(packageJsonPath, JSON.stringify(manifest, null, 2));
87121

88122
const installPaths = installTasks.get(context) ?? new Set<string>();
89-
if (!installPaths.has(packageJsonPath)) {
123+
if (
124+
install === InstallBehavior.Always ||
125+
(install === InstallBehavior.Auto && !installPaths.has(packageJsonPath))
126+
) {
90127
context.addTask(
91128
new NodePackageInstallTask({ workingDirectory: path.dirname(packageJsonPath) }),
92129
);

packages/schematics/angular/utility/dependency_spec.ts

Lines changed: 134 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import {
1515
callRule,
1616
chain,
1717
} from '@angular-devkit/schematics';
18-
import { DependencyType, addDependency } from './dependency';
18+
import { DependencyType, InstallBehavior, addDependency } from './dependency';
1919

2020
async function testRule(rule: Rule, tree: Tree): Promise<TaskConfigurationGenerator[]> {
2121
const tasks: TaskConfigurationGenerator[] = [];
@@ -192,6 +192,58 @@ describe('addDependency', () => {
192192
]);
193193
});
194194

195+
it('schedules a package install task when install behavior is auto', async () => {
196+
const tree = new EmptyTree();
197+
tree.create('/abc/package.json', JSON.stringify({}));
198+
199+
const rule = addDependency('@angular/core', '^14.0.0', {
200+
packageJsonPath: '/abc/package.json',
201+
install: InstallBehavior.Auto,
202+
});
203+
204+
const tasks = await testRule(rule, tree);
205+
206+
expect(tasks.map((task) => task.toConfiguration())).toEqual([
207+
{
208+
name: 'node-package',
209+
options: jasmine.objectContaining({ command: 'install', workingDirectory: '/abc' }),
210+
},
211+
]);
212+
});
213+
214+
it('schedules a package install task when install behavior is always', async () => {
215+
const tree = new EmptyTree();
216+
tree.create('/abc/package.json', JSON.stringify({}));
217+
218+
const rule = addDependency('@angular/core', '^14.0.0', {
219+
packageJsonPath: '/abc/package.json',
220+
install: InstallBehavior.Always,
221+
});
222+
223+
const tasks = await testRule(rule, tree);
224+
225+
expect(tasks.map((task) => task.toConfiguration())).toEqual([
226+
{
227+
name: 'node-package',
228+
options: jasmine.objectContaining({ command: 'install', workingDirectory: '/abc' }),
229+
},
230+
]);
231+
});
232+
233+
it('does not schedule a package install task when install behavior is none', async () => {
234+
const tree = new EmptyTree();
235+
tree.create('/abc/package.json', JSON.stringify({}));
236+
237+
const rule = addDependency('@angular/core', '^14.0.0', {
238+
packageJsonPath: '/abc/package.json',
239+
install: InstallBehavior.None,
240+
});
241+
242+
const tasks = await testRule(rule, tree);
243+
244+
expect(tasks).toEqual([]);
245+
});
246+
195247
it('does not schedule a package install task if version is the same', async () => {
196248
const tree = new EmptyTree();
197249
tree.create(
@@ -208,7 +260,7 @@ describe('addDependency', () => {
208260
expect(tasks).toEqual([]);
209261
});
210262

211-
it('only schedules one package install task for the same manifest path', async () => {
263+
it('only schedules one package install task for the same manifest path by default', async () => {
212264
const tree = new EmptyTree();
213265
tree.create('/package.json', JSON.stringify({}));
214266

@@ -227,6 +279,86 @@ describe('addDependency', () => {
227279
]);
228280
});
229281

282+
it('only schedules one package install task for the same manifest path with auto install behavior', async () => {
283+
const tree = new EmptyTree();
284+
tree.create('/package.json', JSON.stringify({}));
285+
286+
const rule = chain([
287+
addDependency('@angular/core', '^14.0.0', { install: InstallBehavior.Auto }),
288+
addDependency('@angular/common', '^14.0.0', { install: InstallBehavior.Auto }),
289+
]);
290+
291+
const tasks = await testRule(rule, tree);
292+
293+
expect(tasks.map((task) => task.toConfiguration())).toEqual([
294+
{
295+
name: 'node-package',
296+
options: jasmine.objectContaining({ command: 'install', workingDirectory: '/' }),
297+
},
298+
]);
299+
});
300+
301+
it('only schedules one package install task for the same manifest path with mixed auto/none install behavior', async () => {
302+
const tree = new EmptyTree();
303+
tree.create('/package.json', JSON.stringify({}));
304+
305+
const rule = chain([
306+
addDependency('@angular/core', '^14.0.0', { install: InstallBehavior.Auto }),
307+
addDependency('@angular/common', '^14.0.0', { install: InstallBehavior.None }),
308+
]);
309+
310+
const tasks = await testRule(rule, tree);
311+
312+
expect(tasks.map((task) => task.toConfiguration())).toEqual([
313+
{
314+
name: 'node-package',
315+
options: jasmine.objectContaining({ command: 'install', workingDirectory: '/' }),
316+
},
317+
]);
318+
});
319+
320+
it('only schedules one package install task for the same manifest path with mixed always then auto install behavior', async () => {
321+
const tree = new EmptyTree();
322+
tree.create('/package.json', JSON.stringify({}));
323+
324+
const rule = chain([
325+
addDependency('@angular/core', '^14.0.0', { install: InstallBehavior.Always }),
326+
addDependency('@angular/common', '^14.0.0', { install: InstallBehavior.Auto }),
327+
]);
328+
329+
const tasks = await testRule(rule, tree);
330+
331+
expect(tasks.map((task) => task.toConfiguration())).toEqual([
332+
{
333+
name: 'node-package',
334+
options: jasmine.objectContaining({ command: 'install', workingDirectory: '/' }),
335+
},
336+
]);
337+
});
338+
339+
it('schedules multiple package install tasks for the same manifest path with mixed auto then always install behavior', async () => {
340+
const tree = new EmptyTree();
341+
tree.create('/package.json', JSON.stringify({}));
342+
343+
const rule = chain([
344+
addDependency('@angular/core', '^14.0.0', { install: InstallBehavior.Auto }),
345+
addDependency('@angular/common', '^14.0.0', { install: InstallBehavior.Always }),
346+
]);
347+
348+
const tasks = await testRule(rule, tree);
349+
350+
expect(tasks.map((task) => task.toConfiguration())).toEqual([
351+
{
352+
name: 'node-package',
353+
options: jasmine.objectContaining({ command: 'install', workingDirectory: '/' }),
354+
},
355+
{
356+
name: 'node-package',
357+
options: jasmine.objectContaining({ command: 'install', workingDirectory: '/' }),
358+
},
359+
]);
360+
});
361+
230362
it('schedules a package install task for each manifest path present', async () => {
231363
const tree = new EmptyTree();
232364
tree.create('/package.json', JSON.stringify({}));

packages/schematics/angular/utility/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,4 @@ export {
1818
export { Builders as AngularBuilder } from './workspace-models';
1919

2020
// Package dependency related rules and types
21-
export { DependencyType, addDependency } from './dependency';
21+
export { DependencyType, InstallBehavior, addDependency } from './dependency';

0 commit comments

Comments
 (0)