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
|
# 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
|
## Steps to Submit Your Plugin
|
||||||
- Use the following commit message template: `chore(core): nx plugin submission [PLUGIN_NAME]`
|
- Use the following commit message template: `chore(core): nx plugin submission [PLUGIN_NAME]`
|
||||||
|
|||||||
@ -100,7 +100,7 @@ nx list @nrwl/web
|
|||||||
|
|
||||||
This will list all the capabilities in the `@nrwl/web` collection.
|
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
|
## Creating an application
|
||||||
|
|
||||||
|
|||||||
@ -82,7 +82,7 @@ nx list @nrwl/web
|
|||||||
|
|
||||||
This will list all the schematics in the `@nrwl/web` collection.
|
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.
|
> 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
|
### 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)
|
- 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
|
- 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.
|
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.
|
> 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 {
|
import {
|
||||||
ensureProject,
|
ensureProject,
|
||||||
forEachCli,
|
forEachCli,
|
||||||
@ -9,8 +11,6 @@ import {
|
|||||||
tmpProjPath,
|
tmpProjPath,
|
||||||
updateFile
|
updateFile
|
||||||
} from './utils';
|
} from './utils';
|
||||||
import { packagesWeCareAbout } from '@nrwl/workspace/src/command-line/report';
|
|
||||||
import { renameSync } from 'fs';
|
|
||||||
|
|
||||||
forEachCli('nx', () => {
|
forEachCli('nx', () => {
|
||||||
describe('Help', () => {
|
describe('Help', () => {
|
||||||
@ -91,8 +91,6 @@ forEachCli(() => {
|
|||||||
|
|
||||||
// just check for some, not all
|
// just check for some, not all
|
||||||
expect(listOutput).toContain('@nrwl/angular');
|
expect(listOutput).toContain('@nrwl/angular');
|
||||||
expect(listOutput).toContain('@schematics/angular');
|
|
||||||
expect(listOutput).toContain('@ngrx/store');
|
|
||||||
|
|
||||||
expect(listOutput).not.toContain('NX Also available');
|
expect(listOutput).not.toContain('NX Also available');
|
||||||
|
|
||||||
@ -118,7 +116,7 @@ forEachCli(() => {
|
|||||||
// check for builders
|
// check for builders
|
||||||
expect(listOutput).toContain('run-commands');
|
expect(listOutput).toContain('run-commands');
|
||||||
|
|
||||||
// look for uninstalled approved plugin
|
// look for uninstalled core plugin
|
||||||
listOutput = runCommand('npm run nx -- list @nrwl/angular');
|
listOutput = runCommand('npm run nx -- list @nrwl/angular');
|
||||||
|
|
||||||
expect(listOutput).toContain(
|
expect(listOutput).toContain(
|
||||||
@ -129,7 +127,7 @@ forEachCli(() => {
|
|||||||
listOutput = runCommand('npm run nx -- list @wibble/fish');
|
listOutput = runCommand('npm run nx -- list @wibble/fish');
|
||||||
|
|
||||||
expect(listOutput).toContain(
|
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)
|
// 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 yargs = require('yargs');
|
||||||
import { terminal } from '@angular-devkit/core';
|
|
||||||
import { appRootPath } from '../utils/app-root';
|
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 { output } from '../utils/output';
|
||||||
import {
|
import {
|
||||||
getPluginCapabilities,
|
fetchCommunityPlugins,
|
||||||
getPluginVersion,
|
fetchCorePlugins,
|
||||||
readCapabilitiesFromNodeModules
|
getInstalledPluginsFromNodeModules,
|
||||||
} from '../utils/plugin-utils';
|
listCommunityPlugins,
|
||||||
import { approvedPlugins } from '../utils/plugins';
|
listCorePlugins,
|
||||||
|
listInstalledPlugins,
|
||||||
|
listPluginCapabilities
|
||||||
|
} from '../utils/plugins';
|
||||||
|
|
||||||
export interface YargsListArgs extends yargs.Arguments, ListArgs {}
|
export interface YargsListArgs extends yargs.Arguments, ListArgs {}
|
||||||
|
|
||||||
@ -39,149 +39,21 @@ export const list = {
|
|||||||
*/
|
*/
|
||||||
async function listHandler(args: YargsListArgs) {
|
async function listHandler(args: YargsListArgs) {
|
||||||
if (args.plugin) {
|
if (args.plugin) {
|
||||||
listCapabilities(args.plugin);
|
listPluginCapabilities(args.plugin);
|
||||||
} else {
|
} else {
|
||||||
await listPlugins();
|
const corePlugins = await fetchCorePlugins();
|
||||||
}
|
const communityPlugins = await fetchCommunityPlugins();
|
||||||
}
|
const installedPlugins = getInstalledPluginsFromNodeModules(
|
||||||
|
appRootPath,
|
||||||
function getPackageManagerInstallCommand(): string {
|
corePlugins,
|
||||||
let packageManager = detectPackageManager();
|
communityPlugins
|
||||||
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}`
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
if (hasBuilders) {
|
listInstalledPlugins(installedPlugins);
|
||||||
bodyLines.push('');
|
listCorePlugins(installedPlugins, corePlugins);
|
||||||
}
|
listCommunityPlugins(installedPlugins, communityPlugins);
|
||||||
}
|
|
||||||
|
|
||||||
if (hasBuilders) {
|
output.note({
|
||||||
bodyLines.push(terminal.bold(terminal.green('BUILDERS')));
|
title: `Use "nx list [plugin]" to find out more`
|
||||||
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})`;
|
|
||||||
})
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
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 { terminal } from '@angular-devkit/core';
|
||||||
import axios from 'axios';
|
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';
|
'https://raw.githubusercontent.com/nrwl/nx/master/community/approved-plugins.json';
|
||||||
|
|
||||||
interface CommunityPlugin {
|
export async function fetchCommunityPlugins(): Promise<CommunityPlugin[]> {
|
||||||
name: string;
|
|
||||||
url: string;
|
|
||||||
description: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function fetchCommunityPlugins(): Promise<CommunityPlugin[]> {
|
|
||||||
const response = await axios.get<CommunityPlugin[]>(
|
const response = await axios.get<CommunityPlugin[]>(
|
||||||
APPROVED_PLUGINS_JSON_URL
|
COMMUNITY_PLUGINS_JSON_URL
|
||||||
);
|
);
|
||||||
|
|
||||||
return response.data;
|
return response.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function listCommunityPlugins(installedPluginsMap: Set<string>) {
|
export function listCommunityPlugins(
|
||||||
|
installedPlugins: PluginCapabilities[],
|
||||||
|
communityPlugins: CommunityPlugin[]
|
||||||
|
) {
|
||||||
try {
|
try {
|
||||||
const communityPlugins = await fetchCommunityPlugins();
|
const installedPluginsMap: Set<string> = new Set<string>(
|
||||||
|
installedPlugins.map(p => p.name)
|
||||||
|
);
|
||||||
|
|
||||||
const availableCommunityPlugins = communityPlugins.filter(
|
const availableCommunityPlugins = communityPlugins.filter(
|
||||||
p => !installedPluginsMap.has(p.name)
|
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