add-impl.js 9.4 KB

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