diff --git a/package.json b/package.json index 81d0f892c300..e2313bd72561 100644 --- a/package.json +++ b/package.json @@ -93,6 +93,7 @@ "@types/webpack": "^4.4.11", "@types/webpack-dev-server": "^3.1.0", "@types/webpack-sources": "^0.1.5", + "@yarnpkg/lockfile": "1.1.0", "ajv": "6.5.3", "common-tags": "^1.8.0", "conventional-changelog": "^1.1.0", @@ -108,7 +109,7 @@ "license-checker": "^20.1.0", "minimatch": "^3.0.4", "minimist": "^1.2.0", - "npm-registry-client": "^8.6.0", + "npm-registry-client": "8.6.0", "pacote": "^9.2.3", "pidtree": "^0.3.0", "pidusage": "^2.0.17", diff --git a/packages/angular/cli/commands/update-impl.ts b/packages/angular/cli/commands/update-impl.ts index e5f575db4dfb..389ee9728c0f 100644 --- a/packages/angular/cli/commands/update-impl.ts +++ b/packages/angular/cli/commands/update-impl.ts @@ -9,6 +9,7 @@ import { normalize } from '@angular-devkit/core'; import { Arguments, Option } from '../models/interface'; import { SchematicCommand } from '../models/schematic-command'; import { findUp } from '../utilities/find-up'; +import { getPackageManager } from '../utilities/package-manager'; import { Schema as UpdateCommandSchema } from './update'; export class UpdateCommand extends SchematicCommand { @@ -50,6 +51,8 @@ export class UpdateCommand extends SchematicCommand { } async run(options: UpdateCommandSchema & Arguments) { + const packageManager = getPackageManager(this.workspace.root); + return this.runSchematic({ collectionName: this.collectionName, schematicName: this.schematicName, @@ -57,6 +60,7 @@ export class UpdateCommand extends SchematicCommand { dryRun: !!options.dryRun, force: false, showNothingDone: false, + additionalOptions: { packageManager }, }); } } diff --git a/packages/angular/cli/models/schematic-command.ts b/packages/angular/cli/models/schematic-command.ts index e25378040fb0..bebd0c229159 100644 --- a/packages/angular/cli/models/schematic-command.ts +++ b/packages/angular/cli/models/schematic-command.ts @@ -61,7 +61,7 @@ export interface BaseSchematicSchema { export interface RunSchematicOptions extends BaseSchematicSchema { collectionName: string; schematicName: string; - + additionalOptions?: { [key: string]: {} }; schematicOptions?: string[]; showNothingDone?: boolean; } @@ -443,7 +443,11 @@ export abstract class SchematicCommand< // Read the default values from the workspace. const projectName = input.project !== undefined ? '' + input.project : null; const defaults = getSchematicDefaults(collectionName, schematicName, projectName); - input = Object.assign<{}, {}, typeof input>({}, defaults, input); + input = { + ...defaults, + ...input, + ...options.additionalOptions, + }; workflow.reporter.subscribe((event: DryRunEvent) => { nothingDone = false; diff --git a/packages/schematics/update/BUILD b/packages/schematics/update/BUILD index 57a171f12d9b..b429933d9a6a 100644 --- a/packages/schematics/update/BUILD +++ b/packages/schematics/update/BUILD @@ -67,6 +67,8 @@ ts_library( "@npm//@types/jasmine", "@npm//@types/node", "@npm//@types/semver", + "@npm//@yarnpkg/lockfile", + "@npm//ini", "@npm//pacote", ], testonly = True, diff --git a/packages/schematics/update/package.json b/packages/schematics/update/package.json index eeccda4a9338..5d88f9179dd9 100644 --- a/packages/schematics/update/package.json +++ b/packages/schematics/update/package.json @@ -12,6 +12,8 @@ "dependencies": { "@angular-devkit/core": "0.0.0", "@angular-devkit/schematics": "0.0.0", + "@yarnpkg/lockfile": "1.1.0", + "ini": "1.3.5", "pacote": "9.1.1", "semver": "5.5.1", "semver-intersect": "1.4.0", diff --git a/packages/schematics/update/update/index.ts b/packages/schematics/update/update/index.ts index 4baf04372aa5..9a2ab9a55361 100644 --- a/packages/schematics/update/update/index.ts +++ b/packages/schematics/update/update/index.ts @@ -778,11 +778,12 @@ export default function(options: UpdateSchema): Rule { const logger = context.logger; const allDependencies = _getAllDependencies(tree); const packages = _buildPackageList(options, allDependencies, logger); + const usingYarn = options.packageManager === 'yarn'; return observableFrom([...allDependencies.keys()]).pipe( // Grab all package.json from the npm repository. This requires a lot of HTTP calls so we // try to parallelize as many as possible. - mergeMap(depName => getNpmPackageJson(depName, options.registry, logger)), + mergeMap(depName => getNpmPackageJson(depName, options.registry, logger, usingYarn)), // Build a map of all dependencies and their packageJson. reduce>( diff --git a/packages/schematics/update/update/npm.ts b/packages/schematics/update/update/npm.ts index 4e26ddc5e8e4..a73cbfc4205e 100644 --- a/packages/schematics/update/update/npm.ts +++ b/packages/schematics/update/update/npm.ts @@ -6,68 +6,69 @@ * found in the LICENSE file at https://angular.io/license */ import { logging } from '@angular-devkit/core'; -import { readFileSync } from 'fs'; +import { existsSync, readFileSync } from 'fs'; +import { homedir } from 'os'; +import * as path from 'path'; import { Observable, from } from 'rxjs'; import { shareReplay } from 'rxjs/operators'; import { NpmRepositoryPackageJson } from './npm-package-json'; +const ini = require('ini'); +const lockfile = require('@yarnpkg/lockfile'); const pacote = require('pacote'); const npmPackageJsonCache = new Map>(); - let npmrc: { [key: string]: string }; -try { - npmrc = _readNpmRc(); -} catch { - npmrc = {}; -} -function _readNpmRc(): { [key: string]: string } { +function readOptions(yarn = false): { [key: string]: string } { // TODO: have a way to read options without using fs directly. - const path = require('path'); - const fs = require('fs'); - const perProjectNpmrc = path.resolve('.npmrc'); + const cwd = process.cwd(); + const baseFilename = yarn ? 'yarnrc' : 'npmrc'; + const dotFilename = '.' + baseFilename; - const configs: string[] = []; - - if (process.platform === 'win32') { - if (process.env.LOCALAPPDATA) { - configs.push(fs.readFileSync(path.join(process.env.LOCALAPPDATA, '.npmrc'), 'utf8')); - } + let globalPrefix: string; + if (process.env.PREFIX) { + globalPrefix = process.env.PREFIX; } else { - if (process.env.HOME) { - configs.push(fs.readFileSync(path.join(process.env.HOME, '.npmrc'), 'utf8')); + globalPrefix = path.dirname(process.execPath); + if (process.platform !== 'win32') { + globalPrefix = path.dirname(globalPrefix); } } - if (fs.existsSync(perProjectNpmrc)) { - configs.push(fs.readFileSync(perProjectNpmrc, 'utf8')); - } - - const allOptions: { [key: string]: string } = {}; - for (const config of configs) { - const allOptionsArr = config.split(/\r?\n/).map(x => x.trim()); + const defaultConfigLocations = [ + path.join(globalPrefix, 'etc', baseFilename), + path.join(homedir(), dotFilename), + ]; - allOptionsArr.forEach(x => { - const [key, ...value] = x.split('='); - const fullValue = value.join('=').trim(); - if (key && fullValue && fullValue !== 'null') { - allOptions[key.trim()] = fullValue; + const projectConfigLocations: string[] = []; + const root = path.parse(cwd).root; + for (let curDir = path.dirname(cwd); curDir && curDir !== root; curDir = path.dirname(curDir)) { + projectConfigLocations.unshift(path.join(curDir, dotFilename)); + } + projectConfigLocations.push(path.join(cwd, dotFilename)); + + let options: { [key: string]: string } = {}; + for (const location of [...defaultConfigLocations, ...projectConfigLocations]) { + if (existsSync(location)) { + const data = readFileSync(location, 'utf8'); + options = { + ...options, + ...(yarn ? lockfile.parse(data) : ini.parse(data)), + }; + + if (options.cafile) { + const cafile = path.resolve(path.dirname(location), options.cafile); + delete options.cafile; + try { + options.ca = readFileSync(cafile, 'utf8').replace(/\r?\n/, '\\n'); + } catch { } } - }); - - if (allOptions.cafile) { - const cafile = allOptions.cafile; - delete allOptions.cafile; - try { - allOptions.ca = readFileSync(cafile, 'utf8'); - allOptions.ca = allOptions.ca.replace(/\r?\n/, '\\n'); - } catch { } } } - return allOptions; + return options; } /** @@ -82,12 +83,25 @@ export function getNpmPackageJson( packageName: string, registryUrl: string | undefined, _logger: logging.LoggerApi, + usingYarn = false, ): Observable> { const cachedResponse = npmPackageJsonCache.get(packageName); if (cachedResponse) { return cachedResponse; } + if (!npmrc) { + try { + npmrc = readOptions(); + } catch { } + + if (usingYarn) { + try { + npmrc = { ...npmrc, ...readOptions(true) }; + } catch { } + } + } + const resultPromise = pacote.packument( packageName, { diff --git a/packages/schematics/update/update/schema.json b/packages/schematics/update/update/schema.json index 0c2c57040583..d5b020bf0fa3 100644 --- a/packages/schematics/update/update/schema.json +++ b/packages/schematics/update/update/schema.json @@ -53,6 +53,15 @@ "format": "hostname" } ] + }, + "packageManager": { + "description": "The preferred package manager configuration files to use for registry settings.", + "type": "string", + "default": "npm", + "enum": [ + "npm", + "yarn" + ] } }, "required": [ diff --git a/yarn.lock b/yarn.lock index a9a2bf43324b..00c5d763dc13 100644 --- a/yarn.lock +++ b/yarn.lock @@ -735,6 +735,11 @@ resolved "https://registry.yarnpkg.com/@xtuc/long/-/long-4.2.1.tgz#5c85d662f76fa1d34575766c5dcd6615abcd30d8" integrity sha512-FZdkNBDqBRHKQ2MEbSC17xnPFOhZxeJ2YGSfr2BKf3sujG49Qe3bB+rGCwQfIaA7WHnGeGkSijX4FuBCdrzW/g== +"@yarnpkg/lockfile@1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz#e77a97fbd345b76d83245edcd17d393b1b41fb31" + integrity sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ== + JSONStream@^1.0.4: version "1.3.4" resolved "https://registry.yarnpkg.com/JSONStream/-/JSONStream-1.3.4.tgz#615bb2adb0cd34c8f4c447b5f6512fa1d8f16a2e" @@ -4674,7 +4679,7 @@ inherits@2.0.1: resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.1.tgz#b17d08d326b4423e568eff719f91b0b1cbdf69f1" integrity sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE= -ini@^1.3.2, ini@^1.3.4, ini@~1.3.0: +ini@1.3.5, ini@^1.3.2, ini@^1.3.4, ini@~1.3.0: version "1.3.5" resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw== @@ -6792,7 +6797,7 @@ npm-pick-manifest@^2.1.0, npm-pick-manifest@^2.2.3: npm-package-arg "^6.0.0" semver "^5.4.1" -npm-registry-client@^8.6.0: +npm-registry-client@8.6.0: version "8.6.0" resolved "https://registry.yarnpkg.com/npm-registry-client/-/npm-registry-client-8.6.0.tgz#7f1529f91450732e89f8518e0f21459deea3e4c4" integrity sha512-Qs6P6nnopig+Y8gbzpeN/dkt+n7IyVd8f45NTMotGk6Qo7GfBmzwYx6jRLoOOgKiMnaQfYxsuyQlD8Mc3guBhg==