Skip to content

Commit bd040bd

Browse files
clydinalan-agius4
authored andcommitted
feat(@angular/cli): provide additional status messaging for ng add
This adds a spinner as well as shows more information regarding what package version was selected to be installed. Closes #17983
1 parent 9f595f7 commit bd040bd

File tree

5 files changed

+118
-32
lines changed

5 files changed

+118
-32
lines changed

packages/angular/cli/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ ts_library(
7979
"@npm//@types/uuid",
8080
"@npm//ansi-colors",
8181
"@npm//jsonc-parser",
82+
"@npm//ora",
8283
],
8384
)
8485

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

Lines changed: 53 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import {
2222
fetchPackageManifest,
2323
fetchPackageMetadata,
2424
} from '../utilities/package-metadata';
25+
import { Spinner } from '../utilities/spinner';
2526
import { Schema as AddCommandSchema } from './add';
2627

2728
const npa = require('npm-package-arg');
@@ -79,12 +80,18 @@ export class AddCommand extends SchematicCommand<AddCommandSchema> {
7980
}
8081
}
8182

83+
const spinner = new Spinner();
84+
85+
spinner.start('Determining package manager...');
8286
const packageManager = await getPackageManager(this.context.root);
8387
const usingYarn = packageManager === PackageManager.Yarn;
88+
spinner.info(`Using package manager: ${colors.grey(packageManager)}`);
8489

8590
if (packageIdentifier.type === 'tag' && !packageIdentifier.rawSpec) {
8691
// only package name provided; search for viable version
8792
// plus special cases for packages that did not have peer deps setup
93+
spinner.start('Searching for compatible package version...');
94+
8895
let packageMetadata;
8996
try {
9097
packageMetadata = await fetchPackageMetadata(packageIdentifier.name, this.logger, {
@@ -93,7 +100,7 @@ export class AddCommand extends SchematicCommand<AddCommandSchema> {
93100
verbose: options.verbose,
94101
});
95102
} catch (e) {
96-
this.logger.error('Unable to fetch package metadata: ' + e.message);
103+
spinner.fail('Unable to load package information from registry: ' + e.message);
97104

98105
return 1;
99106
}
@@ -111,7 +118,10 @@ export class AddCommand extends SchematicCommand<AddCommandSchema> {
111118
) {
112119
packageIdentifier = npa.resolve('@angular/pwa', '0.12');
113120
}
121+
} else {
122+
packageIdentifier = npa.resolve(latestManifest.name, latestManifest.version);
114123
}
124+
spinner.succeed(`Found compatible package version: ${colors.grey(packageIdentifier)}.`);
115125
} else if (!latestManifest || (await this.hasMismatchedPeer(latestManifest))) {
116126
// 'latest' is invalid so search for most recent matching package
117127
const versionManifests = Object.values(packageMetadata.versions).filter(
@@ -129,17 +139,22 @@ export class AddCommand extends SchematicCommand<AddCommandSchema> {
129139
}
130140

131141
if (!newIdentifier) {
132-
this.logger.warn("Unable to find compatible package. Using 'latest'.");
142+
spinner.warn("Unable to find compatible package. Using 'latest'.");
133143
} else {
134144
packageIdentifier = newIdentifier;
145+
spinner.succeed(`Found compatible package version: ${colors.grey(packageIdentifier)}.`);
135146
}
147+
} else {
148+
packageIdentifier = npa.resolve(latestManifest.name, latestManifest.version);
149+
spinner.succeed(`Found compatible package version: ${colors.grey(packageIdentifier)}.`);
136150
}
137151
}
138152

139153
let collectionName = packageIdentifier.name;
140154
let savePackage: NgAddSaveDepedency | undefined;
141155

142156
try {
157+
spinner.start('Loading package information from registry...');
143158
const manifest = await fetchPackageManifest(packageIdentifier, this.logger, {
144159
registry: options.registry,
145160
verbose: options.verbose,
@@ -150,41 +165,51 @@ export class AddCommand extends SchematicCommand<AddCommandSchema> {
150165
collectionName = manifest.name;
151166

152167
if (await this.hasMismatchedPeer(manifest)) {
153-
this.logger.warn(
168+
spinner.warn(
154169
'Package has unmet peer dependencies. Adding the package may not succeed.',
155170
);
171+
} else {
172+
spinner.succeed(`Package information loaded.`);
156173
}
157174
} catch (e) {
158-
this.logger.error('Unable to fetch package manifest: ' + e.message);
175+
spinner.fail(`Unable to fetch package information for '${packageIdentifier}': ${e.message}`);
159176

160177
return 1;
161178
}
162179

163-
if (savePackage === false) {
164-
// Temporary packages are located in a different directory
165-
// Hence we need to resolve them using the temp path
166-
const tempPath = installTempPackage(
167-
packageIdentifier.raw,
168-
this.logger,
169-
packageManager,
170-
options.registry ? [`--registry="${options.registry}"`] : undefined,
171-
);
172-
const resolvedCollectionPath = require.resolve(
173-
join(collectionName, 'package.json'),
174-
{
175-
paths: [tempPath],
176-
},
177-
);
180+
try {
181+
spinner.start('Installing package...');
182+
if (savePackage === false) {
183+
// Temporary packages are located in a different directory
184+
// Hence we need to resolve them using the temp path
185+
const tempPath = installTempPackage(
186+
packageIdentifier.raw,
187+
undefined,
188+
packageManager,
189+
options.registry ? [`--registry="${options.registry}"`] : undefined,
190+
);
191+
const resolvedCollectionPath = require.resolve(
192+
join(collectionName, 'package.json'),
193+
{
194+
paths: [tempPath],
195+
},
196+
);
178197

179-
collectionName = dirname(resolvedCollectionPath);
180-
} else {
181-
installPackage(
182-
packageIdentifier.raw,
183-
this.logger,
184-
packageManager,
185-
savePackage,
186-
options.registry ? [`--registry="${options.registry}"`] : undefined,
187-
);
198+
collectionName = dirname(resolvedCollectionPath);
199+
} else {
200+
installPackage(
201+
packageIdentifier.raw,
202+
undefined,
203+
packageManager,
204+
savePackage,
205+
options.registry ? [`--registry="${options.registry}"`] : undefined,
206+
);
207+
}
208+
spinner.succeed('Package successfully installed.');
209+
} catch (error) {
210+
spinner.fail(`Package installation failed: ${error.message}`);
211+
212+
return 1;
188213
}
189214

190215
return this.executeSchematic(collectionName, options['--']);

packages/angular/cli/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
"npm-package-arg": "8.1.0",
4040
"npm-pick-manifest": "6.1.0",
4141
"open": "7.3.1",
42+
"ora": "5.3.0",
4243
"pacote": "11.2.3",
4344
"resolve": "1.19.0",
4445
"rimraf": "3.0.2",

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ interface PackageManagerOptions {
2626

2727
export function installPackage(
2828
packageName: string,
29-
logger: logging.Logger,
29+
logger: logging.Logger | undefined,
3030
packageManager: PackageManager = PackageManager.Npm,
3131
save: Exclude<NgAddSaveDepedency, false> = true,
3232
extraArgs: string[] = [],
@@ -40,7 +40,7 @@ export function installPackage(
4040
packageManagerArgs.silent,
4141
];
4242

43-
logger.info(colors.green(`Installing packages for tooling via ${packageManager}.`));
43+
logger?.info(colors.green(`Installing packages for tooling via ${packageManager}.`));
4444

4545
if (save === 'devDependencies') {
4646
installArgs.push(packageManagerArgs.saveDev);
@@ -61,12 +61,12 @@ export function installPackage(
6161
throw new Error(errorMessage + `Package install failed${errorMessage ? ', see above' : ''}.`);
6262
}
6363

64-
logger.info(colors.green(`Installed packages for tooling via ${packageManager}.`));
64+
logger?.info(colors.green(`Installed packages for tooling via ${packageManager}.`));
6565
}
6666

6767
export function installTempPackage(
6868
packageName: string,
69-
logger: logging.Logger,
69+
logger: logging.Logger | undefined,
7070
packageManager: PackageManager = PackageManager.Npm,
7171
extraArgs?: string[],
7272
): string {
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
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 * as ora from 'ora';
10+
import { colors } from './color';
11+
12+
export class Spinner {
13+
private readonly spinner: ora.Ora;
14+
15+
/** When false, only fail messages will be displayed. */
16+
enabled = true;
17+
18+
constructor(text?: string) {
19+
this.spinner = ora({
20+
text,
21+
// The below 2 options are needed because otherwise CTRL+C will be delayed
22+
// when the underlying process is sync.
23+
hideCursor: false,
24+
discardStdin: false,
25+
});
26+
}
27+
28+
set text(text: string) {
29+
this.spinner.text = text;
30+
}
31+
32+
succeed(text?: string): void {
33+
if (this.enabled) {
34+
this.spinner.succeed(text);
35+
}
36+
}
37+
38+
info(text?: string): void {
39+
this.spinner.info(text);
40+
}
41+
42+
fail(text?: string): void {
43+
this.spinner.fail(text && colors.redBright(text));
44+
}
45+
46+
warn(text?: string): void {
47+
this.spinner.fail(text && colors.yellowBright(text));
48+
}
49+
50+
stop(): void {
51+
this.spinner.stop();
52+
}
53+
54+
start(text?: string): void {
55+
if (this.enabled) {
56+
this.spinner.start(text);
57+
}
58+
}
59+
}

0 commit comments

Comments
 (0)