From 46e7b4d49c71718d50fe7293254fea50ffbcfaef Mon Sep 17 00:00:00 2001 From: James Henry Date: Tue, 12 Apr 2022 22:05:55 +0400 Subject: [PATCH] chore(core): improve help output for generators and executors (#9800) --- .eslintrc.json | 12 +- docs/generated/packages/angular.json | 24 +- docs/generated/packages/jest.json | 2 +- docs/generated/packages/js.json | 2 +- docs/generated/packages/nest.json | 2 +- docs/generated/packages/next.json | 6 +- docs/generated/packages/node.json | 1 - docs/generated/packages/react-native.json | 2 +- docs/generated/packages/react.json | 4 +- docs/generated/packages/web.json | 3 +- e2e/cli/src/cli.test.ts | 6 +- nx.json | 3 +- package.json | 1 + packages/angular/project.json | 4 +- .../src/generators/add-linting/schema.json | 1 + .../src/generators/application/schema.json | 1 + .../component-cypress-spec/schema.json | 1 + .../generators/component-story/schema.json | 1 + .../src/generators/component/schema.json | 2 +- .../generators/convert-to-with-mf/schema.json | 2 +- .../convert-tslint-to-eslint/schema.json | 2 +- .../generators/downgrade-module/schema.json | 2 +- .../angular/src/generators/init/schema.json | 2 +- .../src/generators/mfe-host/schema.json | 2 +- .../src/generators/scam-directive/schema.json | 2 +- .../src/generators/scam-pipe/schema.json | 2 +- .../angular/src/generators/scam/schema.json | 2 +- .../src/generators/stories/schema.json | 2 +- packages/cypress/project.json | 4 +- packages/detox/project.json | 4 +- packages/devkit/project.json | 4 +- packages/express/project.json | 4 +- packages/jest/project.json | 4 +- .../src/generators/jest-project/schema.json | 2 +- packages/js/project.json | 6 +- packages/js/src/generators/init/schema.json | 2 +- packages/linter/project.json | 4 +- packages/nest/project.json | 4 +- .../src/generators/application/schema.json | 2 +- packages/next/project.json | 4 +- packages/next/src/executors/build/schema.json | 2 +- .../next/src/executors/server/schema.json | 2 +- .../src/generators/application/schema.json | 1 + .../next/src/generators/component/schema.json | 1 + packages/node/project.json | 4 +- .../node/src/generators/library/schema.json | 1 - packages/nx-plugin/project.json | 4 +- packages/nx/src/command-line/generate.ts | 16 +- packages/nx/src/command-line/run.ts | 2 +- packages/nx/src/config/workspaces.ts | 7 +- packages/nx/src/utils/params.ts | 1 + packages/nx/src/utils/print-help.ts | 377 +++++++++++++++--- packages/react-native/project.json | 4 +- .../storybook-configuration/schema.json | 2 +- packages/react/project.json | 4 +- .../react/src/generators/init/schema.json | 2 +- .../react/src/generators/library/schema.json | 2 +- packages/storybook/project.json | 4 +- packages/web/project.json | 4 +- .../web/src/executors/webpack/schema.json | 2 +- packages/web/src/generators/init/schema.json | 1 + packages/workspace/project.json | 4 +- scripts/depcheck/missing.ts | 7 + tools/eslint-rules/index.ts | 31 ++ tools/eslint-rules/jest.config.js | 17 + tools/eslint-rules/project.json | 14 + .../rules/valid-schema-description.spec.ts | 11 + .../rules/valid-schema-description.ts | 81 ++++ tools/eslint-rules/tsconfig.json | 16 + tools/eslint-rules/tsconfig.lint.json | 9 + tools/eslint-rules/tsconfig.spec.json | 9 + workspace.json | 1 + yarn.lock | 12 +- 73 files changed, 665 insertions(+), 130 deletions(-) create mode 100644 tools/eslint-rules/index.ts create mode 100644 tools/eslint-rules/jest.config.js create mode 100644 tools/eslint-rules/project.json create mode 100644 tools/eslint-rules/rules/valid-schema-description.spec.ts create mode 100644 tools/eslint-rules/rules/valid-schema-description.ts create mode 100644 tools/eslint-rules/tsconfig.json create mode 100644 tools/eslint-rules/tsconfig.lint.json create mode 100644 tools/eslint-rules/tsconfig.spec.json diff --git a/.eslintrc.json b/.eslintrc.json index 9fab9ebd0d468..cf2e1378f703c 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -5,10 +5,18 @@ "node": true }, "ignorePatterns": ["**/*.ts"], - "plugins": ["@typescript-eslint"], + "plugins": ["@typescript-eslint", "@nrwl/nx"], "extends": [], "rules": { "@typescript-eslint/explicit-module-boundary-types": "off" }, - "overrides": [] + "overrides": [ + { + "files": ["**/executors/**/schema.json", "**/generators/**/schema.json"], + "parser": "jsonc-eslint-parser", + "rules": { + "@nrwl/nx/workspace/valid-schema-description": "error" + } + } + ] } diff --git a/docs/generated/packages/angular.json b/docs/generated/packages/angular.json index 9cf228250b2d5..bc715a77e5cf4 100644 --- a/docs/generated/packages/angular.json +++ b/docs/generated/packages/angular.json @@ -12,6 +12,7 @@ "$schema": "http://json-schema.org/schema", "$id": "NxAngularAddLintingGenerator", "title": "Add linting to an Angular project.", + "description": "Adds linting configuration to an Angular project.", "cli": "nx", "type": "object", "properties": { @@ -61,6 +62,7 @@ "$schema": "http://json-schema.org/schema", "$id": "GeneratorNxApp", "title": "Creates an Angular application.", + "description": "Creates an Angular application.", "type": "object", "cli": "nx", "properties": { @@ -245,7 +247,7 @@ "title": "Angular Component Schema", "cli": "nx", "type": "object", - "description": "Creates a new, generic component definition in the given or default project.", + "description": "Creates a new, generic Angular component definition in the given or default project.", "additionalProperties": false, "properties": { "path": { @@ -355,6 +357,7 @@ "$id": "NxAngularComponentCypressSpecGenerator", "type": "object", "cli": "nx", + "description": "Creates a Cypress spec for a UI component that has a story.", "properties": { "projectName": { "type": "string", @@ -410,6 +413,7 @@ "$id": "NxAngularComponentStoryGenerator", "type": "object", "cli": "nx", + "description": "Creates a `stories.ts` file for an Angular component.", "properties": { "projectPath": { "type": "string", @@ -455,7 +459,7 @@ "$id": "NxAngularConvertTSLintToESLintGenerator", "cli": "nx", "title": "Convert an Angular project from TSLint to ESLint", - "description": "Convert an Angular project from TSLint to ESLint. NOTE: Does not work in `--dry-run mode`", + "description": "Convert an Angular project from TSLint to ESLint. NOTE: Does not work in `--dry-run mode`.", "examples": [ { "command": "nx g convert-tslint-to-eslint myapp", @@ -513,7 +517,7 @@ "$schema": "http://json-schema.org/schema", "$id": "NxDowngradeModuleGenerator", "title": "Generates downgradeModule setup.", - "description": "Sets up a Downgrade Module.", + "description": "Sets up a Downgrade Module for using AngularJS and Angular.", "cli": "nx", "type": "object", "properties": { @@ -559,7 +563,7 @@ "$id": "SchematicsAngularModuleInit", "cli": "nx", "title": "Init Angular Plugin", - "description": "Initializes the `@nrwl/angular` plugin. NOTE: Does not work in the `--dry-run` mode", + "description": "Initializes the `@nrwl/angular` plugin. NOTE: Does not work in the `--dry-run` mode.", "type": "object", "properties": { "unitTestRunner": { @@ -1087,7 +1091,7 @@ "cli": "nx", "title": "Convert to withModuleFederation Generator Options Schema", "type": "object", - "description": "Converts an old micro frontend configuration to use the new withModuleFederation helper. It will run successfully if the following conditions are met: \n - Is either a host or remote application \n - Shared npm package configurations have not been modified \n - Name used to identify the Micro Frontend application matches the project name \n\n _**Note:** This generator will overwrite your webpack config. If you have additional custom configuration in your config file, it will be lost!_", + "description": "Converts an old micro frontend configuration to use the new withModuleFederation helper. It will run successfully if the following conditions are met: \n - Is either a host or remote application \n - Shared npm package configurations have not been modified \n - Name used to identify the Micro Frontend application matches the project name \n\n _**Note:** This generator will overwrite your webpack config. If you have additional custom configuration in your config file, it will be lost!_.", "additionalProperties": false, "properties": { "project": { @@ -1113,7 +1117,7 @@ "$id": "NxMFEHost", "cli": "nx", "title": "Nx MFE Host Application", - "description": "Create an Angular Host Micro Frontend Application", + "description": "Create an Angular Host Micro Frontend Application.", "type": "object", "examples": [ { @@ -1433,7 +1437,7 @@ "cli": "nx", "title": "SCAM Generator Options Schema", "type": "object", - "description": "Creates a new, generic component definition in the given or default project.", + "description": "Creates a new, generic Angular component definition in the given or default project.", "additionalProperties": false, "properties": { "path": { @@ -1553,7 +1557,7 @@ "cli": "nx", "title": "SCAM Directive Generator Options Schema", "type": "object", - "description": "Creates a new, generic directive definition in the given or default project.", + "description": "Creates a new, generic Angular directive definition in the given or default project.", "additionalProperties": false, "properties": { "path": { @@ -1626,7 +1630,7 @@ "cli": "nx", "title": "SCAM Pipe Generator Options Schema", "type": "object", - "description": "Creates a new, generic pipe definition in the given or default project.", + "description": "Creates a new, generic Angular pipe definition in the given or default project.", "additionalProperties": false, "properties": { "path": { @@ -1798,7 +1802,7 @@ "$schema": "http://json-schema.org/schema", "$id": "NxAngularStorybookStoriesGenerator", "title": "Create Storybook stories/specs", - "description": "Creates Storybook stories/specs for all components declared in a project.", + "description": "Creates Storybook stories/specs for all Angular components declared in a project.", "cli": "nx", "type": "object", "properties": { diff --git a/docs/generated/packages/jest.json b/docs/generated/packages/jest.json index 4afc00a441413..e8ab8247f38e0 100644 --- a/docs/generated/packages/jest.json +++ b/docs/generated/packages/jest.json @@ -45,7 +45,7 @@ "$id": "NxJestProject", "cli": "nx", "title": "Add Jest Configuration to a project", - "description": "Add Jest Configuration to a project", + "description": "Add Jest Configuration to a project.", "type": "object", "properties": { "project": { diff --git a/docs/generated/packages/js.json b/docs/generated/packages/js.json index 4a727d001138f..72ab19f6ed059 100644 --- a/docs/generated/packages/js.json +++ b/docs/generated/packages/js.json @@ -136,7 +136,7 @@ "$id": "NxTypescriptInit", "cli": "nx", "title": "Init nrwl/js", - "description": "Init generator placeholder for nrwl/js", + "description": "Init generator placeholder for nrwl/js.", "presets": [] }, "aliases": ["lib"], diff --git a/docs/generated/packages/nest.json b/docs/generated/packages/nest.json index f75781d735b15..96af4bd408ba4 100644 --- a/docs/generated/packages/nest.json +++ b/docs/generated/packages/nest.json @@ -12,7 +12,7 @@ "$schema": "http://json-schema.org/schema", "$id": "NxNestApplicationGenerator", "title": "Nx Application Options Schema", - "description": "Nx Application Options Schema", + "description": "Nx Application Options Schema.", "cli": "nx", "type": "object", "properties": { diff --git a/docs/generated/packages/next.json b/docs/generated/packages/next.json index cfb5bef2ad789..3ce08b61bb4e0 100644 --- a/docs/generated/packages/next.json +++ b/docs/generated/packages/next.json @@ -51,6 +51,7 @@ "cli": "nx", "$id": "NxNextApp", "title": "Create a Next.js Application for Nx", + "description": "Create a Next.js Application for Nx.", "examples": [ { "command": "nx g app myapp --directory=myorg", @@ -287,6 +288,7 @@ "cli": "nx", "$id": "NxNextReactComponent", "title": "Create a React Component for Next", + "description": "Create a React Component for Next.", "type": "object", "examples": [ { @@ -560,7 +562,7 @@ "$schema": "http://json-schema.org/schema", "cli": "nx", "title": "Next Build", - "description": "Build a Next.js app", + "description": "Build a Next.js app.", "type": "object", "properties": { "root": { "description": "The source root", "type": "string" }, @@ -612,7 +614,7 @@ "schema": { "cli": "nx", "title": "Next Serve", - "description": "Serve a Next.js app", + "description": "Serve a Next.js app.", "type": "object", "properties": { "dev": { diff --git a/docs/generated/packages/node.json b/docs/generated/packages/node.json index 37d9edf5d695c..d95a25c213dba 100644 --- a/docs/generated/packages/node.json +++ b/docs/generated/packages/node.json @@ -205,7 +205,6 @@ }, "rootDir": { "type": "string", - "alias": "srcRootForCompilationRoot", "description": "Sets the `rootDir` for TypeScript compilation. When not defined, it uses the project's root property, or `srcRootForCompilationRoot` if it is defined." }, "testEnvironment": { diff --git a/docs/generated/packages/react-native.json b/docs/generated/packages/react-native.json index 16aa7026cef6d..aa4af2db23ba2 100644 --- a/docs/generated/packages/react-native.json +++ b/docs/generated/packages/react-native.json @@ -325,7 +325,7 @@ "cli": "nx", "$id": "NxReactNativeStorybookConfigure", "title": "React native Storybook configuration", - "description": "Set up Storybook for a React-Native app or library", + "description": "Set up Storybook for a React-Native app or library.", "type": "object", "properties": { "name": { diff --git a/docs/generated/packages/react.json b/docs/generated/packages/react.json index 8041801934619..41043649a3110 100644 --- a/docs/generated/packages/react.json +++ b/docs/generated/packages/react.json @@ -12,7 +12,7 @@ "$schema": "http://json-schema.org/schema", "$id": "NxReactNgInit", "title": "Init React Plugin", - "description": "Initialize a React Plugin", + "description": "Initialize a React Plugin.", "cli": "nx", "type": "object", "properties": { @@ -218,7 +218,7 @@ "cli": "nx", "$id": "NxReactLibrary", "title": "Create a React Library", - "description": "Create a React Library for an Nx workspace", + "description": "Create a React Library for an Nx workspace.", "type": "object", "examples": [ { diff --git a/docs/generated/packages/web.json b/docs/generated/packages/web.json index 74626a9c88bad..ccc67124427be 100644 --- a/docs/generated/packages/web.json +++ b/docs/generated/packages/web.json @@ -13,6 +13,7 @@ "$id": "NxWebInit", "cli": "nx", "title": "Init Web Plugin", + "description": "Init Web Plugin.", "type": "object", "properties": { "unitTestRunner": { @@ -148,7 +149,7 @@ "implementation": "/packages/web/src/executors/webpack/webpack.impl.ts", "schema": { "title": "Webpack Executor", - "description": "Builds web applications using webpack", + "description": "Builds web applications using webpack.", "cli": "nx", "type": "object", "properties": { diff --git a/e2e/cli/src/cli.test.ts b/e2e/cli/src/cli.test.ts index d442d97e530c1..6fbd4f1269102 100644 --- a/e2e/cli/src/cli.test.ts +++ b/e2e/cli/src/cli.test.ts @@ -72,11 +72,13 @@ describe('Cli', () => { const genHelp = runCLI(`g @nrwl/web:app --help`); expect(genHelp).toContain( - 'The file extension to be used for style files. (default: css)' + 'Find more information and examples at: https://nx.dev/packages/web/generators/application' ); const buildHelp = runCLI(`build ${myapp} --help`); - expect(buildHelp).toContain('The name of the main entry-point file.'); + expect(buildHelp).toContain( + 'Find more information and examples at: https://nx.dev/packages/web/executors/webpack' + ); const affectedHelp = runCLI(`affected --help`); expect(affectedHelp).toContain('Run target for affected projects'); diff --git a/nx.json b/nx.json index d27b025c353e3..dd2c20c6cc802 100644 --- a/nx.json +++ b/nx.json @@ -3,7 +3,8 @@ "package.json": "*", ".eslintrc.json": "*", "scripts/vercel/*": ["nx-dev"], - ".circleci/config.yml": "*" + ".circleci/config.yml": "*", + "tools/eslint-rules/**/*": "*" }, "affected": { "defaultBase": "master" diff --git a/package.json b/package.json index 0b6d8812507cb..df63962e54ddd 100644 --- a/package.json +++ b/package.json @@ -166,6 +166,7 @@ "jest": "27.2.3", "jest-circus": "27.2.3", "jest-preset-angular": "11.1.1", + "jsonc-eslint-parser": "^2.1.0", "jsonc-parser": "3.0.0", "karma": "~4.0.0", "karma-chrome-launcher": "~2.2.0", diff --git a/packages/angular/project.json b/packages/angular/project.json index 7240cb4975d86..64549cc244bf3 100644 --- a/packages/angular/project.json +++ b/packages/angular/project.json @@ -85,7 +85,9 @@ "packages/angular/**/*.spec.tsx", "packages/angular/**/*.spec.js", "packages/angular/**/*.spec.jsx", - "packages/angular/**/*.d.ts" + "packages/angular/**/*.d.ts", + "packages/angular/**/executors/**/schema.json", + "packages/angular/**/generators/**/schema.json" ] }, "outputs": ["{options.outputFile}"] diff --git a/packages/angular/src/generators/add-linting/schema.json b/packages/angular/src/generators/add-linting/schema.json index cdcc62cda8aae..d6b6fe68bdd1d 100644 --- a/packages/angular/src/generators/add-linting/schema.json +++ b/packages/angular/src/generators/add-linting/schema.json @@ -2,6 +2,7 @@ "$schema": "http://json-schema.org/schema", "$id": "NxAngularAddLintingGenerator", "title": "Add linting to an Angular project.", + "description": "Adds linting configuration to an Angular project.", "cli": "nx", "type": "object", "properties": { diff --git a/packages/angular/src/generators/application/schema.json b/packages/angular/src/generators/application/schema.json index 4f38755430ece..8e6170d071279 100644 --- a/packages/angular/src/generators/application/schema.json +++ b/packages/angular/src/generators/application/schema.json @@ -2,6 +2,7 @@ "$schema": "http://json-schema.org/schema", "$id": "GeneratorNxApp", "title": "Creates an Angular application.", + "description": "Creates an Angular application.", "type": "object", "cli": "nx", "properties": { diff --git a/packages/angular/src/generators/component-cypress-spec/schema.json b/packages/angular/src/generators/component-cypress-spec/schema.json index 5101c6169fc66..a4b8b7db26da5 100644 --- a/packages/angular/src/generators/component-cypress-spec/schema.json +++ b/packages/angular/src/generators/component-cypress-spec/schema.json @@ -3,6 +3,7 @@ "$id": "NxAngularComponentCypressSpecGenerator", "type": "object", "cli": "nx", + "description": "Creates a Cypress spec for a UI component that has a story.", "properties": { "projectName": { "type": "string", diff --git a/packages/angular/src/generators/component-story/schema.json b/packages/angular/src/generators/component-story/schema.json index 37926881c3be3..9195473901c9c 100644 --- a/packages/angular/src/generators/component-story/schema.json +++ b/packages/angular/src/generators/component-story/schema.json @@ -3,6 +3,7 @@ "$id": "NxAngularComponentStoryGenerator", "type": "object", "cli": "nx", + "description": "Creates a `stories.ts` file for an Angular component.", "properties": { "projectPath": { "type": "string", diff --git a/packages/angular/src/generators/component/schema.json b/packages/angular/src/generators/component/schema.json index 464f90a3df521..b6b1c73b08f40 100644 --- a/packages/angular/src/generators/component/schema.json +++ b/packages/angular/src/generators/component/schema.json @@ -4,7 +4,7 @@ "title": "Angular Component Schema", "cli": "nx", "type": "object", - "description": "Creates a new, generic component definition in the given or default project.", + "description": "Creates a new, generic Angular component definition in the given or default project.", "additionalProperties": false, "properties": { "path": { diff --git a/packages/angular/src/generators/convert-to-with-mf/schema.json b/packages/angular/src/generators/convert-to-with-mf/schema.json index 5f3e3912911ae..de3deacfdeb6d 100644 --- a/packages/angular/src/generators/convert-to-with-mf/schema.json +++ b/packages/angular/src/generators/convert-to-with-mf/schema.json @@ -4,7 +4,7 @@ "cli": "nx", "title": "Convert to withModuleFederation Generator Options Schema", "type": "object", - "description": "Converts an old micro frontend configuration to use the new withModuleFederation helper. It will run successfully if the following conditions are met: \n - Is either a host or remote application \n - Shared npm package configurations have not been modified \n - Name used to identify the Micro Frontend application matches the project name \n\n _**Note:** This generator will overwrite your webpack config. If you have additional custom configuration in your config file, it will be lost!_", + "description": "Converts an old micro frontend configuration to use the new withModuleFederation helper. It will run successfully if the following conditions are met: \n - Is either a host or remote application \n - Shared npm package configurations have not been modified \n - Name used to identify the Micro Frontend application matches the project name \n\n _**Note:** This generator will overwrite your webpack config. If you have additional custom configuration in your config file, it will be lost!_.", "additionalProperties": false, "properties": { "project": { diff --git a/packages/angular/src/generators/convert-tslint-to-eslint/schema.json b/packages/angular/src/generators/convert-tslint-to-eslint/schema.json index 342b9b53575bf..c3b47a2dee7b2 100644 --- a/packages/angular/src/generators/convert-tslint-to-eslint/schema.json +++ b/packages/angular/src/generators/convert-tslint-to-eslint/schema.json @@ -3,7 +3,7 @@ "$id": "NxAngularConvertTSLintToESLintGenerator", "cli": "nx", "title": "Convert an Angular project from TSLint to ESLint", - "description": "Convert an Angular project from TSLint to ESLint. NOTE: Does not work in `--dry-run mode`", + "description": "Convert an Angular project from TSLint to ESLint. NOTE: Does not work in `--dry-run mode`.", "examples": [ { "command": "nx g convert-tslint-to-eslint myapp", diff --git a/packages/angular/src/generators/downgrade-module/schema.json b/packages/angular/src/generators/downgrade-module/schema.json index bf2849e87e2eb..470c61522c10a 100644 --- a/packages/angular/src/generators/downgrade-module/schema.json +++ b/packages/angular/src/generators/downgrade-module/schema.json @@ -2,7 +2,7 @@ "$schema": "http://json-schema.org/schema", "$id": "NxDowngradeModuleGenerator", "title": "Generates downgradeModule setup.", - "description": "Sets up a Downgrade Module.", + "description": "Sets up a Downgrade Module for using AngularJS and Angular.", "cli": "nx", "type": "object", "properties": { diff --git a/packages/angular/src/generators/init/schema.json b/packages/angular/src/generators/init/schema.json index 4ff04b4ec9dbf..93cdb73fd02b2 100644 --- a/packages/angular/src/generators/init/schema.json +++ b/packages/angular/src/generators/init/schema.json @@ -3,7 +3,7 @@ "$id": "SchematicsAngularModuleInit", "cli": "nx", "title": "Init Angular Plugin", - "description": "Initializes the `@nrwl/angular` plugin. NOTE: Does not work in the `--dry-run` mode", + "description": "Initializes the `@nrwl/angular` plugin. NOTE: Does not work in the `--dry-run` mode.", "type": "object", "properties": { "unitTestRunner": { diff --git a/packages/angular/src/generators/mfe-host/schema.json b/packages/angular/src/generators/mfe-host/schema.json index fd74f7595e9b6..158e8df59cfa9 100644 --- a/packages/angular/src/generators/mfe-host/schema.json +++ b/packages/angular/src/generators/mfe-host/schema.json @@ -3,7 +3,7 @@ "$id": "NxMFEHost", "cli": "nx", "title": "Nx MFE Host Application", - "description": "Create an Angular Host Micro Frontend Application", + "description": "Create an Angular Host Micro Frontend Application.", "type": "object", "examples": [ { diff --git a/packages/angular/src/generators/scam-directive/schema.json b/packages/angular/src/generators/scam-directive/schema.json index 256bc12bc7f67..9445b58d42d69 100644 --- a/packages/angular/src/generators/scam-directive/schema.json +++ b/packages/angular/src/generators/scam-directive/schema.json @@ -4,7 +4,7 @@ "cli": "nx", "title": "SCAM Directive Generator Options Schema", "type": "object", - "description": "Creates a new, generic directive definition in the given or default project.", + "description": "Creates a new, generic Angular directive definition in the given or default project.", "additionalProperties": false, "properties": { "path": { diff --git a/packages/angular/src/generators/scam-pipe/schema.json b/packages/angular/src/generators/scam-pipe/schema.json index 2d999380fbaaa..a3f0cf4cfa112 100644 --- a/packages/angular/src/generators/scam-pipe/schema.json +++ b/packages/angular/src/generators/scam-pipe/schema.json @@ -4,7 +4,7 @@ "cli": "nx", "title": "SCAM Pipe Generator Options Schema", "type": "object", - "description": "Creates a new, generic pipe definition in the given or default project.", + "description": "Creates a new, generic Angular pipe definition in the given or default project.", "additionalProperties": false, "properties": { "path": { diff --git a/packages/angular/src/generators/scam/schema.json b/packages/angular/src/generators/scam/schema.json index d0316d5275e1c..d4d1c909183af 100644 --- a/packages/angular/src/generators/scam/schema.json +++ b/packages/angular/src/generators/scam/schema.json @@ -4,7 +4,7 @@ "cli": "nx", "title": "SCAM Generator Options Schema", "type": "object", - "description": "Creates a new, generic component definition in the given or default project.", + "description": "Creates a new, generic Angular component definition in the given or default project.", "additionalProperties": false, "properties": { "path": { diff --git a/packages/angular/src/generators/stories/schema.json b/packages/angular/src/generators/stories/schema.json index 2b2e7e904fbd2..bbe919f18f3e4 100644 --- a/packages/angular/src/generators/stories/schema.json +++ b/packages/angular/src/generators/stories/schema.json @@ -2,7 +2,7 @@ "$schema": "http://json-schema.org/schema", "$id": "NxAngularStorybookStoriesGenerator", "title": "Create Storybook stories/specs", - "description": "Creates Storybook stories/specs for all components declared in a project.", + "description": "Creates Storybook stories/specs for all Angular components declared in a project.", "cli": "nx", "type": "object", "properties": { diff --git a/packages/cypress/project.json b/packages/cypress/project.json index aa37b4445c447..835e8c0767359 100644 --- a/packages/cypress/project.json +++ b/packages/cypress/project.json @@ -71,7 +71,9 @@ "packages/cypress/**/*.spec.tsx", "packages/cypress/**/*.spec.js", "packages/cypress/**/*.spec.jsx", - "packages/cypress/**/*.d.ts" + "packages/cypress/**/*.d.ts", + "packages/cypress/**/executors/**/schema.json", + "packages/cypress/**/generators/**/schema.json" ] }, "outputs": ["{options.outputFile}"] diff --git a/packages/detox/project.json b/packages/detox/project.json index ee376513c8118..09c9eea7cdf50 100644 --- a/packages/detox/project.json +++ b/packages/detox/project.json @@ -12,7 +12,9 @@ "packages/detox/**/*.spec.tsx", "packages/detox/**/*.spec.js", "packages/detox/**/*.spec.jsx", - "packages/detox/**/*.d.ts" + "packages/detox/**/*.d.ts", + "packages/detox/**/executors/**/schema.json", + "packages/detox/**/generators/**/schema.json" ] }, "outputs": ["{options.outputFile}"] diff --git a/packages/devkit/project.json b/packages/devkit/project.json index 7b68eef398e8f..9ecb04d62b293 100644 --- a/packages/devkit/project.json +++ b/packages/devkit/project.json @@ -71,7 +71,9 @@ "packages/devkit/**/*.spec.tsx", "packages/devkit/**/*.spec.js", "packages/devkit/**/*.spec.jsx", - "packages/devkit/**/*.d.ts" + "packages/devkit/**/*.d.ts", + "packages/devkit/**/executors/**/schema.json", + "packages/devkit/**/generators/**/schema.json" ] }, "outputs": ["{options.outputFile}"] diff --git a/packages/express/project.json b/packages/express/project.json index ea51ddb96b1c6..3546df8822dbd 100644 --- a/packages/express/project.json +++ b/packages/express/project.json @@ -72,7 +72,9 @@ "packages/express/**/*.spec.tsx", "packages/express/**/*.spec.js", "packages/express/**/*.spec.jsx", - "packages/express/**/*.d.ts" + "packages/express/**/*.d.ts", + "packages/express/**/executors/**/schema.json", + "packages/express/**/generators/**/schema.json" ] }, "outputs": ["{options.outputFile}"] diff --git a/packages/jest/project.json b/packages/jest/project.json index 0b633a4bd5109..6630799929f95 100644 --- a/packages/jest/project.json +++ b/packages/jest/project.json @@ -71,7 +71,9 @@ "packages/jest/**/*.spec.tsx", "packages/jest/**/*.spec.js", "packages/jest/**/*.spec.jsx", - "packages/jest/**/*.d.ts" + "packages/jest/**/*.d.ts", + "packages/jest/**/executors/**/schema.json", + "packages/jest/**/generators/**/schema.json" ] }, "outputs": ["{options.outputFile}"] diff --git a/packages/jest/src/generators/jest-project/schema.json b/packages/jest/src/generators/jest-project/schema.json index dfdf041be9f2a..5c1dc952a29e2 100644 --- a/packages/jest/src/generators/jest-project/schema.json +++ b/packages/jest/src/generators/jest-project/schema.json @@ -3,7 +3,7 @@ "$id": "NxJestProject", "cli": "nx", "title": "Add Jest Configuration to a project", - "description": "Add Jest Configuration to a project", + "description": "Add Jest Configuration to a project.", "type": "object", "properties": { "project": { diff --git a/packages/js/project.json b/packages/js/project.json index b1d47c450856c..28ee98d4311ad 100644 --- a/packages/js/project.json +++ b/packages/js/project.json @@ -7,7 +7,11 @@ "executor": "@nrwl/linter:eslint", "outputs": ["{options.outputFile}"], "options": { - "lintFilePatterns": ["packages/js/**/*.ts"] + "lintFilePatterns": [ + "packages/js/**/*.ts", + "packages/js/**/executors/**/schema.json", + "packages/js/**/generators/**/schema.json" + ] } }, "test": { diff --git a/packages/js/src/generators/init/schema.json b/packages/js/src/generators/init/schema.json index 5050b1b6e27fa..00e651cc61390 100644 --- a/packages/js/src/generators/init/schema.json +++ b/packages/js/src/generators/init/schema.json @@ -3,5 +3,5 @@ "$id": "NxTypescriptInit", "cli": "nx", "title": "Init nrwl/js", - "description": "Init generator placeholder for nrwl/js" + "description": "Init generator placeholder for nrwl/js." } diff --git a/packages/linter/project.json b/packages/linter/project.json index 9e1b2c411a786..fcddec0e594c6 100644 --- a/packages/linter/project.json +++ b/packages/linter/project.json @@ -72,7 +72,9 @@ "packages/linter/**/*.spec.tsx", "packages/linter/**/*.spec.js", "packages/linter/**/*.spec.jsx", - "packages/linter/**/*.d.ts" + "packages/linter/**/*.d.ts", + "packages/linter/**/executors/**/schema.json", + "packages/linter/**/generators/**/schema.json" ] }, "outputs": ["{options.outputFile}"] diff --git a/packages/nest/project.json b/packages/nest/project.json index 31caa1aeefd44..531b0be370172 100644 --- a/packages/nest/project.json +++ b/packages/nest/project.json @@ -72,7 +72,9 @@ "packages/nest/**/*.spec.tsx", "packages/nest/**/*.spec.js", "packages/nest/**/*.spec.jsx", - "packages/nest/**/*.d.ts" + "packages/nest/**/*.d.ts", + "packages/nest/**/executors/**/schema.json", + "packages/nest/**/generators/**/schema.json" ] }, "outputs": ["{options.outputFile}"] diff --git a/packages/nest/src/generators/application/schema.json b/packages/nest/src/generators/application/schema.json index 5ea45f51bbabf..11794f0433ff6 100644 --- a/packages/nest/src/generators/application/schema.json +++ b/packages/nest/src/generators/application/schema.json @@ -2,7 +2,7 @@ "$schema": "http://json-schema.org/schema", "$id": "NxNestApplicationGenerator", "title": "Nx Application Options Schema", - "description": "Nx Application Options Schema", + "description": "Nx Application Options Schema.", "cli": "nx", "type": "object", "properties": { diff --git a/packages/next/project.json b/packages/next/project.json index 68222935ecfac..116baa735daf6 100644 --- a/packages/next/project.json +++ b/packages/next/project.json @@ -77,7 +77,9 @@ "packages/next/**/*.spec.tsx", "packages/next/**/*.spec.js", "packages/next/**/*.spec.jsx", - "packages/next/**/*.d.ts" + "packages/next/**/*.d.ts", + "packages/next/**/executors/**/schema.json", + "packages/next/**/generators/**/schema.json" ] }, "outputs": ["{options.outputFile}"] diff --git a/packages/next/src/executors/build/schema.json b/packages/next/src/executors/build/schema.json index 868caf9a1a730..067d3ace015e8 100644 --- a/packages/next/src/executors/build/schema.json +++ b/packages/next/src/executors/build/schema.json @@ -2,7 +2,7 @@ "$schema": "http://json-schema.org/schema", "cli": "nx", "title": "Next Build", - "description": "Build a Next.js app", + "description": "Build a Next.js app.", "type": "object", "properties": { "root": { diff --git a/packages/next/src/executors/server/schema.json b/packages/next/src/executors/server/schema.json index 72ad6020a089e..140dc0c1b9a50 100644 --- a/packages/next/src/executors/server/schema.json +++ b/packages/next/src/executors/server/schema.json @@ -1,7 +1,7 @@ { "cli": "nx", "title": "Next Serve", - "description": "Serve a Next.js app", + "description": "Serve a Next.js app.", "type": "object", "properties": { "dev": { diff --git a/packages/next/src/generators/application/schema.json b/packages/next/src/generators/application/schema.json index 7ffef5ab66475..aa1c2a767805e 100644 --- a/packages/next/src/generators/application/schema.json +++ b/packages/next/src/generators/application/schema.json @@ -3,6 +3,7 @@ "cli": "nx", "$id": "NxNextApp", "title": "Create a Next.js Application for Nx", + "description": "Create a Next.js Application for Nx.", "examples": [ { "command": "nx g app myapp --directory=myorg", diff --git a/packages/next/src/generators/component/schema.json b/packages/next/src/generators/component/schema.json index 915f101ccb1fd..411a8a2498e92 100644 --- a/packages/next/src/generators/component/schema.json +++ b/packages/next/src/generators/component/schema.json @@ -3,6 +3,7 @@ "cli": "nx", "$id": "NxNextReactComponent", "title": "Create a React Component for Next", + "description": "Create a React Component for Next.", "type": "object", "examples": [ { diff --git a/packages/node/project.json b/packages/node/project.json index 0d9ad5b7e8dd1..a105009625f7a 100644 --- a/packages/node/project.json +++ b/packages/node/project.json @@ -72,7 +72,9 @@ "packages/node/**/*.spec.tsx", "packages/node/**/*.spec.js", "packages/node/**/*.spec.jsx", - "packages/node/**/*.d.ts" + "packages/node/**/*.d.ts", + "packages/node/**/executors/**/schema.json", + "packages/node/**/generators/**/schema.json" ] }, "outputs": ["{options.outputFile}"] diff --git a/packages/node/src/generators/library/schema.json b/packages/node/src/generators/library/schema.json index ff09953966186..2ad3588e0e4e8 100644 --- a/packages/node/src/generators/library/schema.json +++ b/packages/node/src/generators/library/schema.json @@ -79,7 +79,6 @@ }, "rootDir": { "type": "string", - "alias": "srcRootForCompilationRoot", "description": "Sets the `rootDir` for TypeScript compilation. When not defined, it uses the project's root property, or `srcRootForCompilationRoot` if it is defined." }, "testEnvironment": { diff --git a/packages/nx-plugin/project.json b/packages/nx-plugin/project.json index 14306eaa2cba0..080fd3bab75e5 100644 --- a/packages/nx-plugin/project.json +++ b/packages/nx-plugin/project.json @@ -72,7 +72,9 @@ "packages/nx-plugin/**/*.spec.tsx", "packages/nx-plugin/**/*.spec.js", "packages/nx-plugin/**/*.spec.jsx", - "packages/nx-plugin/**/*.d.ts" + "packages/nx-plugin/**/*.d.ts", + "packages/nx-plugin/**/executors/**/schema.json", + "packages/nx-plugin/**/generators/**/schema.json" ] }, "outputs": ["{options.outputFile}"] diff --git a/packages/nx/src/command-line/generate.ts b/packages/nx/src/command-line/generate.ts index 683c4bad240be..99c8de88f14e1 100644 --- a/packages/nx/src/command-line/generate.ts +++ b/packages/nx/src/command-line/generate.ts @@ -95,9 +95,14 @@ function readDefaultCollection(nxConfig: NxJsonConfiguration) { return nxConfig.cli ? nxConfig.cli.defaultCollection : null; } -export function printGenHelp(opts: GenerateOptions, schema: Schema) { +export function printGenHelp( + opts: GenerateOptions, + schema: Schema, + normalizedGeneratorName: string, + aliases: string[] +) { printHelp( - `nx generate ${opts.collectionName}:${opts.generatorName}`, + `generate ${opts.collectionName}:${normalizedGeneratorName}`, { ...schema, properties: schema.properties, @@ -105,7 +110,8 @@ export function printGenHelp(opts: GenerateOptions, schema: Schema) { { mode: 'generate', plugin: opts.collectionName, - entity: opts.generatorName, + entity: normalizedGeneratorName, + aliases, } ); } @@ -170,11 +176,11 @@ export async function generate(cwd: string, args: { [k: string]: any }) { readDefaultCollection(workspaceDefinition), 'generate' ); - const { normalizedGeneratorName, schema, implementationFactory } = + const { normalizedGeneratorName, schema, implementationFactory, aliases } = ws.readGenerator(opts.collectionName, opts.generatorName); if (opts.help) { - printGenHelp(opts, schema); + printGenHelp(opts, schema, normalizedGeneratorName, aliases); return 0; } diff --git a/packages/nx/src/command-line/run.ts b/packages/nx/src/command-line/run.ts index 7b90893a6cd2d..ed0b185f4b10f 100644 --- a/packages/nx/src/command-line/run.ts +++ b/packages/nx/src/command-line/run.ts @@ -32,7 +32,7 @@ export function printRunHelp( schema: Schema, plugin: { plugin: string; entity: string } ) { - printHelp(`nx run ${opts.project}:${opts.target}`, schema, { + printHelp(`run ${opts.project}:${opts.target}`, schema, { mode: 'run', ...plugin, }); diff --git a/packages/nx/src/config/workspaces.ts b/packages/nx/src/config/workspaces.ts index fb56a64040b4a..4d682fed63e59 100644 --- a/packages/nx/src/config/workspaces.ts +++ b/packages/nx/src/config/workspaces.ts @@ -164,7 +164,12 @@ export class Workspaces { generatorConfig.implementation, generatorsDir ); - return { normalizedGeneratorName, schema, implementationFactory }; + return { + normalizedGeneratorName, + schema, + implementationFactory, + aliases: generatorConfig.aliases || [], + }; } catch (e) { throw new Error( `Unable to resolve ${collectionName}:${generatorName}.\n${e.message}` diff --git a/packages/nx/src/utils/params.ts b/packages/nx/src/utils/params.ts index 1a909cb606776..0720403e3745e 100644 --- a/packages/nx/src/utils/params.ts +++ b/packages/nx/src/utils/params.ts @@ -55,6 +55,7 @@ export type Schema = { description?: string; definitions?: Properties; additionalProperties?: boolean; + examples?: { command: string; description?: string }[]; }; export type Unmatched = { diff --git a/packages/nx/src/utils/print-help.ts b/packages/nx/src/utils/print-help.ts index 8bbbe1802b4a8..4cb75d419beba 100644 --- a/packages/nx/src/utils/print-help.ts +++ b/packages/nx/src/utils/print-help.ts @@ -1,24 +1,17 @@ -import { Schema } from './params'; import * as chalk from 'chalk'; -import { logger, stripIndent } from './logger'; - -function formatOption( - name: string, - description: string, - maxPropertyNameLength: number -) { - const lengthOfKey = Math.max(maxPropertyNameLength + 4, 22); - return ` --${`${name} `.slice( - 0, - lengthOfKey - )}${description}`; -} +import * as stringWidth from 'string-width'; +// cliui is the CLI layout engine developed by, and used within, yargs +import * as cliui from 'cliui'; +import { logger } from './logger'; +import { output } from './output'; +import { Schema } from './params'; +import { nxVersion } from './versions'; export function printHelp( header: string, schema: Schema, meta: - | { mode: 'generate'; plugin: string; entity: string } + | { mode: 'generate'; plugin: string; entity: string; aliases: string[] } | { mode: 'run'; plugin: string; entity: string } ) { const allPositional = Object.keys(schema.properties).filter((key) => { @@ -26,56 +19,320 @@ export function printHelp( return p['$default'] && p['$default']['$source'] === 'argv'; }); const positional = allPositional.length > 0 ? ` [${allPositional[0]}]` : ''; - const maxPropertyNameLength = Object.keys(schema.properties) - .map((n) => n.length) - .reduce((a, b) => Math.max(a, b), 0); - const args = Object.keys(schema.properties) - .map((name) => { - const d = schema.properties[name]; - const def = d.default ? ` (default: ${d.default})` : ''; - return formatOption( + + logger.info(` +${output.applyNxPrefix( + 'cyan', + chalk.bold( + `${`${header + chalk.reset.cyan(positional)} ${chalk.reset.cyan( + '[options,...]' + )}`}` + ) +)} + +${generateOverviewOutput({ + pluginName: meta.plugin, + name: meta.entity, + description: schema.description, + mode: meta.mode, + aliases: meta.mode === 'generate' ? meta.aliases : [], +})} +${generateOptionsOutput(schema)} +${generateExamplesOutput(schema)} +${generateLinkOutput({ + pluginName: meta.plugin, + name: meta.entity, + type: meta.mode === 'generate' ? 'generators' : 'executors', +})} +`); +} + +function generateOverviewOutput({ + pluginName, + name, + description, + mode, + aliases, +}: { + pluginName: string; + name: string; + description: string; + mode: 'generate' | 'run'; + aliases: string[]; +}): string { + switch (mode) { + case 'generate': + return generateGeneratorOverviewOutput({ + pluginName, + name, + description, + aliases, + }); + case 'run': + return generateExecutorOverviewOutput({ + pluginName, name, - `${d.description}${def}`, - maxPropertyNameLength - ); - }) - .join('\n'); - - const missingFlags = - meta.mode === 'generate' - ? formatOption( - 'dry-run', - 'Preview the changes without updating files', - maxPropertyNameLength - ) - : formatOption( - 'skip-nx-cache', - 'Skip the use of Nx cache.', - maxPropertyNameLength - ); - - let linkDescription = null; - // we need to generalize link generation so it works for non-party-class plugins as well - if (meta.mode === 'generate' && meta.plugin.startsWith('@nrwl/')) { - linkDescription = generateLink(meta.plugin, meta.entity, 'generators'); - } else if (meta.mode === 'run' && meta.plugin.startsWith('@nrwl/')) { - linkDescription = generateLink(meta.plugin, meta.entity, 'executors'); + description, + }); + default: + throw new Error(`Unexpected mode ${mode}`); } +} + +function generateGeneratorOverviewOutput({ + pluginName, + name, + description, + aliases, +}: { + pluginName: string; + name: string; + description: string; + aliases: string[]; +}): string { + const ui = cliui(); + const overviewItemsLabelWidth = + // Chars in labels "From" and "Name" + 4 + + // The `:` char + 1; + + ui.div( + ...[ + { + text: chalk.bold('From:'), + padding: [1, 0, 0, 0], + width: overviewItemsLabelWidth, + }, + { + text: + pluginName + + (pluginName.startsWith('@nrwl/') + ? chalk.dim(` (v${nxVersion})`) + : ''), + padding: [1, 0, 0, 2], + }, + ] + ); + + ui.div( + ...[ + { + text: chalk.bold('Name:'), + padding: [0, 0, 0, 0], + width: overviewItemsLabelWidth, + }, + { + text: `${name}${ + aliases.length ? chalk.dim(` (aliases: ${aliases.join(', ')})`) : '' + }`, + padding: [0, 0, 0, 2], + }, + ] + ); + + ui.div( + ...[ + { + text: description, + padding: [2, 0, 1, 2], + }, + ] + ); + + return ui.toString(); +} - logger.info( - stripIndent(` -${chalk.bold(`${header + positional} [options,...]`)} +function generateExecutorOverviewOutput({ + pluginName, + name, + description, +}: { + pluginName: string; + name: string; + description: string; +}): string { + const ui = cliui(); + const overviewItemsLeftPadding = 2; + const overviewItemsLabelWidth = overviewItemsLeftPadding + 'Executor:'.length; -${chalk.bold('Options')}: -${args} -${missingFlags}${linkDescription} - `) + ui.div( + ...[ + { + text: chalk.bold('Executor:'), + padding: [1, 0, 0, 0], + width: overviewItemsLabelWidth, + }, + { + text: + `${pluginName}:${name}` + + (pluginName.startsWith('@nrwl/') + ? chalk.dim(` (v${nxVersion})`) + : ''), + padding: [1, 0, 0, 0], + }, + ] ); + + ui.div( + ...[ + { + text: description, + padding: [2, 0, 1, 2], + }, + ] + ); + + return ui.toString(); +} + +const formatOptionVal = (maybeStr: unknown) => + typeof maybeStr === 'string' ? `"${maybeStr}"` : JSON.stringify(maybeStr); + +// From our JSON schemas an option could possibly have more than one valid type +const formatOptionType = (optionConfig: Schema['properties'][0]) => { + if (Array.isArray(optionConfig.oneOf)) { + return optionConfig.oneOf + .map((typeConfig) => formatOptionType(typeConfig)) + .join(' OR '); + } + return `[${optionConfig.type}]`; +}; + +function generateOptionsOutput(schema: Schema): string { + const ui = cliui(); + const flagAndAliasLeftPadding = 4; + const flagAndAliasRightPadding = 4; + + // Construct option flags (including optional aliases) and descriptions and track the required space to render them + const optionsToRender = new Map< + string, + { + renderedFlagAndAlias: string; + renderedDescription: string; + renderedTypesAndDefault: string; + } + >(); + let requiredSpaceToRenderAllFlagsAndAliases = 0; + + for (const [optionName, optionConfig] of Object.entries(schema.properties)) { + const renderedFlagAndAlias = + `--${optionName}` + + (optionConfig.alias ? `, -${optionConfig.alias}` : ''); + + const renderedFlagAndAliasTrueWidth = stringWidth(renderedFlagAndAlias); + if ( + renderedFlagAndAliasTrueWidth > requiredSpaceToRenderAllFlagsAndAliases + ) { + requiredSpaceToRenderAllFlagsAndAliases = renderedFlagAndAliasTrueWidth; + } + + const renderedDescription = optionConfig.description; + const renderedTypesAndDefault = `${formatOptionType(optionConfig)}${ + optionConfig.enum + ? ` [choices: ${optionConfig.enum + .map((e) => formatOptionVal(e)) + .join(', ')}]` + : '' + }${ + optionConfig.default + ? ` [default: ${formatOptionVal(optionConfig.default)}]` + : '' + }`; + + optionsToRender.set(optionName, { + renderedFlagAndAlias, + renderedDescription, + renderedTypesAndDefault, + }); + } + + ui.div({ + text: 'Options:', + padding: [1, 0, 0, 0], + }); + + for (const { + renderedFlagAndAlias, + renderedDescription, + renderedTypesAndDefault, + } of optionsToRender.values()) { + const cols = [ + { + text: renderedFlagAndAlias, + width: + requiredSpaceToRenderAllFlagsAndAliases + + flagAndAliasLeftPadding + + flagAndAliasRightPadding, + padding: [0, flagAndAliasRightPadding, 0, flagAndAliasLeftPadding], + }, + { + text: renderedDescription, + padding: [0, 0, 0, 0], + }, + { + text: renderedTypesAndDefault, + padding: [0, 0, 0, 0], + align: 'right', + }, + ]; + + ui.div(...cols); + } + + return ui.toString(); } -function generateLink(plugin: string, entity: string, type: string) { - const link = `https://nx.dev/packages/${plugin.substring( - 6 - )}/${type}/${entity}`; - return chalk.bold(`\n\nFind more information and examples at ${link}`); +function generateExamplesOutput(schema: Schema): string { + if (!schema.examples || schema.examples.length === 0) { + return ''; + } + const ui = cliui(); + const xPadding = 4; + + ui.div({ + text: 'Examples:', + padding: [1, 0, 0, 0], + }); + + for (const { command, description } of schema.examples) { + const cols = [ + { + text: command, + padding: [0, xPadding, 0, xPadding], + }, + { + text: description || '', + padding: [0, 2, 0, 0], + }, + ]; + + ui.div(...cols); + } + + return ui.toString(); +} + +// TODO: generalize link generation so it works for non @nrwl plugins as well +function generateLinkOutput({ + pluginName, + name, + type, +}: { + pluginName: string; + name: string; + type: 'generators' | 'executors'; +}): string { + const nrwlPackagePrefix = '@nrwl/'; + if (!pluginName.startsWith(nrwlPackagePrefix)) { + return ''; + } + + const link = `https://nx.dev/packages/${pluginName.substring( + nrwlPackagePrefix.length + )}/${type}/${name}`; + + return `\n\n${chalk.dim( + 'Find more information and examples at:' + )} ${chalk.bold(link)}`; } diff --git a/packages/react-native/project.json b/packages/react-native/project.json index 2ceb38b9e224c..07d52f2e21d75 100644 --- a/packages/react-native/project.json +++ b/packages/react-native/project.json @@ -12,7 +12,9 @@ "packages/react-native/**/*.spec.tsx", "packages/react-native/**/*.spec.js", "packages/react-native/**/*.spec.jsx", - "packages/react-native/**/*.d.ts" + "packages/react-native/**/*.d.ts", + "packages/react-native/**/executors/**/schema.json", + "packages/react-native/**/generators/**/schema.json" ] }, "outputs": ["{options.outputFile}"] diff --git a/packages/react-native/src/generators/storybook-configuration/schema.json b/packages/react-native/src/generators/storybook-configuration/schema.json index b3c09b8940b0d..2f74ad3daed12 100644 --- a/packages/react-native/src/generators/storybook-configuration/schema.json +++ b/packages/react-native/src/generators/storybook-configuration/schema.json @@ -3,7 +3,7 @@ "cli": "nx", "$id": "NxReactNativeStorybookConfigure", "title": "React native Storybook configuration", - "description": "Set up Storybook for a React-Native app or library", + "description": "Set up Storybook for a React-Native app or library.", "type": "object", "properties": { "name": { diff --git a/packages/react/project.json b/packages/react/project.json index 5bab97bb515ca..2d520a7d5b136 100644 --- a/packages/react/project.json +++ b/packages/react/project.json @@ -82,7 +82,9 @@ "packages/react/**/*.spec.tsx", "packages/react/**/*.spec.js", "packages/react/**/*.spec.jsx", - "packages/react/**/*.d.ts" + "packages/react/**/*.d.ts", + "packages/react/**/executors/**/schema.json", + "packages/react/**/generators/**/schema.json" ] }, "outputs": ["{options.outputFile}"] diff --git a/packages/react/src/generators/init/schema.json b/packages/react/src/generators/init/schema.json index 63d59e2840f72..e91cb068ecb1a 100644 --- a/packages/react/src/generators/init/schema.json +++ b/packages/react/src/generators/init/schema.json @@ -2,7 +2,7 @@ "$schema": "http://json-schema.org/schema", "$id": "NxReactNgInit", "title": "Init React Plugin", - "description": "Initialize a React Plugin", + "description": "Initialize a React Plugin.", "cli": "nx", "type": "object", "properties": { diff --git a/packages/react/src/generators/library/schema.json b/packages/react/src/generators/library/schema.json index f79cf85283739..8da59cea89d33 100644 --- a/packages/react/src/generators/library/schema.json +++ b/packages/react/src/generators/library/schema.json @@ -3,7 +3,7 @@ "cli": "nx", "$id": "NxReactLibrary", "title": "Create a React Library", - "description": "Create a React Library for an Nx workspace", + "description": "Create a React Library for an Nx workspace.", "type": "object", "examples": [ { diff --git a/packages/storybook/project.json b/packages/storybook/project.json index fa7823751215b..0fad7dd5dfde6 100644 --- a/packages/storybook/project.json +++ b/packages/storybook/project.json @@ -82,7 +82,9 @@ "packages/storybook/**/*.spec.tsx", "packages/storybook/**/*.spec.js", "packages/storybook/**/*.spec.jsx", - "packages/storybook/**/*.d.ts" + "packages/storybook/**/*.d.ts", + "packages/storybook/**/executors/**/schema.json", + "packages/storybook/**/generators/**/schema.json" ] }, "outputs": ["{options.outputFile}"] diff --git a/packages/web/project.json b/packages/web/project.json index 547d311e78732..08d115cb58fda 100644 --- a/packages/web/project.json +++ b/packages/web/project.json @@ -77,7 +77,9 @@ "packages/web/**/*.spec.tsx", "packages/web/**/*.spec.js", "packages/web/**/*.spec.jsx", - "packages/web/**/*.d.ts" + "packages/web/**/*.d.ts", + "packages/web/**/executors/**/schema.json", + "packages/web/**/generators/**/schema.json" ] }, "outputs": ["{options.outputFile}"] diff --git a/packages/web/src/executors/webpack/schema.json b/packages/web/src/executors/webpack/schema.json index 1aea465ef6127..ed752415a6911 100644 --- a/packages/web/src/executors/webpack/schema.json +++ b/packages/web/src/executors/webpack/schema.json @@ -1,6 +1,6 @@ { "title": "Webpack Executor", - "description": "Builds web applications using webpack", + "description": "Builds web applications using webpack.", "cli": "nx", "type": "object", "properties": { diff --git a/packages/web/src/generators/init/schema.json b/packages/web/src/generators/init/schema.json index abe9d8b2a4d0d..373aa9aecffba 100644 --- a/packages/web/src/generators/init/schema.json +++ b/packages/web/src/generators/init/schema.json @@ -3,6 +3,7 @@ "$id": "NxWebInit", "cli": "nx", "title": "Init Web Plugin", + "description": "Init Web Plugin.", "type": "object", "properties": { "unitTestRunner": { diff --git a/packages/workspace/project.json b/packages/workspace/project.json index 3161ff293c30a..73e8fe2f2838f 100644 --- a/packages/workspace/project.json +++ b/packages/workspace/project.json @@ -97,7 +97,9 @@ "packages/workspace/**/*.spec.tsx", "packages/workspace/**/*.spec.js", "packages/workspace/**/*.spec.jsx", - "packages/workspace/**/*.d.ts" + "packages/workspace/**/*.d.ts", + "packages/workspace/**/executors/**/schema.json", + "packages/workspace/**/generators/**/schema.json" ] }, "outputs": ["{options.outputFile}"] diff --git a/scripts/depcheck/missing.ts b/scripts/depcheck/missing.ts index d0277928b00e3..59fdeffc1ef6c 100644 --- a/scripts/depcheck/missing.ts +++ b/scripts/depcheck/missing.ts @@ -96,6 +96,13 @@ const IGNORE_MATCHES = { '@angular-devkit/core', '@angular-devkit/architect', '@angular/cli', + /** + * cliui is the CLI layout engine developed by yargs and we want to use the version of it + * which our currently installed yargs version brings in. It in turn depends on a specific + * version of string-width which we also leverage directly in print-help. + */ + 'cliui', + 'string-width', ], web: [ // we don't want to bloat the install of @nrwl/web by including @swc/core and swc-loader as a dependency. diff --git a/tools/eslint-rules/index.ts b/tools/eslint-rules/index.ts new file mode 100644 index 0000000000000..3a451ca03e015 --- /dev/null +++ b/tools/eslint-rules/index.ts @@ -0,0 +1,31 @@ +import { + RULE_NAME as validSchemaDescriptionName, + rule as validSchemaDescription, +} from './rules/valid-schema-description'; +/** + * Import your custom workspace rules at the top of this file. + * + * For example: + * + * import { RULE_NAME as myCustomRuleName, rule as myCustomRule } from './rules/my-custom-rule'; + * + * In order to quickly get started with writing rules you can use the + * following generator command and provide your desired rule name: + * + * ```sh + * npx nx g @nrwl/linter:workspace-rule {{ NEW_RULE_NAME }} + * ``` + */ + +module.exports = { + /** + * Apply the imported custom rules here. + * + * For example (using the example import above): + * + * rules: { + * [myCustomRuleName]: myCustomRule + * } + */ + rules: { [validSchemaDescriptionName]: validSchemaDescription }, +}; diff --git a/tools/eslint-rules/jest.config.js b/tools/eslint-rules/jest.config.js new file mode 100644 index 0000000000000..35580d228ce7a --- /dev/null +++ b/tools/eslint-rules/jest.config.js @@ -0,0 +1,17 @@ +module.exports = { + displayName: 'eslint-rules', + preset: '../../jest.preset.js', + globals: { + 'ts-jest': { + tsconfig: '/tsconfig.spec.json', + }, + }, + transform: { + '^.+\\.[tj]s$': 'ts-jest', + }, + moduleFileExtensions: ['ts', 'js', 'html'], + coverageDirectory: '../../coverage/tools/eslint-rules', + moduleNameMapper: { + '@eslint/eslintrc': '@eslint/eslintrc/dist/eslintrc-universal.cjs', + }, +}; diff --git a/tools/eslint-rules/project.json b/tools/eslint-rules/project.json new file mode 100644 index 0000000000000..49190b8e96d90 --- /dev/null +++ b/tools/eslint-rules/project.json @@ -0,0 +1,14 @@ +{ + "root": "tools/eslint-rules", + "sourceRoot": "tools/eslint-rules", + "targets": { + "test": { + "executor": "@nrwl/jest:jest", + "outputs": ["coverage/tools/eslint-rules"], + "options": { + "jestConfig": "tools/eslint-rules/jest.config.js", + "passWithNoTests": true + } + } + } +} diff --git a/tools/eslint-rules/rules/valid-schema-description.spec.ts b/tools/eslint-rules/rules/valid-schema-description.spec.ts new file mode 100644 index 0000000000000..3994c6804f8b1 --- /dev/null +++ b/tools/eslint-rules/rules/valid-schema-description.spec.ts @@ -0,0 +1,11 @@ +import { TSESLint } from '@typescript-eslint/experimental-utils'; +import { rule, RULE_NAME } from './valid-schema-description'; + +const ruleTester = new TSESLint.RuleTester({ + parser: require.resolve('@typescript-eslint/parser'), +}); + +ruleTester.run(RULE_NAME, rule, { + valid: [`const example = true;`], + invalid: [], +}); diff --git a/tools/eslint-rules/rules/valid-schema-description.ts b/tools/eslint-rules/rules/valid-schema-description.ts new file mode 100644 index 0000000000000..98f782ec8e8b6 --- /dev/null +++ b/tools/eslint-rules/rules/valid-schema-description.ts @@ -0,0 +1,81 @@ +import { ESLintUtils } from '@typescript-eslint/experimental-utils'; +import type { AST } from 'jsonc-eslint-parser'; + +// NOTE: The rule will be available in ESLint configs as "@nrwl/nx/workspace/valid-schema-description" +export const RULE_NAME = 'valid-schema-description'; + +export const rule = ESLintUtils.RuleCreator(() => __filename)({ + name: RULE_NAME, + meta: { + type: 'problem', + docs: { + description: `Ensures that nx schemas contain valid descriptions in order to provide consistent --help output for commands`, + recommended: 'error', + }, + fixable: 'code', + schema: [], + messages: { + requireSchemaDescriptionString: + 'A schema description string is required in order to render --help output correctly', + validSchemaDescription: + 'A schema description should end with a . character for consistency', + }, + }, + defaultOptions: [], + create(context) { + // jsonc-eslint-parser adds this property to parserServices where appropriate + if (!(context.parserServices as any).isJSON) { + return {}; + } + return { + ['JSONExpressionStatement > JSONObjectExpression']( + node: AST.JSONObjectExpression + ) { + const descriptionParentJSONPropertyNode = + resolveDescriptionParentPropertyNode(node); + if (!descriptionParentJSONPropertyNode) { + context.report({ + node: node as any, + messageId: 'requireSchemaDescriptionString', + }); + return; + } + + if (!descriptionParentJSONPropertyNode.value.value.endsWith('.')) { + context.report({ + node: descriptionParentJSONPropertyNode.value as any, + messageId: 'validSchemaDescription', + fix: (fixer) => { + const [start, end] = + descriptionParentJSONPropertyNode.value.range; + return fixer.insertTextAfterRange( + [start, end - 1], // -1 to account for the closing " of the string + '.' + ); + }, + }); + } + }, + }; + }, +}); + +interface JSONPropertyWithStringLiteralValue extends AST.JSONProperty { + key: AST.JSONStringLiteral; + value: AST.JSONStringLiteral; +} + +function resolveDescriptionParentPropertyNode( + node: AST.JSONObjectExpression +): JSONPropertyWithStringLiteralValue | null { + const descriptionParentJSONPropertyNode = node.properties.find((prop) => { + return ( + prop.key.type === 'JSONLiteral' && + prop.key.value === 'description' && + prop.value.type === 'JSONLiteral' && + typeof prop.value.value === 'string' && + prop.value.value.length > 0 + ); + }); + return descriptionParentJSONPropertyNode as JSONPropertyWithStringLiteralValue; +} diff --git a/tools/eslint-rules/tsconfig.json b/tools/eslint-rules/tsconfig.json new file mode 100644 index 0000000000000..51165861a766d --- /dev/null +++ b/tools/eslint-rules/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs" + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lint.json" + }, + { + "path": "./tsconfig.spec.json" + } + ] +} diff --git a/tools/eslint-rules/tsconfig.lint.json b/tools/eslint-rules/tsconfig.lint.json new file mode 100644 index 0000000000000..bb717c5e289e3 --- /dev/null +++ b/tools/eslint-rules/tsconfig.lint.json @@ -0,0 +1,9 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "types": ["node"] + }, + "exclude": ["**/*.spec.ts"], + "include": ["**/*.ts"] +} diff --git a/tools/eslint-rules/tsconfig.spec.json b/tools/eslint-rules/tsconfig.spec.json new file mode 100644 index 0000000000000..a18afb6046889 --- /dev/null +++ b/tools/eslint-rules/tsconfig.spec.json @@ -0,0 +1,9 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "module": "commonjs", + "types": ["jest", "node"] + }, + "include": ["**/*.test.ts", "**/*.spec.ts", "**/*.d.ts"] +} diff --git a/workspace.json b/workspace.json index 2f771032b9e70..2ae0a3a2e89b8 100644 --- a/workspace.json +++ b/workspace.json @@ -34,6 +34,7 @@ "e2e-workspace-create": "e2e/workspace-create", "e2e-workspace-integrations": "e2e/workspace-integrations", "eslint-plugin-nx": "packages/eslint-plugin-nx", + "eslint-rules": "tools/eslint-rules", "express": "packages/express", "jest": "packages/jest", "js": "packages/js", diff --git a/yarn.lock b/yarn.lock index 9077dfb044d58..230d37d3d046a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10706,7 +10706,7 @@ eslint@8.12.0: text-table "^0.2.0" v8-compile-cache "^2.0.3" -espree@^9.3.1: +espree@^9.0.0, espree@^9.3.1: version "9.3.1" resolved "https://registry.yarnpkg.com/espree/-/espree-9.3.1.tgz#8793b4bc27ea4c778c19908e0719e7b8f4115bcd" integrity sha512-bvdyLmJMfwkV3NCRl5ZhJf22zBFo1y8bYh3VYb+bfzqNB4Je68P2sSuXyuFquzWLebHpNd2/d5uv7yoP9ISnGQ== @@ -14608,6 +14608,16 @@ json5@^1.0.1: dependencies: minimist "^1.2.0" +jsonc-eslint-parser@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/jsonc-eslint-parser/-/jsonc-eslint-parser-2.1.0.tgz#4c126b530aa583d85308d0b3041ff81ce402bbb2" + integrity sha512-qCRJWlbP2v6HbmKW7R3lFbeiVWHo+oMJ0j+MizwvauqnCV/EvtAeEeuCgoc/ErtsuoKgYB8U4Ih8AxJbXoE6/g== + dependencies: + acorn "^8.5.0" + eslint-visitor-keys "^3.0.0" + espree "^9.0.0" + semver "^7.3.5" + jsonc-parser@3.0.0, jsonc-parser@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-3.0.0.tgz#abdd785701c7e7eaca8a9ec8cf070ca51a745a22"