"Fossies" - the Fresh Open Source Software Archive

Member "angular-cli-8.3.23/packages/angular/cli/commands/add-impl.ts" (15 Jan 2020, 8672 Bytes) of package /linux/www/angular-cli-8.3.23.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) TypeScript source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file.

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