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.
This commit is contained in:
Charles Lyding 2022-07-19 11:48:26 -04:00 committed by Charles
parent 08ce5670d8
commit c9b2aa41da
4 changed files with 96 additions and 5 deletions

View File

@ -20,6 +20,7 @@ import {
import {
AngularBuilder,
DependencyType,
ExistingBehavior,
addDependency,
updateWorkspace,
} from '@schematics/angular/utility';
@ -92,7 +93,10 @@ export default function (options: E2eOptions): Rule {
]),
),
...E2E_DEV_DEPENDENCIES.map((name) =>
addDependency(name, latestVersions[name], { type: DependencyType.Dev }),
addDependency(name, latestVersions[name], {
type: DependencyType.Dev,
existing: ExistingBehavior.Skip,
}),
),
addScriptsToPackageJson(),
]);

View File

@ -52,6 +52,24 @@ export enum InstallBehavior {
Always,
}
/**
* An enum used to specify the existing dependency behavior for the {@link addDependency}
* schematics rule. The existing behavior affects whether the named dependency will be added
* to the `package.json` when the dependency is already present with a differing specifier.
*/
export enum ExistingBehavior {
/**
* The dependency will not be added or otherwise changed if it already exists.
*/
Skip,
/**
* The dependency's existing specifier will be replaced with the specifier provided in the
* {@link addDependency} call. A warning will also be shown during schematic execution to
* notify the user of the replacement.
*/
Replace,
}
/**
* Adds a package as a dependency to a `package.json`. By default the `package.json` located
* at the schematic's root will be used. The `manifestPath` option can be used to explicitly specify
@ -88,12 +106,18 @@ export function addDependency(
* Defaults to {@link InstallBehavior.Auto}.
*/
install?: InstallBehavior;
/**
* The behavior to use when the dependency already exists within the `package.json`.
* Defaults to {@link ExistingBehavior.Replace}.
*/
existing?: ExistingBehavior;
} = {},
): Rule {
const {
type = DependencyType.Default,
packageJsonPath = '/package.json',
install = InstallBehavior.Auto,
existing = ExistingBehavior.Replace,
} = options;
return (tree, context) => {
@ -113,7 +137,12 @@ export function addDependency(
if (existingSpecifier) {
// Already present but different specifier
// This warning may become an error in the future
if (existing === ExistingBehavior.Skip) {
return;
}
// ExistingBehavior.Replace is the only other behavior currently
context.logger.warn(
`Package dependency "${name}" already exists with a different specifier. ` +
`"${existingSpecifier}" will be replaced with "${specifier}".`,

View File

@ -15,7 +15,7 @@ import {
callRule,
chain,
} from '@angular-devkit/schematics';
import { DependencyType, InstallBehavior, addDependency } from './dependency';
import { DependencyType, ExistingBehavior, InstallBehavior, addDependency } from './dependency';
interface LogEntry {
type: 'warn';
@ -63,7 +63,7 @@ describe('addDependency', () => {
});
});
it('warns if a package is already present with a different specifier', async () => {
it('warns if a package is already present with a different specifier by default', async () => {
const tree = new EmptyTree();
tree.create(
'/package.json',
@ -85,6 +85,64 @@ describe('addDependency', () => {
);
});
it('warns if a package is already present with a different specifier with replace behavior', async () => {
const tree = new EmptyTree();
tree.create(
'/package.json',
JSON.stringify({
dependencies: { '@angular/core': '^13.0.0' },
}),
);
const rule = addDependency('@angular/core', '^14.0.0', { existing: ExistingBehavior.Replace });
const { logs } = await testRule(rule, tree);
expect(logs).toContain(
jasmine.objectContaining({
type: 'warn',
message:
'Package dependency "@angular/core" already exists with a different specifier. ' +
'"^13.0.0" will be replaced with "^14.0.0".',
}),
);
});
it('replaces the specifier if a package is already present with a different specifier with replace behavior', async () => {
const tree = new EmptyTree();
tree.create(
'/package.json',
JSON.stringify({
dependencies: { '@angular/core': '^13.0.0' },
}),
);
const rule = addDependency('@angular/core', '^14.0.0', { existing: ExistingBehavior.Replace });
await testRule(rule, tree);
expect(tree.readJson('/package.json')).toEqual({
dependencies: { '@angular/core': '^14.0.0' },
});
});
it('does not replace the specifier if a package is already present with a different specifier with skip behavior', async () => {
const tree = new EmptyTree();
tree.create(
'/package.json',
JSON.stringify({
dependencies: { '@angular/core': '^13.0.0' },
}),
);
const rule = addDependency('@angular/core', '^14.0.0', { existing: ExistingBehavior.Skip });
await testRule(rule, tree);
expect(tree.readJson('/package.json')).toEqual({
dependencies: { '@angular/core': '^13.0.0' },
});
});
it('adds a package version with other packages in alphabetical order', async () => {
const tree = new EmptyTree();
tree.create(

View File

@ -18,4 +18,4 @@ export {
export { Builders as AngularBuilder } from './workspace-models';
// Package dependency related rules and types
export { DependencyType, InstallBehavior, addDependency } from './dependency';
export { DependencyType, ExistingBehavior, InstallBehavior, addDependency } from './dependency';