From bb12d7c6d62872b63a4406a021e56a5ec7db35cf Mon Sep 17 00:00:00 2001 From: Wes Grimes Date: Mon, 16 Mar 2020 16:42:22 -0400 Subject: [PATCH] feat(core): nx list enhancements and cleanup --- .../PULL_REQUEST_TEMPLATE/COMMUNITY_PLUGIN.MD | 4 +- .../getting-started/getting-started.md | 2 +- docs/react/guides/cli.md | 2 +- docs/shared/nx-plugin.md | 2 +- docs/web/guides/cli.md | 2 +- e2e/cli.test.ts | 10 +- packages/workspace/src/command-line/list.ts | 168 +++--------------- packages/workspace/src/utils/plugin-utils.ts | 104 ----------- packages/workspace/src/utils/plugins.ts | 52 ------ .../utils/{ => plugins}/community-plugins.ts | 24 +-- .../src/utils/plugins/core-plugins.ts | 87 +++++++++ packages/workspace/src/utils/plugins/index.ts | 13 ++ .../src/utils/plugins/installed-plugins.ts | 66 +++++++ .../workspace/src/utils/plugins/models.ts | 31 ++++ .../src/utils/plugins/plugin-capabilities.ts | 114 ++++++++++++ .../workspace/src/utils/plugins/shared.ts | 5 + 16 files changed, 358 insertions(+), 328 deletions(-) delete mode 100644 packages/workspace/src/utils/plugin-utils.ts delete mode 100644 packages/workspace/src/utils/plugins.ts rename packages/workspace/src/utils/{ => plugins}/community-plugins.ts (59%) create mode 100644 packages/workspace/src/utils/plugins/core-plugins.ts create mode 100644 packages/workspace/src/utils/plugins/index.ts create mode 100644 packages/workspace/src/utils/plugins/installed-plugins.ts create mode 100644 packages/workspace/src/utils/plugins/models.ts create mode 100644 packages/workspace/src/utils/plugins/plugin-capabilities.ts create mode 100644 packages/workspace/src/utils/plugins/shared.ts diff --git a/.github/PULL_REQUEST_TEMPLATE/COMMUNITY_PLUGIN.MD b/.github/PULL_REQUEST_TEMPLATE/COMMUNITY_PLUGIN.MD index cd466ba631..e7ba49f0c6 100644 --- a/.github/PULL_REQUEST_TEMPLATE/COMMUNITY_PLUGIN.MD +++ b/.github/PULL_REQUEST_TEMPLATE/COMMUNITY_PLUGIN.MD @@ -2,7 +2,7 @@ _[Please make sure you have read the submission guidelines before posting an PR] # Community Plugin Submission -Thanks for submitting your Nx Plugin to our approved plugins list. Make sure to follow the following steps to ensure that your PR is approved in a timely manner. +Thanks for submitting your Nx Plugin to our community plugins list. Make sure to follow these steps to ensure that your PR is approved in a timely manner. ## Steps to Submit Your Plugin - Use the following commit message template: `chore(core): nx plugin submission [PLUGIN_NAME]` @@ -20,4 +20,4 @@ Example: }] ``` -Once merged, your will plugin will be available when running the `nx list` command, and will also be available in the Plugin browser on [nx.dev](https://nx.dev) \ No newline at end of file +Once merged, your will plugin will be available when running the `nx list` command, and will also be available in the Plugin browser on [nx.dev](https://nx.dev) diff --git a/docs/angular/getting-started/getting-started.md b/docs/angular/getting-started/getting-started.md index 73cab58618..7b56019f30 100644 --- a/docs/angular/getting-started/getting-started.md +++ b/docs/angular/getting-started/getting-started.md @@ -100,7 +100,7 @@ nx list @nrwl/web This will list all the capabilities in the `@nrwl/web` collection. -`nx list` will also output a list of Nrwl-approved plugins that you may want to consider adding to your workspace. +Alongside the Nrwl core plugins `nx list` will also display some community plugins that you may want to consider adding to your workspace. ## Creating an application diff --git a/docs/react/guides/cli.md b/docs/react/guides/cli.md index 1e72f2665e..6d131fca1e 100644 --- a/docs/react/guides/cli.md +++ b/docs/react/guides/cli.md @@ -82,7 +82,7 @@ nx list @nrwl/web This will list all the schematics in the `@nrwl/web` collection. -`nx list` will also output a list of Nrwl-approved plugins that you may want to consider adding to your workspace. +`nx list` will also output a list of Nrwl core and community plugins that you may want to consider adding to your workspace. > Visit the [CLI Commands](/react/guides/cli#cli-commands) section to see more available commands. diff --git a/docs/shared/nx-plugin.md b/docs/shared/nx-plugin.md index e98637ceb5..0b79a25b5e 100644 --- a/docs/shared/nx-plugin.md +++ b/docs/shared/nx-plugin.md @@ -253,7 +253,7 @@ After that, you can then install your plugin like any other npm package, ### Listing your Nx Plugin -Nx provides a utility (`nx list`) that lists all approved plugins. To submit your plugin, please follow the steps below: +Nx provides a utility (`nx list`) that lists both core and community plugins. To submit your plugin, please follow the steps below: - Fork the [Nx repo](https://github.com/nrwl/nx/fork) (if you haven't already) - Update the [`community/approved-plugins.json` file](https://github.com/nrwl/nx/blob/master/community/approved-plugins.json) with a new entry for your plugin that includes name, url and description diff --git a/docs/web/guides/cli.md b/docs/web/guides/cli.md index 93661dbb64..8315ee7aca 100644 --- a/docs/web/guides/cli.md +++ b/docs/web/guides/cli.md @@ -82,7 +82,7 @@ nx list @nrwl/web This will list all the schematics in the `@nrwl/web` collection. -`nx list` will also output a list of Nrwl-approved plugins that you may want to consider adding to your workspace. +`nx list` will also output a list of Nrwl core and community plugins that you may want to consider adding to your workspace. > Visit the [CLI Commands](/web/guides/cli#cli-commands) section to see more available commands. diff --git a/e2e/cli.test.ts b/e2e/cli.test.ts index c38b15da3f..0e7b5916e4 100644 --- a/e2e/cli.test.ts +++ b/e2e/cli.test.ts @@ -1,3 +1,5 @@ +import { packagesWeCareAbout } from '@nrwl/workspace/src/command-line/report'; +import { renameSync } from 'fs'; import { ensureProject, forEachCli, @@ -9,8 +11,6 @@ import { tmpProjPath, updateFile } from './utils'; -import { packagesWeCareAbout } from '@nrwl/workspace/src/command-line/report'; -import { renameSync } from 'fs'; forEachCli('nx', () => { describe('Help', () => { @@ -91,8 +91,6 @@ forEachCli(() => { // just check for some, not all expect(listOutput).toContain('@nrwl/angular'); - expect(listOutput).toContain('@schematics/angular'); - expect(listOutput).toContain('@ngrx/store'); expect(listOutput).not.toContain('NX Also available'); @@ -118,7 +116,7 @@ forEachCli(() => { // check for builders expect(listOutput).toContain('run-commands'); - // look for uninstalled approved plugin + // look for uninstalled core plugin listOutput = runCommand('npm run nx -- list @nrwl/angular'); expect(listOutput).toContain( @@ -129,7 +127,7 @@ forEachCli(() => { listOutput = runCommand('npm run nx -- list @wibble/fish'); expect(listOutput).toContain( - 'NX ERROR Could not find plugin @wibble/fish' + 'NX NOTE @wibble/fish is not currently installed' ); // put back the @nrwl/angular module (or all the other e2e tests after this will fail) diff --git a/packages/workspace/src/command-line/list.ts b/packages/workspace/src/command-line/list.ts index 49414a93a4..595d810f2d 100644 --- a/packages/workspace/src/command-line/list.ts +++ b/packages/workspace/src/command-line/list.ts @@ -1,15 +1,15 @@ import yargs = require('yargs'); -import { terminal } from '@angular-devkit/core'; import { appRootPath } from '../utils/app-root'; -import { listCommunityPlugins } from '../utils/community-plugins'; -import { detectPackageManager } from '../utils/detect-package-manager'; import { output } from '../utils/output'; import { - getPluginCapabilities, - getPluginVersion, - readCapabilitiesFromNodeModules -} from '../utils/plugin-utils'; -import { approvedPlugins } from '../utils/plugins'; + fetchCommunityPlugins, + fetchCorePlugins, + getInstalledPluginsFromNodeModules, + listCommunityPlugins, + listCorePlugins, + listInstalledPlugins, + listPluginCapabilities +} from '../utils/plugins'; export interface YargsListArgs extends yargs.Arguments, ListArgs {} @@ -39,149 +39,21 @@ export const list = { */ async function listHandler(args: YargsListArgs) { if (args.plugin) { - listCapabilities(args.plugin); + listPluginCapabilities(args.plugin); } else { - await listPlugins(); - } -} - -function getPackageManagerInstallCommand(): string { - let packageManager = detectPackageManager(); - let packageManagerInstallCommand = 'npm install --save-dev'; - switch (packageManager) { - case 'yarn': - packageManagerInstallCommand = 'yarn add --dev'; - break; - - case 'pnpm': - packageManagerInstallCommand = 'pnpm install --save-dev'; - break; - } - return packageManagerInstallCommand; -} - -function hasElements(obj: any): boolean { - return obj && Object.values(obj).length > 0; -} - -function listCapabilities(pluginName: string) { - const plugin = getPluginCapabilities(appRootPath, pluginName); - - if (!plugin) { - const approvedPlugin = approvedPlugins.find(p => p.name === pluginName); - if (approvedPlugin) { - const installedPlugins = readCapabilitiesFromNodeModules(appRootPath); - - let workspaceVersion = 'latest'; - if (installedPlugins.some(x => x.name === '@nrwl/workspace')) { - workspaceVersion = getPluginVersion(appRootPath, '@nrwl/workspace'); - } - - output.note({ - title: `${pluginName} is not currently installed`, - bodyLines: [ - `Use "${getPackageManagerInstallCommand()} ${pluginName}@${workspaceVersion}" to add new capabilities`, - '', - `Visit ${terminal.bold( - approvedPlugin.link ? approvedPlugin.link : 'https://nx.dev/' - )} for more information` - ] - }); - } else { - output.error({ - title: `Could not find plugin ${pluginName}` - }); - } - return; - } - - const hasBuilders = hasElements(plugin.builders); - const hasSchematics = hasElements(plugin.schematics); - - if (!hasBuilders && !hasSchematics) { - output.warn({ title: `No capabilities found in ${pluginName}` }); - return; - } - - const bodyLines = []; - - if (hasSchematics) { - bodyLines.push(terminal.bold(terminal.green('SCHEMATICS'))); - bodyLines.push(''); - bodyLines.push( - ...Object.keys(plugin.schematics).map( - name => - `${terminal.bold(name)} : ${plugin.schematics[name].description}` - ) + const corePlugins = await fetchCorePlugins(); + const communityPlugins = await fetchCommunityPlugins(); + const installedPlugins = getInstalledPluginsFromNodeModules( + appRootPath, + corePlugins, + communityPlugins ); - if (hasBuilders) { - bodyLines.push(''); - } - } + listInstalledPlugins(installedPlugins); + listCorePlugins(installedPlugins, corePlugins); + listCommunityPlugins(installedPlugins, communityPlugins); - if (hasBuilders) { - bodyLines.push(terminal.bold(terminal.green('BUILDERS'))); - bodyLines.push(''); - bodyLines.push( - ...Object.keys(plugin.builders).map( - name => `${terminal.bold(name)} : ${plugin.builders[name].description}` - ) - ); - } - - output.log({ - title: `Capabilities in ${plugin.name}:`, - bodyLines - }); -} - -async function listPlugins() { - const installedPlugins = readCapabilitiesFromNodeModules(appRootPath); - - // The following packages are present in any workspace. Hide them to avoid confusion. - const hide = [ - '@angular-devkit/architect', - '@angular-devkit/build-ng-packagr', - '@angular-devkit/build-webpack', - '@angular-eslint/builder' - ]; - - const filtered = installedPlugins.filter(p => hide.indexOf(p.name) === -1); - - output.log({ - title: `Installed plugins:`, - bodyLines: filtered.map(p => { - const capabilities = []; - if (hasElements(p.builders)) { - capabilities.push('builders'); - } - if (hasElements(p.schematics)) { - capabilities.push('schematics'); - } - return `${terminal.bold(p.name)} (${capabilities.join()})`; - }) - }); - - const installedPluginsMap: Set = new Set( - installedPlugins.map(p => p.name) - ); - - const alsoAvailable = approvedPlugins.filter( - p => !installedPluginsMap.has(p.name) - ); - - if (alsoAvailable.length) { - output.log({ - title: `Also available:`, - bodyLines: alsoAvailable.map(p => { - return `${terminal.bold(p.name)} (${p.capabilities})`; - }) + output.note({ + title: `Use "nx list [plugin]" to find out more` }); } - - await listCommunityPlugins(installedPluginsMap); - - output.note({ - title: `Use "nx list [plugin]" to find out more` - }); } diff --git a/packages/workspace/src/utils/plugin-utils.ts b/packages/workspace/src/utils/plugin-utils.ts deleted file mode 100644 index 1d56a4699c..0000000000 --- a/packages/workspace/src/utils/plugin-utils.ts +++ /dev/null @@ -1,104 +0,0 @@ -// Lifted in part from https://github.com/nrwl/angular-console -import { readdirSync } from 'fs'; -import * as path from 'path'; -import { readJsonFile } from './fileutils'; - -export interface Schematic { - factory: string; - schema: string; - description: string; - aliases: string; - hidden: boolean; -} - -export interface Builder { - implementation: string; - schema: string; - description: string; -} - -export interface PluginCapabilities { - name: string; - builders: { [name: string]: Builder }; - schematics: { [name: string]: Schematic }; -} - -export function getPluginVersion(workspaceRoot: string, name: string): string { - try { - const packageJson = readJsonFile( - path.join(workspaceRoot, 'node_modules', name, 'package.json') - ); - return packageJson.version; - } catch { - throw new Error(`Could not read package.json for module ${name}`); - } -} - -export function readCapabilitiesFromNodeModules( - workspaceRoot: string -): Array { - const packages = listOfUnnestedNpmPackages(workspaceRoot); - return packages - .map(name => getPluginCapabilities(workspaceRoot, name)) - .filter(x => x && !!(x.schematics || x.builders)); -} - -export function getPluginCapabilities( - workspaceRoot: string, - pluginName: string -): PluginCapabilities { - try { - const pluginPath = path.join(workspaceRoot, 'node_modules', pluginName); - const packageJson = readJsonFile(path.join(pluginPath, 'package.json')); - return { - name: pluginName, - schematics: tryGetCollection( - pluginPath, - packageJson.schematics, - 'schematics' - ), - builders: tryGetCollection(pluginPath, packageJson.builders, 'builders') - }; - } catch { - return null; - } -} - -function tryGetCollection( - pluginPath: string, - jsonFile: string, - propName: string -): T { - if (!jsonFile) { - return null; - } - - try { - return readJsonFile(path.join(pluginPath, jsonFile))[propName]; - } catch { - return null; - } -} - -let packageList: string[] = []; -export function listOfUnnestedNpmPackages( - workspaceRoot: string, - requery: boolean = false -): string[] { - if (!requery && packageList.length > 0) { - return packageList; - } - - const nodeModulesDir = path.join(workspaceRoot, 'node_modules'); - readdirSync(nodeModulesDir).forEach(npmPackageOrScope => { - if (npmPackageOrScope.startsWith('@')) { - readdirSync(path.join(nodeModulesDir, npmPackageOrScope)).forEach(p => { - packageList.push(`${npmPackageOrScope}/${p}`); - }); - } else { - packageList.push(npmPackageOrScope); - } - }); - - return packageList; -} diff --git a/packages/workspace/src/utils/plugins.ts b/packages/workspace/src/utils/plugins.ts deleted file mode 100644 index be6ea650ad..0000000000 --- a/packages/workspace/src/utils/plugins.ts +++ /dev/null @@ -1,52 +0,0 @@ -/** - * This file is used by `nx list` to display approved plugins - */ - -export interface Plugin { - name: string; - capabilities: 'builders' | 'schematics' | 'builders,schematics'; - link?: string; -} - -export const approvedPlugins: Plugin[] = [ - { - name: '@nrwl/angular', - capabilities: 'schematics' - }, - { - name: '@nrwl/cypress', - capabilities: 'builders,schematics' - }, - { - name: '@nrwl/express', - capabilities: 'builders,schematics' - }, - { - name: '@nrwl/jest', - capabilities: 'builders,schematics' - }, - { - name: '@nrwl/nest', - capabilities: 'builders,schematics' - }, - { - name: '@nrwl/next', - capabilities: 'builders,schematics' - }, - { - name: '@nrwl/node', - capabilities: 'builders,schematics' - }, - { - name: '@nrwl/react', - capabilities: 'builders,schematics' - }, - { - name: '@nrwl/storybook', - capabilities: 'builders,schematics' - }, - { - name: '@nrwl/web', - capabilities: 'builders,schematics' - } -]; diff --git a/packages/workspace/src/utils/community-plugins.ts b/packages/workspace/src/utils/plugins/community-plugins.ts similarity index 59% rename from packages/workspace/src/utils/community-plugins.ts rename to packages/workspace/src/utils/plugins/community-plugins.ts index b40ddb4f3a..d84df2f853 100644 --- a/packages/workspace/src/utils/community-plugins.ts +++ b/packages/workspace/src/utils/plugins/community-plugins.ts @@ -1,27 +1,27 @@ import { terminal } from '@angular-devkit/core'; import axios from 'axios'; -import { output } from './output'; +import { output } from '../output'; +import { CommunityPlugin, PluginCapabilities } from './models'; -const APPROVED_PLUGINS_JSON_URL = +const COMMUNITY_PLUGINS_JSON_URL = 'https://raw.githubusercontent.com/nrwl/nx/master/community/approved-plugins.json'; -interface CommunityPlugin { - name: string; - url: string; - description: string; -} - -async function fetchCommunityPlugins(): Promise { +export async function fetchCommunityPlugins(): Promise { const response = await axios.get( - APPROVED_PLUGINS_JSON_URL + COMMUNITY_PLUGINS_JSON_URL ); return response.data; } -export async function listCommunityPlugins(installedPluginsMap: Set) { +export function listCommunityPlugins( + installedPlugins: PluginCapabilities[], + communityPlugins: CommunityPlugin[] +) { try { - const communityPlugins = await fetchCommunityPlugins(); + const installedPluginsMap: Set = new Set( + installedPlugins.map(p => p.name) + ); const availableCommunityPlugins = communityPlugins.filter( p => !installedPluginsMap.has(p.name) diff --git a/packages/workspace/src/utils/plugins/core-plugins.ts b/packages/workspace/src/utils/plugins/core-plugins.ts new file mode 100644 index 0000000000..e79d45a57a --- /dev/null +++ b/packages/workspace/src/utils/plugins/core-plugins.ts @@ -0,0 +1,87 @@ +import { terminal } from '@angular-devkit/core'; +import { output } from '../output'; +import { CorePlugin, PluginCapabilities } from './models'; + +export function fetchCorePlugins() { + const corePlugins: CorePlugin[] = [ + { + name: '@nrwl/angular', + capabilities: 'schematics' + }, + { + name: '@nrwl/bazel', + capabilities: 'schematics' + }, + { + name: '@nrwl/cypress', + capabilities: 'builders,schematics' + }, + { + name: '@nrwl/express', + capabilities: 'builders,schematics' + }, + { + name: '@nrwl/jest', + capabilities: 'builders,schematics' + }, + { + name: '@nrwl/linter', + capabilities: 'builders' + }, + { + name: '@nrwl/nest', + capabilities: 'builders,schematics' + }, + { + name: '@nrwl/next', + capabilities: 'builders,schematics' + }, + { + name: '@nrwl/node', + capabilities: 'builders,schematics' + }, + { + name: '@nrwl/nx-plugin', + capabilities: 'builders,schematics' + }, + { + name: '@nrwl/react', + capabilities: 'builders,schematics' + }, + { + name: '@nrwl/storybook', + capabilities: 'builders,schematics' + }, + { + name: '@nrwl/web', + capabilities: 'builders,schematics' + }, + { + name: '@nrwl/workspace', + capabilities: 'builders,schematics' + } + ]; + return corePlugins; +} + +export function listCorePlugins( + installedPlugins: PluginCapabilities[], + corePlugins: CorePlugin[] +) { + const installedPluginsMap: Set = new Set( + installedPlugins.map(p => p.name) + ); + + const alsoAvailable = corePlugins.filter( + p => !installedPluginsMap.has(p.name) + ); + + if (alsoAvailable.length) { + output.log({ + title: `Also available:`, + bodyLines: alsoAvailable.map(p => { + return `${terminal.bold(p.name)} (${p.capabilities})`; + }) + }); + } +} diff --git a/packages/workspace/src/utils/plugins/index.ts b/packages/workspace/src/utils/plugins/index.ts new file mode 100644 index 0000000000..959583e75f --- /dev/null +++ b/packages/workspace/src/utils/plugins/index.ts @@ -0,0 +1,13 @@ +export { + fetchCommunityPlugins, + listCommunityPlugins +} from './community-plugins'; +export { fetchCorePlugins, listCorePlugins } from './core-plugins'; +export { + getInstalledPluginsFromNodeModules, + listInstalledPlugins +} from './installed-plugins'; +export { + getPluginCapabilities, + listPluginCapabilities +} from './plugin-capabilities'; diff --git a/packages/workspace/src/utils/plugins/installed-plugins.ts b/packages/workspace/src/utils/plugins/installed-plugins.ts new file mode 100644 index 0000000000..914807ea5f --- /dev/null +++ b/packages/workspace/src/utils/plugins/installed-plugins.ts @@ -0,0 +1,66 @@ +import { terminal } from '@angular-devkit/core'; +import { readdirSync } from 'fs'; +import * as path from 'path'; +import { output } from '../output'; +import { CommunityPlugin, CorePlugin, PluginCapabilities } from './models'; +import { getPluginCapabilities } from './plugin-capabilities'; +import { hasElements } from './shared'; + +function getPackagesFromNodeModules( + workspaceRoot: string, + requery: boolean = false +): string[] { + let packageList: string[] = []; + + if (!requery && packageList.length > 0) { + return packageList; + } + + const nodeModulesDir = path.join(workspaceRoot, 'node_modules'); + readdirSync(nodeModulesDir).forEach(npmPackageOrScope => { + if (npmPackageOrScope.startsWith('@')) { + readdirSync(path.join(nodeModulesDir, npmPackageOrScope)).forEach(p => { + packageList.push(`${npmPackageOrScope}/${p}`); + }); + } else { + packageList.push(npmPackageOrScope); + } + }); + + return packageList; +} + +export function getInstalledPluginsFromNodeModules( + workspaceRoot: string, + corePlugins: CorePlugin[], + communityPlugins: CommunityPlugin[] +): Array { + const corePluginNames = corePlugins.map(p => p.name); + const communityPluginNames = communityPlugins.map(p => p.name); + const packages = getPackagesFromNodeModules(workspaceRoot); + + return packages + .filter( + name => + corePluginNames.indexOf(name) > -1 || + communityPluginNames.indexOf(name) > -1 + ) + .map(name => getPluginCapabilities(workspaceRoot, name)) + .filter(x => x && !!(x.schematics || x.builders)); +} + +export function listInstalledPlugins(installedPlugins: PluginCapabilities[]) { + output.log({ + title: `Installed plugins:`, + bodyLines: installedPlugins.map(p => { + const capabilities = []; + if (hasElements(p.builders)) { + capabilities.push('builders'); + } + if (hasElements(p.schematics)) { + capabilities.push('schematics'); + } + return `${terminal.bold(p.name)} (${capabilities.join()})`; + }) + }); +} diff --git a/packages/workspace/src/utils/plugins/models.ts b/packages/workspace/src/utils/plugins/models.ts new file mode 100644 index 0000000000..57561297a5 --- /dev/null +++ b/packages/workspace/src/utils/plugins/models.ts @@ -0,0 +1,31 @@ +export interface PluginSchematic { + factory: string; + schema: string; + description: string; + aliases: string; + hidden: boolean; +} + +export interface PluginBuilder { + implementation: string; + schema: string; + description: string; +} + +export interface PluginCapabilities { + name: string; + builders: { [name: string]: PluginBuilder }; + schematics: { [name: string]: PluginSchematic }; +} + +export interface CorePlugin { + name: string; + capabilities: 'builders' | 'schematics' | 'builders,schematics'; + link?: string; +} + +export interface CommunityPlugin { + name: string; + url: string; + description: string; +} diff --git a/packages/workspace/src/utils/plugins/plugin-capabilities.ts b/packages/workspace/src/utils/plugins/plugin-capabilities.ts new file mode 100644 index 0000000000..1dfcc0e41f --- /dev/null +++ b/packages/workspace/src/utils/plugins/plugin-capabilities.ts @@ -0,0 +1,114 @@ +import { terminal } from '@angular-devkit/core'; +import * as path from 'path'; +import { appRootPath } from '../app-root'; +import { detectPackageManager } from '../detect-package-manager'; +import { readJsonFile } from '../fileutils'; +import { output } from '../output'; +import { PluginCapabilities } from './models'; +import { hasElements } from './shared'; + +function getPackageManagerInstallCommand(): string { + let packageManager = detectPackageManager(); + let packageManagerInstallCommand = 'npm install --save-dev'; + switch (packageManager) { + case 'yarn': + packageManagerInstallCommand = 'yarn add --dev'; + break; + + case 'pnpm': + packageManagerInstallCommand = 'pnpm install --save-dev'; + break; + } + return packageManagerInstallCommand; +} + +function tryGetCollection( + pluginPath: string, + jsonFile: string, + propName: string +): T { + if (!jsonFile) { + return null; + } + + try { + return readJsonFile(path.join(pluginPath, jsonFile))[propName]; + } catch { + return null; + } +} + +export function getPluginCapabilities( + workspaceRoot: string, + pluginName: string +): PluginCapabilities { + try { + const pluginPath = path.join(workspaceRoot, 'node_modules', pluginName); + const packageJson = readJsonFile(path.join(pluginPath, 'package.json')); + return { + name: pluginName, + schematics: tryGetCollection( + pluginPath, + packageJson.schematics, + 'schematics' + ), + builders: tryGetCollection(pluginPath, packageJson.builders, 'builders') + }; + } catch { + return null; + } +} + +export function listPluginCapabilities(pluginName: string) { + const plugin = getPluginCapabilities(appRootPath, pluginName); + + if (!plugin) { + output.note({ + title: `${pluginName} is not currently installed`, + bodyLines: [ + `Use "${getPackageManagerInstallCommand()} ${pluginName}" to add new capabilities` + ] + }); + + return; + } + + const hasBuilders = hasElements(plugin.builders); + const hasSchematics = hasElements(plugin.schematics); + + if (!hasBuilders && !hasSchematics) { + output.warn({ title: `No capabilities found in ${pluginName}` }); + return; + } + + const bodyLines = []; + + if (hasSchematics) { + bodyLines.push(terminal.bold(terminal.green('SCHEMATICS'))); + bodyLines.push(''); + bodyLines.push( + ...Object.keys(plugin.schematics).map( + name => + `${terminal.bold(name)} : ${plugin.schematics[name].description}` + ) + ); + if (hasBuilders) { + bodyLines.push(''); + } + } + + if (hasBuilders) { + bodyLines.push(terminal.bold(terminal.green('BUILDERS'))); + bodyLines.push(''); + bodyLines.push( + ...Object.keys(plugin.builders).map( + name => `${terminal.bold(name)} : ${plugin.builders[name].description}` + ) + ); + } + + output.log({ + title: `Capabilities in ${plugin.name}:`, + bodyLines + }); +} diff --git a/packages/workspace/src/utils/plugins/shared.ts b/packages/workspace/src/utils/plugins/shared.ts new file mode 100644 index 0000000000..534feccefe --- /dev/null +++ b/packages/workspace/src/utils/plugins/shared.ts @@ -0,0 +1,5 @@ +// Lifted in part from https://github.com/nrwl/angular-console + +export function hasElements(obj: any): boolean { + return obj && Object.values(obj).length > 0; +}