Skip to content

Commit 43aabb4

Browse files
clydinKeen Yee Liau
authored andcommitted
fix(@schematics/update): improve npmrc discovery (#12871)
* fix(@schematics/update): improve npmrc discovery * feat(@angular/cli): support yarnrc options during update when using yarn
1 parent 4566148 commit 43aabb4

File tree

9 files changed

+89
-47
lines changed

9 files changed

+89
-47
lines changed

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@
9393
"@types/webpack": "^4.4.11",
9494
"@types/webpack-dev-server": "^3.1.0",
9595
"@types/webpack-sources": "^0.1.5",
96+
"@yarnpkg/lockfile": "1.1.0",
9697
"ajv": "6.5.3",
9798
"common-tags": "^1.8.0",
9899
"conventional-changelog": "^1.1.0",
@@ -108,7 +109,7 @@
108109
"license-checker": "^20.1.0",
109110
"minimatch": "^3.0.4",
110111
"minimist": "^1.2.0",
111-
"npm-registry-client": "^8.6.0",
112+
"npm-registry-client": "8.6.0",
112113
"pacote": "^9.2.3",
113114
"pidtree": "^0.3.0",
114115
"pidusage": "^2.0.17",

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { normalize } from '@angular-devkit/core';
99
import { Arguments, Option } from '../models/interface';
1010
import { SchematicCommand } from '../models/schematic-command';
1111
import { findUp } from '../utilities/find-up';
12+
import { getPackageManager } from '../utilities/package-manager';
1213
import { Schema as UpdateCommandSchema } from './update';
1314

1415
export class UpdateCommand extends SchematicCommand<UpdateCommandSchema> {
@@ -50,13 +51,16 @@ export class UpdateCommand extends SchematicCommand<UpdateCommandSchema> {
5051
}
5152

5253
async run(options: UpdateCommandSchema & Arguments) {
54+
const packageManager = getPackageManager(this.workspace.root);
55+
5356
return this.runSchematic({
5457
collectionName: this.collectionName,
5558
schematicName: this.schematicName,
5659
schematicOptions: options['--'],
5760
dryRun: !!options.dryRun,
5861
force: false,
5962
showNothingDone: false,
63+
additionalOptions: { packageManager },
6064
});
6165
}
6266
}

packages/angular/cli/models/schematic-command.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ export interface BaseSchematicSchema {
6161
export interface RunSchematicOptions extends BaseSchematicSchema {
6262
collectionName: string;
6363
schematicName: string;
64-
64+
additionalOptions?: { [key: string]: {} };
6565
schematicOptions?: string[];
6666
showNothingDone?: boolean;
6767
}
@@ -443,7 +443,11 @@ export abstract class SchematicCommand<
443443
// Read the default values from the workspace.
444444
const projectName = input.project !== undefined ? '' + input.project : null;
445445
const defaults = getSchematicDefaults(collectionName, schematicName, projectName);
446-
input = Object.assign<{}, {}, typeof input>({}, defaults, input);
446+
input = {
447+
...defaults,
448+
...input,
449+
...options.additionalOptions,
450+
};
447451

448452
workflow.reporter.subscribe((event: DryRunEvent) => {
449453
nothingDone = false;

packages/schematics/update/BUILD

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,8 @@ ts_library(
6767
"@npm//@types/jasmine",
6868
"@npm//@types/node",
6969
"@npm//@types/semver",
70+
"@npm//@yarnpkg/lockfile",
71+
"@npm//ini",
7072
"@npm//pacote",
7173
],
7274
testonly = True,

packages/schematics/update/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
"dependencies": {
1313
"@angular-devkit/core": "0.0.0",
1414
"@angular-devkit/schematics": "0.0.0",
15+
"@yarnpkg/lockfile": "1.1.0",
16+
"ini": "1.3.5",
1517
"pacote": "9.1.1",
1618
"semver": "5.5.1",
1719
"semver-intersect": "1.4.0",

packages/schematics/update/update/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -778,11 +778,12 @@ export default function(options: UpdateSchema): Rule {
778778
const logger = context.logger;
779779
const allDependencies = _getAllDependencies(tree);
780780
const packages = _buildPackageList(options, allDependencies, logger);
781+
const usingYarn = options.packageManager === 'yarn';
781782

782783
return observableFrom([...allDependencies.keys()]).pipe(
783784
// Grab all package.json from the npm repository. This requires a lot of HTTP calls so we
784785
// try to parallelize as many as possible.
785-
mergeMap(depName => getNpmPackageJson(depName, options.registry, logger)),
786+
mergeMap(depName => getNpmPackageJson(depName, options.registry, logger, usingYarn)),
786787

787788
// Build a map of all dependencies and their packageJson.
788789
reduce<NpmRepositoryPackageJson, Map<string, NpmRepositoryPackageJson>>(

packages/schematics/update/update/npm.ts

Lines changed: 55 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -6,68 +6,69 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88
import { logging } from '@angular-devkit/core';
9-
import { readFileSync } from 'fs';
9+
import { existsSync, readFileSync } from 'fs';
10+
import { homedir } from 'os';
11+
import * as path from 'path';
1012
import { Observable, from } from 'rxjs';
1113
import { shareReplay } from 'rxjs/operators';
1214
import { NpmRepositoryPackageJson } from './npm-package-json';
1315

16+
const ini = require('ini');
17+
const lockfile = require('@yarnpkg/lockfile');
1418
const pacote = require('pacote');
1519

1620
const npmPackageJsonCache = new Map<string, Observable<NpmRepositoryPackageJson>>();
17-
1821
let npmrc: { [key: string]: string };
19-
try {
20-
npmrc = _readNpmRc();
21-
} catch {
22-
npmrc = {};
23-
}
2422

2523

26-
function _readNpmRc(): { [key: string]: string } {
24+
function readOptions(yarn = false): { [key: string]: string } {
2725
// TODO: have a way to read options without using fs directly.
28-
const path = require('path');
29-
const fs = require('fs');
30-
const perProjectNpmrc = path.resolve('.npmrc');
26+
const cwd = process.cwd();
27+
const baseFilename = yarn ? 'yarnrc' : 'npmrc';
28+
const dotFilename = '.' + baseFilename;
3129

32-
const configs: string[] = [];
33-
34-
if (process.platform === 'win32') {
35-
if (process.env.LOCALAPPDATA) {
36-
configs.push(fs.readFileSync(path.join(process.env.LOCALAPPDATA, '.npmrc'), 'utf8'));
37-
}
30+
let globalPrefix: string;
31+
if (process.env.PREFIX) {
32+
globalPrefix = process.env.PREFIX;
3833
} else {
39-
if (process.env.HOME) {
40-
configs.push(fs.readFileSync(path.join(process.env.HOME, '.npmrc'), 'utf8'));
34+
globalPrefix = path.dirname(process.execPath);
35+
if (process.platform !== 'win32') {
36+
globalPrefix = path.dirname(globalPrefix);
4137
}
4238
}
4339

44-
if (fs.existsSync(perProjectNpmrc)) {
45-
configs.push(fs.readFileSync(perProjectNpmrc, 'utf8'));
46-
}
47-
48-
const allOptions: { [key: string]: string } = {};
49-
for (const config of configs) {
50-
const allOptionsArr = config.split(/\r?\n/).map(x => x.trim());
40+
const defaultConfigLocations = [
41+
path.join(globalPrefix, 'etc', baseFilename),
42+
path.join(homedir(), dotFilename),
43+
];
5144

52-
allOptionsArr.forEach(x => {
53-
const [key, ...value] = x.split('=');
54-
const fullValue = value.join('=').trim();
55-
if (key && fullValue && fullValue !== 'null') {
56-
allOptions[key.trim()] = fullValue;
45+
const projectConfigLocations: string[] = [];
46+
const root = path.parse(cwd).root;
47+
for (let curDir = path.dirname(cwd); curDir && curDir !== root; curDir = path.dirname(curDir)) {
48+
projectConfigLocations.unshift(path.join(curDir, dotFilename));
49+
}
50+
projectConfigLocations.push(path.join(cwd, dotFilename));
51+
52+
let options: { [key: string]: string } = {};
53+
for (const location of [...defaultConfigLocations, ...projectConfigLocations]) {
54+
if (existsSync(location)) {
55+
const data = readFileSync(location, 'utf8');
56+
options = {
57+
...options,
58+
...(yarn ? lockfile.parse(data) : ini.parse(data)),
59+
};
60+
61+
if (options.cafile) {
62+
const cafile = path.resolve(path.dirname(location), options.cafile);
63+
delete options.cafile;
64+
try {
65+
options.ca = readFileSync(cafile, 'utf8').replace(/\r?\n/, '\\n');
66+
} catch { }
5767
}
58-
});
59-
60-
if (allOptions.cafile) {
61-
const cafile = allOptions.cafile;
62-
delete allOptions.cafile;
63-
try {
64-
allOptions.ca = readFileSync(cafile, 'utf8');
65-
allOptions.ca = allOptions.ca.replace(/\r?\n/, '\\n');
66-
} catch { }
6768
}
6869
}
6970

70-
return allOptions;
71+
return options;
7172
}
7273

7374
/**
@@ -82,12 +83,25 @@ export function getNpmPackageJson(
8283
packageName: string,
8384
registryUrl: string | undefined,
8485
_logger: logging.LoggerApi,
86+
usingYarn = false,
8587
): Observable<Partial<NpmRepositoryPackageJson>> {
8688
const cachedResponse = npmPackageJsonCache.get(packageName);
8789
if (cachedResponse) {
8890
return cachedResponse;
8991
}
9092

93+
if (!npmrc) {
94+
try {
95+
npmrc = readOptions();
96+
} catch { }
97+
98+
if (usingYarn) {
99+
try {
100+
npmrc = { ...npmrc, ...readOptions(true) };
101+
} catch { }
102+
}
103+
}
104+
91105
const resultPromise = pacote.packument(
92106
packageName,
93107
{

packages/schematics/update/update/schema.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,15 @@
5353
"format": "hostname"
5454
}
5555
]
56+
},
57+
"packageManager": {
58+
"description": "The preferred package manager configuration files to use for registry settings.",
59+
"type": "string",
60+
"default": "npm",
61+
"enum": [
62+
"npm",
63+
"yarn"
64+
]
5665
}
5766
},
5867
"required": [

yarn.lock

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -735,6 +735,11 @@
735735
resolved "https://registry.yarnpkg.com/@xtuc/long/-/long-4.2.1.tgz#5c85d662f76fa1d34575766c5dcd6615abcd30d8"
736736
integrity sha512-FZdkNBDqBRHKQ2MEbSC17xnPFOhZxeJ2YGSfr2BKf3sujG49Qe3bB+rGCwQfIaA7WHnGeGkSijX4FuBCdrzW/g==
737737

738+
"@yarnpkg/[email protected]":
739+
version "1.1.0"
740+
resolved "https://registry.yarnpkg.com/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz#e77a97fbd345b76d83245edcd17d393b1b41fb31"
741+
integrity sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==
742+
738743
JSONStream@^1.0.4:
739744
version "1.3.4"
740745
resolved "https://registry.yarnpkg.com/JSONStream/-/JSONStream-1.3.4.tgz#615bb2adb0cd34c8f4c447b5f6512fa1d8f16a2e"
@@ -4674,7 +4679,7 @@ [email protected]:
46744679
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.1.tgz#b17d08d326b4423e568eff719f91b0b1cbdf69f1"
46754680
integrity sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=
46764681

4677-
ini@^1.3.2, ini@^1.3.4, ini@~1.3.0:
4682+
ini@1.3.5, ini@^1.3.2, ini@^1.3.4, ini@~1.3.0:
46784683
version "1.3.5"
46794684
resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927"
46804685
integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==
@@ -6792,7 +6797,7 @@ npm-pick-manifest@^2.1.0, npm-pick-manifest@^2.2.3:
67926797
npm-package-arg "^6.0.0"
67936798
semver "^5.4.1"
67946799

6795-
npm-registry-client@^8.6.0:
6800+
67966801
version "8.6.0"
67976802
resolved "https://registry.yarnpkg.com/npm-registry-client/-/npm-registry-client-8.6.0.tgz#7f1529f91450732e89f8518e0f21459deea3e4c4"
67986803
integrity sha512-Qs6P6nnopig+Y8gbzpeN/dkt+n7IyVd8f45NTMotGk6Qo7GfBmzwYx6jRLoOOgKiMnaQfYxsuyQlD8Mc3guBhg==

0 commit comments

Comments
 (0)