feat(core): nx list enhancements and cleanup
This commit is contained in:
parent
ff55863727
commit
bb12d7c6d6
@ -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)
|
||||
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)
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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.
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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.
|
||||
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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<string> = new Set<string>(
|
||||
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`
|
||||
});
|
||||
}
|
||||
|
||||
@ -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<PluginCapabilities> {
|
||||
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<T>(
|
||||
pluginPath: string,
|
||||
jsonFile: string,
|
||||
propName: string
|
||||
): T {
|
||||
if (!jsonFile) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return readJsonFile<T>(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;
|
||||
}
|
||||
@ -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'
|
||||
}
|
||||
];
|
||||
@ -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<CommunityPlugin[]> {
|
||||
export async function fetchCommunityPlugins(): Promise<CommunityPlugin[]> {
|
||||
const response = await axios.get<CommunityPlugin[]>(
|
||||
APPROVED_PLUGINS_JSON_URL
|
||||
COMMUNITY_PLUGINS_JSON_URL
|
||||
);
|
||||
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function listCommunityPlugins(installedPluginsMap: Set<string>) {
|
||||
export function listCommunityPlugins(
|
||||
installedPlugins: PluginCapabilities[],
|
||||
communityPlugins: CommunityPlugin[]
|
||||
) {
|
||||
try {
|
||||
const communityPlugins = await fetchCommunityPlugins();
|
||||
const installedPluginsMap: Set<string> = new Set<string>(
|
||||
installedPlugins.map(p => p.name)
|
||||
);
|
||||
|
||||
const availableCommunityPlugins = communityPlugins.filter(
|
||||
p => !installedPluginsMap.has(p.name)
|
||||
87
packages/workspace/src/utils/plugins/core-plugins.ts
Normal file
87
packages/workspace/src/utils/plugins/core-plugins.ts
Normal file
@ -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<string> = new Set<string>(
|
||||
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})`;
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
13
packages/workspace/src/utils/plugins/index.ts
Normal file
13
packages/workspace/src/utils/plugins/index.ts
Normal file
@ -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';
|
||||
66
packages/workspace/src/utils/plugins/installed-plugins.ts
Normal file
66
packages/workspace/src/utils/plugins/installed-plugins.ts
Normal file
@ -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<PluginCapabilities> {
|
||||
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()})`;
|
||||
})
|
||||
});
|
||||
}
|
||||
31
packages/workspace/src/utils/plugins/models.ts
Normal file
31
packages/workspace/src/utils/plugins/models.ts
Normal file
@ -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;
|
||||
}
|
||||
114
packages/workspace/src/utils/plugins/plugin-capabilities.ts
Normal file
114
packages/workspace/src/utils/plugins/plugin-capabilities.ts
Normal file
@ -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<T>(
|
||||
pluginPath: string,
|
||||
jsonFile: string,
|
||||
propName: string
|
||||
): T {
|
||||
if (!jsonFile) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return readJsonFile<T>(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
|
||||
});
|
||||
}
|
||||
5
packages/workspace/src/utils/plugins/shared.ts
Normal file
5
packages/workspace/src/utils/plugins/shared.ts
Normal file
@ -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;
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user