feat(core): speed up and clean up create-nx-workspace

This commit is contained in:
vsavkin 2021-03-11 14:55:38 -05:00 committed by Victor Savkin
parent 7457b16811
commit cad02e4399
7 changed files with 390 additions and 38 deletions

View File

@ -64,7 +64,7 @@ yarn local-registry disable
To publish packages to a local registry, do the following: To publish packages to a local registry, do the following:
- Run `yarn local-registry start` in Terminal 1 (keep it running) - Run `yarn local-registry start` in Terminal 1 (keep it running)
- Run `npm adduser` in Terminal 2 (real credentials are not required, you just need to be logged in) - Run `npm adduser --registry http://localhost:4873` in Terminal 2 (real credentials are not required, you just need to be logged in)
- Run `yarn local-registry enable` in Terminal 2 - Run `yarn local-registry enable` in Terminal 2
- Run `yarn nx-release 999.9.9 --local` in Terminal 2 - Run `yarn nx-release 999.9.9 --local` in Terminal 2
- Run `cd /tmp` in Terminal 2 - Run `cd /tmp` in Terminal 2

View File

@ -1,19 +1,31 @@
#!/usr/bin/env node #!/usr/bin/env node
import { output } from '@nrwl/workspace/src/utilities/output';
import { unparse } from '@nrwl/workspace/src/tasks-runner/utils';
import { Schema, Preset } from '@nrwl/workspace/src/generators/new/new';
import {
getPackageManagerCommand,
getPackageManagerVersion,
} from '@nrwl/tao/src/shared/package-manager';
import { execSync } from 'child_process'; import { execSync } from 'child_process';
import { writeFileSync } from 'fs'; import { writeFileSync } from 'fs';
import * as inquirer from 'inquirer'; import * as inquirer from 'inquirer';
import * as path from 'path'; import * as path from 'path';
import { dirSync } from 'tmp'; import { dirSync } from 'tmp';
import * as yargsParser from 'yargs-parser'; import * as yargsParser from 'yargs-parser';
import { showNxWarning } from './shared'; import { showNxWarning, unparse } from './shared';
import { output } from './output';
import {
getPackageManagerCommand,
getPackageManagerVersion,
} from './package-manager';
export enum Preset {
Empty = 'empty',
OSS = 'oss',
WebComponents = 'web-components',
Angular = 'angular',
AngularWithNest = 'angular-nest',
React = 'react',
ReactWithExpress = 'react-express',
NextJs = 'next',
Gatsby = 'gatsby',
Nest = 'nest',
Express = 'express',
}
const presetOptions: { value: Preset; name: string }[] = [ const presetOptions: { value: Preset; name: string }[] = [
{ {
@ -73,12 +85,7 @@ const cliVersion = 'NX_VERSION';
const nxVersion = 'NX_VERSION'; const nxVersion = 'NX_VERSION';
const prettierVersion = 'PRETTIER_VERSION'; const prettierVersion = 'PRETTIER_VERSION';
interface WorkspaceArgs extends Schema { const parsedArgs: any = yargsParser(process.argv.slice(2), {
_?: string[];
help?: boolean;
}
const parsedArgs: WorkspaceArgs = yargsParser(process.argv.slice(2), {
string: [ string: [
'cli', 'cli',
'preset', 'preset',
@ -168,7 +175,7 @@ function showHelp() {
`); `);
} }
function determineWorkspaceName(parsedArgs: WorkspaceArgs): Promise<string> { function determineWorkspaceName(parsedArgs: any): Promise<string> {
const workspaceName: string = parsedArgs._[0]; const workspaceName: string = parsedArgs._[0];
if (workspaceName) { if (workspaceName) {
@ -195,7 +202,7 @@ function determineWorkspaceName(parsedArgs: WorkspaceArgs): Promise<string> {
}); });
} }
function determinePreset(parsedArgs: WorkspaceArgs): Promise<Preset> { function determinePreset(parsedArgs: any): Promise<Preset> {
if (parsedArgs.preset) { if (parsedArgs.preset) {
if (Object.values(Preset).indexOf(parsedArgs.preset) === -1) { if (Object.values(Preset).indexOf(parsedArgs.preset) === -1) {
output.error({ output.error({
@ -225,10 +232,7 @@ function determinePreset(parsedArgs: WorkspaceArgs): Promise<Preset> {
.then((a: { Preset: Preset }) => a.Preset); .then((a: { Preset: Preset }) => a.Preset);
} }
function determineAppName( function determineAppName(preset: Preset, parsedArgs: any): Promise<string> {
preset: Preset,
parsedArgs: WorkspaceArgs
): Promise<string> {
if (preset === Preset.Empty || preset === Preset.OSS) { if (preset === Preset.Empty || preset === Preset.OSS) {
return Promise.resolve(''); return Promise.resolve('');
} }
@ -259,7 +263,7 @@ function determineAppName(
function determineCli( function determineCli(
preset: Preset, preset: Preset,
parsedArgs: WorkspaceArgs parsedArgs: any
): Promise<'nx' | 'angular'> { ): Promise<'nx' | 'angular'> {
if (parsedArgs.cli) { if (parsedArgs.cli) {
if (['nx', 'angular'].indexOf(parsedArgs.cli) === -1) { if (['nx', 'angular'].indexOf(parsedArgs.cli) === -1) {
@ -283,7 +287,7 @@ function determineCli(
} }
} }
function determineStyle(preset: Preset, parsedArgs: WorkspaceArgs) { function determineStyle(preset: Preset, parsedArgs: any) {
if ( if (
preset === Preset.Empty || preset === Preset.Empty ||
preset === Preset.OSS || preset === Preset.OSS ||
@ -371,7 +375,7 @@ function determineStyle(preset: Preset, parsedArgs: WorkspaceArgs) {
return Promise.resolve(parsedArgs.style); return Promise.resolve(parsedArgs.style);
} }
function determineLinter(preset: Preset, parsedArgs: WorkspaceArgs) { function determineLinter(preset: Preset, parsedArgs: any) {
if (!parsedArgs.linter) { if (!parsedArgs.linter) {
if (preset === Preset.Angular || preset === Preset.AngularWithNest) { if (preset === Preset.Angular || preset === Preset.AngularWithNest) {
return inquirer return inquirer
@ -411,7 +415,13 @@ function determineLinter(preset: Preset, parsedArgs: WorkspaceArgs) {
} }
function createSandbox(packageManager: string) { function createSandbox(packageManager: string) {
console.log(`Creating a sandbox with Nx...`); output.log({
title: 'Nx is creating your workspace.',
bodyLines: [
'To make sure the command works reliably in all environments, and that the preset is applied correctly,',
`Nx will run "${packageManager} install" several times. Please wait.`,
],
});
const tmpDir = dirSync().name; const tmpDir = dirSync().name;
writeFileSync( writeFileSync(
path.join(tmpDir, 'package.json'), path.join(tmpDir, 'package.json'),
@ -434,13 +444,12 @@ function createSandbox(packageManager: string) {
return tmpDir; return tmpDir;
} }
function createApp(tmpDir: string, name: string, parsedArgs: WorkspaceArgs) { function createApp(tmpDir: string, name: string, parsedArgs: any) {
const { _, cli, ...restArgs } = parsedArgs; const { _, cli, ...restArgs } = parsedArgs;
const args = unparse(restArgs).join(' '); const args = unparse(restArgs).join(' ');
const pmc = getPackageManagerCommand(packageManager); const pmc = getPackageManagerCommand(packageManager);
const command = `new ${name} ${args} --collection=@nrwl/workspace`; const command = `new ${name} ${args} --collection=@nrwl/workspace`;
console.log(command);
let nxWorkspaceRoot = `"${process.cwd().replace(/\\/g, '/')}"`; let nxWorkspaceRoot = `"${process.cwd().replace(/\\/g, '/')}"`;
@ -456,14 +465,27 @@ function createApp(tmpDir: string, name: string, parsedArgs: WorkspaceArgs) {
nxWorkspaceRoot = `\\"${nxWorkspaceRoot.slice(1, -1)}\\"`; nxWorkspaceRoot = `\\"${nxWorkspaceRoot.slice(1, -1)}\\"`;
} }
} }
const fullCommand = `${pmc.exec} tao ${command}/collection.json --cli=${cli} --nxWorkspaceRoot=${nxWorkspaceRoot}`;
execSync( try {
`${pmc.exec} tao ${command}/collection.json --cli=${cli} --nxWorkspaceRoot=${nxWorkspaceRoot}`, execSync(fullCommand, {
{ stdio: ['ignore', 'ignore', 'ignore'],
cwd: tmpDir,
});
} catch (e) {
output.error({
title:
'Something went wrong. Rerunning the command with verbose logging.',
});
execSync(fullCommand, {
stdio: [0, 1, 2], stdio: [0, 1, 2],
cwd: tmpDir, cwd: tmpDir,
});
} }
);
output.success({
title: 'Nx has successfully created the workspace.',
});
if (parsedArgs.nxCloud) { if (parsedArgs.nxCloud) {
output.addVerticalSeparator(); output.addVerticalSeparator();
@ -474,7 +496,7 @@ function createApp(tmpDir: string, name: string, parsedArgs: WorkspaceArgs) {
} }
} }
async function askAboutNxCloud(parsedArgs: WorkspaceArgs) { async function askAboutNxCloud(parsedArgs: any) {
if (parsedArgs.nxCloud === undefined) { if (parsedArgs.nxCloud === undefined) {
return inquirer return inquirer
.prompt([ .prompt([

View File

@ -0,0 +1,210 @@
import * as chalk from 'chalk';
/*
* 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 interface CLIErrorMessageConfig {
title: string;
bodyLines?: string[];
slug?: string;
}
export interface CLIWarnMessageConfig {
title: string;
bodyLines?: string[];
slug?: string;
}
export interface CLINoteMessageConfig {
title: string;
bodyLines?: string[];
}
export interface CLISuccessMessageConfig {
title: string;
bodyLines?: string[];
}
/**
* Automatically disable styling applied by chalk if CI=true
*/
if (process.env.CI === 'true') {
(chalk as any).level = 0;
}
class CLIOutput {
private readonly NX_PREFIX = `${chalk.cyan(
'>'
)} ${chalk.reset.inverse.bold.cyan(' NX ')}`;
/**
* Longer dash character which forms more of a continuous line when place side to side
* with itself, unlike the standard dash character
*/
private readonly VERTICAL_SEPARATOR =
'———————————————————————————————————————————————';
/**
* Expose some color and other utility functions so that other parts of the codebase that need
* more fine-grained control of message bodies are still using a centralized
* implementation.
*/
colors = {
gray: chalk.gray,
};
bold = chalk.bold;
underline = chalk.underline;
private writeToStdOut(str: string) {
process.stdout.write(str);
}
private writeOutputTitle({
label,
title,
}: {
label?: string;
title: string;
}): void {
let outputTitle: string;
if (label) {
outputTitle = `${this.NX_PREFIX} ${label} ${title}\n`;
} else {
outputTitle = `${this.NX_PREFIX} ${title}\n`;
}
this.writeToStdOut(outputTitle);
}
private writeOptionalOutputBody(bodyLines?: string[]): void {
if (!bodyLines) {
return;
}
this.addNewline();
bodyLines.forEach((bodyLine) => this.writeToStdOut(' ' + bodyLine + '\n'));
}
addNewline() {
this.writeToStdOut('\n');
}
addVerticalSeparator() {
this.writeToStdOut(`\n${chalk.gray(this.VERTICAL_SEPARATOR)}\n\n`);
}
addVerticalSeparatorWithoutNewLines() {
this.writeToStdOut(`${chalk.gray(this.VERTICAL_SEPARATOR)}\n`);
}
error({ title, slug, bodyLines }: CLIErrorMessageConfig) {
this.addNewline();
this.writeOutputTitle({
label: chalk.reset.inverse.bold.red(' ERROR '),
title: chalk.bold.red(title),
});
this.writeOptionalOutputBody(bodyLines);
/**
* Optional slug to be used in an Nx error message redirect URL
*/
if (slug && typeof slug === 'string') {
this.addNewline();
this.writeToStdOut(
chalk.grey(' ' + 'Learn more about this error: ') +
'https://errors.nx.dev/' +
slug +
'\n'
);
}
this.addNewline();
}
warn({ title, slug, bodyLines }: CLIWarnMessageConfig) {
this.addNewline();
this.writeOutputTitle({
label: chalk.reset.inverse.bold.yellow(' WARNING '),
title: chalk.bold.yellow(title),
});
this.writeOptionalOutputBody(bodyLines);
/**
* Optional slug to be used in an Nx warning message redirect URL
*/
if (slug && typeof slug === 'string') {
this.addNewline();
this.writeToStdOut(
chalk.grey(' ' + 'Learn more about this warning: ') +
'https://errors.nx.dev/' +
slug +
'\n'
);
}
this.addNewline();
}
note({ title, bodyLines }: CLINoteMessageConfig) {
this.addNewline();
this.writeOutputTitle({
label: chalk.reset.inverse.bold.keyword('orange')(' NOTE '),
title: chalk.bold.keyword('orange')(title),
});
this.writeOptionalOutputBody(bodyLines);
this.addNewline();
}
success({ title, bodyLines }: CLISuccessMessageConfig) {
this.addNewline();
this.writeOutputTitle({
label: chalk.reset.inverse.bold.green(' SUCCESS '),
title: chalk.bold.green(title),
});
this.writeOptionalOutputBody(bodyLines);
this.addNewline();
}
logSingleLine(message: string) {
this.addNewline();
this.writeOutputTitle({
title: message,
});
this.addNewline();
}
logCommand(message: string, isCached: boolean = false) {
this.addNewline();
this.writeToStdOut(chalk.bold(`> ${message} `));
if (isCached) {
this.writeToStdOut(chalk.bold.grey(`[retrieved from cache]`));
}
this.addNewline();
}
log({ title, bodyLines }: CLIWarnMessageConfig) {
this.addNewline();
this.writeOutputTitle({
title: chalk.white(title),
});
this.writeOptionalOutputBody(bodyLines);
this.addNewline();
}
}
export const output = new CLIOutput();

View File

@ -0,0 +1,81 @@
import { execSync } from 'child_process';
import { existsSync } from 'fs';
import { join } from 'path';
/*
* 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 detectPackageManager(dir = '') {
return existsSync(join(dir, 'yarn.lock'))
? 'yarn'
: existsSync(join(dir, 'pnpm-lock.yaml'))
? 'pnpm'
: 'npm';
}
/**
* Returns commands for the package manager used in the workspace.
* By default, the package manager is derived based on the lock file,
* but it can also be passed in explicitly.
*
* Example:
*
* ```javascript
* execSync(`${getPackageManagerCommand().addDev} my-dev-package`);
* ```
*
*/
export function getPackageManagerCommand(
packageManager = detectPackageManager()
): {
install: string;
add: string;
addDev: string;
rm: string;
exec: string;
list: string;
run: (script: string, args: string) => string;
} {
switch (packageManager) {
case 'yarn':
return {
install: 'yarn',
add: 'yarn add',
addDev: 'yarn add -D',
rm: 'yarn remove',
exec: 'yarn',
run: (script: string, args: string) => `yarn ${script} ${args}`,
list: 'yarn list',
};
case 'pnpm':
return {
install: 'pnpm install --no-frozen-lockfile', // explicitly disable in case of CI
add: 'pnpm add',
addDev: 'pnpm add -D',
rm: 'pnpm rm',
exec: 'pnpx',
run: (script: string, args: string) => `pnpm run ${script} -- ${args}`,
list: 'pnpm ls --depth 100',
};
case 'npm':
return {
install: 'npm install --legacy-peer-deps',
add: 'npm install --legacy-peer-deps',
addDev: 'npm install --legacy-peer-deps -D',
rm: 'npm rm',
exec: 'npx',
run: (script: string, args: string) => `npm run ${script} -- ${args}`,
list: 'npm ls',
};
}
}
export function getPackageManagerVersion(
packageManager: 'npm' | 'yarn' | 'pnpm'
): string {
return execSync(`${packageManager} --version`).toString('utf-8').trim();
}

View File

@ -1,7 +1,12 @@
import * as path from 'path'; import * as path from 'path';
import { execSync } from 'child_process'; import { execSync } from 'child_process';
import { output } from '@nrwl/workspace/src/utilities/output'; import { output } from './output';
import * as flatten from 'flat';
/*
* 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 showNxWarning(workspaceName: string) { export function showNxWarning(workspaceName: string) {
try { try {
const pathToRunNxCommand = path.resolve(process.cwd(), workspaceName); const pathToRunNxCommand = path.resolve(process.cwd(), workspaceName);
@ -21,3 +26,36 @@ export function showNxWarning(workspaceName: string) {
}); });
} }
} }
export function unparse(options: Object): string[] {
const unparsed = [];
for (const key of Object.keys(options)) {
const value = options[key];
unparseOption(key, value, unparsed);
}
return unparsed;
}
function unparseOption(key: string, value: any, unparsed: string[]) {
if (value === true) {
unparsed.push(`--${key}`);
} else if (value === false) {
unparsed.push(`--no-${key}`);
} else if (Array.isArray(value)) {
value.forEach((item) => unparseOption(key, item, unparsed));
} else if (Object.prototype.toString.call(value) === '[object Object]') {
const flattened = flatten(value, { safe: true });
for (const flattenedKey in flattened) {
unparseOption(
`${key}.${flattenedKey}`,
flattened[flattenedKey],
unparsed
);
}
} else if (typeof value === 'string' && value.includes(' ')) {
unparsed.push(`--${key}="${value}"`);
} else if (value != null) {
unparsed.push(`--${key}=${value}`);
}
}

View File

@ -27,11 +27,10 @@
}, },
"homepage": "https://nx.dev", "homepage": "https://nx.dev",
"dependencies": { "dependencies": {
"@nrwl/workspace": "*",
"tmp": "0.0.33", "tmp": "0.0.33",
"yargs-parser": "20.0.0", "yargs-parser": "20.0.0",
"tslib": "^2.0.0",
"inquirer": "^6.3.1", "inquirer": "^6.3.1",
"typescript": "~4.0.3" "flat": "^5.0.2",
"chalk": "4.1.0"
} }
} }

View File

@ -1,7 +1,9 @@
const childProcess = require('child_process'); const childProcess = require('child_process');
const fs = require('fs-extra'); const fs = require('fs-extra');
childProcess.execSync(`npx ng-packagr -p packages/angular/ng-package.json`); childProcess.execSync(`npx ng-packagr -p packages/angular/ng-package.json`, {
stdio: [0, 1, 2],
});
fs.removeSync('packages/angular/dist/src'); fs.removeSync('packages/angular/dist/src');
fs.copySync('packages/angular/dist/', 'build/packages/angular/'); fs.copySync('packages/angular/dist/', 'build/packages/angular/');
fs.removeSync('packages/angular/dist/'); fs.removeSync('packages/angular/dist/');