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['--']);
}