Skip to content

Commit

Permalink
fix(@angular/cli): change package installation to async
Browse files Browse the repository at this point in the history
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)
  • Loading branch information
alan-agius4 authored and clydin committed May 5, 2021
1 parent c8d2d68 commit 95cb13e
Show file tree
Hide file tree
Showing 3 changed files with 69 additions and 63 deletions.
52 changes: 25 additions & 27 deletions packages/angular/cli/commands/add-impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -202,36 +202,34 @@ export class AddCommand extends SchematicCommand<AddCommandSchema> {
}
}

try {
spinner.start('Installing package...');
if (savePackage === false) {
// Temporary packages are located in a different directory
// Hence we need to resolve them using the temp path
const tempPath = installTempPackage(
packageIdentifier.raw,
undefined,
packageManager,
options.registry ? [`--registry="${options.registry}"`] : undefined,
);
const resolvedCollectionPath = require.resolve(join(collectionName, 'package.json'), {
paths: [tempPath],
});
if (savePackage === false) {
// Temporary packages are located in a different directory
// Hence we need to resolve them using the temp path
const { status, tempPath } = await installTempPackage(
packageIdentifier.raw,
packageManager,
options.registry ? [`--registry="${options.registry}"`] : undefined,
);
const resolvedCollectionPath = require.resolve(join(collectionName, 'package.json'), {
paths: [tempPath],
});

collectionName = dirname(resolvedCollectionPath);
} else {
installPackage(
packageIdentifier.raw,
undefined,
packageManager,
savePackage,
options.registry ? [`--registry="${options.registry}"`] : undefined,
);
if (status !== 0) {
return status;
}
spinner.succeed('Package successfully installed.');
} catch (error) {
spinner.fail(`Package installation failed: ${error.message}`);

return 1;
collectionName = dirname(resolvedCollectionPath);
} else {
const status = await installPackage(
packageIdentifier.raw,
packageManager,
savePackage,
options.registry ? [`--registry="${options.registry}"`] : undefined,
);

if (status !== 0) {
return status;
}
}

return this.executeSchematic(collectionName, options['--']);
Expand Down
1 change: 0 additions & 1 deletion packages/angular/cli/commands/update-impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,6 @@ export class UpdateCommand extends Command<UpdateCommandSchema> {

return runTempPackageBin(
`@angular/cli@${options.next ? 'next' : 'latest'}`,
this.logger,
this.packageManager,
process.argv.slice(2),
);
Expand Down
79 changes: 44 additions & 35 deletions packages/angular/cli/utilities/install-package.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,14 @@
* found in the LICENSE file at https://angular.io/license
*/

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

interface PackageManagerOptions {
silent: string;
Expand All @@ -24,14 +23,13 @@ interface PackageManagerOptions {
noLockfile: string;
}

export function installPackage(
export async function installPackage(
packageName: string,
logger: logging.Logger | undefined,
packageManager: PackageManager = PackageManager.Npm,
save: Exclude<NgAddSaveDepedency, false> = true,
extraArgs: string[] = [],
cwd = process.cwd(),
) {
): Promise<1 | 0> {
const packageManagerArgs = getPackageManagerArguments(packageManager);

const installArgs: string[] = [
Expand All @@ -40,40 +38,48 @@ export function installPackage(
packageManagerArgs.silent,
];

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

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

const { status, stderr, stdout, error } = spawnSync(
packageManager,
[...installArgs, ...extraArgs],
{
return new Promise((resolve, reject) => {
const childProcess = spawn(packageManager, [...installArgs, ...extraArgs], {
stdio: 'pipe',
shell: true,
encoding: 'utf8',
cwd,
},
);

if (status !== 0) {
let errorMessage = ((error && error.message) || stderr || stdout || '').trim();
if (errorMessage) {
errorMessage += '\n';
}
throw new Error(errorMessage + `Package install failed${errorMessage ? ', see above' : ''}.`);
}

logger?.info(colors.green(`Installed packages for tooling via ${packageManager}.`));
}).on('close', (code: number) => {
if (code === 0) {
spinner.succeed('Package successfully installed.');
resolve(0);
} else {
spinner.stop();
bufferedOutput.forEach(({ stream, data }) => stream.write(data));
spinner.fail('Package install failed, see above.');
reject(1);
}
});

childProcess.stdout?.on('data', (data: Buffer) =>
bufferedOutput.push({ stream: process.stdout, data: data }),
);
childProcess.stderr?.on('data', (data: Buffer) =>
bufferedOutput.push({ stream: process.stderr, data: data }),
);
});
}

export function installTempPackage(
export async function installTempPackage(
packageName: string,
logger: logging.Logger | undefined,
packageManager: PackageManager = PackageManager.Npm,
extraArgs?: string[],
): string {
): Promise<{
status: 1 | 0;
tempPath: string;
}> {
const tempPath = mkdtempSync(join(realpathSync(tmpdir()), 'angular-cli-packages-'));

// clean up temp directory on process exit
Expand Down Expand Up @@ -113,23 +119,26 @@ export function installTempPackage(
packageManagerArgs.noLockfile,
];

installPackage(packageName, logger, packageManager, true, installArgs, tempPath);

return tempNodeModules;
return {
status: await installPackage(packageName, packageManager, true, installArgs, tempPath),
tempPath,
};
}

export function runTempPackageBin(
export async function runTempPackageBin(
packageName: string,
logger: logging.Logger,
packageManager: PackageManager = PackageManager.Npm,
args: string[] = [],
): number {
const tempNodeModulesPath = installTempPackage(packageName, logger, packageManager);
): Promise<number> {
const { status: code, tempPath } = await installTempPackage(packageName, packageManager);
if (code !== 0) {
return code;
}

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

// Get a binary location for this package
Expand Down

0 comments on commit 95cb13e

Please sign in to comment.