feat(nx-plugin): reuse utilities from create-nx-workspace for create-nx-plugin (#15743)

This commit is contained in:
Craigory Coppola 2023-03-31 17:23:21 -04:00 committed by GitHub
parent 39646cfa9a
commit 6e7234c1aa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
43 changed files with 744 additions and 692 deletions

View File

@ -8,7 +8,8 @@
"plugins": ["@typescript-eslint", "@nrwl/nx"],
"extends": [],
"rules": {
"@typescript-eslint/explicit-module-boundary-types": "off"
"@typescript-eslint/explicit-module-boundary-types": "off",
"no-restricted-imports": ["error", "create-nx-workspace"]
},
"overrides": [
{

View File

@ -5512,6 +5512,14 @@
"children": [],
"isExternal": false,
"disableCollapsible": false
},
{
"id": "preset",
"path": "/packages/nx-plugin/generators/preset",
"name": "preset",
"children": [],
"isExternal": false,
"disableCollapsible": false
}
],
"isExternal": false,

View File

@ -1964,6 +1964,15 @@
"originalFilePath": "/packages/nx-plugin/src/generators/lint-checks/schema.json",
"path": "/packages/nx-plugin/generators/plugin-lint-checks",
"type": "generator"
},
"/packages/nx-plugin/generators/preset": {
"description": "Initializes a workspace with an nx-plugin inside of it. Use as: `create-nx-workspace --preset @nrwl/nx-plugin`.",
"file": "generated/packages/nx-plugin/generators/preset.json",
"hidden": true,
"name": "preset",
"originalFilePath": "/packages/nx-plugin/src/generators/preset/schema.json",
"path": "/packages/nx-plugin/generators/preset",
"type": "generator"
}
},
"path": "/packages/nx-plugin"

View File

@ -1939,6 +1939,15 @@
"originalFilePath": "/packages/nx-plugin/src/generators/lint-checks/schema.json",
"path": "nx-plugin/generators/plugin-lint-checks",
"type": "generator"
},
{
"description": "Initializes a workspace with an nx-plugin inside of it. Use as: `create-nx-workspace --preset @nrwl/nx-plugin`.",
"file": "generated/packages/nx-plugin/generators/preset.json",
"hidden": true,
"name": "preset",
"originalFilePath": "/packages/nx-plugin/src/generators/preset/schema.json",
"path": "nx-plugin/generators/preset",
"type": "generator"
}
],
"githubRoot": "https://github.com/nrwl/nx/blob/master",

View File

@ -0,0 +1,28 @@
{
"name": "preset",
"factory": "./src/generators/preset/generator",
"schema": {
"$schema": "http://json-schema.org/schema",
"cli": "nx",
"$id": "NxPluginPreset",
"title": "Generator ran by create-nx-plugin",
"description": "Initializes a workspace with an nx-plugin inside of it. Use as: `create-nx-plugin` or `create-nx-workspace --preset @nrwl/nx-plugin`.",
"type": "object",
"properties": {
"pluginName": {
"type": "string",
"description": "Plugin name",
"aliases": ["name"]
}
},
"required": ["pluginName"],
"presets": []
},
"description": "Initializes a workspace with an nx-plugin inside of it. Use as: `create-nx-workspace --preset @nrwl/nx-plugin`.",
"hidden": true,
"x-use-standalone-layout": true,
"implementation": "/packages/nx-plugin/src/generators/preset/generator.ts",
"aliases": [],
"path": "/packages/nx-plugin/src/generators/preset/schema.json",
"type": "generator"
}

View File

@ -235,7 +235,7 @@ export function runCreatePlugin(
} create-nx-plugin@${getPublishedVersion()} ${name}`;
if (pluginName) {
command += ` --pluginName=${pluginName}`;
command += ` --pluginName=${pluginName} --no-nxCloud`;
}
if (packageManager && !useDetectedPm) {

View File

@ -6,6 +6,7 @@ import {
uniq,
runCreatePlugin,
cleanupProject,
tmpProjPath,
} from '@nrwl/e2e/utils';
describe('create-nx-plugin', () => {
@ -13,8 +14,7 @@ describe('create-nx-plugin', () => {
afterEach(() => cleanupProject());
// TODO: Re-enable to work with pnpm
xit('should be able to create a plugin repo and run plugin e2e', () => {
it('should be able to create a plugin repo and run plugin e2e', () => {
const wsName = uniq('ws-plugin');
const pluginName = uniq('plugin');
@ -26,10 +26,11 @@ describe('create-nx-plugin', () => {
checkFilesExist(
'package.json',
packageManagerLockFile[packageManager],
`packages/${pluginName}/package.json`,
`packages/${pluginName}/project.json`
`project.json`,
`generators.json`,
`executors.json`
);
expect(() => runCLI(`e2e ${pluginName}-e2e`)).not.toThrow();
expect(() => runCLI(`e2e e2e`)).not.toThrow();
});
});

View File

@ -1,6 +1,16 @@
{
"extends": "../../.eslintrc",
"rules": {},
"rules": {
"no-restricted-imports": [
"error",
"@nrwl/workspace",
"@angular-devkit/core",
"@angular-devkit/architect",
"@angular-devkit/schematics",
"nx",
"@nrwl/devkit"
]
},
"ignorePatterns": ["!**/*"],
"overrides": [
{

View File

@ -1,156 +1,43 @@
#!/usr/bin/env node
// we can't import from '@nrwl/workspace' because it will require typescript
import {
getPackageManagerCommand,
NxJsonConfiguration,
readJsonFile,
writeJsonFile,
output,
} from '@nrwl/devkit';
import { execSync } from 'child_process';
import { rmSync } from 'fs';
import * as path from 'path';
import { dirSync } from 'tmp';
import { initializeGitRepo, showNxWarning } from './shared';
import {
detectInvokedPackageManager,
PackageManager,
} from './detect-invoked-package-manager';
import chalk = require('chalk');
import enquirer = require('enquirer');
import yargsParser = require('yargs-parser');
import yargs = require('yargs');
import {
determineCI,
determineDefaultBase,
determineNxCloud,
determinePackageManager,
} from 'create-nx-workspace/src/internal-utils/prompts';
import {
withAllPrompts,
withCI,
withGitOptions,
withNxCloud,
withOptions,
withPackageManager,
} from 'create-nx-workspace/src/internal-utils/yargs-options';
import { createWorkspace, CreateWorkspaceOptions } from 'create-nx-workspace';
import { output } from 'create-nx-workspace/src/utils/output';
import { CI } from 'create-nx-workspace/src/utils/ci/ci-list';
import type { PackageManager } from 'create-nx-workspace/src/utils/package-manager';
export const yargsDecorator = {
'Options:': `${chalk.green`Options`}:`,
'Examples:': `${chalk.green`Examples`}:`,
boolean: `${chalk.blue`boolean`}`,
count: `${chalk.blue`count`}`,
string: `${chalk.blue`string`}`,
array: `${chalk.blue`array`}`,
required: `${chalk.blue`required`}`,
'default:': `${chalk.blue`default`}:`,
'choices:': `${chalk.blue`choices`}:`,
'aliases:': `${chalk.blue`aliases`}:`,
};
const nxVersion = require('../package.json').version;
const tsVersion = 'TYPESCRIPT_VERSION'; // This gets replaced with the typescript version in the root package.json during build
const prettierVersion = 'PRETTIER_VERSION'; // This gets replaced with the prettier version in the root package.json during build
const parsedArgs = yargsParser(process.argv, {
string: ['pluginName', 'packageManager', 'importPath'],
alias: {
importPath: 'import-path',
pluginName: 'plugin-name',
packageManager: 'pm',
},
boolean: ['help'],
});
function createSandbox(packageManager: string) {
console.log(`Creating a sandbox with Nx...`);
const tmpDir = dirSync().name;
writeJsonFile(path.join(tmpDir, 'package.json'), {
dependencies: {
'@nrwl/workspace': nxVersion,
nx: nxVersion,
typescript: tsVersion,
prettier: prettierVersion,
},
license: 'MIT',
});
execSync(`${packageManager} install --silent --ignore-scripts`, {
cwd: tmpDir,
stdio: [0, 1, 2],
});
return tmpDir;
}
function createWorkspace(
tmpDir: string,
packageManager: PackageManager,
parsedArgs: any,
name: string
) {
// Ensure to use packageManager for args
// if it's not already passed in from previous process
if (!parsedArgs.packageManager) {
parsedArgs.packageManager = packageManager;
}
const args = [
name,
...process.argv.slice(parsedArgs._[2] ? 3 : 2).map((a) => `"${a}"`),
].join(' ');
const command = `new ${args} --preset=empty --collection=@nrwl/workspace`;
console.log(command);
const pmc = getPackageManagerCommand(packageManager);
execSync(
`${
pmc.exec
} nx ${command}/generators.json --nxWorkspaceRoot="${process.cwd()}"`,
{
stdio: [0, 1, 2],
cwd: tmpDir,
}
);
execSync(`${packageManager} add -D @nrwl/nx-plugin@${nxVersion}`, {
cwd: name,
stdio: [0, 1, 2],
});
}
function createNxPlugin(
workspaceName,
pluginName,
packageManager,
parsedArgs: any
) {
const importPath = parsedArgs.importPath ?? `@${workspaceName}/${pluginName}`;
const command = `nx generate @nrwl/nx-plugin:plugin ${pluginName} --importPath=${importPath}`;
console.log(command);
const pmc = getPackageManagerCommand(packageManager);
execSync(`${pmc.exec} ${command}`, {
cwd: workspaceName,
stdio: [0, 1, 2],
});
}
function updateWorkspace(workspaceName: string) {
const nxJsonPath = path.join(workspaceName, 'nx.json');
const nxJson = readJsonFile<NxJsonConfiguration>(nxJsonPath);
nxJson.workspaceLayout = {
appsDir: 'e2e',
libsDir: 'packages',
};
writeJsonFile(nxJsonPath, nxJson);
rmSync(path.join(workspaceName, 'apps'), { recursive: true, force: true });
rmSync(path.join(workspaceName, 'libs'), { recursive: true, force: true });
}
function determineWorkspaceName(parsedArgs: any): Promise<string> {
const workspaceName: string = parsedArgs._[2];
if (workspaceName) {
return Promise.resolve(workspaceName);
}
return enquirer
.prompt([
{
name: 'WorkspaceName',
message: `Workspace name (e.g., org name) `,
type: 'input',
},
])
.then((a: { WorkspaceName: string }) => {
if (!a.WorkspaceName) {
output.error({
title: 'Invalid workspace name',
bodyLines: [`Workspace name cannot be empty`],
});
process.exit(1);
}
return a.WorkspaceName;
});
}
function determinePluginName(parsedArgs) {
function determinePluginName(parsedArgs: CreateNxPluginArguments) {
if (parsedArgs.pluginName) {
return Promise.resolve(parsedArgs.pluginName);
}
@ -158,54 +45,112 @@ function determinePluginName(parsedArgs) {
return enquirer
.prompt([
{
name: 'PluginName',
name: 'pluginName',
message: `Plugin name `,
type: 'input',
validate: (s) => (s.length ? true : 'Name cannot be empty'),
},
])
.then((a: { PluginName: string }) => {
if (!a.PluginName) {
.then((a: { pluginName: string }) => {
if (!a.pluginName) {
output.error({
title: 'Invalid name',
bodyLines: [`Name cannot be empty`],
});
process.exit(1);
}
return a.PluginName;
return a.pluginName;
});
}
function showHelp() {
console.log(`
Usage: <name> [options]
Create a new Nx workspace
Args:
name workspace name (e.g., org name)
Options:
pluginName the name of the plugin to be created
`);
interface CreateNxPluginArguments {
pluginName: string;
packageManager: PackageManager;
ci: CI;
allPrompts: boolean;
nxCloud: boolean;
}
if (parsedArgs.help) {
showHelp();
process.exit(0);
export const commandsObject: yargs.Argv<CreateNxPluginArguments> = yargs
.wrap(yargs.terminalWidth())
.parserConfiguration({
'strip-dashed': true,
'dot-notation': true,
})
.command(
// this is the default and only command
'$0 [name] [options]',
'Create a new Nx plugin workspace',
(yargs) =>
withOptions(
yargs.positional('pluginName', {
describe: chalk.dim`Plugin name`,
type: 'string',
alias: ['name'],
}),
withNxCloud,
withCI,
withAllPrompts,
withPackageManager,
withGitOptions
),
async (argv: yargs.ArgumentsCamelCase<CreateNxPluginArguments>) => {
await main(argv).catch((error) => {
const { version } = require('../package.json');
output.error({
title: `Something went wrong! v${version}`,
});
throw error;
});
},
[normalizeArgsMiddleware]
)
.help('help', chalk.dim`Show help`)
.updateLocale(yargsDecorator)
.version(
'version',
chalk.dim`Show version`,
nxVersion
) as yargs.Argv<CreateNxPluginArguments>;
async function main(parsedArgs: yargs.Arguments<CreateNxPluginArguments>) {
const populatedArguments: CreateNxPluginArguments & CreateWorkspaceOptions = {
...parsedArgs,
name: parsedArgs.pluginName.includes('/')
? parsedArgs.pluginName.split('/')[1]
: parsedArgs.pluginName,
};
await createWorkspace('@nrwl/nx-plugin', populatedArguments);
}
const packageManager: PackageManager =
parsedArgs.packageManager || detectInvokedPackageManager();
determineWorkspaceName(parsedArgs).then((workspaceName) => {
return determinePluginName(parsedArgs).then((pluginName) => {
const tmpDir = createSandbox(packageManager);
createWorkspace(tmpDir, packageManager, parsedArgs, workspaceName);
updateWorkspace(workspaceName);
createNxPlugin(workspaceName, pluginName, packageManager, parsedArgs);
return initializeGitRepo(workspaceName).then(() => {
showNxWarning(workspaceName);
/**
* This function is used to normalize the arguments passed to the command.
* It would:
* - normalize the preset.
* @param argv user arguments
*/
async function normalizeArgsMiddleware(
argv: yargs.Arguments<CreateNxPluginArguments>
): Promise<void> {
try {
const name = await determinePluginName(argv);
const packageManager = await determinePackageManager(argv);
const defaultBase = await determineDefaultBase(argv);
const nxCloud = await determineNxCloud(argv);
const ci = await determineCI(argv, nxCloud);
Object.assign(argv, {
name,
nxCloud,
packageManager,
defaultBase,
ci,
});
});
});
} catch (e) {
console.error(e);
process.exit(1);
}
}
// Trigger Yargs
commandsObject.argv;

View File

@ -1,32 +0,0 @@
const packageManagerList = ['pnpm', 'yarn', 'npm'] as const;
export type PackageManager = typeof packageManagerList[number];
/**
* Detects which package manager was used to invoke create-nx-{plugin|workspace} command
* based on the main Module process that invokes the command
* - npx returns 'npm'
* - pnpx returns 'pnpm'
* - yarn create returns 'yarn'
*
* Default to 'npm'
*/
export function detectInvokedPackageManager(): PackageManager {
let detectedPackageManager: PackageManager = 'npm';
// mainModule is deprecated since Node 14, fallback for older versions
const invoker = require.main || process['mainModule'];
// default to `npm`
if (!invoker) {
return detectedPackageManager;
}
for (const pkgManager of packageManagerList) {
if (invoker.path.includes(pkgManager)) {
detectedPackageManager = pkgManager;
break;
}
}
return detectedPackageManager;
}

View File

@ -1,106 +0,0 @@
import * as path from 'path';
import { execSync, spawn, SpawnOptions } from 'child_process';
import { output } from '@nrwl/devkit';
export function showNxWarning(workspaceName: string) {
try {
const pathToRunNxCommand = path.resolve(process.cwd(), workspaceName);
execSync('nx --version', {
cwd: pathToRunNxCommand,
stdio: ['ignore', 'ignore', 'ignore'],
});
} catch {
// no nx found
output.addVerticalSeparator();
output.note({
title: `Nx CLI is not installed globally.`,
bodyLines: [
`This means that you might have to use "yarn nx" or "npx nx" to execute commands in the workspace.`,
`Run "yarn global add nx" or "npm install -g nx" to be able to execute command directly.`,
],
});
}
}
/*
* Because we don't want to depend on @nrwl/workspace
* 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;
}
}
function checkGitVersion(): string | null {
try {
let gitVersionOutput = execSync('git --version').toString().trim();
return gitVersionOutput.match(/[0-9]+\.[0-9]+\.+[0-9]+/)[0];
} catch {
return null;
}
}
/*
* Because we don't want to depend on create-nx-workspace
* we duplicate the helper functions from create-nx-workspace in this file.
*/
export async function initializeGitRepo(directory: string) {
const execute = (args: ReadonlyArray<string>, ignoreErrorStream = false) => {
const errorStream = ignoreErrorStream ? 'ignore' : process.stderr;
const spawnOptions: SpawnOptions = {
stdio: [process.stdin, 'ignore', errorStream],
shell: true,
cwd: directory,
env: process.env,
};
return new Promise<void>((resolve, reject) => {
spawn('git', args, spawnOptions).on('close', (code) => {
if (code === 0) {
resolve();
} else {
reject(code);
}
});
});
};
const gitVersion = checkGitVersion();
if (!gitVersion) {
return;
}
const insideRepo = await execute(
['rev-parse', '--is-inside-work-tree'],
true
).then(
() => true,
() => false
);
if (insideRepo) {
output.log({
title:
'Directory is already under version control. Skipping initialization of git.',
});
return;
}
const defaultBase = deduceDefaultBase();
const [gitMajor, gitMinor] = gitVersion.split('.');
if (+gitMajor > 2 || (+gitMajor === 2 && +gitMinor >= 28)) {
await execute(['init', '-b', defaultBase]);
} else {
await execute(['init']);
await execute(['checkout', '-b', defaultBase]); // Git < 2.28 doesn't support -b on git init.
}
await execute(['add', '.']);
const message = 'Initial commit';
await execute(['commit', `-m "${message}"`]);
output.log({
title: 'Successfully initialized git.',
});
}

View File

@ -29,11 +29,10 @@
},
"homepage": "https://nx.dev",
"dependencies": {
"@nrwl/devkit": "file:../devkit",
"create-nx-workspace": "file:../create-nx-workspace",
"chalk": "^4.1.0",
"enquirer": "~2.3.6",
"nx": "file:../nx",
"tmp": "~0.2.1",
"yargs-parser": "21.1.1"
"yargs": "^17.6.2"
},
"publishConfig": {
"access": "public"

View File

@ -2,20 +2,11 @@ import * as enquirer from 'enquirer';
import * as yargs from 'yargs';
import * as chalk from 'chalk';
import { ciList } from '../src/utils/ci/ci-list';
import { CreateWorkspaceOptions } from '../src/create-workspace-options';
import { createWorkspace } from '../src/create-workspace';
import { isKnownPreset, Preset } from '../src/utils/preset/preset';
import { presetOptions } from '../src/utils/preset/preset-options';
import { messages } from '../src/utils/nx/ab-testing';
import { output } from '../src/utils/output';
import { deduceDefaultBase } from '../src/utils/git/default-base';
import { stringifyCollection } from '../src/utils/string-utils';
import {
detectInvokedPackageManager,
PackageManager,
packageManagerList,
} from '../src/utils/package-manager';
import { nxVersion } from '../src/utils/nx/nx-version';
import { pointToTutorialAndCourse } from '../src/utils/preset/point-to-tutorial-and-course';
@ -23,6 +14,20 @@ import { yargsDecorator } from './decorator';
import { getThirdPartyPreset } from '../src/utils/preset/get-third-party-preset';
import { Framework, frameworkList } from './types/framework-list';
import { Bundler, bundlerList } from './types/bundler-list';
import {
determineCI,
determineDefaultBase,
determineNxCloud,
determinePackageManager,
} from '../src/internal-utils/prompts';
import {
withAllPrompts,
withCI,
withGitOptions,
withNxCloud,
withOptions,
withPackageManager,
} from '../src/internal-utils/yargs-options';
interface Arguments extends CreateWorkspaceOptions {
preset: string;
@ -46,6 +51,7 @@ export const commandsObject: yargs.Argv<Arguments> = yargs
'$0 [name] [options]',
'Create a new Nx workspace',
(yargs) =>
withOptions(
yargs
.option('name', {
describe: chalk.dim`Workspace name (e.g. org name)`,
@ -95,54 +101,14 @@ export const commandsObject: yargs.Argv<Arguments> = yargs
.option('docker', {
describe: chalk.dim`Generate a Dockerfile with your node-server`,
type: 'boolean',
})
.option('nxCloud', {
describe: chalk.dim(messages.getPromptMessage('nxCloudCreation')),
type: 'boolean',
})
.option('ci', {
describe: chalk.dim`Generate a CI workflow file`,
choices: ciList,
defaultDescription: '',
type: 'string',
})
.option('allPrompts', {
alias: 'a',
describe: chalk.dim`Show all prompts`,
type: 'boolean',
default: false,
})
.option('packageManager', {
alias: 'pm',
describe: chalk.dim`Package manager to use`,
choices: [...packageManagerList].sort(),
defaultDescription: 'npm',
type: 'string',
})
.option('defaultBase', {
defaultDescription: 'main',
describe: chalk.dim`Default base to use for new projects`,
type: 'string',
})
.option('skipGit', {
describe: chalk.dim`Skip initializing a git repository`,
type: 'boolean',
default: false,
alias: 'g',
})
.option('commit.name', {
describe: chalk.dim`Name of the committer`,
type: 'string',
})
.option('commit.email', {
describe: chalk.dim`E-mail of the committer`,
type: 'string',
})
.option('commit.message', {
describe: chalk.dim`Commit message`,
type: 'string',
default: 'Initial commit',
}),
withNxCloud,
withCI,
withAllPrompts,
withPackageManager,
withGitOptions
),
async (argv: yargs.ArgumentsCamelCase<Arguments>) => {
await main(argv).catch((error) => {
const { version } = require('../package.json');
@ -404,77 +370,6 @@ async function determineMonorepoStyle(): Promise<string> {
return a.MonorepoStyle;
}
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<{ packageManager: PackageManager }>([
{
name: 'packageManager',
message: `Which package manager to use `,
initial: 'npm' as any,
type: 'autocomplete',
choices: [
{ name: 'npm', message: 'NPM' },
{ name: 'yarn', message: 'Yarn' },
{ name: 'pnpm', message: 'PNPM' },
],
},
])
.then((a) => 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<{ DefaultBase: string }>([
{
name: 'DefaultBase',
message: `Main branch name `,
initial: `main`,
type: 'input',
},
])
.then((a) => {
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());
}
async function determinePreset(parsedArgs: any): Promise<Preset> {
if (parsedArgs.preset) {
if (Object.values(Preset).indexOf(parsedArgs.preset) === -1) {
@ -832,79 +727,3 @@ async function determineBundler(
return Promise.resolve(parsedArgs.bundler);
}
async function determineNxCloud(
parsedArgs: yargs.Arguments<Arguments>
): Promise<boolean> {
if (parsedArgs.nxCloud === undefined) {
return enquirer
.prompt<{ NxCloud: 'Yes' | 'No' }>([
{
name: 'NxCloud',
message: messages.getPromptMessage('nxCloudCreation'),
type: 'autocomplete',
choices: [
{
name: 'Yes',
hint: 'I want faster builds',
},
{
name: 'No',
},
],
initial: 'Yes' as any,
},
])
.then((a) => a.NxCloud === 'Yes');
} else {
return parsedArgs.nxCloud;
}
}
async function determineCI(
parsedArgs: yargs.Arguments<Arguments>,
nxCloud: boolean
): Promise<string> {
if (!nxCloud) {
if (parsedArgs.ci) {
output.warn({
title: 'Invalid CI value',
bodyLines: [
`CI option only works when Nx Cloud is enabled.`,
`The value provided will be ignored.`,
],
});
}
return '';
}
if (parsedArgs.ci) {
return parsedArgs.ci;
}
if (parsedArgs.allPrompts) {
return (
enquirer
.prompt<{ CI: string }>([
{
name: 'CI',
message: `CI workflow file to generate? `,
type: 'autocomplete',
initial: '' as any,
choices: [
{ message: 'none', name: '' },
{ message: 'GitHub Actions', name: 'github' },
{ message: 'Circle CI', name: 'circleci' },
{ message: 'Azure DevOps', name: 'azure' },
],
},
])
// enquirer ignores name and value if they are falsy and takes
// first field that has a truthy value, so wee need to explicitly
// check for none
.then((a: { CI: string }) => (a.CI !== 'none' ? a.CI : ''))
);
}
return '';
}

View File

@ -1 +1,2 @@
export * from './src/create-workspace';
export type { CreateWorkspaceOptions } from './src/create-workspace-options';

View File

@ -36,11 +36,12 @@ export async function createWorkspace<T extends CreateWorkspaceOptions>(
const tmpDir = await createSandbox(packageManager);
// nx new requires preset currently. We should probably make it optional.
const directory = await createEmptyWorkspace<T>(
tmpDir,
name,
packageManager,
options
{ ...options, preset }
);
// If the preset is a third-party preset, we need to call createPreset to install it

View File

@ -0,0 +1,159 @@
import * as yargs from 'yargs';
import { messages } from '../utils/nx/ab-testing';
import enquirer = require('enquirer');
import { CI } from '../utils/ci/ci-list';
import { output } from '../utils/output';
import { deduceDefaultBase } from '../utils/git/default-base';
import {
detectInvokedPackageManager,
PackageManager,
packageManagerList,
} from '../utils/package-manager';
import { stringifyCollection } from '../utils/string-utils';
export async function determineNxCloud(
parsedArgs: yargs.Arguments<{ nxCloud: boolean }>
): Promise<boolean> {
if (parsedArgs.nxCloud === undefined) {
return enquirer
.prompt<{ NxCloud: 'Yes' | 'No' }>([
{
name: 'NxCloud',
message: messages.getPromptMessage('nxCloudCreation'),
type: 'autocomplete',
choices: [
{
name: 'Yes',
hint: 'I want faster builds',
},
{
name: 'No',
},
],
initial: 'Yes' as any,
},
])
.then((a) => a.NxCloud === 'Yes');
} else {
return parsedArgs.nxCloud;
}
}
export async function determineCI(
parsedArgs: yargs.Arguments<{ ci?: CI; allPrompts?: boolean }>,
nxCloud: boolean
): Promise<string> {
if (!nxCloud) {
if (parsedArgs.ci) {
output.warn({
title: 'Invalid CI value',
bodyLines: [
`CI option only works when Nx Cloud is enabled.`,
`The value provided will be ignored.`,
],
});
}
return '';
}
if (parsedArgs.ci) {
return parsedArgs.ci;
}
if (parsedArgs.allPrompts) {
return (
enquirer
.prompt<{ CI: string }>([
{
name: 'CI',
message: `CI workflow file to generate? `,
type: 'autocomplete',
initial: '' as any,
choices: [
{ message: 'none', name: '' },
{ message: 'GitHub Actions', name: 'github' },
{ message: 'Circle CI', name: 'circleci' },
{ message: 'Azure DevOps', name: 'azure' },
],
},
])
// enquirer ignores name and value if they are falsy and takes
// first field that has a truthy value, so wee need to explicitly
// check for none
.then((a: { CI: string }) => (a.CI !== 'none' ? a.CI : ''))
);
}
return '';
}
export async function determineDefaultBase(
parsedArgs: yargs.Arguments<{ defaultBase?: string }>
): Promise<string> {
if (parsedArgs.defaultBase) {
return Promise.resolve(parsedArgs.defaultBase);
}
if (parsedArgs.allPrompts) {
return enquirer
.prompt<{ DefaultBase: string }>([
{
name: 'DefaultBase',
message: `Main branch name `,
initial: `main`,
type: 'input',
},
])
.then((a) => {
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());
}
export async function determinePackageManager(
parsedArgs: yargs.Arguments<{ packageManager: string }>
): 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<{ packageManager: PackageManager }>([
{
name: 'packageManager',
message: `Which package manager to use `,
initial: 'npm' as any,
type: 'autocomplete',
choices: [
{ name: 'npm', message: 'NPM' },
{ name: 'yarn', message: 'Yarn' },
{ name: 'pnpm', message: 'PNPM' },
],
},
])
.then((a) => a.packageManager);
}
return Promise.resolve(detectInvokedPackageManager());
}

View File

@ -0,0 +1,79 @@
import chalk = require('chalk');
import yargs = require('yargs');
import { CreateWorkspaceOptions } from '../create-workspace-options';
import { ciList } from '../utils/ci/ci-list';
import { messages } from '../utils/nx/ab-testing';
import { packageManagerList } from '../utils/package-manager';
export function withNxCloud<T = unknown>(argv: yargs.Argv<T>) {
const result = argv.option('nxCloud', {
describe: chalk.dim(messages.getPromptMessage('nxCloudCreation')),
type: 'boolean',
});
return result;
}
export function withCI<T = unknown>(argv: yargs.Argv<T>) {
return argv.option('ci', {
describe: chalk.dim`Generate a CI workflow file`,
choices: ciList,
defaultDescription: '',
type: 'string',
});
}
export function withAllPrompts<T = unknown>(argv: yargs.Argv<T>) {
return argv.option('allPrompts', {
alias: 'a',
describe: chalk.dim`Show all prompts`,
type: 'boolean',
default: false,
});
}
export function withPackageManager<T = unknown>(argv: yargs.Argv<T>) {
return argv.option('packageManager', {
alias: 'pm',
describe: chalk.dim`Package manager to use`,
choices: [...packageManagerList].sort(),
defaultDescription: 'npm',
type: 'string',
});
}
export function withGitOptions<T = unknown>(argv: yargs.Argv<T>) {
return argv
.option('defaultBase', {
defaultDescription: 'main',
describe: chalk.dim`Default base to use for new projects`,
type: 'string',
})
.option('skipGit', {
describe: chalk.dim`Skip initializing a git repository`,
type: 'boolean',
default: false,
alias: 'g',
})
.option('commit.name', {
describe: chalk.dim`Name of the committer`,
type: 'string',
})
.option('commit.email', {
describe: chalk.dim`E-mail of the committer`,
type: 'string',
})
.option('commit.message', {
describe: chalk.dim`Commit message`,
type: 'string',
default: 'Initial commit',
});
}
export function withOptions<T>(
argv: yargs.Argv<T>,
...options: ((argv: yargs.Argv<T>) => yargs.Argv<T>)[]
): any {
// Reversing the options keeps the execution order correct.
// e.g. [withCI, withGIT] should transform into withGIT(withCI) so withCI resolves first.
return options.reverse().reduce((argv, option) => option(argv), argv);
}

View File

@ -141,19 +141,21 @@ function normalizeOptions(
options: Options[0]
): Options[0] {
const base = { ...DEFAULT_OPTIONS, ...options };
const pathPrefix =
sourceProject.data.root !== '.' ? `${sourceProject.data.root}/` : '';
return {
...base,
executorsJson: base.executorsJson
? `${sourceProject.data.root}/${base.executorsJson}`
? `${pathPrefix}${base.executorsJson}`
: undefined,
generatorsJson: base.generatorsJson
? `${sourceProject.data.root}/${base.generatorsJson}`
? `${pathPrefix}${base.generatorsJson}`
: undefined,
migrationsJson: base.migrationsJson
? `${sourceProject.data.root}/${base.migrationsJson}`
? `${pathPrefix}${base.migrationsJson}`
: undefined,
packageJson: base.packageJson
? `${sourceProject.data.root}/${base.packageJson}`
? `${pathPrefix}${base.packageJson}`
: undefined,
};
}

View File

@ -1,5 +0,0 @@
{
"name": "<%= importPath %>",
"version": "0.0.1",
"type": "commonjs"
}

View File

@ -35,6 +35,7 @@ import {
typesNodeVersion,
} from '../../utils/versions';
import jsInitGenerator from '../init/init';
import { PackageJson } from 'nx/src/utils/package-json';
export async function libraryGenerator(
tree: Tree,
@ -44,7 +45,9 @@ export async function libraryGenerator(
schema.directory
);
schema.directory = projectDirectory;
const libsDir = layoutDirectory ?? getWorkspaceLayout(tree).libsDir;
const libsDir = schema.rootProject
? '.'
: layoutDirectory ?? getWorkspaceLayout(tree).libsDir;
return projectGenerator(tree, schema, libsDir, join(__dirname, './files'));
}
@ -235,6 +238,7 @@ export async function addLint(
`${options.projectRoot}/**/*.${options.js ? 'js' : 'ts'}`,
],
setParserOptionsProject: options.setParserOptionsProject,
rootProject: options.rootProject,
});
}
@ -308,7 +312,25 @@ function createFiles(tree: Tree, options: NormalizedSchema, filesDir: string) {
toJS(tree);
}
const packageJsonPath = join(options.projectRoot, 'package.json');
const packageJsonPath = joinPathFragments(
options.projectRoot,
'package.json'
);
if (tree.exists(packageJsonPath)) {
updateJson(tree, packageJsonPath, (json) => {
json.name = options.importPath;
json.version = '0.0.1';
json.type = 'commonjs';
return json;
});
} else {
writeJson<PackageJson>(tree, packageJsonPath, {
name: options.importPath,
version: '0.0.1',
type: 'commonjs',
});
}
if (options.config === 'npm-scripts') {
updateJson(tree, packageJsonPath, (json) => {
json.scripts = {
@ -317,11 +339,14 @@ function createFiles(tree: Tree, options: NormalizedSchema, filesDir: string) {
};
return json;
});
} else if (!options.bundler || options.bundler === 'none') {
} else if (
(!options.bundler || options.bundler === 'none') &&
!(options.projectRoot === '.')
) {
tree.delete(packageJsonPath);
}
if (options.minimal) {
if (options.minimal && !(options.projectRoot === '.')) {
tree.delete(join(options.projectRoot, 'README.md'));
}
@ -437,6 +462,8 @@ function normalizeOptions(
const name = names(options.name).fileName;
const projectDirectory = options.directory
? `${names(options.directory).fileName}/${name}`
: options.rootProject
? '.'
: name;
if (!options.unitTestRunner && options.bundler === 'vite') {
@ -449,7 +476,9 @@ function normalizeOptions(
options.linter = Linter.EsLint;
}
const projectName = projectDirectory.replace(new RegExp('/', 'g'), '-');
const projectName = options.rootProject
? name
: projectDirectory.replace(new RegExp('/', 'g'), '-');
const fileName = getCaseAwareFileName({
fileName: options.simpleModuleName ? name : projectName,
pascalCaseFiles: options.pascalCaseFiles,

View File

@ -32,6 +32,7 @@ export interface LibraryGeneratorSchema {
bundler?: Bundler;
skipTypeCheck?: boolean;
minimal?: boolean;
rootProject?: boolean;
}
export interface ExecutorOptions {

View File

@ -33,6 +33,13 @@
"factory": "./src/generators/lint-checks/generator",
"schema": "./src/generators/lint-checks/schema.json",
"description": "Adds linting configuration to validate common json files for nx plugins."
},
"preset": {
"factory": "./src/generators/preset/generator",
"schema": "./src/generators/preset/schema.json",
"description": "Initializes a workspace with an nx-plugin inside of it. Use as: `create-nx-workspace --preset @nrwl/nx-plugin`.",
"hidden": true,
"x-use-standalone-layout": true
}
},
"schematics": {

View File

@ -35,9 +35,12 @@ function normalizeOptions(host: Tree, options: Schema): NormalizedSchema {
const { npmScope, appsDir: defaultAppsDir } = getWorkspaceLayout(host);
const appsDir = layoutDirectory ?? defaultAppsDir;
const projectName = `${options.pluginName}-e2e`;
const projectRoot = projectDirectory
const projectName = options.rootProject ? 'e2e' : `${options.pluginName}-e2e`;
const projectRoot =
projectDirectory && !options.rootProject
? joinPathFragments(appsDir, `${projectDirectory}-e2e`)
: options.rootProject
? projectName
: joinPathFragments(appsDir, projectName);
const pluginPropertyName = names(options.pluginName).propertyName;

View File

@ -9,4 +9,5 @@ export interface Schema {
minimal?: boolean;
linter?: Linter;
skipFormat?: boolean;
rootProject?: boolean;
}

View File

@ -107,7 +107,7 @@ describe('NxPlugin Executor Generator', () => {
expect(() => tree.exists(`${libConfig.root}/executors.json`)).not.toThrow();
expect(readJson(tree, `${libConfig.root}/package.json`).executors).toBe(
'executors.json'
'./executors.json'
);
});

View File

@ -75,7 +75,7 @@ function createExecutorsJson(host: Tree, options: NormalizedSchema) {
host,
joinPathFragments(options.projectRoot, 'package.json'),
(json) => {
json.executors ??= 'executors.json';
json.executors ??= './executors.json';
return json;
}
);

View File

@ -111,7 +111,7 @@ describe('NxPlugin Generator Generator', () => {
tree.exists(`${libConfig.root}/generators.json`)
).not.toThrow();
expect(readJson(tree, `${libConfig.root}/package.json`).generators).toBe(
'generators.json'
'./generators.json'
);
});

View File

@ -91,7 +91,7 @@ function createGeneratorsJson(host: Tree, options: NormalizedSchema) {
host,
joinPathFragments(options.projectRoot, 'package.json'),
(json) => {
json.generators ??= 'generators.json';
json.generators ??= './generators.json';
return json;
}
);

View File

@ -15,7 +15,12 @@ import type { Schema } from './schema';
import * as path from 'path';
import { addMigrationJsonChecks } from '../lint-checks/generator';
import type { Linter as EsLint } from 'eslint';
import { PackageJson, readNxMigrateConfig } from 'nx/src/utils/package-json';
import {
NxMigrationsConfiguration,
PackageJson,
PackageJsonTargetConfiguration,
readNxMigrateConfig,
} from 'nx/src/utils/package-json';
interface NormalizedSchema extends Schema {
projectRoot: string;
projectSourceRoot: string;
@ -98,19 +103,25 @@ function updateMigrationsJson(host: Tree, options: NormalizedSchema) {
}
function updatePackageJson(host: Tree, options: NormalizedSchema) {
updateJson(host, path.join(options.projectRoot, 'package.json'), (json) => {
if (!json['nx-migrations'] || !json['nx-migrations'].migrations) {
if (json['nx-migrations']) {
json['nx-migrations'].migrations = './migrations.json';
} else {
json['nx-migrations'] = {
updateJson<PackageJson>(
host,
path.join(options.projectRoot, 'package.json'),
(json) => {
const migrationKey = json['ng-update'] ? 'ng-update' : 'nx-migrations';
const preexistingValue = json[migrationKey];
if (typeof preexistingValue === 'string') {
return json;
} else if (!json[migrationKey]) {
json[migrationKey] = {
migrations: './migrations.json',
};
}
} else if (preexistingValue.migrations) {
preexistingValue.migrations = './migrations.json';
}
return json;
});
}
);
}
function updateWorkspaceJson(host: Tree, options: NormalizedSchema) {

View File

@ -1,4 +0,0 @@
{
"$schema": "http://json-schema.org/schema",
"executors": {}
}

View File

@ -1,6 +0,0 @@
{
"$schema": "http://json-schema.org/schema",
"name": "<%= name %>",
"version": "0.0.1",
"generators": {}
}

View File

@ -1,7 +0,0 @@
{
"name": "<%= npmPackageName %>",
"version": "0.0.1",
"main": "src/index.js",
"generators": "./generators.json",
"executors": "./executors.json"
}

View File

@ -138,11 +138,7 @@ describe('NxPlugin Plugin Generator', () => {
it('should not create generator and executor files for minimal setups', async () => {
await pluginGenerator(tree, getSchema({ name: 'myPlugin', minimal: true }));
[
'libs/my-plugin/project.json',
'libs/my-plugin/generators.json',
'libs/my-plugin/executors.json',
].forEach((path) => expect(tree.exists(path)).toBeTruthy());
expect(tree.exists('libs/my-plugin/project.json')).toBeTruthy();
[
'libs/my-plugin/src/generators/my-plugin/schema.d.ts',
@ -156,25 +152,6 @@ describe('NxPlugin Plugin Generator', () => {
'libs/my-plugin/src/executors/build/schema.json',
'libs/my-plugin/src/executors/build/schema.d.ts',
].forEach((path) => expect(tree.exists(path)).toBeFalsy());
expect(tree.read('libs/my-plugin/generators.json', 'utf-8'))
.toMatchInlineSnapshot(`
"{
\\"$schema\\": \\"http://json-schema.org/schema\\",
\\"name\\": \\"my-plugin\\",
\\"version\\": \\"0.0.1\\",
\\"generators\\": {}
}
"
`);
expect(tree.read('libs/my-plugin/executors.json', 'utf-8'))
.toMatchInlineSnapshot(`
"{
\\"$schema\\": \\"http://json-schema.org/schema\\",
\\"executors\\": {}
}
"
`);
});
describe('--unitTestRunner', () => {

View File

@ -53,7 +53,7 @@ async function addFiles(host: Tree, options: NormalizedSchema) {
});
}
function updateWorkspaceJson(host: Tree, options: NormalizedSchema) {
function updatePluginConfig(host: Tree, options: NormalizedSchema) {
const project = readProjectConfiguration(host, options.name);
if (project.targets.build) {
@ -100,13 +100,14 @@ export async function pluginGenerator(host: Tree, schema: Schema) {
addDependenciesToPackageJson(
host,
{},
{
'@nrwl/devkit': nxVersion,
tslib: tsLibVersion,
},
{
'@nrwl/jest': nxVersion,
'@nrwl/js': nxVersion,
'@swc-node/register': swcNodeVersion,
tslib: tsLibVersion,
}
);
@ -115,7 +116,7 @@ export async function pluginGenerator(host: Tree, schema: Schema) {
addSwcDependencies(host);
await addFiles(host, options);
updateWorkspaceJson(host, options);
updatePluginConfig(host, options);
if (options.e2eTestRunner !== 'none') {
await e2eProjectGenerator(host, {
@ -125,6 +126,7 @@ export async function pluginGenerator(host: Tree, schema: Schema) {
npmPackageName: options.npmPackageName,
minimal: options.minimal ?? false,
skipFormat: true,
rootProject: options.rootProject,
});
}

View File

@ -14,4 +14,5 @@ export interface Schema {
setParserOptionsProject?: boolean;
compiler: 'swc' | 'tsc';
minimal?: boolean;
rootProject?: boolean;
}

View File

@ -31,9 +31,13 @@ export function normalizeOptions(
const name = names(options.name).fileName;
const fullProjectDirectory = projectDirectory
? `${names(projectDirectory).fileName}/${name}`
: options.rootProject
? '.'
: name;
const projectName = fullProjectDirectory.replace(new RegExp('/', 'g'), '-');
const projectName = options.rootProject
? name
: fullProjectDirectory.replace(new RegExp('/', 'g'), '-');
const fileName = projectName;
const projectRoot = joinPathFragments(libsDir, fullProjectDirectory);

View File

@ -0,0 +1,30 @@
import { createTreeWithEmptyWorkspace } from '@nrwl/devkit/testing';
import {
Tree,
readProjectConfiguration,
readJson,
readNxJson,
} from '@nrwl/devkit';
import generator from './generator';
import { PackageJson } from 'nx/src/utils/package-json';
describe('preset generator', () => {
let tree: Tree;
beforeEach(() => {
tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
});
it('should create a plugin', async () => {
await generator(tree, {
pluginName: 'my-plugin',
});
const config = readProjectConfiguration(tree, 'my-plugin');
expect(config).toBeDefined();
const packageJson = readJson<PackageJson>(tree, 'package.json');
expect(packageJson.generators).toEqual('./generators.json');
expect(packageJson.executors).toEqual('./executors.json');
expect(readNxJson(tree).npmScope).not.toBeDefined();
});
});

View File

@ -0,0 +1,45 @@
import {
Tree,
readJson,
joinPathFragments,
updateJson,
updateNxJson,
readNxJson,
} from '@nrwl/devkit';
import { Linter } from '@nrwl/linter';
import { PackageJson } from 'nx/src/utils/package-json';
import { pluginGenerator } from '../plugin/plugin';
import { PresetGeneratorSchema } from './schema';
export default async function (tree: Tree, options: PresetGeneratorSchema) {
const task = await pluginGenerator(tree, {
compiler: 'tsc',
linter: Linter.EsLint,
name: options.pluginName.includes('/')
? options.pluginName.split('/')[1]
: options.pluginName,
skipFormat: false,
skipLintChecks: false,
skipTsConfig: false,
unitTestRunner: 'jest',
importPath: options.pluginName,
rootProject: true,
});
removeNpmScope(tree);
moveNxPluginToDevDeps(tree);
return task;
}
function removeNpmScope(tree: Tree) {
updateNxJson(tree, { ...readNxJson(tree), npmScope: undefined });
}
function moveNxPluginToDevDeps(tree: Tree) {
updateJson<PackageJson>(tree, 'package.json', (json) => {
const nxPluginEntry = json.dependencies['@nrwl/nx-plugin'];
delete json.dependencies['@nrwl/nx-plugin'];
json.devDependencies['@nrwl/nx-plugin'] = nxPluginEntry;
return json;
});
}

View File

@ -0,0 +1,3 @@
export interface PresetGeneratorSchema {
pluginName: string;
}

View File

@ -0,0 +1,16 @@
{
"$schema": "http://json-schema.org/schema",
"cli": "nx",
"$id": "NxPluginPreset",
"title": "Generator ran by create-nx-plugin",
"description": "Initializes a workspace with an nx-plugin inside of it. Use as: `create-nx-plugin` or `create-nx-workspace --preset @nrwl/nx-plugin`.",
"type": "object",
"properties": {
"pluginName": {
"type": "string",
"description": "Plugin name",
"aliases": ["name"]
}
},
"required": ["pluginName"]
}

View File

@ -13,7 +13,9 @@ function removeSpecialFlags(generatorOptions: { [p: string]: any }): void {
export async function newWorkspace(cwd: string, args: { [k: string]: any }) {
const ws = new Workspaces(null);
return handleErrors(false, async () => {
return handleErrors(
process.env.NX_VERBOSE_LOGGING === 'true' || args.verbose,
async () => {
const isInteractive = args.interactive;
const { normalizedGeneratorName, schema, implementationFactory } =
ws.readGenerator('@nrwl/workspace/generators.json', 'new');
@ -39,5 +41,6 @@ export async function newWorkspace(cwd: string, args: { [k: string]: any }) {
if (task) {
await task();
}
});
}
);
}

View File

@ -77,10 +77,16 @@ export function createPackageJson(
);
// for standalone projects we don't want to include all the root dependencies
if (graph.nodes[projectName].data.root === '.') {
packageJson = {
name: packageJson.name,
version: packageJson.version,
};
// TODO: We should probably think more on this - Nx can't always
// detect all external dependencies, and there's not a way currently
// to tell Nx that we need one of these deps. For non-standalone projects
// we tell people to add it to the package.json of the project, and we
// merge it. For standalone, this pattern doesn't work because of this piece of code.
// It breaks expectations, but also, I don't know another way around it currently.
// If Nx doesn't pick up a dep, say some css lib that is only imported in a .scss file,
// we need to be able to tell it to keep that dep in the generated package.json.
delete packageJson.dependencies;
delete packageJson.devDependencies;
}
} catch (e) {}
}

View File

@ -107,6 +107,8 @@
"@nrwl/workspace": ["packages/workspace"],
"@nrwl/workspace/*": ["packages/workspace/*"],
"@nrwl/workspace/testing": ["packages/workspace/testing"],
"create-nx-workspace": ["packages/create-nx-workspace/index.ts"],
"create-nx-workspace/*": ["packages/create-nx-workspace/*"],
"nx": ["packages/nx"],
"nx-dev/ui-primitives": ["nx-dev/ui-primitives/src/index.ts"],
"nx/*": ["packages/nx/*"]