Skip to content

Commit 95cb13e

Browse files
alan-agius4clydin
authored andcommitted
fix(@angular/cli): change package installation to async
With this change we change the package installation to async. This is needed as otherwise during `ng-add` the spinner gets stuck. With this change we also add the spinner in the installation methods. (cherry picked from commit d7ef0d0)
1 parent c8d2d68 commit 95cb13e

File tree

3 files changed

+69
-63
lines changed

3 files changed

+69
-63
lines changed

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

+25-27
Original file line numberDiff line numberDiff line change
@@ -202,36 +202,34 @@ export class AddCommand extends SchematicCommand<AddCommandSchema> {
202202
}
203203
}
204204

205-
try {
206-
spinner.start('Installing package...');
207-
if (savePackage === false) {
208-
// Temporary packages are located in a different directory
209-
// Hence we need to resolve them using the temp path
210-
const tempPath = installTempPackage(
211-
packageIdentifier.raw,
212-
undefined,
213-
packageManager,
214-
options.registry ? [`--registry="${options.registry}"`] : undefined,
215-
);
216-
const resolvedCollectionPath = require.resolve(join(collectionName, 'package.json'), {
217-
paths: [tempPath],
218-
});
205+
if (savePackage === false) {
206+
// Temporary packages are located in a different directory
207+
// Hence we need to resolve them using the temp path
208+
const { status, tempPath } = await installTempPackage(
209+
packageIdentifier.raw,
210+
packageManager,
211+
options.registry ? [`--registry="${options.registry}"`] : undefined,
212+
);
213+
const resolvedCollectionPath = require.resolve(join(collectionName, 'package.json'), {
214+
paths: [tempPath],
215+
});
219216

220-
collectionName = dirname(resolvedCollectionPath);
221-
} else {
222-
installPackage(
223-
packageIdentifier.raw,
224-
undefined,
225-
packageManager,
226-
savePackage,
227-
options.registry ? [`--registry="${options.registry}"`] : undefined,
228-
);
217+
if (status !== 0) {
218+
return status;
229219
}
230-
spinner.succeed('Package successfully installed.');
231-
} catch (error) {
232-
spinner.fail(`Package installation failed: ${error.message}`);
233220

234-
return 1;
221+
collectionName = dirname(resolvedCollectionPath);
222+
} else {
223+
const status = await installPackage(
224+
packageIdentifier.raw,
225+
packageManager,
226+
savePackage,
227+
options.registry ? [`--registry="${options.registry}"`] : undefined,
228+
);
229+
230+
if (status !== 0) {
231+
return status;
232+
}
235233
}
236234

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

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

-1
Original file line numberDiff line numberDiff line change
@@ -285,7 +285,6 @@ export class UpdateCommand extends Command<UpdateCommandSchema> {
285285

286286
return runTempPackageBin(
287287
`@angular/cli@${options.next ? 'next' : 'latest'}`,
288-
this.logger,
289288
this.packageManager,
290289
process.argv.slice(2),
291290
);

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

+44-35
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,14 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9-
import { logging } from '@angular-devkit/core';
10-
import { spawnSync } from 'child_process';
9+
import { spawn, spawnSync } from 'child_process';
1110
import { existsSync, mkdtempSync, readFileSync, realpathSync, writeFileSync } from 'fs';
1211
import { tmpdir } from 'os';
1312
import { join, resolve } from 'path';
1413
import * as rimraf from 'rimraf';
1514
import { PackageManager } from '../lib/config/workspace-schema';
16-
import { colors } from '../utilities/color';
1715
import { NgAddSaveDepedency } from '../utilities/package-metadata';
16+
import { Spinner } from './spinner';
1817

1918
interface PackageManagerOptions {
2019
silent: string;
@@ -24,14 +23,13 @@ interface PackageManagerOptions {
2423
noLockfile: string;
2524
}
2625

27-
export function installPackage(
26+
export async function installPackage(
2827
packageName: string,
29-
logger: logging.Logger | undefined,
3028
packageManager: PackageManager = PackageManager.Npm,
3129
save: Exclude<NgAddSaveDepedency, false> = true,
3230
extraArgs: string[] = [],
3331
cwd = process.cwd(),
34-
) {
32+
): Promise<1 | 0> {
3533
const packageManagerArgs = getPackageManagerArguments(packageManager);
3634

3735
const installArgs: string[] = [
@@ -40,40 +38,48 @@ export function installPackage(
4038
packageManagerArgs.silent,
4139
];
4240

43-
logger?.info(colors.green(`Installing packages for tooling via ${packageManager}.`));
41+
const spinner = new Spinner();
42+
spinner.start('Installing package...');
4443

4544
if (save === 'devDependencies') {
4645
installArgs.push(packageManagerArgs.saveDev);
4746
}
47+
const bufferedOutput: { stream: NodeJS.WriteStream; data: Buffer }[] = [];
4848

49-
const { status, stderr, stdout, error } = spawnSync(
50-
packageManager,
51-
[...installArgs, ...extraArgs],
52-
{
49+
return new Promise((resolve, reject) => {
50+
const childProcess = spawn(packageManager, [...installArgs, ...extraArgs], {
5351
stdio: 'pipe',
5452
shell: true,
55-
encoding: 'utf8',
5653
cwd,
57-
},
58-
);
59-
60-
if (status !== 0) {
61-
let errorMessage = ((error && error.message) || stderr || stdout || '').trim();
62-
if (errorMessage) {
63-
errorMessage += '\n';
64-
}
65-
throw new Error(errorMessage + `Package install failed${errorMessage ? ', see above' : ''}.`);
66-
}
67-
68-
logger?.info(colors.green(`Installed packages for tooling via ${packageManager}.`));
54+
}).on('close', (code: number) => {
55+
if (code === 0) {
56+
spinner.succeed('Package successfully installed.');
57+
resolve(0);
58+
} else {
59+
spinner.stop();
60+
bufferedOutput.forEach(({ stream, data }) => stream.write(data));
61+
spinner.fail('Package install failed, see above.');
62+
reject(1);
63+
}
64+
});
65+
66+
childProcess.stdout?.on('data', (data: Buffer) =>
67+
bufferedOutput.push({ stream: process.stdout, data: data }),
68+
);
69+
childProcess.stderr?.on('data', (data: Buffer) =>
70+
bufferedOutput.push({ stream: process.stderr, data: data }),
71+
);
72+
});
6973
}
7074

71-
export function installTempPackage(
75+
export async function installTempPackage(
7276
packageName: string,
73-
logger: logging.Logger | undefined,
7477
packageManager: PackageManager = PackageManager.Npm,
7578
extraArgs?: string[],
76-
): string {
79+
): Promise<{
80+
status: 1 | 0;
81+
tempPath: string;
82+
}> {
7783
const tempPath = mkdtempSync(join(realpathSync(tmpdir()), 'angular-cli-packages-'));
7884

7985
// clean up temp directory on process exit
@@ -113,23 +119,26 @@ export function installTempPackage(
113119
packageManagerArgs.noLockfile,
114120
];
115121

116-
installPackage(packageName, logger, packageManager, true, installArgs, tempPath);
117-
118-
return tempNodeModules;
122+
return {
123+
status: await installPackage(packageName, packageManager, true, installArgs, tempPath),
124+
tempPath,
125+
};
119126
}
120127

121-
export function runTempPackageBin(
128+
export async function runTempPackageBin(
122129
packageName: string,
123-
logger: logging.Logger,
124130
packageManager: PackageManager = PackageManager.Npm,
125131
args: string[] = [],
126-
): number {
127-
const tempNodeModulesPath = installTempPackage(packageName, logger, packageManager);
132+
): Promise<number> {
133+
const { status: code, tempPath } = await installTempPackage(packageName, packageManager);
134+
if (code !== 0) {
135+
return code;
136+
}
128137

129138
// Remove version/tag etc... from package name
130139
// Ex: @angular/cli@latest -> @angular/cli
131140
const packageNameNoVersion = packageName.substring(0, packageName.lastIndexOf('@'));
132-
const pkgLocation = join(tempNodeModulesPath, packageNameNoVersion);
141+
const pkgLocation = join(tempPath, packageNameNoVersion);
133142
const packageJsonPath = join(pkgLocation, 'package.json');
134143

135144
// Get a binary location for this package

0 commit comments

Comments
 (0)