feat(core): toggle additional prompts in CNW with a flag (#9546)
* feat(core): toggle additional prompts in CNW with a flag * feat(repo): handle cli params with yargs * feat(core): add docs generation * chore(repo): add colors * fix(core): migrate to yargs 17.x * fix(core): update markup generation
This commit is contained in:
parent
66e2c37582
commit
9e06020575
80
docs/generated/cli/create-nx-workspace.md
Normal file
80
docs/generated/cli/create-nx-workspace.md
Normal file
@ -0,0 +1,80 @@
|
||||
---
|
||||
title: 'create-nx-workspace - CLI command'
|
||||
description: 'Create a new Nx workspace'
|
||||
---
|
||||
|
||||
# create-nx-workspace
|
||||
|
||||
Create a new Nx workspace
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
create-nx-workspace [name] [options]
|
||||
```
|
||||
|
||||
Install `create-nx-workspace` globally to invoke the command directly, or use `npx create-nx-workspace`, `yarn create nx-workspace`, or `pnpx create-nx-workspace`.
|
||||
|
||||
## Options
|
||||
|
||||
### allPrompts
|
||||
|
||||
Default: `false`
|
||||
|
||||
Show all prompts
|
||||
|
||||
### appName
|
||||
|
||||
The name of the application when a preset with pregenerated app is selected
|
||||
|
||||
### cli
|
||||
|
||||
Choices: `["nx", "angular"]`
|
||||
|
||||
CLI to power the Nx workspace
|
||||
|
||||
### defaultBase
|
||||
|
||||
Default: `main`
|
||||
|
||||
Default base to use for new projects
|
||||
|
||||
### help
|
||||
|
||||
Show help
|
||||
|
||||
### interactive
|
||||
|
||||
Enable interactive mode with presets
|
||||
|
||||
### name
|
||||
|
||||
Workspace name (e.g. org name)
|
||||
|
||||
### nxCloud
|
||||
|
||||
Default: `true`
|
||||
|
||||
Use Nx Cloud
|
||||
|
||||
### packageManager
|
||||
|
||||
Default: `npm`
|
||||
|
||||
Choices: `["npm", "pnpm", "yarn"]`
|
||||
|
||||
Package manager to use
|
||||
|
||||
### preset
|
||||
|
||||
Choices: `["apps", "empty", "core", "npm", "ts", "web-components", "angular", "angular-nest", "react", "react-express", "react-native", "next", "nest", "express"]`
|
||||
|
||||
Customizes the initial content of your workspace. To build your own see https://nx.dev/nx-plugin/overview#preset
|
||||
|
||||
### style
|
||||
|
||||
Style option to be used when a preset with pregenerated app is selected
|
||||
|
||||
### version
|
||||
|
||||
Show version number
|
||||
@ -360,6 +360,11 @@
|
||||
"name": "CLI",
|
||||
"id": "cli",
|
||||
"itemList": [
|
||||
{
|
||||
"name": "create-nx-workspace",
|
||||
"id": "create-nx-workspace",
|
||||
"file": "generated/cli/create-nx-workspace"
|
||||
},
|
||||
{
|
||||
"name": "generate",
|
||||
"id": "generate",
|
||||
|
||||
@ -10,6 +10,7 @@ import {
|
||||
copySync,
|
||||
createFileSync,
|
||||
ensureDirSync,
|
||||
existsSync,
|
||||
moveSync,
|
||||
readdirSync,
|
||||
readFileSync,
|
||||
@ -28,7 +29,15 @@ import chalk = require('chalk');
|
||||
import isCI = require('is-ci');
|
||||
import treeKill = require('tree-kill');
|
||||
import { Workspaces } from '../../packages/nx/src/shared/workspace';
|
||||
import { detectPackageManager } from '../../packages/create-nx-workspace/bin/package-manager';
|
||||
import { PackageManager } from 'nx/src/utils/package-manager';
|
||||
|
||||
export function detectPackageManager(dir: string = ''): PackageManager {
|
||||
return existsSync(join(dir, 'yarn.lock'))
|
||||
? 'yarn'
|
||||
: existsSync(join(dir, 'pnpm-lock.yaml'))
|
||||
? 'pnpm'
|
||||
: 'npm';
|
||||
}
|
||||
|
||||
const kill = require('kill-port');
|
||||
export const isWindows = require('is-windows');
|
||||
|
||||
@ -1,24 +1,37 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { exec } from 'child_process';
|
||||
import { writeFileSync } from 'fs';
|
||||
import * as enquirer from 'enquirer';
|
||||
import * as path from 'path';
|
||||
import { dirSync } from 'tmp';
|
||||
import * as yargsParser from 'yargs-parser';
|
||||
import * as yargs from 'yargs';
|
||||
import { showNxWarning, unparse } from './shared';
|
||||
import { output } from './output';
|
||||
import * as ora from 'ora';
|
||||
|
||||
import {
|
||||
detectInvokedPackageManager,
|
||||
getPackageManagerCommand,
|
||||
getPackageManagerVersion,
|
||||
PackageManager,
|
||||
packageManagerList,
|
||||
} from './package-manager';
|
||||
import { validateNpmPackage } from './validate-npm-package';
|
||||
import { deduceDefaultBase } from './default-base';
|
||||
import { stringifyCollection } from './utils';
|
||||
import { yargsDecorator } from './decorator';
|
||||
|
||||
export enum Preset {
|
||||
type Arguments = {
|
||||
name: string;
|
||||
preset: string;
|
||||
appName: string;
|
||||
cli: string;
|
||||
style: string;
|
||||
nxCloud: boolean;
|
||||
allPrompts: boolean;
|
||||
packageManager: string;
|
||||
defaultBase: string;
|
||||
};
|
||||
|
||||
enum Preset {
|
||||
Apps = 'apps',
|
||||
Empty = 'empty', // same as apps, deprecated
|
||||
Core = 'core',
|
||||
@ -101,41 +114,93 @@ const cliVersion = 'NX_VERSION';
|
||||
const nxVersion = 'NX_VERSION';
|
||||
const prettierVersion = 'PRETTIER_VERSION';
|
||||
|
||||
const parsedArgs: any = yargsParser(process.argv.slice(2), {
|
||||
string: [
|
||||
'name',
|
||||
'cli',
|
||||
'preset',
|
||||
'appName',
|
||||
'style',
|
||||
'defaultBase',
|
||||
'packageManager',
|
||||
],
|
||||
alias: {
|
||||
packageManager: 'pm',
|
||||
},
|
||||
boolean: ['help', 'interactive', 'nxCloud'],
|
||||
default: {
|
||||
interactive: false,
|
||||
},
|
||||
configuration: {
|
||||
export const commandsObject: yargs.Argv<Arguments> = yargs
|
||||
.wrap(yargs.terminalWidth())
|
||||
.parserConfiguration({
|
||||
'strip-dashed': true,
|
||||
'strip-aliased': true,
|
||||
},
|
||||
}) as any;
|
||||
'dot-notation': false,
|
||||
})
|
||||
.command(
|
||||
// this is the default and only command
|
||||
'$0 [name] [options]',
|
||||
'Create a new Nx workspace',
|
||||
(yargs) =>
|
||||
yargs
|
||||
.option('name', {
|
||||
describe: `Workspace name (e.g. org name)`,
|
||||
type: 'string',
|
||||
})
|
||||
.option('preset', {
|
||||
describe: `Customizes the initial content of your workspace. To build your own see https://nx.dev/nx-plugin/overview#preset`,
|
||||
choices: Object.values(Preset),
|
||||
type: 'string',
|
||||
})
|
||||
.option('appName', {
|
||||
describe: `The name of the application when a preset with pregenerated app is selected`,
|
||||
type: 'string',
|
||||
})
|
||||
.option('interactive', {
|
||||
describe: `Enable interactive mode with presets`,
|
||||
type: 'boolean',
|
||||
})
|
||||
.option('cli', {
|
||||
describe: `CLI to power the Nx workspace`,
|
||||
choices: ['nx', 'angular'],
|
||||
type: 'string',
|
||||
})
|
||||
.option('style', {
|
||||
describe: `Style option to be used when a preset with pregenerated app is selected`,
|
||||
type: 'string',
|
||||
})
|
||||
.option('nxCloud', {
|
||||
describe: `Use Nx Cloud`,
|
||||
defaultDescription: 'true',
|
||||
type: 'boolean',
|
||||
})
|
||||
.option('allPrompts', {
|
||||
alias: 'a',
|
||||
describe: `Show all prompts`,
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
})
|
||||
.option('packageManager', {
|
||||
alias: 'pm',
|
||||
describe: `Package manager to use`,
|
||||
choices: [...packageManagerList].sort(),
|
||||
defaultDescription: 'npm',
|
||||
type: 'string',
|
||||
})
|
||||
.option('defaultBase', {
|
||||
defaultDescription: 'main',
|
||||
describe: `Default base to use for new projects`,
|
||||
type: 'string',
|
||||
}),
|
||||
async (argv: yargs.ArgumentsCamelCase<Arguments>) => {
|
||||
await main(argv).catch((error) => {
|
||||
const { version } = require('../package.json');
|
||||
output.error({
|
||||
title: `Something went wrong! v${version}`,
|
||||
});
|
||||
throw error;
|
||||
});
|
||||
},
|
||||
[getConfiguration]
|
||||
)
|
||||
.help('help')
|
||||
.updateLocale(yargsDecorator)
|
||||
.version(nxVersion) as yargs.Argv<Arguments>;
|
||||
|
||||
if (parsedArgs.help) {
|
||||
showHelp();
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
(async function main() {
|
||||
const packageManager: PackageManager =
|
||||
parsedArgs.packageManager || detectInvokedPackageManager();
|
||||
|
||||
const { name, cli, preset, appName, style, nxCloud } = await getConfiguration(
|
||||
parsedArgs
|
||||
);
|
||||
async function main(parsedArgs: yargs.Arguments<Arguments>) {
|
||||
const {
|
||||
name,
|
||||
cli,
|
||||
preset,
|
||||
appName,
|
||||
style,
|
||||
nxCloud,
|
||||
packageManager,
|
||||
defaultBase,
|
||||
} = parsedArgs;
|
||||
|
||||
output.log({
|
||||
title: `Nx is creating your v${cliVersion} workspace.`,
|
||||
@ -147,101 +212,73 @@ if (parsedArgs.help) {
|
||||
|
||||
const tmpDir = await createSandbox(packageManager);
|
||||
|
||||
await createApp(tmpDir, name, packageManager, {
|
||||
await createApp(tmpDir, name, packageManager as PackageManager, {
|
||||
...parsedArgs,
|
||||
cli,
|
||||
preset,
|
||||
appName,
|
||||
style,
|
||||
nxCloud,
|
||||
defaultBase,
|
||||
});
|
||||
|
||||
let nxCloudInstallRes;
|
||||
if (nxCloud) {
|
||||
nxCloudInstallRes = await setupNxCloud(name, packageManager);
|
||||
nxCloudInstallRes = await setupNxCloud(
|
||||
name,
|
||||
packageManager as PackageManager
|
||||
);
|
||||
}
|
||||
|
||||
showNxWarning(name);
|
||||
pointToTutorialAndCourse(preset);
|
||||
pointToTutorialAndCourse(preset as Preset);
|
||||
|
||||
if (nxCloud && nxCloudInstallRes.code === 0) {
|
||||
printNxCloudSuccessMessage(nxCloudInstallRes.stdout);
|
||||
}
|
||||
})().catch((error) => {
|
||||
const { version } = require('../package.json');
|
||||
output.error({
|
||||
title: `Something went wrong! v${version}`,
|
||||
});
|
||||
throw error;
|
||||
});
|
||||
|
||||
function showHelp() {
|
||||
const options = Object.values(Preset)
|
||||
.map((preset) => `"${preset}"`)
|
||||
.join(', ');
|
||||
|
||||
console.log(`
|
||||
Usage: create-nx-workspace <name> [options] [new workspace options]
|
||||
|
||||
Create a new Nx workspace
|
||||
|
||||
Options:
|
||||
|
||||
name Workspace name (e.g., org name)
|
||||
|
||||
preset Customizes the initial content of your workspace (options: ${options}). To build your own see https://nx.dev/nx-plugin/overview#preset
|
||||
|
||||
appName The name of the application created by some presets
|
||||
|
||||
cli CLI to power the Nx workspace (options: "nx", "angular")
|
||||
|
||||
style Default style option to be used when a non-empty preset is selected
|
||||
options: ("css", "scss", "less") plus ("styl") for all non-Angular and ("styled-components", "@emotion/styled", "styled-jsx") for React, Next.js
|
||||
|
||||
interactive Enable interactive mode when using presets (boolean)
|
||||
|
||||
packageManager Package manager to use (alias: "pm")
|
||||
options: ("npm", "yarn", "pnpm")
|
||||
|
||||
defaultBase Name of the main branch (default: "main")
|
||||
|
||||
nx-cloud Use Nx Cloud (boolean)
|
||||
`);
|
||||
}
|
||||
|
||||
async function getConfiguration(parsedArgs) {
|
||||
async function getConfiguration(
|
||||
argv: yargs.Arguments<Arguments>
|
||||
): Promise<void> {
|
||||
try {
|
||||
let style, appName;
|
||||
|
||||
const name = await determineWorkspaceName(parsedArgs);
|
||||
let preset = await determineThirdPartyPackage(parsedArgs);
|
||||
const name = await determineWorkspaceName(argv);
|
||||
let preset = await determineThirdPartyPackage(argv);
|
||||
|
||||
if (!preset) {
|
||||
preset = await determinePreset(parsedArgs);
|
||||
appName = await determineAppName(preset, parsedArgs);
|
||||
style = await determineStyle(preset, parsedArgs);
|
||||
preset = await determinePreset(argv);
|
||||
appName = await determineAppName(preset, argv);
|
||||
style = await determineStyle(preset, argv);
|
||||
}
|
||||
|
||||
const cli = await determineCli(preset, parsedArgs);
|
||||
const nxCloud = await askAboutNxCloud(parsedArgs);
|
||||
const cli = await determineCli(preset, argv);
|
||||
const packageManager = await determinePackageManager(argv);
|
||||
const defaultBase = await determineDefaultBase(argv);
|
||||
const nxCloud = await determineNxCloud(argv);
|
||||
|
||||
return {
|
||||
Object.assign(argv, {
|
||||
name,
|
||||
preset,
|
||||
appName,
|
||||
style,
|
||||
cli,
|
||||
nxCloud,
|
||||
};
|
||||
packageManager,
|
||||
defaultBase,
|
||||
});
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
function determineWorkspaceName(parsedArgs: any): Promise<string> {
|
||||
function determineWorkspaceName(
|
||||
parsedArgs: yargs.Arguments<Arguments>
|
||||
): Promise<string> {
|
||||
const workspaceName: string = parsedArgs._[0]
|
||||
? parsedArgs._[0]
|
||||
? parsedArgs._[0].toString()
|
||||
: parsedArgs.name;
|
||||
|
||||
if (workspaceName) {
|
||||
@ -268,8 +305,85 @@ function determineWorkspaceName(parsedArgs: any): Promise<string> {
|
||||
});
|
||||
}
|
||||
|
||||
async function determineThirdPartyPackage({ preset }) {
|
||||
if (preset && Object.values(Preset).indexOf(preset) === -1) {
|
||||
async function determinePackageManager(
|
||||
parsedArgs: yargs.Arguments<Arguments>
|
||||
): Promise<PackageManager> {
|
||||
const packageManager: string = parsedArgs.packageManager;
|
||||
|
||||
if (packageManager) {
|
||||
if (packageManagerList.includes(packageManager as PackageManager)) {
|
||||
return Promise.resolve(packageManager as PackageManager);
|
||||
}
|
||||
output.error({
|
||||
title: 'Invalid package manager',
|
||||
bodyLines: [
|
||||
`Package manager must be one of ${stringifyCollection([
|
||||
...packageManagerList,
|
||||
])}`,
|
||||
],
|
||||
});
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (parsedArgs.allPrompts) {
|
||||
return enquirer
|
||||
.prompt([
|
||||
{
|
||||
name: 'PackageManager',
|
||||
message: `Which package manager to use `,
|
||||
initial: 'npm' as any,
|
||||
type: 'select',
|
||||
choices: [
|
||||
{ name: 'npm', message: 'NPM' },
|
||||
{ name: 'yarn', message: 'Yarn' },
|
||||
{ name: 'pnpm', message: 'PNPM' },
|
||||
],
|
||||
},
|
||||
])
|
||||
.then((a: { PackageManager }) => a.PackageManager);
|
||||
}
|
||||
|
||||
return Promise.resolve(detectInvokedPackageManager());
|
||||
}
|
||||
|
||||
async function determineDefaultBase(
|
||||
parsedArgs: yargs.Arguments<Arguments>
|
||||
): Promise<string> {
|
||||
if (parsedArgs.defaultBase) {
|
||||
return Promise.resolve(parsedArgs.defaultBase);
|
||||
}
|
||||
if (parsedArgs.allPrompts) {
|
||||
return enquirer
|
||||
.prompt([
|
||||
{
|
||||
name: 'DefaultBase',
|
||||
message: `Main branch name `,
|
||||
initial: `main`,
|
||||
type: 'input',
|
||||
},
|
||||
])
|
||||
.then((a: { DefaultBase: string }) => {
|
||||
if (!a.DefaultBase) {
|
||||
output.error({
|
||||
title: 'Invalid branch name',
|
||||
bodyLines: [`Branch name cannot be empty`],
|
||||
});
|
||||
process.exit(1);
|
||||
}
|
||||
return a.DefaultBase;
|
||||
});
|
||||
}
|
||||
return Promise.resolve(deduceDefaultBase());
|
||||
}
|
||||
|
||||
function isKnownPreset(preset: string): preset is Preset {
|
||||
return Object.values(Preset).includes(preset as Preset);
|
||||
}
|
||||
|
||||
async function determineThirdPartyPackage({
|
||||
preset,
|
||||
}: yargs.Arguments<Arguments>) {
|
||||
if (preset && !isKnownPreset(preset)) {
|
||||
const packageName = preset.match(/.+@/)
|
||||
? preset[0] + preset.substring(1).split('@')[0]
|
||||
: preset;
|
||||
@ -293,7 +407,7 @@ async function determineThirdPartyPackage({ preset }) {
|
||||
}
|
||||
}
|
||||
|
||||
function determinePreset(parsedArgs: any): Promise<Preset> {
|
||||
async function determinePreset(parsedArgs: any): Promise<Preset> {
|
||||
if (parsedArgs.preset) {
|
||||
if (Object.values(Preset).indexOf(parsedArgs.preset) === -1) {
|
||||
output.error({
|
||||
@ -323,7 +437,10 @@ function determinePreset(parsedArgs: any): Promise<Preset> {
|
||||
.then((a: { Preset: Preset }) => a.Preset);
|
||||
}
|
||||
|
||||
function determineAppName(preset: Preset, parsedArgs: any): Promise<string> {
|
||||
async function determineAppName(
|
||||
preset: Preset,
|
||||
parsedArgs: yargs.Arguments<Arguments>
|
||||
): Promise<string> {
|
||||
if (
|
||||
preset === Preset.Apps ||
|
||||
preset === Preset.Core ||
|
||||
@ -358,12 +475,16 @@ function determineAppName(preset: Preset, parsedArgs: any): Promise<string> {
|
||||
});
|
||||
}
|
||||
|
||||
function determineCli(
|
||||
function isValidCli(cli: string): cli is 'angular' | 'nx' {
|
||||
return ['nx', 'angular'].indexOf(cli) !== -1;
|
||||
}
|
||||
|
||||
async function determineCli(
|
||||
preset: Preset,
|
||||
parsedArgs: any
|
||||
parsedArgs: yargs.Arguments<Arguments>
|
||||
): Promise<'nx' | 'angular'> {
|
||||
if (parsedArgs.cli) {
|
||||
if (['nx', 'angular'].indexOf(parsedArgs.cli) === -1) {
|
||||
if (!isValidCli(parsedArgs.cli)) {
|
||||
output.error({
|
||||
title: 'Invalid cli',
|
||||
bodyLines: [`It must be one of the following:`, '', 'nx', 'angular'],
|
||||
@ -384,7 +505,10 @@ function determineCli(
|
||||
}
|
||||
}
|
||||
|
||||
function determineStyle(preset: Preset, parsedArgs: any) {
|
||||
async function determineStyle(
|
||||
preset: Preset,
|
||||
parsedArgs: yargs.Arguments<Arguments>
|
||||
) {
|
||||
if (
|
||||
preset === Preset.Apps ||
|
||||
preset === Preset.Core ||
|
||||
@ -625,7 +749,7 @@ function execAndWait(command: string, cwd: string) {
|
||||
});
|
||||
}
|
||||
|
||||
async function askAboutNxCloud(parsedArgs: any) {
|
||||
async function determineNxCloud(parsedArgs: yargs.Arguments<Arguments>) {
|
||||
if (parsedArgs.nxCloud === undefined) {
|
||||
return enquirer
|
||||
.prompt([
|
||||
|
||||
6
packages/create-nx-workspace/bin/decorator.ts
Normal file
6
packages/create-nx-workspace/bin/decorator.ts
Normal file
@ -0,0 +1,6 @@
|
||||
import * as chalk from 'chalk';
|
||||
|
||||
export const yargsDecorator = {
|
||||
'Options:': `${chalk.bold('Options')}:`,
|
||||
'Positionals:': `${chalk.bold('Positionals')}:`,
|
||||
};
|
||||
17
packages/create-nx-workspace/bin/default-base.ts
Normal file
17
packages/create-nx-workspace/bin/default-base.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import { execSync } from 'child_process';
|
||||
|
||||
/*
|
||||
* Because we don't want to depend on @nrwl/workspace (to speed up the workspace creation)
|
||||
* we duplicate the helper functions from @nrwl/workspace in this file.
|
||||
*/
|
||||
export function deduceDefaultBase(): string {
|
||||
const nxDefaultBase = 'main';
|
||||
try {
|
||||
return (
|
||||
execSync('git config --get init.defaultBranch').toString().trim() ||
|
||||
nxDefaultBase
|
||||
);
|
||||
} catch {
|
||||
return nxDefaultBase;
|
||||
}
|
||||
}
|
||||
5
packages/create-nx-workspace/bin/index.ts
Normal file
5
packages/create-nx-workspace/bin/index.ts
Normal file
@ -0,0 +1,5 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { commandsObject } from './create-nx-workspace';
|
||||
|
||||
commandsObject.argv;
|
||||
@ -7,7 +7,7 @@ import { join } from 'path';
|
||||
* we duplicate the helper functions from @nrwl/workspace in this file.
|
||||
*/
|
||||
|
||||
const packageManagerList = ['pnpm', 'yarn', 'npm'] as const;
|
||||
export const packageManagerList = ['pnpm', 'yarn', 'npm'] as const;
|
||||
|
||||
export type PackageManager = typeof packageManagerList[number];
|
||||
|
||||
@ -109,7 +109,7 @@ export function detectInvokedPackageManager(): PackageManager {
|
||||
if (!invoker) {
|
||||
return detectedPackageManager;
|
||||
}
|
||||
|
||||
console.log(invoker.path);
|
||||
for (const pkgManager of packageManagerList) {
|
||||
if (invoker.path.includes(pkgManager)) {
|
||||
detectedPackageManager = pkgManager;
|
||||
|
||||
3
packages/create-nx-workspace/bin/utils.ts
Normal file
3
packages/create-nx-workspace/bin/utils.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export function stringifyCollection(items: string[]): string {
|
||||
return items.map((item) => `"${item}"`).join(', ');
|
||||
}
|
||||
@ -19,7 +19,7 @@
|
||||
"CLI"
|
||||
],
|
||||
"bin": {
|
||||
"create-nx-workspace": "./bin/create-nx-workspace.js"
|
||||
"create-nx-workspace": "./bin/index.js"
|
||||
},
|
||||
"author": "Victor Savkin",
|
||||
"license": "MIT",
|
||||
@ -29,7 +29,7 @@
|
||||
"homepage": "https://nx.dev",
|
||||
"dependencies": {
|
||||
"tmp": "~0.2.1",
|
||||
"yargs-parser": "21.0.1",
|
||||
"yargs": "^17.4.0",
|
||||
"enquirer": "~2.3.6",
|
||||
"flat": "^5.0.2",
|
||||
"chalk": "4.1.0",
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
import { execSync } from 'child_process';
|
||||
|
||||
// TODO (v13): Update to main
|
||||
export function deduceDefaultBase(): string {
|
||||
const nxDefaultBase = 'main';
|
||||
try {
|
||||
|
||||
@ -1,6 +1,9 @@
|
||||
import * as chalk from 'chalk';
|
||||
import { execSync } from 'child_process';
|
||||
import { removeSync } from 'fs-extra';
|
||||
import { join } from 'path';
|
||||
import { generateCLIDocumentation } from './generate-cli-data';
|
||||
import { generateCNWocumentation } from './generate-cnw-documentation';
|
||||
import { generateDevkitDocumentation } from './generate-devkit-documentation';
|
||||
import { generatePackageSchemas } from './package-schemas/generatePackageSchemas';
|
||||
|
||||
@ -9,7 +12,16 @@ async function generate() {
|
||||
console.log(`${chalk.blue('i')} Generating Documentation`);
|
||||
generatePackageSchemas();
|
||||
generateDevkitDocumentation();
|
||||
await generateCLIDocumentation();
|
||||
|
||||
const commandsOutputDirectory = join(
|
||||
__dirname,
|
||||
'../../docs/',
|
||||
'generated',
|
||||
'cli'
|
||||
);
|
||||
removeSync(commandsOutputDirectory);
|
||||
await generateCNWocumentation(commandsOutputDirectory);
|
||||
await generateCLIDocumentation(commandsOutputDirectory);
|
||||
|
||||
console.log(`\n${chalk.green('✓')} Generated Documentation\n`);
|
||||
} catch (e) {
|
||||
|
||||
@ -1,16 +1,19 @@
|
||||
import * as chalk from 'chalk';
|
||||
import { readFileSync } from 'fs';
|
||||
import { readJsonSync, removeSync } from 'fs-extra';
|
||||
import { readJsonSync } from 'fs-extra';
|
||||
import { join } from 'path';
|
||||
import { dedent } from 'tslint/lib/utils';
|
||||
import {
|
||||
formatDeprecated,
|
||||
generateMarkdownFile,
|
||||
generateOptionsMarkdown,
|
||||
getCommands,
|
||||
parseCommand,
|
||||
ParsedCommand,
|
||||
sortAlphabeticallyFunction,
|
||||
} from './utils';
|
||||
import { register as registerTsConfigPaths } from 'tsconfig-paths';
|
||||
|
||||
import { examples } from '../../packages/nx/src/command-line/examples';
|
||||
import { dedent } from 'tslint/lib/utils';
|
||||
|
||||
const importFresh = require('import-fresh');
|
||||
|
||||
@ -24,21 +27,9 @@ const sharedCommands = [
|
||||
'test',
|
||||
];
|
||||
|
||||
interface ParsedCommandOption {
|
||||
name: string;
|
||||
description: string;
|
||||
default: string;
|
||||
deprecated: boolean | string;
|
||||
}
|
||||
|
||||
interface ParsedCommand {
|
||||
name: string;
|
||||
commandString: string;
|
||||
description: string;
|
||||
options?: Array<ParsedCommandOption>;
|
||||
}
|
||||
|
||||
export async function generateCLIDocumentation() {
|
||||
export async function generateCLIDocumentation(
|
||||
commandsOutputDirectory: string
|
||||
) {
|
||||
/**
|
||||
* For certain commands, they will output dynamic data at runtime in a real workspace,
|
||||
* so we leverage an envrionment variable to inform the logic of the context that we
|
||||
@ -57,65 +48,6 @@ export async function generateCLIDocumentation() {
|
||||
'../../packages/nx/src/command-line/nx-commands'
|
||||
);
|
||||
|
||||
const commandsOutputDirectory = join(
|
||||
__dirname,
|
||||
'../../docs/',
|
||||
'generated',
|
||||
'cli'
|
||||
);
|
||||
removeSync(commandsOutputDirectory);
|
||||
|
||||
function getCommands(command) {
|
||||
return command
|
||||
.getInternalMethods()
|
||||
.getCommandInstance()
|
||||
.getCommandHandlers();
|
||||
}
|
||||
async function parseCommandInstance(
|
||||
name: string,
|
||||
command: any
|
||||
): Promise<ParsedCommand> {
|
||||
// It is not a function return a strip down version of the command
|
||||
if (
|
||||
!(
|
||||
command.builder &&
|
||||
command.builder.constructor &&
|
||||
command.builder.call &&
|
||||
command.builder.apply
|
||||
)
|
||||
) {
|
||||
return {
|
||||
name,
|
||||
commandString: command.original,
|
||||
description: command.description,
|
||||
};
|
||||
}
|
||||
// Show all the options we can get from yargs
|
||||
const builder = await command.builder(
|
||||
importFresh('yargs')().getInternalMethods().reset()
|
||||
);
|
||||
const builderDescriptions = builder
|
||||
.getInternalMethods()
|
||||
.getUsageInstance()
|
||||
.getDescriptions();
|
||||
const builderDefaultOptions = builder.getOptions().default;
|
||||
const builderDeprecatedOptions = builder.getDeprecatedOptions();
|
||||
return {
|
||||
name,
|
||||
description: command.description,
|
||||
commandString: command.original,
|
||||
options:
|
||||
Object.keys(builderDescriptions).map((key) => ({
|
||||
name: key,
|
||||
description: builderDescriptions[key]
|
||||
? builderDescriptions[key].replace('__yargsString__:', '')
|
||||
: '',
|
||||
default: builderDefaultOptions[key],
|
||||
deprecated: builderDeprecatedOptions[key],
|
||||
})) || null,
|
||||
};
|
||||
}
|
||||
|
||||
function generateMarkdown(command: ParsedCommand) {
|
||||
let template = dedent`
|
||||
---
|
||||
@ -146,25 +78,7 @@ nx ${command.commandString}
|
||||
});
|
||||
}
|
||||
|
||||
if (Array.isArray(command.options) && !!command.options.length) {
|
||||
template += '\n## Options';
|
||||
|
||||
command.options
|
||||
.sort((a, b) => sortAlphabeticallyFunction(a.name, b.name))
|
||||
.forEach((option) => {
|
||||
template += dedent`
|
||||
### ${option.deprecated ? `~~${option.name}~~` : option.name}
|
||||
${
|
||||
option.default === undefined || option.default === ''
|
||||
? ''
|
||||
: `Default: \`${option.default}\`\n`
|
||||
}
|
||||
`;
|
||||
template += dedent`
|
||||
${formatDeprecated(option.description, option.deprecated)}
|
||||
`;
|
||||
});
|
||||
}
|
||||
template += generateOptionsMarkdown(command);
|
||||
|
||||
return {
|
||||
name: command.name
|
||||
@ -181,7 +95,7 @@ nx ${command.commandString}
|
||||
Object.keys(nxCommands)
|
||||
.filter((name) => !sharedCommands.includes(name))
|
||||
.filter((name) => nxCommands[name].description)
|
||||
.map((name) => parseCommandInstance(name, nxCommands[name]))
|
||||
.map((name) => parseCommand(name, nxCommands[name]))
|
||||
.map(async (command) => generateMarkdown(await command))
|
||||
.map(async (templateObject) =>
|
||||
generateMarkdownFile(commandsOutputDirectory, await templateObject)
|
||||
|
||||
65
scripts/documentation/generate-cnw-documentation.ts
Normal file
65
scripts/documentation/generate-cnw-documentation.ts
Normal file
@ -0,0 +1,65 @@
|
||||
import * as chalk from 'chalk';
|
||||
import { dedent } from 'tslint/lib/utils';
|
||||
import {
|
||||
generateMarkdownFile,
|
||||
getCommands,
|
||||
parseCommand,
|
||||
ParsedCommand,
|
||||
sortAlphabeticallyFunction,
|
||||
formatDeprecated,
|
||||
generateOptionsMarkdown,
|
||||
} from './utils';
|
||||
const importFresh = require('import-fresh');
|
||||
|
||||
export async function generateCNWocumentation(commandsOutputDirectory: string) {
|
||||
process.env.NX_GENERATE_DOCS_PROCESS = 'true';
|
||||
|
||||
console.log(
|
||||
`\n${chalk.blue(
|
||||
'i'
|
||||
)} Generating Documentation for Create Nx Workspace Command`
|
||||
);
|
||||
|
||||
const { commandsObject } = importFresh(
|
||||
'../../packages/create-nx-workspace/bin/create-nx-workspace'
|
||||
);
|
||||
|
||||
const command = getCommands(commandsObject)['$0'];
|
||||
const parsedCommand = await parseCommand('create-nx-workspace', command);
|
||||
const markdown = generateMarkdown(parsedCommand);
|
||||
generateMarkdownFile(commandsOutputDirectory, markdown);
|
||||
|
||||
delete process.env.NX_GENERATE_DOCS_PROCESS;
|
||||
|
||||
console.log(
|
||||
`${chalk.green(
|
||||
'✓'
|
||||
)} Generated Documentation for Create Nx Workspace Command`
|
||||
);
|
||||
}
|
||||
|
||||
function generateMarkdown(command: ParsedCommand) {
|
||||
let template = dedent`
|
||||
---
|
||||
title: "${command.name} - CLI command"
|
||||
description: "${command.description}"
|
||||
---
|
||||
# ${command.name}
|
||||
|
||||
${command.description}
|
||||
|
||||
## Usage
|
||||
|
||||
\`\`\`bash
|
||||
${command.commandString}
|
||||
\`\`\`
|
||||
|
||||
Install \`create-nx-workspace\` globally to invoke the command directly, or use \`npx create-nx-workspace\`, \`yarn create nx-workspace\`, or \`pnpx create-nx-workspace\`.\n`;
|
||||
|
||||
template += generateOptionsMarkdown(command);
|
||||
|
||||
return {
|
||||
name: command.name,
|
||||
template,
|
||||
};
|
||||
}
|
||||
@ -1,7 +1,9 @@
|
||||
import { outputFileSync, readJsonSync } from 'fs-extra';
|
||||
import { join } from 'path';
|
||||
import { format, resolveConfig } from 'prettier';
|
||||
import { dedent } from 'tslint/lib/utils';
|
||||
const stripAnsi = require('strip-ansi');
|
||||
const importFresh = require('import-fresh');
|
||||
|
||||
export function sortAlphabeticallyFunction(a: string, b: string): number {
|
||||
const nameA = a.toUpperCase(); // ignore upper and lowercase
|
||||
@ -99,7 +101,106 @@ export function formatDeprecated(
|
||||
? `**Deprecated:** ${description}`
|
||||
: `
|
||||
**Deprecated:** ${deprecated}
|
||||
|
||||
|
||||
${description}
|
||||
`;
|
||||
}
|
||||
|
||||
export function getCommands(command) {
|
||||
return command.getInternalMethods().getCommandInstance().getCommandHandlers();
|
||||
}
|
||||
|
||||
export interface ParsedCommandOption {
|
||||
name: string;
|
||||
description: string;
|
||||
default: string;
|
||||
deprecated: boolean | string;
|
||||
}
|
||||
|
||||
export interface ParsedCommand {
|
||||
name: string;
|
||||
commandString: string;
|
||||
description: string;
|
||||
options?: Array<ParsedCommandOption>;
|
||||
}
|
||||
|
||||
export async function parseCommand(
|
||||
name: string,
|
||||
command: any
|
||||
): Promise<ParsedCommand> {
|
||||
// It is not a function return a strip down version of the command
|
||||
if (
|
||||
!(
|
||||
command.builder &&
|
||||
command.builder.constructor &&
|
||||
command.builder.call &&
|
||||
command.builder.apply
|
||||
)
|
||||
) {
|
||||
return {
|
||||
name,
|
||||
commandString: command.original,
|
||||
description: command.description,
|
||||
};
|
||||
}
|
||||
|
||||
// Show all the options we can get from yargs
|
||||
const builder = await command.builder(
|
||||
importFresh('yargs')().getInternalMethods().reset()
|
||||
);
|
||||
const builderDescriptions = builder
|
||||
.getInternalMethods()
|
||||
.getUsageInstance()
|
||||
.getDescriptions();
|
||||
const builderDefaultOptions = builder.getOptions().default;
|
||||
const builderAutomatedOptions = builder.getOptions().defaultDescription;
|
||||
const builderDeprecatedOptions = builder.getDeprecatedOptions();
|
||||
const builderOptionsChoices = builder.getOptions().choices;
|
||||
|
||||
return {
|
||||
name,
|
||||
description: command.description,
|
||||
commandString: command.original.replace('$0', name),
|
||||
options:
|
||||
Object.keys(builderDescriptions).map((key) => ({
|
||||
name: key,
|
||||
description: builderDescriptions[key]
|
||||
? builderDescriptions[key].replace('__yargsString__:', '')
|
||||
: '',
|
||||
default: builderDefaultOptions[key] ?? builderAutomatedOptions[key],
|
||||
choices: builderOptionsChoices[key],
|
||||
deprecated: builderDeprecatedOptions[key],
|
||||
})) || null,
|
||||
};
|
||||
}
|
||||
|
||||
export function generateOptionsMarkdown(command): string {
|
||||
let response = '';
|
||||
if (Array.isArray(command.options) && !!command.options.length) {
|
||||
response += '\n## Options';
|
||||
|
||||
command.options
|
||||
.sort((a, b) => sortAlphabeticallyFunction(a.name, b.name))
|
||||
.forEach((option) => {
|
||||
response += dedent`
|
||||
### ${option.deprecated ? `~~${option.name}~~` : option.name}
|
||||
${
|
||||
option.default === undefined || option.default === ''
|
||||
? ''
|
||||
: `Default: \`${option.default}\`\n`
|
||||
}`;
|
||||
response += dedent`
|
||||
${
|
||||
option.choices === undefined
|
||||
? ''
|
||||
: `Choices: \`[${option.choices
|
||||
.map((c) => `"${c}"`)
|
||||
.join(', ')}]\`\n`
|
||||
}`;
|
||||
response += dedent`
|
||||
${formatDeprecated(option.description, option.deprecated)}
|
||||
`;
|
||||
});
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user