Skip to content

Commit

Permalink
fix(core): not exit when one plugin installation failed
Browse files Browse the repository at this point in the history
  • Loading branch information
xiongemi committed Nov 21, 2024
1 parent 4ef5f6e commit 4960194
Show file tree
Hide file tree
Showing 8 changed files with 511 additions and 286 deletions.
4 changes: 3 additions & 1 deletion packages/js/src/utils/typescript/ts-solution-setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,5 +94,7 @@ export function assertNotUsingTsSolutionSetup(
],
});

process.exit(1);
throw new Error(
`The ${artifactString} doesn't yet support the existing TypeScript setup`
);
}
151 changes: 107 additions & 44 deletions packages/nx/src/command-line/import/import.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@ import { tmpdir } from 'tmp';
import { prompt } from 'enquirer';
import { output } from '../../utils/output';
import * as createSpinner from 'ora';
import { detectPlugins, installPlugins } from '../init/init-v2';
import {
detectPlugins,
runPackageManagerInstallPlugins,
} from '../init/init-v2';
import { readNxJson } from '../../config/nx-json';
import { workspaceRoot } from '../../utils/workspace-root';
import {
Expand All @@ -33,6 +36,7 @@ import {
checkCompatibleWithPlugins,
updatePluginsInNxJson,
} from './utils/check-compatible-with-plugins';
import { configurePlugins } from './utils/configure-plugins';

const importRemoteName = '__tmp_nx_import__';

Expand Down Expand Up @@ -64,7 +68,7 @@ export interface ImportOptions {

export async function importHandler(options: ImportOptions) {
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());

if (await destinationGitClient.hasUncommittedChanges()) {
Expand Down Expand Up @@ -284,69 +288,81 @@ export async function importHandler(options: ImportOptions) {
});
}

// If install fails, we should continue since the errors could be resolved later.
let installFailed = false;
if (nxJson.plugins?.length > 0) {
// Check compatibility with existing plugins for the workspace included new imported projects
const imcompatiblePlugins = await checkCompatibleWithPlugins(
const incompatiblePlugins = await checkCompatibleWithPlugins(
nxJson.plugins,
workspaceRoot,
workspaceRoot
);
if (Object.keys(imcompatiblePlugins).length > 0) {
updatePluginsInNxJson(workspaceRoot, imcompatiblePlugins);
if (Object.keys(incompatiblePlugins).length > 0) {
updatePluginsInNxJson(workspaceRoot, incompatiblePlugins);
await destinationGitClient.amendCommit();
Object.entries(incompatiblePlugins).forEach(
([pluginName, excludeFiles]) => {
if (excludeFiles.size === 0) {
return;
}
const files = Array.from(excludeFiles).filter((file) =>
file.startsWith(relativeDestination)
);
if (files.length === 0) {
return;
}
output.warn({
title: `Incompatible files found for ${pluginName} plugin`,
bodyLines: [
`Added the following files to the exclude list for ${pluginName}:`,
...files.map((file) => ` - ${chalk.bold(file)}`),
],
});
}
);
}
}
if (plugins.length > 0) {
try {
output.log({ title: 'Installing Plugins' });
installPlugins(workspaceRoot, plugins, pmc, updatePackageScripts);

await destinationGitClient.amendCommit();
} catch (e) {
installFailed = true;
output.error({
title: `Install failed: ${e.message || 'Unknown error'}`,
bodyLines: [e.stack],
});
}
// Check compatibility with new plugins for the workspace included new imported projects
const imcompatiblePlugins = await checkCompatibleWithPlugins(
plugins.map((plugin) => plugin + '/plugin'), // plugins contains package name, but we need plugin name
workspaceRoot
);
if (Object.keys(imcompatiblePlugins).length > 0) {
updatePluginsInNxJson(workspaceRoot, imcompatiblePlugins);
await destinationGitClient.amendCommit();
}
} else if (await needsInstall(packageManager, originalPackageWorkspaces)) {
try {
output.log({
title: 'Installing dependencies for imported code',
});

runInstall(workspaceRoot, getPackageManagerCommand(packageManager));
const installed = await runInstallDestinationRepo(
plugins,
pmc,
packageManager,
originalPackageWorkspaces,
destinationGitClient
);

await destinationGitClient.amendCommit();
} catch (e) {
installFailed = true;
output.error({
title: `Install failed: ${e.message || 'Unknown error'}`,
bodyLines: [e.stack],
});
}
if (installed && plugins.length > 0) {
await configurePlugins(
workspaceRoot,
plugins,
updatePackageScripts,
pmc,
destinationGitClient,
verbose
);
}

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

if (installFailed) {
if (installed === false) {
const pmc = getPackageManagerCommand(packageManager);
output.warn({
title: `The import was successful, but the install failed`,
bodyLines: [
`You may need to run "${pmc.install}" manually to resolve the issue. The error is logged above.`,
],
});
if (plugins.length > 0) {
output.warn({
title: `Failed to install plugins`,
bodyLines: [
'The following plugins were not installed:',
...Object.keys(plugins).map((p) => `- ${chalk.bold(p)}`),
'You may need to run commands manually to install plugins:',
...Object.keys(plugins).map(
(p) => `- ${chalk.bold(pmc.exec + ' nx add ' + p)}`
),
],
});
}
}

await warnOnMissingWorkspacesEntry(packageManager, pmc, relativeDestination);
Expand Down Expand Up @@ -415,6 +431,53 @@ async function createTemporaryRemote(
await destinationGitClient.fetch(remoteName);
}

/**
* Run install for the imported code and plugins
* @returns true if the install failed
*/
async function runInstallDestinationRepo(
plugins: string[],
pmc: PackageManagerCommands,
packageManager: PackageManager,
originalPackageWorkspaces: Set<string>,
destinationGitClient: GitRepository
): Promise<boolean> {
// If install fails, we should continue since the errors could be resolved later.
let installed = true;
if (plugins.length > 0) {
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) => `- ${p}`),
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) {
installed = false;
output.error({
title: `Install failed: ${e.message || 'Unknown error'}`,
bodyLines: [e.stack],
});
}
}
return installed;
}

// If the user imports a project that isn't in NPM/Yarn/PNPM workspaces, then its dependencies
// will not be installed. We should warn users and provide instructions on how to fix this.
async function warnOnMissingWorkspacesEntry(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { existsSync } from 'node:fs';
import { join } from 'node:path';
import { bold } from 'chalk';

import { retrieveProjectConfigurations } from '../../../project-graph/utils/retrieve-workspace-files';
import {
Expand All @@ -14,15 +13,12 @@ import {
loadNxPlugins,
} from '../../../project-graph/plugins/internal-api';
import {
AggregateCreateNodesError,
isAggregateCreateNodesError,
isMergeNodesError,
MergeNodesError,
ProjectConfigurationsError,
} from '../../../project-graph/error-types';
import { workspaceRoot } from '../../../utils/workspace-root';
import { readJsonFile, writeJsonFile } from '../../../utils/fileutils';
import { output } from '../../../utils/output';

/**
* This function checks if the imported project is compatible with the plugins.
Expand All @@ -47,7 +43,7 @@ export async function checkCompatibleWithPlugins(
let pluginNametoExcludeFiles: {
[pluginName: string]: Set<string>;
} = {};
const nxJson = readNxJson(workspaceRoot);
const nxJson = readNxJson(root);
try {
await retrieveProjectConfigurations(
nxPlugins,
Expand Down Expand Up @@ -160,14 +156,6 @@ export function updatePluginsInNxJson(
if (!plugin || excludeFiles.size === 0) {
return;
}
output.warn({
title: `Incompatible projects found for ${pluginName} plugin`,
bodyLines: [
`Added the following files to the exclude list for ${pluginName}:`,
...Array.from(excludeFiles).map((file) => ` - ${bold(file)}`),
],
});

(plugin.exclude ?? []).forEach((e) => excludeFiles.add(e));
plugin.exclude = Array.from(excludeFiles);
}
Expand Down
112 changes: 112 additions & 0 deletions packages/nx/src/command-line/import/utils/configure-plugins.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import { bold } from 'chalk';
import {
checkCompatibleWithPlugins,
updatePluginsInNxJson,
} from './check-compatible-with-plugins';
import { output } from '../../../utils/output';
import { PackageManagerCommands } from '../../../utils/package-manager';
import { GitRepository } from '../../../utils/git-utils';
import { installPlugins } from '../../init/init-v2';

/**
* Configures plugins
* @param repoRoot
* @param plugins
* @param updatePackageScripts
* @param verbose
* @returns
*/
export async function configurePlugins(
repoRoot: string,
plugins: string[],
updatePackageScripts: boolean,
pmc: PackageManagerCommands,
destinationGitClient: GitRepository,
verbose: boolean = false
): Promise<{
succeededPlugins: string[];
failedPlugins: { [plugin: string]: Error };
incompatiblePlugins: { [plugin: string]: Set<string> };
}> {
if (plugins.length === 0) {
return;
}

let { succeededPlugins, failedPlugins } = await installPlugins(
repoRoot,
plugins,
updatePackageScripts,
verbose
);

let incompatiblePlugins: { [plugin: string]: Set<string> } = {};
if (succeededPlugins.length > 0) {
destinationGitClient.amendCommit();
incompatiblePlugins = await checkCompatibleWithPlugins(
succeededPlugins.map((plugin) => plugin + '/plugin'), // succeededPlugins are the package names, but we need the plugin names
repoRoot
);
if (Object.keys(incompatiblePlugins).length > 0) {
// Remove incompatible plugins from the list of succeeded plugins
succeededPlugins = succeededPlugins.filter(
(plugin) => !incompatiblePlugins[plugin + '/plugin']
);
if (succeededPlugins.length > 0) {
output.success({
title: 'Installed Plugins',
bodyLines: succeededPlugins.map((p) => `- ${bold(p)}`),
});
}
updatePluginsInNxJson(repoRoot, incompatiblePlugins);
destinationGitClient.amendCommit();
output.warn({
title: `Imcompatible plugins found`,
bodyLines: [
'The following plugins were not compatible:',
...Object.keys(incompatiblePlugins).map((p) => `- ${bold(p)}`),
`Please review the files and remove them from the exclude list if they are compatible with the plugin.`,
],
});

Object.entries(incompatiblePlugins).forEach(
([pluginName, excludeFiles]) => {
if (excludeFiles.size === 0) {
return;
}
output.warn({
title: `Incompatible files found for ${pluginName}`,
bodyLines: [
`Added the following files to the exclude list for ${pluginName}:`,
...Array.from(excludeFiles).map((file) => ` - ${bold(file)}`),
],
});
}
);
}
}
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: [error.stack ?? error.message ?? error.toString()],
});
});
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, incompatiblePlugins };
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import * as createSpinner from 'ora';
import { dirname, join, relative } from 'path';
import { mkdir, rm } from 'node:fs/promises';
import { join, relative } from 'path';
import { GitRepository } from '../../../utils/git-utils';

export async function prepareSourceRepo(
Expand Down
Loading

0 comments on commit 4960194

Please sign in to comment.