Skip to content

Commit d133ba6

Browse files
alan-agius4vikerman
authored andcommitted
feat(@angular/cli): add support for ng-add packages that should not be saved as dependencies
With this change the CLI offers a way for a package authors to specify if during `ng add` the package should be saved as a `dependencies`, `devDependencies` or not saved at all. Such config needs to be specified in `package.json` Example: ```json "ng-add": { "save": false } ``` Possible values are; - false - Don't add the package to `package.json` - true - Add the package to the `dependencies` - `dependencies` - Add the package to the `dependencies` - `devDependencies` - Add the package to the `devDependencies` Closes #12003 , closes #15764 and closes #13237
1 parent 750baf9 commit d133ba6

File tree

7 files changed

+123
-22
lines changed

7 files changed

+123
-22
lines changed

packages/angular/cli/commands/add-impl.ts

Lines changed: 28 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,11 @@ import { isPackageNameSafeForAnalytics } from '../models/analytics';
1313
import { Arguments } from '../models/interface';
1414
import { RunSchematicOptions, SchematicCommand } from '../models/schematic-command';
1515
import npmInstall from '../tasks/npm-install';
16+
import npmUninstall from '../tasks/npm-uninstall';
1617
import { colors } from '../utilities/color';
1718
import { getPackageManager } from '../utilities/package-manager';
1819
import {
20+
NgAddSaveDepedency,
1921
PackageManifest,
2022
fetchPackageManifest,
2123
fetchPackageMetadata,
@@ -113,31 +115,39 @@ export class AddCommand extends SchematicCommand<AddCommandSchema> {
113115
}
114116

115117
let collectionName = packageIdentifier.name;
116-
if (!packageIdentifier.registry) {
117-
try {
118-
const manifest = await fetchPackageManifest(packageIdentifier, this.logger, {
119-
registry: options.registry,
120-
verbose: options.verbose,
121-
usingYarn,
122-
});
118+
let dependencyType: NgAddSaveDepedency | undefined;
123119

124-
collectionName = manifest.name;
120+
try {
121+
const manifest = await fetchPackageManifest(packageIdentifier, this.logger, {
122+
registry: options.registry,
123+
verbose: options.verbose,
124+
usingYarn,
125+
});
125126

126-
if (await this.hasMismatchedPeer(manifest)) {
127-
this.logger.warn(
128-
'Package has unmet peer dependencies. Adding the package may not succeed.',
129-
);
130-
}
131-
} catch (e) {
132-
this.logger.error('Unable to fetch package manifest: ' + e.message);
127+
dependencyType = manifest['ng-add'] && manifest['ng-add'].save;
128+
collectionName = manifest.name;
133129

134-
return 1;
130+
if (await this.hasMismatchedPeer(manifest)) {
131+
this.logger.warn(
132+
'Package has unmet peer dependencies. Adding the package may not succeed.',
133+
);
135134
}
135+
} catch (e) {
136+
this.logger.error('Unable to fetch package manifest: ' + e.message);
137+
138+
return 1;
136139
}
137140

138-
await npmInstall(packageIdentifier.raw, this.logger, packageManager, this.workspace.root);
141+
await npmInstall(packageIdentifier.raw, this.logger, packageManager, dependencyType);
142+
143+
const schematicResult = await this.executeSchematic(collectionName, options['--']);
144+
145+
if (dependencyType === false) {
146+
// Uninstall the package if it was not meant to be retained.
147+
return npmUninstall(packageIdentifier.raw, this.logger, packageManager);
148+
}
139149

140-
return this.executeSchematic(collectionName, options['--']);
150+
return schematicResult;
141151
}
142152

143153
async reportAnalytics(

packages/angular/cli/tasks/npm-install.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,13 @@
99
import { logging } from '@angular-devkit/core';
1010
import { spawn } from 'child_process';
1111
import { colors } from '../utilities/color';
12+
import { NgAddSaveDepedency } from '../utilities/package-metadata';
1213

1314
export default async function(
1415
packageName: string,
1516
logger: logging.Logger,
1617
packageManager: string,
17-
projectRoot: string,
18-
save = true,
18+
save: NgAddSaveDepedency = true,
1919
) {
2020
const installArgs: string[] = [];
2121
switch (packageManager) {
@@ -42,9 +42,14 @@ export default async function(
4242
}
4343

4444
if (!save) {
45+
// IMP: yarn doesn't have a no-save option
4546
installArgs.push('--no-save');
4647
}
4748

49+
if (save === 'devDependencies') {
50+
installArgs.push(packageManager === 'yarn' ? '--dev' : '--save-dev');
51+
}
52+
4853
installArgs.push('--quiet');
4954

5055
await new Promise((resolve, reject) => {
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/**
2+
* @license
3+
* Copyright Google Inc. All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
import { logging } from '@angular-devkit/core';
10+
import { spawn } from 'child_process';
11+
import { colors } from '../utilities/color';
12+
13+
export default async function(
14+
packageName: string,
15+
logger: logging.Logger,
16+
packageManager: string,
17+
) {
18+
const installArgs: string[] = [];
19+
switch (packageManager) {
20+
case 'cnpm':
21+
case 'pnpm':
22+
case 'npm':
23+
installArgs.push('uninstall');
24+
break;
25+
26+
case 'yarn':
27+
installArgs.push('remove');
28+
break;
29+
30+
default:
31+
packageManager = 'npm';
32+
installArgs.push('uninstall');
33+
break;
34+
}
35+
36+
installArgs.push(packageName, '--quiet');
37+
38+
logger.info(colors.green(`Uninstalling packages for tooling via ${packageManager}.`));
39+
40+
await new Promise((resolve, reject) => {
41+
spawn(packageManager, installArgs, { stdio: 'inherit', shell: true }).on(
42+
'close',
43+
(code: number) => {
44+
if (code === 0) {
45+
logger.info(colors.green(`Uninstalling packages for tooling via ${packageManager}.`));
46+
resolve();
47+
} else {
48+
reject('Package uninstallation failed, see above.');
49+
}
50+
},
51+
);
52+
});
53+
}

packages/angular/cli/utilities/package-metadata.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ export interface PackageDependencies {
1818
[dependency: string]: string;
1919
}
2020

21+
export type NgAddSaveDepedency = 'dependencies' | 'devDependencies' | boolean;
22+
2123
export interface PackageIdentifier {
2224
type: 'git' | 'tag' | 'version' | 'range' | 'file' | 'directory' | 'remote';
2325
name: string;
@@ -39,8 +41,9 @@ export interface PackageManifest {
3941
devDependencies: PackageDependencies;
4042
peerDependencies: PackageDependencies;
4143
optionalDependencies: PackageDependencies;
42-
43-
'ng-add'?: {};
44+
'ng-add'?: {
45+
save?: NgAddSaveDepedency;
46+
};
4447
'ng-update'?: {
4548
migrations: string;
4649
packageGroup: { [name: string]: string };

packages/angular/cli/utilities/package-tree.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@
55
* Use of this source code is governed by an MIT-style license that can be
66
* found in the LICENSE file at https://angular.io/license
77
*/
8+
9+
10+
import { NgAddSaveDepedency } from './package-metadata';
11+
812
export interface PackageTreeNodeBase {
913
name: string;
1014
path: string;
@@ -22,6 +26,9 @@ export interface PackageTreeNodeBase {
2226
'ng-update'?: {
2327
migrations?: string;
2428
};
29+
'ng-add'?: {
30+
save?: NgAddSaveDepedency;
31+
};
2532
};
2633
parent?: PackageTreeNode;
2734
children: PackageTreeNode[];

packages/angular/pwa/package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@
99
"schematics"
1010
],
1111
"schematics": "./collection.json",
12+
"ng-add": {
13+
"save": false
14+
},
1215
"dependencies": {
1316
"@angular-devkit/core": "0.0.0",
1417
"@angular-devkit/schematics": "0.0.0",
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { join } from 'path';
2+
import { expectFileToExist, expectFileToMatch, rimraf, readFile } from '../../../utils/fs';
3+
import { ng } from '../../../utils/process';
4+
5+
export default async function () {
6+
// forcibly remove in case another test doesn't clean itself up
7+
await rimraf('node_modules/@angular/pwa');
8+
9+
await ng('add', '@angular/pwa');
10+
11+
await expectFileToExist(join(process.cwd(), 'src/manifest.webmanifest'));
12+
13+
// Angular PWA doesn't install as a dependency
14+
const { dependencies, devDependencies } = JSON.parse(await readFile(join(process.cwd(), 'package.json')));
15+
const hasPWADep = Object.keys({ ...dependencies, ...devDependencies })
16+
.some(d => d === '@angular/pwa');
17+
if (hasPWADep) {
18+
throw new Error(`Expected 'package.json' not to contain a dependency on '@angular/pwa'.`);
19+
}
20+
}

0 commit comments

Comments
 (0)