UNPKG

11.4 kBJavaScriptView Raw
1"use strict";
2Object.defineProperty(exports, "__esModule", { value: true });
3/**
4 * @license
5 * Copyright Google Inc. All Rights Reserved.
6 *
7 * Use of this source code is governed by an MIT-style license that can be
8 * found in the LICENSE file at https://angular.io/license
9 */
10const core_1 = require("@angular-devkit/core");
11const tools_1 = require("@angular-devkit/schematics/tools");
12const path_1 = require("path");
13const semver_1 = require("semver");
14const schema_1 = require("../lib/config/schema");
15const analytics_1 = require("../models/analytics");
16const schematic_command_1 = require("../models/schematic-command");
17const install_package_1 = require("../tasks/install-package");
18const color_1 = require("../utilities/color");
19const package_manager_1 = require("../utilities/package-manager");
20const package_metadata_1 = require("../utilities/package-metadata");
21const npa = require('npm-package-arg');
22class AddCommand extends schematic_command_1.SchematicCommand {
23 constructor() {
24 super(...arguments);
25 this.allowPrivateSchematics = true;
26 }
27 async initialize(options) {
28 if (options.registry) {
29 return super.initialize({ ...options, packageRegistry: options.registry });
30 }
31 else {
32 return super.initialize(options);
33 }
34 }
35 async run(options) {
36 if (!options.collection) {
37 this.logger.fatal(`The "ng add" command requires a name argument to be specified eg. ` +
38 `${color_1.colors.yellow('ng add [name] ')}. For more details, use "ng help".`);
39 return 1;
40 }
41 let packageIdentifier;
42 try {
43 packageIdentifier = npa(options.collection);
44 }
45 catch (e) {
46 this.logger.error(e.message);
47 return 1;
48 }
49 if (packageIdentifier.registry && this.isPackageInstalled(packageIdentifier.name)) {
50 let validVersion = false;
51 const installedVersion = await this.findProjectVersion(packageIdentifier.name);
52 if (installedVersion) {
53 if (packageIdentifier.type === 'range') {
54 validVersion = semver_1.satisfies(installedVersion, packageIdentifier.fetchSpec);
55 }
56 else if (packageIdentifier.type === 'version') {
57 const v1 = semver_1.valid(packageIdentifier.fetchSpec);
58 const v2 = semver_1.valid(installedVersion);
59 validVersion = v1 !== null && v1 === v2;
60 }
61 else if (!packageIdentifier.rawSpec) {
62 validVersion = true;
63 }
64 }
65 if (validVersion) {
66 // Already installed so just run schematic
67 this.logger.info('Skipping installation: Package already installed');
68 return this.executeSchematic(packageIdentifier.name, options['--']);
69 }
70 }
71 const packageManager = await package_manager_1.getPackageManager(this.workspace.root);
72 const usingYarn = packageManager === schema_1.PackageManager.Yarn;
73 if (packageIdentifier.type === 'tag' && !packageIdentifier.rawSpec) {
74 // only package name provided; search for viable version
75 // plus special cases for packages that did not have peer deps setup
76 let packageMetadata;
77 try {
78 packageMetadata = await package_metadata_1.fetchPackageMetadata(packageIdentifier.name, this.logger, {
79 registry: options.registry,
80 usingYarn,
81 verbose: options.verbose,
82 });
83 }
84 catch (e) {
85 this.logger.error('Unable to fetch package metadata: ' + e.message);
86 return 1;
87 }
88 const latestManifest = packageMetadata.tags['latest'];
89 if (latestManifest && Object.keys(latestManifest.peerDependencies).length === 0) {
90 if (latestManifest.name === '@angular/pwa') {
91 const version = await this.findProjectVersion('@angular/cli');
92 // tslint:disable-next-line:no-any
93 const semverOptions = { includePrerelease: true };
94 if (version &&
95 ((semver_1.validRange(version) && semver_1.intersects(version, '7', semverOptions)) ||
96 (semver_1.valid(version) && semver_1.satisfies(version, '7', semverOptions)))) {
97 packageIdentifier = npa.resolve('@angular/pwa', '0.12');
98 }
99 }
100 }
101 else if (!latestManifest || (await this.hasMismatchedPeer(latestManifest))) {
102 // 'latest' is invalid so search for most recent matching package
103 const versionManifests = Object.values(packageMetadata.versions).filter((value) => !semver_1.prerelease(value.version));
104 versionManifests.sort((a, b) => semver_1.rcompare(a.version, b.version, true));
105 let newIdentifier;
106 for (const versionManifest of versionManifests) {
107 if (!(await this.hasMismatchedPeer(versionManifest))) {
108 newIdentifier = npa.resolve(packageIdentifier.name, versionManifest.version);
109 break;
110 }
111 }
112 if (!newIdentifier) {
113 this.logger.warn("Unable to find compatible package. Using 'latest'.");
114 }
115 else {
116 packageIdentifier = newIdentifier;
117 }
118 }
119 }
120 let collectionName = packageIdentifier.name;
121 let savePackage;
122 try {
123 const manifest = await package_metadata_1.fetchPackageManifest(packageIdentifier, this.logger, {
124 registry: options.registry,
125 verbose: options.verbose,
126 usingYarn,
127 });
128 savePackage = manifest['ng-add'] && manifest['ng-add'].save;
129 collectionName = manifest.name;
130 if (await this.hasMismatchedPeer(manifest)) {
131 this.logger.warn('Package has unmet peer dependencies. Adding the package may not succeed.');
132 }
133 }
134 catch (e) {
135 this.logger.error('Unable to fetch package manifest: ' + e.message);
136 return 1;
137 }
138 if (savePackage === false) {
139 // Temporary packages are located in a different directory
140 // Hence we need to resolve them using the temp path
141 const tempPath = install_package_1.installTempPackage(packageIdentifier.raw, this.logger, packageManager, options.registry ? [`--registry="${options.registry}"`] : undefined);
142 const resolvedCollectionPath = require.resolve(path_1.join(collectionName, 'package.json'), {
143 paths: [tempPath],
144 });
145 collectionName = path_1.dirname(resolvedCollectionPath);
146 }
147 else {
148 install_package_1.installPackage(packageIdentifier.raw, this.logger, packageManager, savePackage, options.registry ? [`--registry="${options.registry}"`] : undefined);
149 }
150 return this.executeSchematic(collectionName, options['--']);
151 }
152 async reportAnalytics(paths, options, dimensions = [], metrics = []) {
153 const collection = options.collection;
154 // Add the collection if it's safe listed.
155 if (collection && analytics_1.isPackageNameSafeForAnalytics(collection)) {
156 dimensions[core_1.analytics.NgCliAnalyticsDimensions.NgAddCollection] = collection;
157 }
158 else {
159 delete dimensions[core_1.analytics.NgCliAnalyticsDimensions.NgAddCollection];
160 }
161 return super.reportAnalytics(paths, options, dimensions, metrics);
162 }
163 isPackageInstalled(name) {
164 try {
165 require.resolve(path_1.join(name, 'package.json'), { paths: [this.workspace.root] });
166 return true;
167 }
168 catch (e) {
169 if (e.code !== 'MODULE_NOT_FOUND') {
170 throw e;
171 }
172 }
173 return false;
174 }
175 async executeSchematic(collectionName, options = []) {
176 const runOptions = {
177 schematicOptions: options,
178 collectionName,
179 schematicName: 'ng-add',
180 dryRun: false,
181 force: false,
182 };
183 try {
184 return await this.runSchematic(runOptions);
185 }
186 catch (e) {
187 if (e instanceof tools_1.NodePackageDoesNotSupportSchematics) {
188 this.logger.error(core_1.tags.oneLine `
189 The package that you are trying to add does not support schematics. You can try using
190 a different version of the package or contact the package author to add ng-add support.
191 `);
192 return 1;
193 }
194 throw e;
195 }
196 }
197 async findProjectVersion(name) {
198 let installedPackage;
199 try {
200 installedPackage = require.resolve(path_1.join(name, 'package.json'), {
201 paths: [this.workspace.root],
202 });
203 }
204 catch (_a) { }
205 if (installedPackage) {
206 try {
207 const installed = await package_metadata_1.fetchPackageManifest(path_1.dirname(installedPackage), this.logger);
208 return installed.version;
209 }
210 catch (_b) { }
211 }
212 let projectManifest;
213 try {
214 projectManifest = await package_metadata_1.fetchPackageManifest(this.workspace.root, this.logger);
215 }
216 catch (_c) { }
217 if (projectManifest) {
218 const version = projectManifest.dependencies[name] || projectManifest.devDependencies[name];
219 if (version) {
220 return version;
221 }
222 }
223 return null;
224 }
225 async hasMismatchedPeer(manifest) {
226 for (const peer in manifest.peerDependencies) {
227 let peerIdentifier;
228 try {
229 peerIdentifier = npa.resolve(peer, manifest.peerDependencies[peer]);
230 }
231 catch (_a) {
232 this.logger.warn(`Invalid peer dependency ${peer} found in package.`);
233 continue;
234 }
235 if (peerIdentifier.type === 'version' || peerIdentifier.type === 'range') {
236 try {
237 const version = await this.findProjectVersion(peer);
238 if (!version) {
239 continue;
240 }
241 // tslint:disable-next-line:no-any
242 const options = { includePrerelease: true };
243 if (!semver_1.intersects(version, peerIdentifier.rawSpec, options) &&
244 !semver_1.satisfies(version, peerIdentifier.rawSpec, options)) {
245 return true;
246 }
247 }
248 catch (_b) {
249 // Not found or invalid so ignore
250 continue;
251 }
252 }
253 else {
254 // type === 'tag' | 'file' | 'directory' | 'remote' | 'git'
255 // Cannot accurately compare these as the tag/location may have changed since install
256 }
257 }
258 return false;
259 }
260}
261exports.AddCommand = AddCommand;