async run()

in packages/angular/cli/commands/add-impl.ts [54:263]


  async run(options: AddCommandSchema & Arguments) {
    await ensureCompatibleNpm(this.context.root);

    if (!options.collection) {
      this.logger.fatal(
        `The "ng add" command requires a name argument to be specified eg. ` +
          `${colors.yellow('ng add [name] ')}. For more details, use "ng help".`,
      );

      return 1;
    }

    let packageIdentifier;
    try {
      packageIdentifier = npa(options.collection);
    } catch (e) {
      this.logger.error(e.message);

      return 1;
    }

    if (
      packageIdentifier.name &&
      packageIdentifier.registry &&
      this.isPackageInstalled(packageIdentifier.name)
    ) {
      const validVersion = await this.isProjectVersionValid(packageIdentifier);
      if (validVersion) {
        // Already installed so just run schematic
        this.logger.info('Skipping installation: Package already installed');

        return this.executeSchematic(packageIdentifier.name, options['--']);
      }
    }

    const spinner = new Spinner();

    spinner.start('Determining package manager...');
    const packageManager = await getPackageManager(this.context.root);
    const usingYarn = packageManager === PackageManager.Yarn;
    spinner.info(`Using package manager: ${colors.grey(packageManager)}`);

    if (packageIdentifier.name && packageIdentifier.type === 'tag' && !packageIdentifier.rawSpec) {
      // only package name provided; search for viable version
      // plus special cases for packages that did not have peer deps setup
      spinner.start('Searching for compatible package version...');

      let packageMetadata;
      try {
        packageMetadata = await fetchPackageMetadata(packageIdentifier.name, this.logger, {
          registry: options.registry,
          usingYarn,
          verbose: options.verbose,
        });
      } catch (e) {
        spinner.fail('Unable to load package information from registry: ' + e.message);

        return 1;
      }

      // Start with the version tagged as `latest` if it exists
      const latestManifest = packageMetadata.tags['latest'];
      if (latestManifest) {
        packageIdentifier = npa.resolve(latestManifest.name, latestManifest.version);
      }

      // Adjust the version based on name and peer dependencies
      if (latestManifest && Object.keys(latestManifest.peerDependencies).length === 0) {
        if (latestManifest.name === '@angular/pwa') {
          const version = await this.findProjectVersion('@angular/cli');
          const semverOptions = { includePrerelease: true };

          if (
            version &&
            ((validRange(version) && intersects(version, '7', semverOptions)) ||
              (valid(version) && satisfies(version, '7', semverOptions)))
          ) {
            packageIdentifier = npa.resolve('@angular/pwa', '0.12');
          }
        }

        spinner.succeed(
          `Found compatible package version: ${colors.grey(packageIdentifier.toString())}.`,
        );
      } else if (!latestManifest || (await this.hasMismatchedPeer(latestManifest))) {
        // 'latest' is invalid so search for most recent matching package
        const versionExclusions = packageVersionExclusions[packageMetadata.name];
        const versionManifests = Object.values(packageMetadata.versions).filter(
          (value: PackageManifest) => {
            // Prerelease versions are not stable and should not be considered by default
            if (prerelease(value.version)) {
              return false;
            }
            // Deprecated versions should not be used or considered
            if (value.deprecated) {
              return false;
            }
            // Excluded package versions should not be considered
            if (versionExclusions && satisfies(value.version, versionExclusions)) {
              return false;
            }

            return true;
          },
        );

        versionManifests.sort((a, b) => rcompare(a.version, b.version, true));

        let newIdentifier;
        for (const versionManifest of versionManifests) {
          if (!(await this.hasMismatchedPeer(versionManifest))) {
            newIdentifier = npa.resolve(versionManifest.name, versionManifest.version);
            break;
          }
        }

        if (!newIdentifier) {
          spinner.warn("Unable to find compatible package. Using 'latest' tag.");
        } else {
          packageIdentifier = newIdentifier;
          spinner.succeed(
            `Found compatible package version: ${colors.grey(packageIdentifier.toString())}.`,
          );
        }
      } else {
        spinner.succeed(
          `Found compatible package version: ${colors.grey(packageIdentifier.toString())}.`,
        );
      }
    }

    let collectionName = packageIdentifier.name;
    let savePackage: NgAddSaveDepedency | undefined;

    try {
      spinner.start('Loading package information from registry...');
      const manifest = await fetchPackageManifest(packageIdentifier.toString(), this.logger, {
        registry: options.registry,
        verbose: options.verbose,
        usingYarn,
      });

      savePackage = manifest['ng-add']?.save;
      collectionName = manifest.name;

      if (await this.hasMismatchedPeer(manifest)) {
        spinner.warn('Package has unmet peer dependencies. Adding the package may not succeed.');
      } else {
        spinner.succeed(`Package information loaded.`);
      }
    } catch (e) {
      spinner.fail(`Unable to fetch package information for '${packageIdentifier}': ${e.message}`);

      return 1;
    }

    if (!options.skipConfirmation) {
      const confirmationResponse = await askConfirmation(
        `\nThe package ${colors.blue(packageIdentifier.raw)} will be installed and executed.\n` +
          'Would you like to proceed?',
        true,
        false,
      );

      if (!confirmationResponse) {
        if (!isTTY()) {
          this.logger.error(
            'No terminal detected. ' +
              `'--skip-confirmation' can be used to bypass installation confirmation. ` +
              `Ensure package name is correct prior to '--skip-confirmation' option usage.`,
          );
        }
        this.logger.error('Command aborted.');

        return 1;
      }
    }

    if (savePackage === false) {
      // Temporary packages are located in a different directory
      // Hence we need to resolve them using the temp path
      const { status, tempNodeModules } = await installTempPackage(
        packageIdentifier.raw,
        packageManager,
        options.registry ? [`--registry="${options.registry}"`] : undefined,
      );
      const resolvedCollectionPath = require.resolve(join(collectionName, 'package.json'), {
        paths: [tempNodeModules],
      });

      if (status !== 0) {
        return status;
      }

      collectionName = dirname(resolvedCollectionPath);
    } else {
      const status = await installPackage(
        packageIdentifier.raw,
        packageManager,
        savePackage,
        options.registry ? [`--registry="${options.registry}"`] : undefined,
      );

      if (status !== 0) {
        return status;
      }
    }

    return this.executeSchematic(collectionName, options['--']);
  }