Skip to content

Commit 0921dbc

Browse files
committed
feat(@angular/cli): support yarn workspaces in ng update
1 parent 6c0ab83 commit 0921dbc

File tree

5 files changed

+168
-135
lines changed

5 files changed

+168
-135
lines changed

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

+67-50
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,12 @@ import {
2727
fetchPackageManifest,
2828
fetchPackageMetadata,
2929
} from '../utilities/package-metadata';
30-
import { PackageTreeNode, findNodeDependencies, readPackageTree } from '../utilities/package-tree';
30+
import {
31+
PackageTreeNode,
32+
findPackageJson,
33+
getTreeFromNodeModules,
34+
readPackageJson,
35+
} from '../utilities/package-tree';
3136
import { Schema as UpdateCommandSchema } from './update';
3237

3338
const npa = require('npm-package-arg') as (selector: string) => PackageIdentifier;
@@ -40,7 +45,7 @@ const oldConfigFileNames = ['.angular-cli.json', 'angular-cli.json'];
4045

4146
const NG_VERSION_9_POST_MSG = colors.cyan(
4247
'\nYour project has been updated to Angular version 9!\n' +
43-
'For more info, please see: https://v9.angular.io/guide/updating-to-version-9',
48+
'For more info, please see: https://v9.angular.io/guide/updating-to-version-9',
4449
);
4550

4651
/**
@@ -84,7 +89,7 @@ export class UpdateCommand extends Command<UpdateCommandSchema> {
8489
let logs: string[] = [];
8590
const files = new Set<string>();
8691

87-
const reporterSubscription = this.workflow.reporter.subscribe(event => {
92+
const reporterSubscription = this.workflow.reporter.subscribe((event) => {
8893
// Strip leading slash to prevent confusion.
8994
const eventPath = event.path.startsWith('/') ? event.path.substr(1) : event.path;
9095

@@ -114,11 +119,11 @@ export class UpdateCommand extends Command<UpdateCommandSchema> {
114119
}
115120
});
116121

117-
const lifecycleSubscription = this.workflow.lifeCycle.subscribe(event => {
122+
const lifecycleSubscription = this.workflow.lifeCycle.subscribe((event) => {
118123
if (event.kind == 'end' || event.kind == 'post-tasks-start') {
119124
if (!error) {
120125
// Output the logging queue, no error happened.
121-
logs.forEach(log => this.logger.info(log));
126+
logs.forEach((log) => this.logger.info(log));
122127
logs = [];
123128
}
124129
}
@@ -141,12 +146,14 @@ export class UpdateCommand extends Command<UpdateCommandSchema> {
141146
return { success: !error, files };
142147
} catch (e) {
143148
if (e instanceof UnsuccessfulWorkflowExecution) {
144-
this.logger.error(`${colors.symbols.cross} Migration failed. See above for further details.\n`);
149+
this.logger.error(
150+
`${colors.symbols.cross} Migration failed. See above for further details.\n`,
151+
);
145152
} else {
146153
const logPath = writeErrorToLogFile(e);
147154
this.logger.fatal(
148155
`${colors.symbols.cross} Migration failed: ${e.message}\n` +
149-
` See "${logPath}" for further details.\n`,
156+
` See "${logPath}" for further details.\n`,
150157
);
151158
}
152159

@@ -164,7 +171,7 @@ export class UpdateCommand extends Command<UpdateCommandSchema> {
164171
commit?: boolean,
165172
): Promise<boolean> {
166173
const collection = this.workflow.engine.createCollection(collectionPath);
167-
const name = collection.listSchematicNames().find(name => name === migrationName);
174+
const name = collection.listSchematicNames().find((name) => name === migrationName);
168175
if (!name) {
169176
this.logger.error(`Cannot find migration '${migrationName}' in '${packageName}'.`);
170177

@@ -213,20 +220,20 @@ export class UpdateCommand extends Command<UpdateCommandSchema> {
213220
return true;
214221
}
215222

216-
this.logger.info(
217-
colors.cyan(`** Executing migrations of package '${packageName}' **\n`),
218-
);
223+
this.logger.info(colors.cyan(`** Executing migrations of package '${packageName}' **\n`));
219224

220225
return this.executePackageMigrations(migrations, packageName, commit);
221226
}
222227

223228
private async executePackageMigrations(
224-
migrations: Iterable<{ name: string; description: string; collection: { name: string }}>,
229+
migrations: Iterable<{ name: string; description: string; collection: { name: string } }>,
225230
packageName: string,
226231
commit = false,
227232
): Promise<boolean> {
228233
for (const migration of migrations) {
229-
this.logger.info(`${colors.symbols.pointer} ${migration.description.replace(/\. /g, '.\n ')}`);
234+
this.logger.info(
235+
`${colors.symbols.pointer} ${migration.description.replace(/\. /g, '.\n ')}`,
236+
);
230237

231238
const result = await this.executeSchematic(migration.collection.name, migration.name);
232239
if (!result.success) {
@@ -279,10 +286,11 @@ export class UpdateCommand extends Command<UpdateCommandSchema> {
279286
}
280287

281288
// Check if the current installed CLI version is older than the latest version.
282-
if (!disableVersionCheck && await this.checkCLILatestVersion(options.verbose, options.next)) {
289+
if (!disableVersionCheck && (await this.checkCLILatestVersion(options.verbose, options.next))) {
283290
this.logger.warn(
284-
`The installed local Angular CLI version is older than the latest ${options.next ? 'pre-release' : 'stable'} version.\n` +
285-
'Installing a temporary version to perform the update.',
291+
`The installed local Angular CLI version is older than the latest ${
292+
options.next ? 'pre-release' : 'stable'
293+
} version.\n` + 'Installing a temporary version to perform the update.',
286294
);
287295

288296
return runTempPackageBin(
@@ -305,7 +313,7 @@ export class UpdateCommand extends Command<UpdateCommandSchema> {
305313
return 1;
306314
}
307315

308-
if (packages.some(v => v.name === packageIdentifier.name)) {
316+
if (packages.some((v) => v.name === packageIdentifier.name)) {
309317
this.logger.error(`Duplicate package '${packageIdentifier.name}' specified.`);
310318

311319
return 1;
@@ -377,15 +385,14 @@ export class UpdateCommand extends Command<UpdateCommandSchema> {
377385

378386
this.logger.info('Collecting installed dependencies...');
379387

380-
const packageTree = await readPackageTree(this.workspace.root);
381-
const rootDependencies = findNodeDependencies(packageTree);
388+
const rootDependencies = await getTreeFromNodeModules(this.workspace.root);
382389

383-
this.logger.info(`Found ${Object.keys(rootDependencies).length} dependencies.`);
390+
this.logger.info(`Found ${rootDependencies.size} dependencies.`);
384391

385392
if (options.all) {
386393
// 'all' option and a zero length packages have already been checked.
387394
// Add all direct dependencies to be updated
388-
for (const dep of Object.keys(rootDependencies)) {
395+
for (const dep of rootDependencies.keys()) {
389396
const packageIdentifier = npa(dep);
390397
if (options.next) {
391398
packageIdentifier.fetchSpec = 'next';
@@ -400,15 +407,17 @@ export class UpdateCommand extends Command<UpdateCommandSchema> {
400407
next: options.next || false,
401408
verbose: options.verbose || false,
402409
packageManager: this.packageManager,
403-
packages: options.all ? Object.keys(rootDependencies) : [],
410+
packages: options.all ? rootDependencies.keys() : [],
404411
});
405412

406413
return success ? 0 : 1;
407414
}
408415

409416
if (options.migrateOnly) {
410417
if (!options.from && typeof options.migrateOnly !== 'string') {
411-
this.logger.error('"from" option is required when using the "migrate-only" option without a migration name.');
418+
this.logger.error(
419+
'"from" option is required when using the "migrate-only" option without a migration name.',
420+
);
412421

413422
return 1;
414423
} else if (packages.length !== 1) {
@@ -424,8 +433,9 @@ export class UpdateCommand extends Command<UpdateCommandSchema> {
424433
}
425434

426435
const packageName = packages[0].name;
427-
const packageDependency = rootDependencies[packageName];
428-
let packageNode = packageDependency && packageDependency.node;
436+
const packageDependency = rootDependencies.get(packageName);
437+
let packagePath = packageDependency?.path;
438+
let packageNode = packageDependency?.package;
429439
if (packageDependency && !packageNode) {
430440
this.logger.error('Package found in package.json but is not installed.');
431441

@@ -434,19 +444,17 @@ export class UpdateCommand extends Command<UpdateCommandSchema> {
434444
// Allow running migrations on transitively installed dependencies
435445
// There can technically be nested multiple versions
436446
// TODO: If multiple, this should find all versions and ask which one to use
437-
const child = packageTree.children.find(c => c.name === packageName);
438-
if (child) {
439-
packageNode = child;
440-
}
447+
packagePath = path.dirname(findPackageJson(this.workspace.root, packageName));
448+
packageNode = await readPackageJson(packagePath);
441449
}
442450

443-
if (!packageNode) {
451+
if (!packageNode || !packagePath) {
444452
this.logger.error('Package is not installed.');
445453

446454
return 1;
447455
}
448456

449-
const updateMetadata = packageNode.package['ng-update'];
457+
const updateMetadata = packageNode['ng-update'];
450458
let migrations = updateMetadata && updateMetadata.migrations;
451459
if (migrations === undefined) {
452460
this.logger.error('Package does not provide migrations.');
@@ -477,14 +485,14 @@ export class UpdateCommand extends Command<UpdateCommandSchema> {
477485
}
478486

479487
// Check if it is a package-local location
480-
const localMigrations = path.join(packageNode.path, migrations);
488+
const localMigrations = path.join(packagePath, migrations);
481489
if (fs.existsSync(localMigrations)) {
482490
migrations = localMigrations;
483491
} else {
484492
// Try to resolve from package location.
485493
// This avoids issues with package hoisting.
486494
try {
487-
migrations = require.resolve(migrations, { paths: [packageNode.path] });
495+
migrations = require.resolve(migrations, { paths: [packagePath] });
488496
} catch (e) {
489497
if (e.code === 'MODULE_NOT_FOUND') {
490498
this.logger.error('Migrations for package were not found.');
@@ -513,7 +521,7 @@ export class UpdateCommand extends Command<UpdateCommandSchema> {
513521
}
514522

515523
const migrationRange = new semver.Range(
516-
'>' + from + ' <=' + (options.to || packageNode.package.version),
524+
'>' + from + ' <=' + (options.to || packageNode.version),
517525
);
518526

519527
success = await this.executeMigrations(
@@ -526,10 +534,10 @@ export class UpdateCommand extends Command<UpdateCommandSchema> {
526534

527535
if (success) {
528536
if (
529-
packageName === '@angular/core'
530-
&& options.from
531-
&& +options.from.split('.')[0] < 9
532-
&& (options.to || packageNode.package.version).split('.')[0] === '9'
537+
packageName === '@angular/core' &&
538+
options.from &&
539+
+options.from.split('.')[0] < 9 &&
540+
(options.to || packageNode.version).split('.')[0] === '9'
533541
) {
534542
this.logger.info(NG_VERSION_9_POST_MSG);
535543
}
@@ -547,8 +555,8 @@ export class UpdateCommand extends Command<UpdateCommandSchema> {
547555

548556
// Validate packages actually are part of the workspace
549557
for (const pkg of packages) {
550-
const node = rootDependencies[pkg.name] && rootDependencies[pkg.name].node;
551-
if (!node) {
558+
const node = rootDependencies.get(pkg.name);
559+
if (!node?.package) {
552560
this.logger.error(`Package '${pkg.name}' is not a dependency.`);
553561

554562
return 1;
@@ -627,7 +635,7 @@ export class UpdateCommand extends Command<UpdateCommandSchema> {
627635
return 1;
628636
}
629637

630-
if (manifest.version === node.package.version) {
638+
if (manifest.version === node.package?.version) {
631639
this.logger.info(`Package '${packageName}' is already up to date.`);
632640
continue;
633641
}
@@ -650,7 +658,8 @@ export class UpdateCommand extends Command<UpdateCommandSchema> {
650658

651659
if (success && options.createCommits) {
652660
const committed = this.commit(
653-
`Angular CLI update for packages - ${packagesToUpdate.join(', ')}`);
661+
`Angular CLI update for packages - ${packagesToUpdate.join(', ')}`,
662+
);
654663
if (!committed) {
655664
return 1;
656665
}
@@ -681,7 +690,14 @@ export class UpdateCommand extends Command<UpdateCommandSchema> {
681690
}
682691
}
683692

684-
if (migrations.some(m => m.package === '@angular/core' && m.to.split('.')[0] === '9' && +m.from.split('.')[0] < 9)) {
693+
if (
694+
migrations.some(
695+
(m) =>
696+
m.package === '@angular/core' &&
697+
m.to.split('.')[0] === '9' &&
698+
+m.from.split('.')[0] < 9,
699+
)
700+
) {
685701
this.logger.info(NG_VERSION_9_POST_MSG);
686702
}
687703
}
@@ -713,8 +729,7 @@ export class UpdateCommand extends Command<UpdateCommandSchema> {
713729
try {
714730
createCommit(message);
715731
} catch (err) {
716-
this.logger.error(
717-
`Failed to commit update (${message}):\n${err.stderr}`);
732+
this.logger.error(`Failed to commit update (${message}):\n${err.stderr}`);
718733

719734
return false;
720735
}
@@ -723,8 +738,7 @@ export class UpdateCommand extends Command<UpdateCommandSchema> {
723738
const hash = findCurrentGitSha();
724739
const shortMessage = message.split('\n')[0];
725740
if (hash) {
726-
this.logger.info(` Committed migration step (${getShortHash(hash)}): ${
727-
shortMessage}.`);
741+
this.logger.info(` Committed migration step (${getShortHash(hash)}): ${shortMessage}.`);
728742
} else {
729743
// Commit was successful, but reading the hash was not. Something weird happened,
730744
// but nothing that would stop the update. Just log the weirdness and continue.
@@ -737,7 +751,10 @@ export class UpdateCommand extends Command<UpdateCommandSchema> {
737751

738752
private checkCleanGit(): boolean {
739753
try {
740-
const topLevel = execSync('git rev-parse --show-toplevel', { encoding: 'utf8', stdio: 'pipe' });
754+
const topLevel = execSync('git rev-parse --show-toplevel', {
755+
encoding: 'utf8',
756+
stdio: 'pipe',
757+
});
741758
const result = execSync('git status --porcelain', { encoding: 'utf8', stdio: 'pipe' });
742759
if (result.trim().length === 0) {
743760
return true;
@@ -762,7 +779,7 @@ export class UpdateCommand extends Command<UpdateCommandSchema> {
762779
/**
763780
* Checks if the current installed CLI version is older than the latest version.
764781
* @returns `true` when the installed version is older.
765-
*/
782+
*/
766783
private async checkCLILatestVersion(verbose = false, next = false): Promise<boolean> {
767784
const { version: installedCLIVersion } = require('../package.json');
768785

@@ -808,7 +825,7 @@ function createCommit(message: string) {
808825
*/
809826
function findCurrentGitSha(): string | null {
810827
try {
811-
const hash = execSync('git rev-parse HEAD', {encoding: 'utf8', stdio: 'pipe'});
828+
const hash = execSync('git rev-parse HEAD', { encoding: 'utf8', stdio: 'pipe' });
812829

813830
return hash.trim();
814831
} catch {

packages/angular/cli/package.json

+1-2
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@
3939
"npm-pick-manifest": "6.1.0",
4040
"open": "7.2.0",
4141
"pacote": "9.5.12",
42-
"read-package-tree": "5.3.1",
4342
"rimraf": "3.0.2",
4443
"semver": "7.3.2",
4544
"symbol-observable": "1.2.0",
@@ -50,7 +49,7 @@
5049
"migrations": "@schematics/angular/migrations/migration-collection.json",
5150
"packageGroup": {
5251
"@angular/cli": "0.0.0",
53-
"@angular-devkit/build-angular": "0.0.0",
52+
"@angular-devkit/build-angular": "0.0.0",
5453
"@angular-devkit/build-ng-packagr": "0.0.0",
5554
"@angular-devkit/build-webpack": "0.0.0",
5655
"@angular-devkit/core": "0.0.0",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
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 path from 'path';
10+
11+
import Module = require('module');
12+
13+
// Polyfill Node's `Module.createRequireFromPath` if not present (added in Node v10.12.0)
14+
export const createRequire =
15+
// @ts-ignore
16+
Module.createRequire ||
17+
Module.createRequireFromPath ||
18+
function (filename: string) {
19+
const mod = new Module(filename);
20+
mod.filename = filename;
21+
// @ts-ignore
22+
mod.paths = Module._nodeModulePaths(path.dirname(filename));
23+
// @ts-ignore
24+
mod._compile(`module.exports = require;`, filename);
25+
26+
return mod.exports;
27+
};

0 commit comments

Comments
 (0)