Skip to content

Commit 1d7465b

Browse files
authored
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 #
1 parent 67d0e33 commit 1d7465b

File tree

9 files changed

+402
-190
lines changed

9 files changed

+402
-190
lines changed

e2e/eslint/src/linter.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -772,7 +772,7 @@ describe('Linter', () => {
772772
const mylib = uniq('mylib');
773773

774774
runCLI(
775-
`generate @nx/node:app --name=${myapp} --linter=eslint --directory="." --e2eTestRunner=jest --no-interactive`
775+
`generate @nx/node:app --name=${myapp} --linter=eslint --directory="." --unitTestRunner=jest --e2eTestRunner=jest --no-interactive`
776776
);
777777
runCLI('reset', { env: { CI: 'false' } });
778778
verifySuccessfulStandaloneSetup(myapp);

packages/devkit/src/utils/add-plugin.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,9 @@ import {
1515
writeJson,
1616
} from 'nx/src/devkit-exports';
1717
import {
18+
isProjectConfigurationsError,
19+
isProjectsWithNoNameError,
1820
LoadedNxPlugin,
19-
ProjectConfigurationsError,
2021
retrieveProjectConfigurations,
2122
} from 'nx/src/devkit-internals';
2223

@@ -130,8 +131,12 @@ async function _addPluginInternal<PluginOptions>(
130131
);
131132
} catch (e) {
132133
// Errors are okay for this because we're only running 1 plugin
133-
if (e instanceof ProjectConfigurationsError) {
134+
if (isProjectConfigurationsError(e)) {
134135
projConfigs = e.partialProjectConfigurationsResult;
136+
// ignore errors from projects with no name
137+
if (!e.errors.every(isProjectsWithNoNameError)) {
138+
throw e;
139+
}
135140
} else {
136141
throw e;
137142
}
@@ -171,8 +176,12 @@ async function _addPluginInternal<PluginOptions>(
171176
);
172177
} catch (e) {
173178
// Errors are okay for this because we're only running 1 plugin
174-
if (e instanceof ProjectConfigurationsError) {
179+
if (isProjectConfigurationsError(e)) {
175180
projConfigs = e.partialProjectConfigurationsResult;
181+
// ignore errors from projects with no name
182+
if (!e.errors.every(isProjectsWithNoNameError)) {
183+
throw e;
184+
}
176185
} else {
177186
throw e;
178187
}

packages/js/src/utils/typescript/ts-solution-setup.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,9 @@ export function assertNotUsingTsSolutionSetup(
9898
],
9999
});
100100

101-
process.exit(1);
101+
throw new Error(
102+
`The ${artifactString} doesn't yet support the existing TypeScript setup. See the error above.`
103+
);
102104
}
103105

104106
export function findRuntimeTsConfigName(

packages/nx/src/command-line/add/add.ts

Lines changed: 36 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
import { exec } from 'child_process';
22
import { existsSync } from 'fs';
33
import * as ora from 'ora';
4-
import { isAngularPluginInstalled } from '../../adapter/angular-json';
5-
import type { GeneratorsJsonEntry } from '../../config/misc-interfaces';
4+
import * as yargsParser from 'yargs-parser';
65
import { readNxJson, type NxJsonConfiguration } from '../../config/nx-json';
7-
import { runNxAsync, runNxSync } from '../../utils/child-process';
6+
import { runNxAsync } from '../../utils/child-process';
87
import { writeJsonFile } from '../../utils/fileutils';
98
import { logger } from '../../utils/logger';
109
import { output } from '../../utils/output';
@@ -14,12 +13,15 @@ import {
1413
getPackageManagerVersion,
1514
} from '../../utils/package-manager';
1615
import { handleErrors } from '../../utils/handle-errors';
17-
import { getPluginCapabilities } from '../../utils/plugins';
1816
import { nxVersion } from '../../utils/versions';
1917
import { workspaceRoot } from '../../utils/workspace-root';
2018
import type { AddOptions } from './command-object';
2119
import { normalizeVersionForNxJson } from '../init/implementation/dot-nx/add-nx-scripts';
2220
import { gte } from 'semver';
21+
import {
22+
installPlugin,
23+
getFailedToInstallPluginErrorMessages,
24+
} from '../init/configure-plugins';
2325

2426
export function addHandler(options: AddOptions): Promise<number> {
2527
return handleErrors(options.verbose, async () => {
@@ -109,80 +111,54 @@ async function initializePlugin(
109111
options: AddOptions,
110112
nxJson: NxJsonConfiguration
111113
): Promise<void> {
112-
const capabilities = await getPluginCapabilities(workspaceRoot, pkgName, {});
113-
const generators = capabilities?.generators;
114-
if (!generators) {
115-
output.log({
116-
title: `No generators found in ${pkgName}. Skipping initialization.`,
117-
});
118-
return;
119-
}
114+
const parsedCommandArgs: { [key: string]: any } = yargsParser(
115+
options.__overrides_unparsed__,
116+
{
117+
configuration: {
118+
'parse-numbers': false,
119+
'parse-positional-numbers': false,
120+
'dot-notation': false,
121+
'camel-case-expansion': false,
122+
},
123+
}
124+
);
120125

121-
const initGenerator = findInitGenerator(generators);
122-
if (!initGenerator) {
123-
output.log({
124-
title: `No "init" generator found in ${pkgName}. Skipping initialization.`,
125-
});
126-
return;
126+
if (coreNxPluginVersions.has(pkgName)) {
127+
parsedCommandArgs.keepExistingVersions = true;
128+
129+
if (
130+
options.updatePackageScripts ||
131+
(options.updatePackageScripts === undefined &&
132+
nxJson.useInferencePlugins !== false &&
133+
process.env.NX_ADD_PLUGINS !== 'false')
134+
) {
135+
parsedCommandArgs.updatePackageScripts = true;
136+
}
127137
}
128138

129139
const spinner = ora(`Initializing ${pkgName}...`);
130140
spinner.start();
131141

132142
try {
133-
const args = [];
134-
if (coreNxPluginVersions.has(pkgName)) {
135-
args.push(`--keepExistingVersions`);
136-
137-
if (
138-
options.updatePackageScripts ||
139-
(options.updatePackageScripts === undefined &&
140-
nxJson.useInferencePlugins !== false &&
141-
process.env.NX_ADD_PLUGINS !== 'false')
142-
) {
143-
args.push(`--updatePackageScripts`);
144-
}
145-
}
146-
147-
if (options.__overrides_unparsed__.length) {
148-
args.push(...options.__overrides_unparsed__);
149-
}
150-
151-
runNxSync(`g ${pkgName}:${initGenerator} ${args.join(' ')}`, {
152-
stdio: [0, 1, 2],
153-
});
143+
await installPlugin(
144+
pkgName,
145+
workspaceRoot,
146+
options.verbose,
147+
parsedCommandArgs
148+
);
154149
} catch (e) {
155150
spinner.fail();
156151
output.addNewline();
157-
logger.error(e);
158152
output.error({
159-
title: `Failed to initialize ${pkgName}. Please check the error above for more details.`,
153+
title: `Failed to initialize ${pkgName}`,
154+
bodyLines: getFailedToInstallPluginErrorMessages(e),
160155
});
161156
process.exit(1);
162157
}
163158

164159
spinner.succeed();
165160
}
166161

167-
function findInitGenerator(
168-
generators: Record<string, GeneratorsJsonEntry>
169-
): string | undefined {
170-
if (generators['init']) {
171-
return 'init';
172-
}
173-
174-
const angularPluginInstalled = isAngularPluginInstalled();
175-
if (angularPluginInstalled && generators['ng-add']) {
176-
return 'ng-add';
177-
}
178-
179-
return Object.keys(generators).find(
180-
(name) =>
181-
generators[name].aliases?.includes('init') ||
182-
(angularPluginInstalled && generators[name].aliases?.includes('ng-add'))
183-
);
184-
}
185-
186162
function parsePackageSpecifier(
187163
packageSpecifier: string
188164
): [pkgName: string, version: string] {

packages/nx/src/command-line/import/import.ts

Lines changed: 81 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { tmpdir } from 'tmp';
77
import { prompt } from 'enquirer';
88
import { output } from '../../utils/output';
99
import * as createSpinner from 'ora';
10-
import { detectPlugins, installPlugins } from '../init/init-v2';
10+
import { detectPlugins } from '../init/init-v2';
1111
import { readNxJson } from '../../config/nx-json';
1212
import { workspaceRoot } from '../../utils/workspace-root';
1313
import {
@@ -24,11 +24,11 @@ import { runInstall } from '../init/implementation/utils';
2424
import { getBaseRef } from '../../utils/command-line-utils';
2525
import { prepareSourceRepo } from './utils/prepare-source-repo';
2626
import { mergeRemoteSource } from './utils/merge-remote-source';
27-
import {
28-
getPackagesInPackageManagerWorkspace,
29-
needsInstall,
30-
} from './utils/needs-install';
3127
import { minimatch } from 'minimatch';
28+
import {
29+
configurePlugins,
30+
runPackageManagerInstallPlugins,
31+
} from '../init/configure-plugins';
3232

3333
const importRemoteName = '__tmp_nx_import__';
3434

@@ -60,7 +60,7 @@ export interface ImportOptions {
6060

6161
export async function importHandler(options: ImportOptions) {
6262
process.env.NX_RUNNING_NX_IMPORT = 'true';
63-
let { sourceRepository, ref, source, destination } = options;
63+
let { sourceRepository, ref, source, destination, verbose } = options;
6464
const destinationGitClient = new GitRepository(process.cwd());
6565

6666
if (await destinationGitClient.hasUncommittedChanges()) {
@@ -219,11 +219,6 @@ export async function importHandler(options: ImportOptions) {
219219
}
220220

221221
const packageManager = detectPackageManager(workspaceRoot);
222-
223-
const originalPackageWorkspaces = await getPackagesInPackageManagerWorkspace(
224-
packageManager
225-
);
226-
227222
const sourceIsNxWorkspace = existsSync(join(sourceGitClient.root, 'nx.json'));
228223

229224
const relativeDestination = relative(
@@ -287,42 +282,30 @@ export async function importHandler(options: ImportOptions) {
287282
destinationGitClient
288283
);
289284

290-
// If install fails, we should continue since the errors could be resolved later.
291-
let installFailed = false;
292-
if (plugins.length > 0) {
293-
try {
294-
output.log({ title: 'Installing Plugins' });
295-
installPlugins(workspaceRoot, plugins, pmc, updatePackageScripts);
296-
297-
await destinationGitClient.amendCommit();
298-
} catch (e) {
299-
installFailed = true;
300-
output.error({
301-
title: `Install failed: ${e.message || 'Unknown error'}`,
302-
bodyLines: [e.stack],
303-
});
304-
}
305-
} else if (await needsInstall(packageManager, originalPackageWorkspaces)) {
306-
try {
307-
output.log({
308-
title: 'Installing dependencies for imported code',
309-
});
310-
311-
runInstall(workspaceRoot, getPackageManagerCommand(packageManager));
312-
313-
await destinationGitClient.amendCommit();
314-
} catch (e) {
315-
installFailed = true;
316-
output.error({
317-
title: `Install failed: ${e.message || 'Unknown error'}`,
318-
bodyLines: [e.stack],
319-
});
285+
let installed = await runInstallDestinationRepo(
286+
packageManager,
287+
destinationGitClient
288+
);
289+
290+
if (installed && plugins.length > 0) {
291+
installed = await runPluginsInstall(plugins, pmc, destinationGitClient);
292+
if (installed) {
293+
const { succeededPlugins } = await configurePlugins(
294+
plugins,
295+
updatePackageScripts,
296+
pmc,
297+
workspaceRoot,
298+
verbose
299+
);
300+
if (succeededPlugins.length > 0) {
301+
await destinationGitClient.amendCommit();
302+
}
320303
}
321304
}
322305

323306
console.log(await destinationGitClient.showStat());
324307

325-
if (installFailed) {
308+
if (installed === false) {
326309
const pmc = getPackageManagerCommand(packageManager);
327310
output.warn({
328311
title: `The import was successful, but the install failed`,
@@ -397,6 +380,62 @@ async function createTemporaryRemote(
397380
}
398381

399382
/**
383+
* Run install for the imported code and plugins
384+
* @returns true if the install failed
385+
*/
386+
async function runInstallDestinationRepo(
387+
packageManager: PackageManager,
388+
destinationGitClient: GitRepository
389+
): Promise<boolean> {
390+
let installed = true;
391+
try {
392+
output.log({
393+
title: 'Installing dependencies for imported code',
394+
});
395+
runInstall(workspaceRoot, getPackageManagerCommand(packageManager));
396+
await destinationGitClient.amendCommit();
397+
} catch (e) {
398+
installed = false;
399+
output.error({
400+
title: `Install failed: ${e.message || 'Unknown error'}`,
401+
bodyLines: [e.stack],
402+
});
403+
}
404+
return installed;
405+
}
406+
407+
async function runPluginsInstall(
408+
plugins: string[],
409+
pmc: PackageManagerCommands,
410+
destinationGitClient: GitRepository
411+
) {
412+
let installed = true;
413+
output.log({ title: 'Installing Plugins' });
414+
try {
415+
runPackageManagerInstallPlugins(workspaceRoot, pmc, plugins);
416+
await destinationGitClient.amendCommit();
417+
} catch (e) {
418+
installed = false;
419+
output.error({
420+
title: `Install failed: ${e.message || 'Unknown error'}`,
421+
bodyLines: [
422+
'The following plugins were not installed:',
423+
...plugins.map((p) => `- ${chalk.bold(p)}`),
424+
e.stack,
425+
],
426+
});
427+
output.error({
428+
title: `To install the plugins manually`,
429+
bodyLines: [
430+
'You may need to run commands to install the plugins:',
431+
...plugins.map((p) => `- ${chalk.bold(pmc.exec + ' nx add ' + p)}`),
432+
],
433+
});
434+
}
435+
return installed;
436+
}
437+
438+
/*
400439
* If the user imports a project that isn't in the workspaces entry, we should add that path to the workspaces entry.
401440
*/
402441
async function handleMissingWorkspacesEntry(

0 commit comments

Comments
 (0)