You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

313 lines
14 KiB

"use strict";
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.AddCommand = void 0;
const core_1 = require("@angular-devkit/core");
const tools_1 = require("@angular-devkit/schematics/tools");
const npm_package_arg_1 = __importDefault(require("npm-package-arg"));
const path_1 = require("path");
const semver_1 = require("semver");
const workspace_schema_1 = require("../lib/config/workspace-schema");
const analytics_1 = require("../models/analytics");
const schematic_command_1 = require("../models/schematic-command");
const color_1 = require("../utilities/color");
const install_package_1 = require("../utilities/install-package");
const package_manager_1 = require("../utilities/package-manager");
const package_metadata_1 = require("../utilities/package-metadata");
const prompt_1 = require("../utilities/prompt");
const spinner_1 = require("../utilities/spinner");
const tty_1 = require("../utilities/tty");
class AddCommand extends schematic_command_1.SchematicCommand {
constructor() {
super(...arguments);
this.allowPrivateSchematics = true;
}
async initialize(options) {
if (options.registry) {
return super.initialize({ ...options, packageRegistry: options.registry });
}
else {
return super.initialize(options);
}
}
async run(options) {
var _a;
await package_manager_1.ensureCompatibleNpm(this.context.root);
if (!options.collection) {
this.logger.fatal(`The "ng add" command requires a name argument to be specified eg. ` +
`${color_1.colors.yellow('ng add [name] ')}. For more details, use "ng help".`);
return 1;
}
let packageIdentifier;
try {
packageIdentifier = npm_package_arg_1.default(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_1.Spinner();
spinner.start('Determining package manager...');
const packageManager = await package_manager_1.getPackageManager(this.context.root);
const usingYarn = packageManager === workspace_schema_1.PackageManager.Yarn;
spinner.info(`Using package manager: ${color_1.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 package_metadata_1.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;
}
const latestManifest = packageMetadata.tags['latest'];
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 &&
((semver_1.validRange(version) && semver_1.intersects(version, '7', semverOptions)) ||
(semver_1.valid(version) && semver_1.satisfies(version, '7', semverOptions)))) {
packageIdentifier = npm_package_arg_1.default.resolve('@angular/pwa', '0.12');
}
}
else {
packageIdentifier = npm_package_arg_1.default.resolve(latestManifest.name, latestManifest.version);
}
spinner.succeed(`Found compatible package version: ${color_1.colors.grey(packageIdentifier.toString())}.`);
}
else if (!latestManifest || (await this.hasMismatchedPeer(latestManifest))) {
// 'latest' is invalid so search for most recent matching package
const versionManifests = Object.values(packageMetadata.versions).filter((value) => !semver_1.prerelease(value.version) && !value.deprecated);
versionManifests.sort((a, b) => semver_1.rcompare(a.version, b.version, true));
let newIdentifier;
for (const versionManifest of versionManifests) {
if (!(await this.hasMismatchedPeer(versionManifest))) {
newIdentifier = npm_package_arg_1.default.resolve(packageIdentifier.name, versionManifest.version);
break;
}
}
if (!newIdentifier) {
spinner.warn("Unable to find compatible package. Using 'latest'.");
}
else {
packageIdentifier = newIdentifier;
spinner.succeed(`Found compatible package version: ${color_1.colors.grey(packageIdentifier.toString())}.`);
}
}
else {
packageIdentifier = npm_package_arg_1.default.resolve(latestManifest.name, latestManifest.version);
spinner.succeed(`Found compatible package version: ${color_1.colors.grey(packageIdentifier.toString())}.`);
}
}
let collectionName = packageIdentifier.name;
let savePackage;
try {
spinner.start('Loading package information from registry...');
const manifest = await package_metadata_1.fetchPackageManifest(packageIdentifier.toString(), this.logger, {
registry: options.registry,
verbose: options.verbose,
usingYarn,
});
savePackage = (_a = manifest['ng-add']) === null || _a === void 0 ? void 0 : _a.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 prompt_1.askConfirmation(`\nThe package ${color_1.colors.blue(packageIdentifier.raw)} will be installed and executed.\n` +
'Would you like to proceed?', true, false);
if (!confirmationResponse) {
if (!tty_1.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 install_package_1.installTempPackage(packageIdentifier.raw, packageManager, options.registry ? [`--registry="${options.registry}"`] : undefined);
const resolvedCollectionPath = require.resolve(path_1.join(collectionName, 'package.json'), {
paths: [tempNodeModules],
});
if (status !== 0) {
return status;
}
collectionName = path_1.dirname(resolvedCollectionPath);
}
else {
const status = await install_package_1.installPackage(packageIdentifier.raw, packageManager, savePackage, options.registry ? [`--registry="${options.registry}"`] : undefined);
if (status !== 0) {
return status;
}
}
return this.executeSchematic(collectionName, options['--']);
}
async isProjectVersionValid(packageIdentifier) {
if (!packageIdentifier.name) {
return false;
}
let validVersion = false;
const installedVersion = await this.findProjectVersion(packageIdentifier.name);
if (installedVersion) {
if (packageIdentifier.type === 'range' && packageIdentifier.fetchSpec) {
validVersion = semver_1.satisfies(installedVersion, packageIdentifier.fetchSpec);
}
else if (packageIdentifier.type === 'version') {
const v1 = semver_1.valid(packageIdentifier.fetchSpec);
const v2 = semver_1.valid(installedVersion);
validVersion = v1 !== null && v1 === v2;
}
else if (!packageIdentifier.rawSpec) {
validVersion = true;
}
}
return validVersion;
}
async reportAnalytics(paths, options, dimensions = [], metrics = []) {
const collection = options.collection;
// Add the collection if it's safe listed.
if (collection && analytics_1.isPackageNameSafeForAnalytics(collection)) {
dimensions[core_1.analytics.NgCliAnalyticsDimensions.NgAddCollection] = collection;
}
else {
delete dimensions[core_1.analytics.NgCliAnalyticsDimensions.NgAddCollection];
}
return super.reportAnalytics(paths, options, dimensions, metrics);
}
isPackageInstalled(name) {
try {
require.resolve(path_1.join(name, 'package.json'), { paths: [this.context.root] });
return true;
}
catch (e) {
if (e.code !== 'MODULE_NOT_FOUND') {
throw e;
}
}
return false;
}
async executeSchematic(collectionName, options = []) {
const runOptions = {
schematicOptions: options,
collectionName,
schematicName: 'ng-add',
dryRun: false,
force: false,
};
try {
return await this.runSchematic(runOptions);
}
catch (e) {
if (e instanceof tools_1.NodePackageDoesNotSupportSchematics) {
this.logger.error(core_1.tags.oneLine `
The package that you are trying to add does not support schematics. You can try using
a different version of the package or contact the package author to add ng-add support.
`);
return 1;
}
throw e;
}
}
async findProjectVersion(name) {
let installedPackage;
try {
installedPackage = require.resolve(path_1.join(name, 'package.json'), {
paths: [this.context.root],
});
}
catch { }
if (installedPackage) {
try {
const installed = await package_metadata_1.fetchPackageManifest(path_1.dirname(installedPackage), this.logger);
return installed.version;
}
catch { }
}
let projectManifest;
try {
projectManifest = await package_metadata_1.fetchPackageManifest(this.context.root, this.logger);
}
catch { }
if (projectManifest) {
const version = projectManifest.dependencies[name] || projectManifest.devDependencies[name];
if (version) {
return version;
}
}
return null;
}
async hasMismatchedPeer(manifest) {
for (const peer in manifest.peerDependencies) {
let peerIdentifier;
try {
peerIdentifier = npm_package_arg_1.default.resolve(peer, manifest.peerDependencies[peer]);
}
catch {
this.logger.warn(`Invalid peer dependency ${peer} found in package.`);
continue;
}
if (peerIdentifier.type === 'version' || peerIdentifier.type === 'range') {
try {
const version = await this.findProjectVersion(peer);
if (!version) {
continue;
}
const options = { includePrerelease: true };
if (!semver_1.intersects(version, peerIdentifier.rawSpec, options) &&
!semver_1.satisfies(version, peerIdentifier.rawSpec, options)) {
return true;
}
}
catch {
// Not found or invalid so ignore
continue;
}
}
else {
// type === 'tag' | 'file' | 'directory' | 'remote' | 'git'
// Cannot accurately compare these as the tag/location may have changed since install
}
}
return false;
}
}
exports.AddCommand = AddCommand;