cleanup(core): refactor nx list command to remove unused code and code duplication (#21748)

<!-- 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` -->

## Current Behavior
<!-- This is the behavior we have today -->
the code for the `nx list` command has unused and duplicated code.
 
## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->
no unused code and code duplication

## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->

Fixes #

---------

Co-authored-by: Craigory Coppola <craigorycoppola@gmail.com>
This commit is contained in:
Phillip Barta 2024-07-24 17:56:27 +02:00 committed by GitHub
parent a8dc251cce
commit aeec5cc31a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 303 additions and 436 deletions

View File

@ -1,21 +1,17 @@
import { workspaceRoot } from '../../utils/workspace-root';
import { output } from '../../utils/output';
import {
fetchCorePlugins,
getInstalledPluginsAndCapabilities,
listCorePlugins,
listInstalledPlugins,
listPluginCapabilities,
} from '../../utils/plugins';
import {
getLocalWorkspacePlugins,
listLocalWorkspacePlugins,
} from '../../utils/plugins/local-plugins';
import { readNxJson } from '../../config/nx-json';
import {
createProjectGraphAsync,
readProjectsConfigurationFromProjectGraph,
} from '../../project-graph/project-graph';
import { readNxJson } from '../../config/nx-json';
import { output } from '../../utils/output';
import {
getInstalledPluginsAndCapabilities,
getLocalWorkspacePlugins,
listAlsoAvailableCorePlugins,
listPluginCapabilities,
listPlugins,
} from '../../utils/plugins';
import { workspaceRoot } from '../../utils/workspace-root';
export interface ListArgs {
/** The name of an installed plugin to query */
@ -31,14 +27,13 @@ export interface ListArgs {
*
*/
export async function listHandler(args: ListArgs): Promise<void> {
const nxJson = readNxJson();
const projectGraph = await createProjectGraphAsync({ exitOnError: true });
const projects = readProjectsConfigurationFromProjectGraph(projectGraph);
if (args.plugin) {
await listPluginCapabilities(args.plugin, projects.projects);
} else {
const corePlugins = fetchCorePlugins();
const nxJson = readNxJson();
const localPlugins = await getLocalWorkspacePlugins(projects, nxJson);
const installedPlugins = await getInstalledPluginsAndCapabilities(
@ -47,22 +42,20 @@ export async function listHandler(args: ListArgs): Promise<void> {
);
if (localPlugins.size) {
listLocalWorkspacePlugins(localPlugins);
listPlugins(localPlugins, 'Local workspace plugins:');
}
listInstalledPlugins(installedPlugins);
listCorePlugins(installedPlugins, corePlugins);
listPlugins(installedPlugins, 'Installed plugins:');
listAlsoAvailableCorePlugins(installedPlugins);
output.note({
title: 'Community Plugins',
bodyLines: [
'Looking for a technology / framework not listed above?',
'There are many excellent plugins maintained by the Nx community.',
'Search for the one you need here: https://nx.dev/plugins/registry.',
'Search for the one you need here: https://nx.dev/plugin-registry.',
],
});
output.note({
title: `Use "nx list [plugin]" to find out more`,
});
output.note({ title: `Use "nx list [plugin]" to find out more` });
}
}

View File

@ -1,30 +0,0 @@
import { get } from 'https';
import type { CommunityPlugin } from './models';
const COMMUNITY_PLUGINS_JSON_URL =
'https://raw.githubusercontent.com/nrwl/nx/master/community/approved-plugins.json';
export async function fetchCommunityPlugins(): Promise<CommunityPlugin[]> {
return new Promise((resolve, reject) => {
const req = get(COMMUNITY_PLUGINS_JSON_URL, (res) => {
if (res.statusCode < 200 || res.statusCode >= 300) {
reject(new Error(`Request failed with status code ${res.statusCode}`));
}
const data = [];
res.on('data', (chunk) => {
data.push(chunk);
});
res.on('end', () => {
try {
resolve(JSON.parse(Buffer.concat(data).toString('utf-8')));
} catch (e) {
reject(e);
}
});
});
req.on('error', reject);
req.end();
});
}

View File

@ -1,122 +1,112 @@
import * as chalk from 'chalk';
import { output } from '../output';
import type { CorePlugin, PluginCapabilities } from './models';
export function fetchCorePlugins(): CorePlugin[] {
return [
{
name: '@nx/angular',
capabilities: 'executors,generators',
},
{
name: '@nx/cypress',
capabilities: 'executors,generators',
},
{
name: '@nx/detox',
capabilities: 'executors,generators',
},
{
name: '@nx/esbuild',
capabilities: 'executors,generators',
},
{
name: '@nx/expo',
capabilities: 'executors,generators',
},
{
name: '@nx/express',
capabilities: 'generators',
},
{
name: '@nx/jest',
capabilities: 'executors,generators',
},
{
name: '@nx/js',
capabilities: 'executors,generators',
},
{
name: '@nx/eslint',
capabilities: 'executors,generators',
},
{
name: '@nx/nest',
capabilities: 'generators',
},
{
name: '@nx/next',
capabilities: 'executors,generators',
},
{
name: '@nx/node',
capabilities: 'executors,generators',
},
{
name: '@nx/nuxt',
capabilities: 'generators',
},
{
name: 'nx',
capabilities: 'executors',
},
{
name: '@nx/plugin',
capabilities: 'executors,generators',
},
{
name: '@nx/react',
capabilities: 'executors,generators',
},
{
name: '@nx/react-native',
capabilities: 'executors,generators',
},
{
name: '@nx/remix',
capabilities: 'executors,generators',
},
{
name: '@nx/rollup',
capabilities: 'executors,generators',
},
{
name: '@nx/storybook',
capabilities: 'executors,generators',
},
{
name: '@nx/vite',
capabilities: 'executors,generators',
},
{
name: '@nx/web',
capabilities: 'executors,generators',
},
{
name: '@nx/webpack',
capabilities: 'executors,generators',
},
{
name: '@nx/workspace',
capabilities: 'executors,generators',
},
];
export interface CorePlugin {
name: string;
capabilities: 'executors' | 'generators' | 'executors,generators' | 'graph';
link?: string;
}
export function listCorePlugins(
installedPlugins: Map<string, PluginCapabilities>,
corePlugins: CorePlugin[]
): void {
const alsoAvailable = corePlugins.filter(
(p) => !installedPlugins.has(p.name)
);
if (alsoAvailable.length) {
output.log({
title: `Also available:`,
bodyLines: alsoAvailable.map((p) => {
return `${chalk.bold(p.name)} (${p.capabilities})`;
}),
});
}
}
export const CORE_PLUGINS: CorePlugin[] = [
{
name: '@nx/angular',
capabilities: 'executors,generators',
},
{
name: '@nx/cypress',
capabilities: 'executors,generators',
},
{
name: '@nx/detox',
capabilities: 'executors,generators',
},
{
name: '@nx/esbuild',
capabilities: 'executors,generators',
},
{
name: '@nx/expo',
capabilities: 'executors,generators',
},
{
name: '@nx/express',
capabilities: 'generators',
},
{
name: '@nx/gradle',
capabilities: 'graph',
},
{
name: '@nx/jest',
capabilities: 'executors,generators',
},
{
name: '@nx/js',
capabilities: 'executors,generators',
},
{
name: '@nx/eslint',
capabilities: 'executors,generators',
},
{
name: '@nx/nest',
capabilities: 'generators',
},
{
name: '@nx/next',
capabilities: 'executors,generators',
},
{
name: '@nx/node',
capabilities: 'executors,generators',
},
{
name: '@nx/nuxt',
capabilities: 'generators',
},
{
name: 'nx',
capabilities: 'executors',
},
{
name: '@nx/plugin',
capabilities: 'executors,generators',
},
{
name: '@nx/react',
capabilities: 'executors,generators',
},
{
name: '@nx/react-native',
capabilities: 'executors,generators',
},
{
name: '@nx/remix',
capabilities: 'executors,generators',
},
{
name: '@nx/rollup',
capabilities: 'executors,generators',
},
{
name: '@nx/storybook',
capabilities: 'executors,generators',
},
{
name: '@nx/vite',
capabilities: 'executors,generators',
},
{
name: '@nx/vue',
capabilities: 'generators',
},
{
name: '@nx/web',
capabilities: 'executors,generators',
},
{
name: '@nx/webpack',
capabilities: 'executors,generators',
},
{
name: '@nx/workspace',
capabilities: 'executors,generators',
},
];

View File

@ -1,10 +1,8 @@
export { fetchCommunityPlugins } from './community-plugins';
export { fetchCorePlugins, listCorePlugins } from './core-plugins';
export { getInstalledPluginsAndCapabilities } from './installed-plugins';
export { getLocalWorkspacePlugins } from './local-plugins';
export {
getInstalledPluginsAndCapabilities,
listInstalledPlugins,
} from './installed-plugins';
export {
getPluginCapabilities,
listPlugins,
listAlsoAvailableCorePlugins,
listPluginCapabilities,
} from './plugin-capabilities';
} from './output';
export { getPluginCapabilities } from './plugin-capabilities';

View File

@ -1,15 +1,14 @@
import * as chalk from 'chalk';
import { output } from '../output';
import type { PluginCapabilities } from './models';
import { getPluginCapabilities } from './plugin-capabilities';
import { hasElements } from './shared';
import { readJsonFile } from '../fileutils';
import { PackageJson, readModulePackageJson } from '../package-json';
import { workspaceRoot } from '../workspace-root';
import { join } from 'path';
import { readNxJson } from '../../config/nx-json';
import { getNxRequirePaths } from '../installation-directory';
import { ProjectConfiguration } from '../../config/workspace-json-project-json';
import { readJsonFile } from '../fileutils';
import { getNxRequirePaths } from '../installation-directory';
import { PackageJson, readModulePackageJson } from '../package-json';
import { workspaceRoot } from '../workspace-root';
import {
PluginCapabilities,
getPluginCapabilities,
} from './plugin-capabilities';
export function findInstalledPlugins(): PackageJson[] {
const packageJsonDeps = getDependenciesFromPackageJson();
@ -92,31 +91,3 @@ export async function getInstalledPluginsAndCapabilities(
return result;
}
export function listInstalledPlugins(
installedPlugins: Map<string, PluginCapabilities>
) {
const bodyLines: string[] = [];
for (const [, p] of installedPlugins) {
const capabilities = [];
if (hasElements(p.executors)) {
capabilities.push('executors');
}
if (hasElements(p.generators)) {
capabilities.push('generators');
}
if (p.projectGraphExtension) {
capabilities.push('graph-extensions');
}
if (p.projectInference) {
capabilities.push('project-inference');
}
bodyLines.push(`${chalk.bold(p.name)} (${capabilities.join()})`);
}
output.log({
title: `Installed plugins:`,
bodyLines: bodyLines,
});
}

View File

@ -1,15 +1,14 @@
import * as chalk from 'chalk';
import { output } from '../output';
import type { PluginCapabilities } from './models';
import { hasElements } from './shared';
import { existsSync } from 'fs';
import { join } from 'path';
import { NxJsonConfiguration } from '../../config/nx-json';
import { ProjectsConfigurations } from '../../config/workspace-json-project-json';
import { readJsonFile } from '../fileutils';
import { PackageJson } from '../package-json';
import { ProjectsConfigurations } from '../../config/workspace-json-project-json';
import { join } from 'path';
import { workspaceRoot } from '../workspace-root';
import { existsSync } from 'fs';
import { getPluginCapabilities } from './plugin-capabilities';
import { NxJsonConfiguration, readNxJson } from '../../config/nx-json';
import {
PluginCapabilities,
getPluginCapabilities,
} from './plugin-capabilities';
export async function getLocalWorkspacePlugins(
projectsConfiguration: ProjectsConfigurations,
@ -45,31 +44,3 @@ export async function getLocalWorkspacePlugins(
}
return plugins;
}
export function listLocalWorkspacePlugins(
installedPlugins: Map<string, PluginCapabilities>
) {
const bodyLines: string[] = [];
for (const [, p] of installedPlugins) {
const capabilities = [];
if (hasElements(p.executors)) {
capabilities.push('executors');
}
if (hasElements(p.generators)) {
capabilities.push('generators');
}
if (p.projectGraphExtension) {
capabilities.push('graph-extension');
}
if (p.projectInference) {
capabilities.push('project-inference');
}
bodyLines.push(`${chalk.bold(p.name)} (${capabilities.join()})`);
}
output.log({
title: `Local workspace plugins:`,
bodyLines: bodyLines,
});
}

View File

@ -1,24 +0,0 @@
import {
ExecutorsJsonEntry,
GeneratorsJsonEntry,
} from '../../config/misc-interfaces';
export interface PluginCapabilities {
name: string;
executors?: { [name: string]: ExecutorsJsonEntry };
generators?: { [name: string]: GeneratorsJsonEntry };
projectInference?: boolean;
projectGraphExtension?: boolean;
}
export interface CorePlugin {
name: string;
capabilities: 'executors' | 'generators' | 'executors,generators';
link?: string;
}
export interface CommunityPlugin {
name: string;
url: string;
description: string;
}

View File

@ -0,0 +1,142 @@
import * as chalk from 'chalk';
import { ProjectConfiguration } from '../../config/workspace-json-project-json';
import { output } from '../output';
import { getPackageManagerCommand } from '../package-manager';
import { workspaceRoot } from '../workspace-root';
import { CORE_PLUGINS } from './core-plugins';
import {
PluginCapabilities,
getPluginCapabilities,
} from './plugin-capabilities';
import { readModulePackageJson } from '../package-json';
export function listPlugins(
plugins: Map<string, PluginCapabilities>,
title: string
) {
readModulePackageJson;
const bodyLines: string[] = [];
for (const [, p] of plugins) {
const capabilities = [];
if (hasElements(p.executors)) {
capabilities.push('executors');
}
if (hasElements(p.generators)) {
capabilities.push('generators');
}
if (p.projectGraphExtension) {
capabilities.push('graph-extension');
}
if (p.projectInference) {
capabilities.push('project-inference');
}
bodyLines.push(`${chalk.bold(p.name)} (${capabilities.join()})`);
}
output.log({
title: title,
bodyLines: bodyLines,
});
}
export function listAlsoAvailableCorePlugins(
installedPlugins: Map<string, PluginCapabilities>
): void {
const alsoAvailable = CORE_PLUGINS.filter(
(p) => !installedPlugins.has(p.name)
);
if (alsoAvailable.length) {
output.log({
title: `Also available:`,
bodyLines: alsoAvailable.map((p) => {
return `${chalk.bold(p.name)} (${p.capabilities})`;
}),
});
}
}
export async function listPluginCapabilities(
pluginName: string,
projects: Record<string, ProjectConfiguration>
) {
const plugin = await getPluginCapabilities(
workspaceRoot,
pluginName,
projects
);
if (!plugin) {
const pmc = getPackageManagerCommand();
output.note({
title: `${pluginName} is not currently installed`,
bodyLines: [
`Use "${pmc.addDev} ${pluginName}" to install the plugin.`,
`After that, use "${pmc.exec} nx g ${pluginName}:init" to add the required peer deps and initialize the plugin.`,
],
});
return;
}
const hasBuilders = hasElements(plugin.executors);
const hasGenerators = hasElements(plugin.generators);
const hasProjectGraphExtension = !!plugin.projectGraphExtension;
const hasProjectInference = !!plugin.projectInference;
if (
!hasBuilders &&
!hasGenerators &&
!hasProjectGraphExtension &&
!hasProjectInference
) {
output.warn({ title: `No capabilities found in ${pluginName}` });
return;
}
const bodyLines = [];
if (hasGenerators) {
bodyLines.push(chalk.bold(chalk.green('GENERATORS')));
bodyLines.push('');
bodyLines.push(
...Object.keys(plugin.generators).map(
(name) => `${chalk.bold(name)} : ${plugin.generators[name].description}`
)
);
if (hasBuilders) {
bodyLines.push('');
}
}
if (hasBuilders) {
bodyLines.push(chalk.bold(chalk.green('EXECUTORS/BUILDERS')));
bodyLines.push('');
bodyLines.push(
...Object.keys(plugin.executors).map((name) => {
const definition = plugin.executors[name];
return typeof definition === 'string'
? chalk.bold(name)
: `${chalk.bold(name)} : ${definition.description}`;
})
);
}
if (hasProjectGraphExtension) {
bodyLines.push(`✔️ Project Graph Extension`);
}
if (hasProjectInference) {
bodyLines.push(`✔️ Project Inference`);
}
output.log({
title: `Capabilities in ${plugin.name}:`,
bodyLines,
});
}
function hasElements(obj: any): boolean {
return obj && Object.values(obj).length > 0;
}

View File

@ -1,19 +1,22 @@
import * as chalk from 'chalk';
import { dirname, join } from 'path';
import {
ExecutorsJsonEntry,
GeneratorsJsonEntry,
} from '../../config/misc-interfaces';
import { ProjectConfiguration } from '../../config/workspace-json-project-json';
import { NxPlugin, readPluginPackageJson } from '../../project-graph/plugins';
import { loadNxPlugin } from '../../project-graph/plugins/loader';
import { readJsonFile } from '../fileutils';
import { getNxRequirePaths } from '../installation-directory';
import { output } from '../output';
import { NxPlugin, readPluginPackageJson } from '../../project-graph/plugins';
import { loadNxPlugin } from '../../project-graph/plugins/loader';
import { PackageJson } from '../package-json';
import { getPackageManagerCommand } from '../package-manager';
import { workspaceRoot } from '../workspace-root';
import { hasElements } from './shared';
import type { PluginCapabilities } from './models';
import type { ExecutorsJsonEntry } from '../../config/misc-interfaces';
export interface PluginCapabilities {
name: string;
executors?: { [name: string]: ExecutorsJsonEntry };
generators?: { [name: string]: GeneratorsJsonEntry };
projectInference?: boolean;
projectGraphExtension?: boolean;
}
function tryGetCollection<T extends object>(
packageJsonPath: string,
@ -121,145 +124,3 @@ async function tryGetModule(
return null;
}
}
export async function listPluginCapabilities(
pluginName: string,
projects: Record<string, ProjectConfiguration>
) {
const plugin = await getPluginCapabilities(
workspaceRoot,
pluginName,
projects
);
if (!plugin) {
const pmc = getPackageManagerCommand();
output.note({
title: `${pluginName} is not currently installed`,
bodyLines: [
`Use "${pmc.addDev} ${pluginName}" to install the plugin.`,
`After that, use "${pmc.exec} nx g ${pluginName}:init" to add the required peer deps and initialize the plugin.`,
],
});
return;
}
const hasBuilders = hasElements(plugin.executors);
const hasGenerators = hasElements(plugin.generators);
const hasProjectGraphExtension = !!plugin.projectGraphExtension;
const hasProjectInference = !!plugin.projectInference;
if (
!hasBuilders &&
!hasGenerators &&
!hasProjectGraphExtension &&
!hasProjectInference
) {
output.warn({ title: `No capabilities found in ${pluginName}` });
return;
}
const bodyLines = [];
if (hasGenerators) {
bodyLines.push(chalk.bold(chalk.green('GENERATORS')));
bodyLines.push('');
bodyLines.push(
...Object.keys(plugin.generators).map(
(name) => `${chalk.bold(name)} : ${plugin.generators[name].description}`
)
);
if (hasBuilders) {
bodyLines.push('');
}
}
if (hasBuilders) {
bodyLines.push(chalk.bold(chalk.green('EXECUTORS/BUILDERS')));
bodyLines.push('');
bodyLines.push(
...Object.keys(plugin.executors).map(
(name) =>
`${chalk.bold(name)} : ${resolveExecutorDescription(
pluginName,
plugin.executors[name],
projects
)}`
)
);
}
if (hasProjectGraphExtension) {
bodyLines.push(`✔️ Project Graph Extension`);
}
if (hasProjectInference) {
bodyLines.push(`✔️ Project Inference`);
}
output.log({
title: `Capabilities in ${plugin.name}:`,
bodyLines,
});
}
function resolveExecutorDescription(
pluginName: string,
executorJsonEntry: ExecutorsJsonEntry,
projects: Record<string, ProjectConfiguration>,
requirePaths = getNxRequirePaths(workspaceRoot)
) {
try {
if (typeof executorJsonEntry === 'string') {
// it points to another executor, resolve it
const [pkgName, executor] = executorJsonEntry.split(':');
// read the package.json of the parent plugin
const { path: packageJsonPath } = readPluginPackageJson(
pluginName,
projects,
requirePaths
);
// accumulate the require paths to resolve nested packages
const cummulativeRequirePaths = [
...requirePaths,
dirname(packageJsonPath),
];
const collection = loadExecutorsCollection(
pkgName,
projects,
cummulativeRequirePaths
);
return resolveExecutorDescription(
pkgName,
collection[executor],
projects,
cummulativeRequirePaths
);
}
return executorJsonEntry.description;
} catch {
return 'No description available';
}
}
function loadExecutorsCollection(
pluginName: string,
projects: Record<string, ProjectConfiguration>,
requirePaths: string[]
): { [name: string]: ExecutorsJsonEntry } {
const { json: packageJson, path: packageJsonPath } = readPluginPackageJson(
pluginName,
projects,
requirePaths
);
return {
...tryGetCollection(packageJsonPath, packageJson.builders, 'builders'),
...tryGetCollection(packageJsonPath, packageJson.executors, 'builders'),
...tryGetCollection(packageJsonPath, packageJson.builders, 'executors'),
...tryGetCollection(packageJsonPath, packageJson.executors, 'executors'),
};
}

View File

@ -1,5 +0,0 @@
// Lifted in part from https://github.com/nrwl/angular-console
export function hasElements(obj: any): boolean {
return obj && Object.values(obj).length > 0;
}