Skip to content

Commit a8fe4fc

Browse files
committed
fix(@schematics/angular): Allow skipping existing dependencies in E2E schematic
The E2E schematic will now (as it did previously) skip adding dependencies if they already exist within the `package.json` regardless of the specifier. This is accomplished with the `existing` option for the `addDependency` rule which allows defining the behavior for when a dependency already exists. Currently the two option behaviors are skip and replace with replace being the default to retain behavior for existing rule usages. (cherry picked from commit c9b2aa4)
1 parent cf83bfd commit a8fe4fc

File tree

4 files changed

+96
-5
lines changed

4 files changed

+96
-5
lines changed

packages/schematics/angular/e2e/index.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import {
2020
import {
2121
AngularBuilder,
2222
DependencyType,
23+
ExistingBehavior,
2324
addDependency,
2425
updateWorkspace,
2526
} from '@schematics/angular/utility';
@@ -92,7 +93,10 @@ export default function (options: E2eOptions): Rule {
9293
]),
9394
),
9495
...E2E_DEV_DEPENDENCIES.map((name) =>
95-
addDependency(name, latestVersions[name], { type: DependencyType.Dev }),
96+
addDependency(name, latestVersions[name], {
97+
type: DependencyType.Dev,
98+
existing: ExistingBehavior.Skip,
99+
}),
96100
),
97101
addScriptsToPackageJson(),
98102
]);

packages/schematics/angular/utility/dependency.ts

+30-1
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,24 @@ export enum InstallBehavior {
5252
Always,
5353
}
5454

55+
/**
56+
* An enum used to specify the existing dependency behavior for the {@link addDependency}
57+
* schematics rule. The existing behavior affects whether the named dependency will be added
58+
* to the `package.json` when the dependency is already present with a differing specifier.
59+
*/
60+
export enum ExistingBehavior {
61+
/**
62+
* The dependency will not be added or otherwise changed if it already exists.
63+
*/
64+
Skip,
65+
/**
66+
* The dependency's existing specifier will be replaced with the specifier provided in the
67+
* {@link addDependency} call. A warning will also be shown during schematic execution to
68+
* notify the user of the replacement.
69+
*/
70+
Replace,
71+
}
72+
5573
/**
5674
* Adds a package as a dependency to a `package.json`. By default the `package.json` located
5775
* at the schematic's root will be used. The `manifestPath` option can be used to explicitly specify
@@ -88,12 +106,18 @@ export function addDependency(
88106
* Defaults to {@link InstallBehavior.Auto}.
89107
*/
90108
install?: InstallBehavior;
109+
/**
110+
* The behavior to use when the dependency already exists within the `package.json`.
111+
* Defaults to {@link ExistingBehavior.Replace}.
112+
*/
113+
existing?: ExistingBehavior;
91114
} = {},
92115
): Rule {
93116
const {
94117
type = DependencyType.Default,
95118
packageJsonPath = '/package.json',
96119
install = InstallBehavior.Auto,
120+
existing = ExistingBehavior.Replace,
97121
} = options;
98122

99123
return (tree, context) => {
@@ -113,7 +137,12 @@ export function addDependency(
113137

114138
if (existingSpecifier) {
115139
// Already present but different specifier
116-
// This warning may become an error in the future
140+
141+
if (existing === ExistingBehavior.Skip) {
142+
return;
143+
}
144+
145+
// ExistingBehavior.Replace is the only other behavior currently
117146
context.logger.warn(
118147
`Package dependency "${name}" already exists with a different specifier. ` +
119148
`"${existingSpecifier}" will be replaced with "${specifier}".`,

packages/schematics/angular/utility/dependency_spec.ts

+60-2
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, InstallBehavior, addDependency } from './dependency';
18+
import { DependencyType, ExistingBehavior, InstallBehavior, addDependency } from './dependency';
1919

2020
interface LogEntry {
2121
type: 'warn';
@@ -63,7 +63,7 @@ describe('addDependency', () => {
6363
});
6464
});
6565

66-
it('warns if a package is already present with a different specifier', async () => {
66+
it('warns if a package is already present with a different specifier by default', async () => {
6767
const tree = new EmptyTree();
6868
tree.create(
6969
'/package.json',
@@ -85,6 +85,64 @@ describe('addDependency', () => {
8585
);
8686
});
8787

88+
it('warns if a package is already present with a different specifier with replace behavior', async () => {
89+
const tree = new EmptyTree();
90+
tree.create(
91+
'/package.json',
92+
JSON.stringify({
93+
dependencies: { '@angular/core': '^13.0.0' },
94+
}),
95+
);
96+
97+
const rule = addDependency('@angular/core', '^14.0.0', { existing: ExistingBehavior.Replace });
98+
99+
const { logs } = await testRule(rule, tree);
100+
expect(logs).toContain(
101+
jasmine.objectContaining({
102+
type: 'warn',
103+
message:
104+
'Package dependency "@angular/core" already exists with a different specifier. ' +
105+
'"^13.0.0" will be replaced with "^14.0.0".',
106+
}),
107+
);
108+
});
109+
110+
it('replaces the specifier if a package is already present with a different specifier with replace behavior', async () => {
111+
const tree = new EmptyTree();
112+
tree.create(
113+
'/package.json',
114+
JSON.stringify({
115+
dependencies: { '@angular/core': '^13.0.0' },
116+
}),
117+
);
118+
119+
const rule = addDependency('@angular/core', '^14.0.0', { existing: ExistingBehavior.Replace });
120+
121+
await testRule(rule, tree);
122+
123+
expect(tree.readJson('/package.json')).toEqual({
124+
dependencies: { '@angular/core': '^14.0.0' },
125+
});
126+
});
127+
128+
it('does not replace the specifier if a package is already present with a different specifier with skip behavior', async () => {
129+
const tree = new EmptyTree();
130+
tree.create(
131+
'/package.json',
132+
JSON.stringify({
133+
dependencies: { '@angular/core': '^13.0.0' },
134+
}),
135+
);
136+
137+
const rule = addDependency('@angular/core', '^14.0.0', { existing: ExistingBehavior.Skip });
138+
139+
await testRule(rule, tree);
140+
141+
expect(tree.readJson('/package.json')).toEqual({
142+
dependencies: { '@angular/core': '^13.0.0' },
143+
});
144+
});
145+
88146
it('adds a package version with other packages in alphabetical order', async () => {
89147
const tree = new EmptyTree();
90148
tree.create(

packages/schematics/angular/utility/index.ts

+1-1
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, InstallBehavior, addDependency } from './dependency';
21+
export { DependencyType, ExistingBehavior, InstallBehavior, addDependency } from './dependency';

0 commit comments

Comments
 (0)