feat(core): not exit when one plugin installation failed (#28684)
<!-- Please make sure you have read the submission guidelines before posting an PR --> <!-- https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr --> <!-- Please make sure that your commit message follows our format --> <!-- Example: `fix(nx): must begin with lowercase` --> <!-- If this is a particularly complex change or feature addition, you can request a dedicated Nx release for this pull request branch. Mention someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they will confirm if the PR warrants its own release for testing purposes, and generate it for you if appropriate. --> ## Current Behavior <!-- This is the behavior we have today --> it tries to install all plugins at once. if failed, it will stop the remaining plugins from being installed. ## Expected Behavior <!-- This is the behavior we should expect with the changes in this PR --> it will continue if one plugin failed. success message: <img width="301" alt="Screenshot 2024-12-11 at 11 36 14 AM" src="https://github.com/user-attachments/assets/2bc389f0-4fda-4959-afab-57594c9d600b"> failed message: <img width="894" alt="Screenshot 2024-12-12 at 2 58 17 PM" src="https://github.com/user-attachments/assets/7b51d5e9-f308-48c3-9b7d-bc4219802acb" /> ## Related Issue(s) <!-- Please link the issue being fixed so it gets closed when this is merged. --> Fixes #
This commit is contained in:
parent
67d0e33874
commit
1d7465b02e
@ -772,7 +772,7 @@ describe('Linter', () => {
|
|||||||
const mylib = uniq('mylib');
|
const mylib = uniq('mylib');
|
||||||
|
|
||||||
runCLI(
|
runCLI(
|
||||||
`generate @nx/node:app --name=${myapp} --linter=eslint --directory="." --e2eTestRunner=jest --no-interactive`
|
`generate @nx/node:app --name=${myapp} --linter=eslint --directory="." --unitTestRunner=jest --e2eTestRunner=jest --no-interactive`
|
||||||
);
|
);
|
||||||
runCLI('reset', { env: { CI: 'false' } });
|
runCLI('reset', { env: { CI: 'false' } });
|
||||||
verifySuccessfulStandaloneSetup(myapp);
|
verifySuccessfulStandaloneSetup(myapp);
|
||||||
|
|||||||
@ -15,8 +15,9 @@ import {
|
|||||||
writeJson,
|
writeJson,
|
||||||
} from 'nx/src/devkit-exports';
|
} from 'nx/src/devkit-exports';
|
||||||
import {
|
import {
|
||||||
|
isProjectConfigurationsError,
|
||||||
|
isProjectsWithNoNameError,
|
||||||
LoadedNxPlugin,
|
LoadedNxPlugin,
|
||||||
ProjectConfigurationsError,
|
|
||||||
retrieveProjectConfigurations,
|
retrieveProjectConfigurations,
|
||||||
} from 'nx/src/devkit-internals';
|
} from 'nx/src/devkit-internals';
|
||||||
|
|
||||||
@ -130,8 +131,12 @@ async function _addPluginInternal<PluginOptions>(
|
|||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// Errors are okay for this because we're only running 1 plugin
|
// Errors are okay for this because we're only running 1 plugin
|
||||||
if (e instanceof ProjectConfigurationsError) {
|
if (isProjectConfigurationsError(e)) {
|
||||||
projConfigs = e.partialProjectConfigurationsResult;
|
projConfigs = e.partialProjectConfigurationsResult;
|
||||||
|
// ignore errors from projects with no name
|
||||||
|
if (!e.errors.every(isProjectsWithNoNameError)) {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
@ -171,8 +176,12 @@ async function _addPluginInternal<PluginOptions>(
|
|||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// Errors are okay for this because we're only running 1 plugin
|
// Errors are okay for this because we're only running 1 plugin
|
||||||
if (e instanceof ProjectConfigurationsError) {
|
if (isProjectConfigurationsError(e)) {
|
||||||
projConfigs = e.partialProjectConfigurationsResult;
|
projConfigs = e.partialProjectConfigurationsResult;
|
||||||
|
// ignore errors from projects with no name
|
||||||
|
if (!e.errors.every(isProjectsWithNoNameError)) {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -98,7 +98,9 @@ export function assertNotUsingTsSolutionSetup(
|
|||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
process.exit(1);
|
throw new Error(
|
||||||
|
`The ${artifactString} doesn't yet support the existing TypeScript setup. See the error above.`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function findRuntimeTsConfigName(
|
export function findRuntimeTsConfigName(
|
||||||
|
|||||||
@ -1,10 +1,9 @@
|
|||||||
import { exec } from 'child_process';
|
import { exec } from 'child_process';
|
||||||
import { existsSync } from 'fs';
|
import { existsSync } from 'fs';
|
||||||
import * as ora from 'ora';
|
import * as ora from 'ora';
|
||||||
import { isAngularPluginInstalled } from '../../adapter/angular-json';
|
import * as yargsParser from 'yargs-parser';
|
||||||
import type { GeneratorsJsonEntry } from '../../config/misc-interfaces';
|
|
||||||
import { readNxJson, type NxJsonConfiguration } from '../../config/nx-json';
|
import { readNxJson, type NxJsonConfiguration } from '../../config/nx-json';
|
||||||
import { runNxAsync, runNxSync } from '../../utils/child-process';
|
import { runNxAsync } from '../../utils/child-process';
|
||||||
import { writeJsonFile } from '../../utils/fileutils';
|
import { writeJsonFile } from '../../utils/fileutils';
|
||||||
import { logger } from '../../utils/logger';
|
import { logger } from '../../utils/logger';
|
||||||
import { output } from '../../utils/output';
|
import { output } from '../../utils/output';
|
||||||
@ -14,12 +13,15 @@ import {
|
|||||||
getPackageManagerVersion,
|
getPackageManagerVersion,
|
||||||
} from '../../utils/package-manager';
|
} from '../../utils/package-manager';
|
||||||
import { handleErrors } from '../../utils/handle-errors';
|
import { handleErrors } from '../../utils/handle-errors';
|
||||||
import { getPluginCapabilities } from '../../utils/plugins';
|
|
||||||
import { nxVersion } from '../../utils/versions';
|
import { nxVersion } from '../../utils/versions';
|
||||||
import { workspaceRoot } from '../../utils/workspace-root';
|
import { workspaceRoot } from '../../utils/workspace-root';
|
||||||
import type { AddOptions } from './command-object';
|
import type { AddOptions } from './command-object';
|
||||||
import { normalizeVersionForNxJson } from '../init/implementation/dot-nx/add-nx-scripts';
|
import { normalizeVersionForNxJson } from '../init/implementation/dot-nx/add-nx-scripts';
|
||||||
import { gte } from 'semver';
|
import { gte } from 'semver';
|
||||||
|
import {
|
||||||
|
installPlugin,
|
||||||
|
getFailedToInstallPluginErrorMessages,
|
||||||
|
} from '../init/configure-plugins';
|
||||||
|
|
||||||
export function addHandler(options: AddOptions): Promise<number> {
|
export function addHandler(options: AddOptions): Promise<number> {
|
||||||
return handleErrors(options.verbose, async () => {
|
return handleErrors(options.verbose, async () => {
|
||||||
@ -109,30 +111,20 @@ async function initializePlugin(
|
|||||||
options: AddOptions,
|
options: AddOptions,
|
||||||
nxJson: NxJsonConfiguration
|
nxJson: NxJsonConfiguration
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const capabilities = await getPluginCapabilities(workspaceRoot, pkgName, {});
|
const parsedCommandArgs: { [key: string]: any } = yargsParser(
|
||||||
const generators = capabilities?.generators;
|
options.__overrides_unparsed__,
|
||||||
if (!generators) {
|
{
|
||||||
output.log({
|
configuration: {
|
||||||
title: `No generators found in ${pkgName}. Skipping initialization.`,
|
'parse-numbers': false,
|
||||||
});
|
'parse-positional-numbers': false,
|
||||||
return;
|
'dot-notation': false,
|
||||||
|
'camel-case-expansion': false,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
);
|
||||||
|
|
||||||
const initGenerator = findInitGenerator(generators);
|
|
||||||
if (!initGenerator) {
|
|
||||||
output.log({
|
|
||||||
title: `No "init" generator found in ${pkgName}. Skipping initialization.`,
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const spinner = ora(`Initializing ${pkgName}...`);
|
|
||||||
spinner.start();
|
|
||||||
|
|
||||||
try {
|
|
||||||
const args = [];
|
|
||||||
if (coreNxPluginVersions.has(pkgName)) {
|
if (coreNxPluginVersions.has(pkgName)) {
|
||||||
args.push(`--keepExistingVersions`);
|
parsedCommandArgs.keepExistingVersions = true;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
options.updatePackageScripts ||
|
options.updatePackageScripts ||
|
||||||
@ -140,23 +132,26 @@ async function initializePlugin(
|
|||||||
nxJson.useInferencePlugins !== false &&
|
nxJson.useInferencePlugins !== false &&
|
||||||
process.env.NX_ADD_PLUGINS !== 'false')
|
process.env.NX_ADD_PLUGINS !== 'false')
|
||||||
) {
|
) {
|
||||||
args.push(`--updatePackageScripts`);
|
parsedCommandArgs.updatePackageScripts = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.__overrides_unparsed__.length) {
|
const spinner = ora(`Initializing ${pkgName}...`);
|
||||||
args.push(...options.__overrides_unparsed__);
|
spinner.start();
|
||||||
}
|
|
||||||
|
|
||||||
runNxSync(`g ${pkgName}:${initGenerator} ${args.join(' ')}`, {
|
try {
|
||||||
stdio: [0, 1, 2],
|
await installPlugin(
|
||||||
});
|
pkgName,
|
||||||
|
workspaceRoot,
|
||||||
|
options.verbose,
|
||||||
|
parsedCommandArgs
|
||||||
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
spinner.fail();
|
spinner.fail();
|
||||||
output.addNewline();
|
output.addNewline();
|
||||||
logger.error(e);
|
|
||||||
output.error({
|
output.error({
|
||||||
title: `Failed to initialize ${pkgName}. Please check the error above for more details.`,
|
title: `Failed to initialize ${pkgName}`,
|
||||||
|
bodyLines: getFailedToInstallPluginErrorMessages(e),
|
||||||
});
|
});
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
@ -164,25 +159,6 @@ async function initializePlugin(
|
|||||||
spinner.succeed();
|
spinner.succeed();
|
||||||
}
|
}
|
||||||
|
|
||||||
function findInitGenerator(
|
|
||||||
generators: Record<string, GeneratorsJsonEntry>
|
|
||||||
): string | undefined {
|
|
||||||
if (generators['init']) {
|
|
||||||
return 'init';
|
|
||||||
}
|
|
||||||
|
|
||||||
const angularPluginInstalled = isAngularPluginInstalled();
|
|
||||||
if (angularPluginInstalled && generators['ng-add']) {
|
|
||||||
return 'ng-add';
|
|
||||||
}
|
|
||||||
|
|
||||||
return Object.keys(generators).find(
|
|
||||||
(name) =>
|
|
||||||
generators[name].aliases?.includes('init') ||
|
|
||||||
(angularPluginInstalled && generators[name].aliases?.includes('ng-add'))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function parsePackageSpecifier(
|
function parsePackageSpecifier(
|
||||||
packageSpecifier: string
|
packageSpecifier: string
|
||||||
): [pkgName: string, version: string] {
|
): [pkgName: string, version: string] {
|
||||||
|
|||||||
@ -7,7 +7,7 @@ import { tmpdir } from 'tmp';
|
|||||||
import { prompt } from 'enquirer';
|
import { prompt } from 'enquirer';
|
||||||
import { output } from '../../utils/output';
|
import { output } from '../../utils/output';
|
||||||
import * as createSpinner from 'ora';
|
import * as createSpinner from 'ora';
|
||||||
import { detectPlugins, installPlugins } from '../init/init-v2';
|
import { detectPlugins } from '../init/init-v2';
|
||||||
import { readNxJson } from '../../config/nx-json';
|
import { readNxJson } from '../../config/nx-json';
|
||||||
import { workspaceRoot } from '../../utils/workspace-root';
|
import { workspaceRoot } from '../../utils/workspace-root';
|
||||||
import {
|
import {
|
||||||
@ -24,11 +24,11 @@ import { runInstall } from '../init/implementation/utils';
|
|||||||
import { getBaseRef } from '../../utils/command-line-utils';
|
import { getBaseRef } from '../../utils/command-line-utils';
|
||||||
import { prepareSourceRepo } from './utils/prepare-source-repo';
|
import { prepareSourceRepo } from './utils/prepare-source-repo';
|
||||||
import { mergeRemoteSource } from './utils/merge-remote-source';
|
import { mergeRemoteSource } from './utils/merge-remote-source';
|
||||||
import {
|
|
||||||
getPackagesInPackageManagerWorkspace,
|
|
||||||
needsInstall,
|
|
||||||
} from './utils/needs-install';
|
|
||||||
import { minimatch } from 'minimatch';
|
import { minimatch } from 'minimatch';
|
||||||
|
import {
|
||||||
|
configurePlugins,
|
||||||
|
runPackageManagerInstallPlugins,
|
||||||
|
} from '../init/configure-plugins';
|
||||||
|
|
||||||
const importRemoteName = '__tmp_nx_import__';
|
const importRemoteName = '__tmp_nx_import__';
|
||||||
|
|
||||||
@ -60,7 +60,7 @@ export interface ImportOptions {
|
|||||||
|
|
||||||
export async function importHandler(options: ImportOptions) {
|
export async function importHandler(options: ImportOptions) {
|
||||||
process.env.NX_RUNNING_NX_IMPORT = 'true';
|
process.env.NX_RUNNING_NX_IMPORT = 'true';
|
||||||
let { sourceRepository, ref, source, destination } = options;
|
let { sourceRepository, ref, source, destination, verbose } = options;
|
||||||
const destinationGitClient = new GitRepository(process.cwd());
|
const destinationGitClient = new GitRepository(process.cwd());
|
||||||
|
|
||||||
if (await destinationGitClient.hasUncommittedChanges()) {
|
if (await destinationGitClient.hasUncommittedChanges()) {
|
||||||
@ -219,11 +219,6 @@ export async function importHandler(options: ImportOptions) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const packageManager = detectPackageManager(workspaceRoot);
|
const packageManager = detectPackageManager(workspaceRoot);
|
||||||
|
|
||||||
const originalPackageWorkspaces = await getPackagesInPackageManagerWorkspace(
|
|
||||||
packageManager
|
|
||||||
);
|
|
||||||
|
|
||||||
const sourceIsNxWorkspace = existsSync(join(sourceGitClient.root, 'nx.json'));
|
const sourceIsNxWorkspace = existsSync(join(sourceGitClient.root, 'nx.json'));
|
||||||
|
|
||||||
const relativeDestination = relative(
|
const relativeDestination = relative(
|
||||||
@ -287,42 +282,30 @@ export async function importHandler(options: ImportOptions) {
|
|||||||
destinationGitClient
|
destinationGitClient
|
||||||
);
|
);
|
||||||
|
|
||||||
// If install fails, we should continue since the errors could be resolved later.
|
let installed = await runInstallDestinationRepo(
|
||||||
let installFailed = false;
|
packageManager,
|
||||||
if (plugins.length > 0) {
|
destinationGitClient
|
||||||
try {
|
);
|
||||||
output.log({ title: 'Installing Plugins' });
|
|
||||||
installPlugins(workspaceRoot, plugins, pmc, updatePackageScripts);
|
|
||||||
|
|
||||||
|
if (installed && plugins.length > 0) {
|
||||||
|
installed = await runPluginsInstall(plugins, pmc, destinationGitClient);
|
||||||
|
if (installed) {
|
||||||
|
const { succeededPlugins } = await configurePlugins(
|
||||||
|
plugins,
|
||||||
|
updatePackageScripts,
|
||||||
|
pmc,
|
||||||
|
workspaceRoot,
|
||||||
|
verbose
|
||||||
|
);
|
||||||
|
if (succeededPlugins.length > 0) {
|
||||||
await destinationGitClient.amendCommit();
|
await destinationGitClient.amendCommit();
|
||||||
} catch (e) {
|
|
||||||
installFailed = true;
|
|
||||||
output.error({
|
|
||||||
title: `Install failed: ${e.message || 'Unknown error'}`,
|
|
||||||
bodyLines: [e.stack],
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
} else if (await needsInstall(packageManager, originalPackageWorkspaces)) {
|
|
||||||
try {
|
|
||||||
output.log({
|
|
||||||
title: 'Installing dependencies for imported code',
|
|
||||||
});
|
|
||||||
|
|
||||||
runInstall(workspaceRoot, getPackageManagerCommand(packageManager));
|
|
||||||
|
|
||||||
await destinationGitClient.amendCommit();
|
|
||||||
} catch (e) {
|
|
||||||
installFailed = true;
|
|
||||||
output.error({
|
|
||||||
title: `Install failed: ${e.message || 'Unknown error'}`,
|
|
||||||
bodyLines: [e.stack],
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(await destinationGitClient.showStat());
|
console.log(await destinationGitClient.showStat());
|
||||||
|
|
||||||
if (installFailed) {
|
if (installed === false) {
|
||||||
const pmc = getPackageManagerCommand(packageManager);
|
const pmc = getPackageManagerCommand(packageManager);
|
||||||
output.warn({
|
output.warn({
|
||||||
title: `The import was successful, but the install failed`,
|
title: `The import was successful, but the install failed`,
|
||||||
@ -397,6 +380,62 @@ async function createTemporaryRemote(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Run install for the imported code and plugins
|
||||||
|
* @returns true if the install failed
|
||||||
|
*/
|
||||||
|
async function runInstallDestinationRepo(
|
||||||
|
packageManager: PackageManager,
|
||||||
|
destinationGitClient: GitRepository
|
||||||
|
): Promise<boolean> {
|
||||||
|
let installed = true;
|
||||||
|
try {
|
||||||
|
output.log({
|
||||||
|
title: 'Installing dependencies for imported code',
|
||||||
|
});
|
||||||
|
runInstall(workspaceRoot, getPackageManagerCommand(packageManager));
|
||||||
|
await destinationGitClient.amendCommit();
|
||||||
|
} catch (e) {
|
||||||
|
installed = false;
|
||||||
|
output.error({
|
||||||
|
title: `Install failed: ${e.message || 'Unknown error'}`,
|
||||||
|
bodyLines: [e.stack],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return installed;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function runPluginsInstall(
|
||||||
|
plugins: string[],
|
||||||
|
pmc: PackageManagerCommands,
|
||||||
|
destinationGitClient: GitRepository
|
||||||
|
) {
|
||||||
|
let installed = true;
|
||||||
|
output.log({ title: 'Installing Plugins' });
|
||||||
|
try {
|
||||||
|
runPackageManagerInstallPlugins(workspaceRoot, pmc, plugins);
|
||||||
|
await destinationGitClient.amendCommit();
|
||||||
|
} catch (e) {
|
||||||
|
installed = false;
|
||||||
|
output.error({
|
||||||
|
title: `Install failed: ${e.message || 'Unknown error'}`,
|
||||||
|
bodyLines: [
|
||||||
|
'The following plugins were not installed:',
|
||||||
|
...plugins.map((p) => `- ${chalk.bold(p)}`),
|
||||||
|
e.stack,
|
||||||
|
],
|
||||||
|
});
|
||||||
|
output.error({
|
||||||
|
title: `To install the plugins manually`,
|
||||||
|
bodyLines: [
|
||||||
|
'You may need to run commands to install the plugins:',
|
||||||
|
...plugins.map((p) => `- ${chalk.bold(pmc.exec + ' nx add ' + p)}`),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return installed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
* If the user imports a project that isn't in the workspaces entry, we should add that path to the workspaces entry.
|
* If the user imports a project that isn't in the workspaces entry, we should add that path to the workspaces entry.
|
||||||
*/
|
*/
|
||||||
async function handleMissingWorkspacesEntry(
|
async function handleMissingWorkspacesEntry(
|
||||||
|
|||||||
@ -1,44 +0,0 @@
|
|||||||
import {
|
|
||||||
isWorkspacesEnabled,
|
|
||||||
PackageManager,
|
|
||||||
} from '../../../utils/package-manager';
|
|
||||||
import { workspaceRoot } from '../../../utils/workspace-root';
|
|
||||||
import { getGlobPatternsFromPackageManagerWorkspaces } from '../../../plugins/package-json';
|
|
||||||
import { globWithWorkspaceContext } from '../../../utils/workspace-context';
|
|
||||||
|
|
||||||
export async function getPackagesInPackageManagerWorkspace(
|
|
||||||
packageManager: PackageManager
|
|
||||||
) {
|
|
||||||
if (!isWorkspacesEnabled(packageManager, workspaceRoot)) {
|
|
||||||
return new Set<string>();
|
|
||||||
}
|
|
||||||
const patterns = getGlobPatternsFromPackageManagerWorkspaces(workspaceRoot);
|
|
||||||
return new Set(await globWithWorkspaceContext(workspaceRoot, patterns));
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function needsInstall(
|
|
||||||
packageManager: PackageManager,
|
|
||||||
originalPackagesInPackageManagerWorkspaces: Set<string>
|
|
||||||
) {
|
|
||||||
if (!isWorkspacesEnabled(packageManager, workspaceRoot)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const updatedPackagesInPackageManagerWorkspaces =
|
|
||||||
await getPackagesInPackageManagerWorkspace(packageManager);
|
|
||||||
|
|
||||||
if (
|
|
||||||
updatedPackagesInPackageManagerWorkspaces.size !==
|
|
||||||
originalPackagesInPackageManagerWorkspaces.size
|
|
||||||
) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const pkg of updatedPackagesInPackageManagerWorkspaces) {
|
|
||||||
if (!originalPackagesInPackageManagerWorkspaces.has(pkg)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
@ -1,6 +1,5 @@
|
|||||||
import * as createSpinner from 'ora';
|
import * as createSpinner from 'ora';
|
||||||
import { dirname, join, relative } from 'path';
|
import { join, relative } from 'path';
|
||||||
import { mkdir, rm } from 'node:fs/promises';
|
|
||||||
import { GitRepository } from '../../../utils/git-utils';
|
import { GitRepository } from '../../../utils/git-utils';
|
||||||
|
|
||||||
export async function prepareSourceRepo(
|
export async function prepareSourceRepo(
|
||||||
|
|||||||
253
packages/nx/src/command-line/init/configure-plugins.ts
Normal file
253
packages/nx/src/command-line/init/configure-plugins.ts
Normal file
@ -0,0 +1,253 @@
|
|||||||
|
import * as createSpinner from 'ora';
|
||||||
|
import { bold } from 'chalk';
|
||||||
|
|
||||||
|
import {
|
||||||
|
getPackageManagerCommand,
|
||||||
|
PackageManagerCommands,
|
||||||
|
} from '../../utils/package-manager';
|
||||||
|
import { GitRepository } from '../../utils/git-utils';
|
||||||
|
import { output } from '../../utils/output';
|
||||||
|
import { flushChanges, FsTree } from '../../generators/tree';
|
||||||
|
import {
|
||||||
|
Generator as NxGenerator,
|
||||||
|
GeneratorCallback,
|
||||||
|
GeneratorsJsonEntry,
|
||||||
|
} from '../../config/misc-interfaces';
|
||||||
|
import { getGeneratorInformation } from '../generate/generator-utils';
|
||||||
|
import { workspaceRoot } from '../../utils/workspace-root';
|
||||||
|
import { addDepsToPackageJson, runInstall } from './implementation/utils';
|
||||||
|
import { getPluginCapabilities } from '../../utils/plugins';
|
||||||
|
import { isAngularPluginInstalled } from '../../adapter/angular-json';
|
||||||
|
import {
|
||||||
|
isAggregateCreateNodesError,
|
||||||
|
isProjectConfigurationsError,
|
||||||
|
isProjectsWithNoNameError,
|
||||||
|
} from '../../project-graph/error-types';
|
||||||
|
|
||||||
|
export function runPackageManagerInstallPlugins(
|
||||||
|
repoRoot: string,
|
||||||
|
pmc: PackageManagerCommands = getPackageManagerCommand(),
|
||||||
|
plugins: string[]
|
||||||
|
) {
|
||||||
|
if (plugins.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
addDepsToPackageJson(repoRoot, plugins);
|
||||||
|
runInstall(repoRoot, pmc);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Installs a plugin by running its init generator. It will change the file system tree passed in.
|
||||||
|
* @param plugin The name of the plugin to install
|
||||||
|
* @param repoRoot repo root
|
||||||
|
* @param verbose verbose
|
||||||
|
* @param options options passed to init generator
|
||||||
|
* @returns void
|
||||||
|
*/
|
||||||
|
export async function installPlugin(
|
||||||
|
plugin: string,
|
||||||
|
repoRoot: string = workspaceRoot,
|
||||||
|
verbose: boolean = false,
|
||||||
|
options: { [k: string]: any }
|
||||||
|
): Promise<void> {
|
||||||
|
const host = new FsTree(repoRoot, verbose, `install ${plugin}`);
|
||||||
|
const capabilities = await getPluginCapabilities(repoRoot, plugin, {});
|
||||||
|
const generators = capabilities?.generators;
|
||||||
|
if (!generators) {
|
||||||
|
throw new Error(`No generators found in ${plugin}.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const initGenerator = findInitGenerator(generators);
|
||||||
|
if (!initGenerator) {
|
||||||
|
output.log({
|
||||||
|
title: `No "init" generator found in ${plugin}. Skipping initialization.`,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const { implementationFactory } = getGeneratorInformation(
|
||||||
|
plugin,
|
||||||
|
initGenerator,
|
||||||
|
repoRoot,
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
|
||||||
|
const implementation: NxGenerator = implementationFactory();
|
||||||
|
const task: GeneratorCallback | void = await implementation(host, options);
|
||||||
|
flushChanges(repoRoot, host.listChanges());
|
||||||
|
if (task) {
|
||||||
|
await task();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Install plugins
|
||||||
|
* Get the implementation of the plugin's init generator and run it
|
||||||
|
* @returns a list of succeeded plugins and a map of failed plugins to errors
|
||||||
|
*/
|
||||||
|
export async function installPlugins(
|
||||||
|
plugins: string[],
|
||||||
|
updatePackageScripts: boolean,
|
||||||
|
repoRoot: string = workspaceRoot,
|
||||||
|
verbose: boolean = false
|
||||||
|
): Promise<{
|
||||||
|
succeededPlugins: string[];
|
||||||
|
failedPlugins: { [plugin: string]: Error };
|
||||||
|
}> {
|
||||||
|
if (plugins.length === 0) {
|
||||||
|
return {
|
||||||
|
succeededPlugins: [],
|
||||||
|
failedPlugins: {},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const spinner = createSpinner();
|
||||||
|
let succeededPlugins = [];
|
||||||
|
const failedPlugins: {
|
||||||
|
[pluginName: string]: Error;
|
||||||
|
} = {};
|
||||||
|
|
||||||
|
for (const plugin of plugins) {
|
||||||
|
try {
|
||||||
|
spinner.start('Installing plugin ' + plugin);
|
||||||
|
await installPlugin(plugin, repoRoot, verbose, {
|
||||||
|
keepExistingVersions: true,
|
||||||
|
updatePackageScripts,
|
||||||
|
addPlugin: true,
|
||||||
|
skipFormat: false,
|
||||||
|
skipPackageJson: false,
|
||||||
|
});
|
||||||
|
succeededPlugins.push(plugin);
|
||||||
|
spinner.succeed('Installed plugin ' + plugin);
|
||||||
|
} catch (e) {
|
||||||
|
failedPlugins[plugin] = e;
|
||||||
|
spinner.fail('Failed to install plugin ' + plugin);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
succeededPlugins,
|
||||||
|
failedPlugins,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures plugins, installs them, and outputs the results
|
||||||
|
* @returns a list of succeeded plugins and a map of failed plugins to errors
|
||||||
|
*/
|
||||||
|
export async function configurePlugins(
|
||||||
|
plugins: string[],
|
||||||
|
updatePackageScripts: boolean,
|
||||||
|
pmc: PackageManagerCommands,
|
||||||
|
repoRoot: string = workspaceRoot,
|
||||||
|
verbose: boolean = false
|
||||||
|
): Promise<{
|
||||||
|
succeededPlugins: string[];
|
||||||
|
failedPlugins: { [plugin: string]: Error };
|
||||||
|
}> {
|
||||||
|
if (plugins.length === 0) {
|
||||||
|
return {
|
||||||
|
succeededPlugins: [],
|
||||||
|
failedPlugins: {},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
output.log({ title: '🔨 Configuring plugins' });
|
||||||
|
let { succeededPlugins, failedPlugins } = await installPlugins(
|
||||||
|
plugins,
|
||||||
|
updatePackageScripts,
|
||||||
|
repoRoot,
|
||||||
|
verbose
|
||||||
|
);
|
||||||
|
|
||||||
|
if (succeededPlugins.length > 0) {
|
||||||
|
output.success({
|
||||||
|
title: 'Installed Plugins',
|
||||||
|
bodyLines: succeededPlugins.map((p) => `- ${bold(p)}`),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (Object.keys(failedPlugins).length > 0) {
|
||||||
|
output.error({
|
||||||
|
title: `Failed to install plugins`,
|
||||||
|
bodyLines: [
|
||||||
|
'The following plugins were not installed:',
|
||||||
|
...Object.keys(failedPlugins).map((p) => `- ${bold(p)}`),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
Object.entries(failedPlugins).forEach(([plugin, error]) => {
|
||||||
|
output.error({
|
||||||
|
title: `Failed to install ${plugin}`,
|
||||||
|
bodyLines: getFailedToInstallPluginErrorMessages(error),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
output.error({
|
||||||
|
title: `To install the plugins manually`,
|
||||||
|
bodyLines: [
|
||||||
|
'You may need to run commands to install the plugins:',
|
||||||
|
...Object.keys(failedPlugins).map(
|
||||||
|
(p) => `- ${bold(pmc.exec + ' nx add ' + p)}`
|
||||||
|
),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return { succeededPlugins, failedPlugins };
|
||||||
|
}
|
||||||
|
|
||||||
|
function findInitGenerator(
|
||||||
|
generators: Record<string, GeneratorsJsonEntry>
|
||||||
|
): string | undefined {
|
||||||
|
if (generators['init']) {
|
||||||
|
return 'init';
|
||||||
|
}
|
||||||
|
|
||||||
|
const angularPluginInstalled = isAngularPluginInstalled();
|
||||||
|
if (angularPluginInstalled && generators['ng-add']) {
|
||||||
|
return 'ng-add';
|
||||||
|
}
|
||||||
|
|
||||||
|
return Object.keys(generators).find(
|
||||||
|
(name) =>
|
||||||
|
generators[name].aliases?.includes('init') ||
|
||||||
|
(angularPluginInstalled && generators[name].aliases?.includes('ng-add'))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getFailedToInstallPluginErrorMessages(e: any): string[] {
|
||||||
|
const errorBodyLines = [];
|
||||||
|
if (isProjectConfigurationsError(e) && e.errors.length > 0) {
|
||||||
|
for (const error of e.errors) {
|
||||||
|
if (isAggregateCreateNodesError(error)) {
|
||||||
|
const innerErrors = error.errors;
|
||||||
|
for (const [file, e] of innerErrors) {
|
||||||
|
if (file) {
|
||||||
|
errorBodyLines.push(` - ${bold(file)}: ${e.message}`);
|
||||||
|
} else {
|
||||||
|
errorBodyLines.push(` - ${e.message}`);
|
||||||
|
}
|
||||||
|
if (e.stack) {
|
||||||
|
const innerStackTrace =
|
||||||
|
' ' + e.stack.split('\n')?.join('\n ');
|
||||||
|
errorBodyLines.push(innerStackTrace);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (!isProjectsWithNoNameError(error)) {
|
||||||
|
// swallow ProjectsWithNameError
|
||||||
|
if (error.message) {
|
||||||
|
errorBodyLines.push(` - ${error.message}`);
|
||||||
|
}
|
||||||
|
if (error.stack) {
|
||||||
|
const innerStackTrace =
|
||||||
|
' ' + error.stack.split('\n')?.join('\n ');
|
||||||
|
errorBodyLines.push(innerStackTrace);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (e.message) {
|
||||||
|
errorBodyLines.push(` - ${e.message}`);
|
||||||
|
}
|
||||||
|
if (e.stack) {
|
||||||
|
const innerStackTrace = ' ' + e.stack.split('\n')?.join('\n ');
|
||||||
|
errorBodyLines.push(innerStackTrace);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return errorBodyLines;
|
||||||
|
}
|
||||||
@ -1,26 +1,21 @@
|
|||||||
import { existsSync } from 'fs';
|
import { existsSync } from 'fs';
|
||||||
|
|
||||||
import { PackageJson } from '../../utils/package-json';
|
import { PackageJson } from '../../utils/package-json';
|
||||||
import { prerelease } from 'semver';
|
import { prerelease } from 'semver';
|
||||||
import { output } from '../../utils/output';
|
import { output } from '../../utils/output';
|
||||||
import {
|
import { getPackageManagerCommand } from '../../utils/package-manager';
|
||||||
getPackageManagerCommand,
|
|
||||||
PackageManagerCommands,
|
|
||||||
} from '../../utils/package-manager';
|
|
||||||
import { generateDotNxSetup } from './implementation/dot-nx/add-nx-scripts';
|
import { generateDotNxSetup } from './implementation/dot-nx/add-nx-scripts';
|
||||||
import { runNxSync } from '../../utils/child-process';
|
import { runNxSync } from '../../utils/child-process';
|
||||||
import { readJsonFile } from '../../utils/fileutils';
|
import { readJsonFile } from '../../utils/fileutils';
|
||||||
import { nxVersion } from '../../utils/versions';
|
import { nxVersion } from '../../utils/versions';
|
||||||
import {
|
import {
|
||||||
addDepsToPackageJson,
|
|
||||||
createNxJsonFile,
|
createNxJsonFile,
|
||||||
initCloud,
|
initCloud,
|
||||||
isMonorepo,
|
isMonorepo,
|
||||||
printFinalMessage,
|
printFinalMessage,
|
||||||
runInstall,
|
|
||||||
updateGitIgnore,
|
updateGitIgnore,
|
||||||
} from './implementation/utils';
|
} from './implementation/utils';
|
||||||
import { prompt } from 'enquirer';
|
import { prompt } from 'enquirer';
|
||||||
import { execSync } from 'child_process';
|
|
||||||
import { addNxToAngularCliRepo } from './implementation/angular';
|
import { addNxToAngularCliRepo } from './implementation/angular';
|
||||||
import { globWithWorkspaceContextSync } from '../../utils/workspace-context';
|
import { globWithWorkspaceContextSync } from '../../utils/workspace-context';
|
||||||
import { connectExistingRepoToNxCloudPrompt } from '../connect/connect-to-nx-cloud';
|
import { connectExistingRepoToNxCloudPrompt } from '../connect/connect-to-nx-cloud';
|
||||||
@ -28,41 +23,17 @@ import { addNxToNpmRepo } from './implementation/add-nx-to-npm-repo';
|
|||||||
import { addNxToMonorepo } from './implementation/add-nx-to-monorepo';
|
import { addNxToMonorepo } from './implementation/add-nx-to-monorepo';
|
||||||
import { NxJsonConfiguration, readNxJson } from '../../config/nx-json';
|
import { NxJsonConfiguration, readNxJson } from '../../config/nx-json';
|
||||||
import { getPackageNameFromImportPath } from '../../utils/get-package-name-from-import-path';
|
import { getPackageNameFromImportPath } from '../../utils/get-package-name-from-import-path';
|
||||||
|
import {
|
||||||
|
configurePlugins,
|
||||||
|
runPackageManagerInstallPlugins,
|
||||||
|
} from './configure-plugins';
|
||||||
|
|
||||||
export interface InitArgs {
|
export interface InitArgs {
|
||||||
interactive: boolean;
|
interactive: boolean;
|
||||||
nxCloud?: boolean;
|
nxCloud?: boolean;
|
||||||
useDotNxInstallation?: boolean;
|
useDotNxInstallation?: boolean;
|
||||||
integrated?: boolean; // For Angular projects only
|
integrated?: boolean; // For Angular projects only
|
||||||
}
|
verbose?: boolean;
|
||||||
|
|
||||||
export function installPlugins(
|
|
||||||
repoRoot: string,
|
|
||||||
plugins: string[],
|
|
||||||
pmc: PackageManagerCommands,
|
|
||||||
updatePackageScripts: boolean
|
|
||||||
) {
|
|
||||||
if (plugins.length === 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
addDepsToPackageJson(repoRoot, plugins);
|
|
||||||
|
|
||||||
runInstall(repoRoot, pmc);
|
|
||||||
|
|
||||||
output.log({ title: '🔨 Configuring plugins' });
|
|
||||||
for (const plugin of plugins) {
|
|
||||||
execSync(
|
|
||||||
`${pmc.exec} nx g ${plugin}:init --keepExistingVersions ${
|
|
||||||
updatePackageScripts ? '--updatePackageScripts' : ''
|
|
||||||
}`,
|
|
||||||
{
|
|
||||||
stdio: [0, 1, 2],
|
|
||||||
cwd: repoRoot,
|
|
||||||
windowsHide: false,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function initHandler(options: InitArgs): Promise<void> {
|
export async function initHandler(options: InitArgs): Promise<void> {
|
||||||
@ -146,7 +117,14 @@ export async function initHandler(options: InitArgs): Promise<void> {
|
|||||||
|
|
||||||
output.log({ title: '📦 Installing Nx' });
|
output.log({ title: '📦 Installing Nx' });
|
||||||
|
|
||||||
installPlugins(repoRoot, plugins, pmc, updatePackageScripts);
|
runPackageManagerInstallPlugins(repoRoot, pmc, plugins);
|
||||||
|
await configurePlugins(
|
||||||
|
plugins,
|
||||||
|
updatePackageScripts,
|
||||||
|
pmc,
|
||||||
|
repoRoot,
|
||||||
|
options.verbose
|
||||||
|
);
|
||||||
|
|
||||||
if (useNxCloud) {
|
if (useNxCloud) {
|
||||||
output.log({ title: '🛠️ Setting up Nx Cloud' });
|
output.log({ title: '🛠️ Setting up Nx Cloud' });
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user