From 4d0bb6fdcfab84a82fc413ed5c0b2c82a3f538ad Mon Sep 17 00:00:00 2001 From: Emily Xiong Date: Fri, 24 Mar 2023 00:11:27 -0400 Subject: [PATCH] feat(nx-plugin): add create-package plugin --- .circleci/config.yml | 3 +- docs/generated/manifests/menus.json | 8 ++ docs/generated/manifests/packages.json | 9 ++ docs/generated/packages-metadata.json | 9 ++ .../nx-plugin/generators/create-package.json | 84 ++++++++++++ .../packages/workspace/generators/preset.json | 1 + e2e/detox/src/detox.test.ts | 16 --- e2e/nx-plugin/src/nx-plugin.test.ts | 21 ++- .../bin/create-nx-workspace.ts | 1 + packages/create-nx-workspace/index.ts | 1 + packages/create-nx-workspace/package.json | 2 - packages/create-nx-workspace/project.json | 2 +- .../src/create-empty-workspace.ts | 4 +- ...tions.d.ts => create-workspace-options.ts} | 1 + .../src/create-workspace.ts | 5 +- packages/nx-plugin/generators.json | 10 ++ packages/nx-plugin/generators.ts | 1 + .../create-package/create-package.spec.ts | 124 ++++++++++++++++++ .../create-package/create-package.ts | 118 +++++++++++++++++ .../bin/__name__.ts__tmpl__ | 91 +++++++++++++ .../create-framework-app/bin/index.ts__tmpl__ | 5 + .../files/e2e/__name__.spec.ts__tmpl__ | 0 .../src/generators/create-package/schema.d.ts | 15 +++ .../src/generators/create-package/schema.json | 78 +++++++++++ .../create-package/utils/normalize-schema.ts | 46 +++++++ .../src/generators/plugin/plugin.spec.ts | 2 +- .../nx-plugin/src/generators/plugin/plugin.ts | 4 +- .../src/generators/preset/schema.json | 3 +- 28 files changed, 633 insertions(+), 31 deletions(-) create mode 100644 docs/generated/packages/nx-plugin/generators/create-package.json rename packages/create-nx-workspace/src/{create-workspace-options.d.ts => create-workspace-options.ts} (95%) create mode 100644 packages/nx-plugin/src/generators/create-package/create-package.spec.ts create mode 100644 packages/nx-plugin/src/generators/create-package/create-package.ts create mode 100644 packages/nx-plugin/src/generators/create-package/files/create-framework-app/bin/__name__.ts__tmpl__ create mode 100644 packages/nx-plugin/src/generators/create-package/files/create-framework-app/bin/index.ts__tmpl__ create mode 100644 packages/nx-plugin/src/generators/create-package/files/e2e/__name__.spec.ts__tmpl__ create mode 100644 packages/nx-plugin/src/generators/create-package/schema.d.ts create mode 100644 packages/nx-plugin/src/generators/create-package/schema.json create mode 100644 packages/nx-plugin/src/generators/create-package/utils/normalize-schema.ts diff --git a/.circleci/config.yml b/.circleci/config.yml index 5e844bd383d8e1..8f4a2c2d47383d 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -231,9 +231,8 @@ jobs: - rust/install - run: name: Run E2E Tests for macOS - # FIXME: remove --exclude=e2e-detox once we have a fix for the detox tests command: | - npx nx run-many -t e2e-macos --parallel=1 --exclude=e2e-detox + npx nx run-many -t e2e-macos --parallel=1 no_output_timeout: 45m # ------------------------- diff --git a/docs/generated/manifests/menus.json b/docs/generated/manifests/menus.json index e033a70c743b48..a64070e2f849c8 100644 --- a/docs/generated/manifests/menus.json +++ b/docs/generated/manifests/menus.json @@ -5448,6 +5448,14 @@ "isExternal": false, "disableCollapsible": false }, + { + "id": "create-package", + "path": "/packages/nx-plugin/generators/create-package", + "name": "create-package", + "children": [], + "isExternal": false, + "disableCollapsible": false + }, { "id": "e2e-project", "path": "/packages/nx-plugin/generators/e2e-project", diff --git a/docs/generated/manifests/packages.json b/docs/generated/manifests/packages.json index 64b37593bc7f41..c2190a2f99c07d 100644 --- a/docs/generated/manifests/packages.json +++ b/docs/generated/manifests/packages.json @@ -1920,6 +1920,15 @@ "path": "/packages/nx-plugin/generators/plugin", "type": "generator" }, + "/packages/nx-plugin/generators/create-package": { + "description": "Create a framework package that uses Nx CLI", + "file": "generated/packages/nx-plugin/generators/create-package.json", + "hidden": false, + "name": "create-package", + "originalFilePath": "/packages/nx-plugin/src/generators/create-package/schema.json", + "path": "/packages/nx-plugin/generators/create-package", + "type": "generator" + }, "/packages/nx-plugin/generators/e2e-project": { "description": "Create a E2E application for a Nx Plugin.", "file": "generated/packages/nx-plugin/generators/e2e-project.json", diff --git a/docs/generated/packages-metadata.json b/docs/generated/packages-metadata.json index b23b2fd5f872c5..b2a5eb3fd111ef 100644 --- a/docs/generated/packages-metadata.json +++ b/docs/generated/packages-metadata.json @@ -1895,6 +1895,15 @@ "path": "nx-plugin/generators/plugin", "type": "generator" }, + { + "description": "Create a framework package that uses Nx CLI", + "file": "generated/packages/nx-plugin/generators/create-package.json", + "hidden": false, + "name": "create-package", + "originalFilePath": "/packages/nx-plugin/src/generators/create-package/schema.json", + "path": "nx-plugin/generators/create-package", + "type": "generator" + }, { "description": "Create a E2E application for a Nx Plugin.", "file": "generated/packages/nx-plugin/generators/e2e-project.json", diff --git a/docs/generated/packages/nx-plugin/generators/create-package.json b/docs/generated/packages/nx-plugin/generators/create-package.json new file mode 100644 index 00000000000000..ec697bc118a999 --- /dev/null +++ b/docs/generated/packages/nx-plugin/generators/create-package.json @@ -0,0 +1,84 @@ +{ + "name": "create-package", + "factory": "./src/generators/create-package/create-package", + "schema": { + "$schema": "http://json-schema.org/schema", + "cli": "nx", + "$id": "NxPluginCreatePackage", + "title": "Create a framework package", + "description": "Create a framework package that uses Nx CLI.", + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The package name, like `create-framework-app`. Note this must be a valid NPM name to be published.", + "$default": { "$source": "argv", "index": 0 }, + "x-priority": "important" + }, + "project": { + "type": "string", + "description": "The name of the generator project.", + "alias": "p", + "$default": { "$source": "projectName" }, + "x-prompt": "What is the name of the project for the generator?", + "x-priority": "important" + }, + "unitTestRunner": { + "type": "string", + "enum": ["jest", "none"], + "description": "Test runner to use for unit tests.", + "default": "jest" + }, + "directory": { + "type": "string", + "description": "A directory where the app is placed." + }, + "linter": { + "description": "The tool to use for running lint checks.", + "type": "string", + "enum": ["eslint"], + "default": "eslint" + }, + "tags": { + "type": "string", + "description": "Add tags to the library (used for linting).", + "alias": "t" + }, + "skipFormat": { + "description": "Skip formatting files.", + "type": "boolean", + "default": false, + "x-priority": "internal" + }, + "skipTsConfig": { + "type": "boolean", + "default": false, + "description": "Do not update tsconfig.json for development experience.", + "x-priority": "internal" + }, + "setParserOptionsProject": { + "type": "boolean", + "description": "Whether or not to configure the ESLint `parserOptions.project` option. We do not do this by default for lint performance reasons.", + "default": false + }, + "compiler": { + "type": "string", + "enum": ["tsc", "swc"], + "default": "tsc", + "description": "The compiler used by the build and test targets." + }, + "importPath": { + "type": "string", + "description": "How the plugin will be published, like `create-framework-app`. Note this must be a valid NPM name. Will use name if not provided." + } + }, + "required": ["name", "project"], + "presets": [] + }, + "description": "Create a framework package that uses Nx CLI", + "implementation": "/packages/nx-plugin/src/generators/create-package/create-package.ts", + "aliases": [], + "hidden": false, + "path": "/packages/nx-plugin/src/generators/create-package/schema.json", + "type": "generator" +} diff --git a/docs/generated/packages/workspace/generators/preset.json b/docs/generated/packages/workspace/generators/preset.json index 651d03ffd18cf5..632188aa383caf 100644 --- a/docs/generated/packages/workspace/generators/preset.json +++ b/docs/generated/packages/workspace/generators/preset.json @@ -86,6 +86,7 @@ "default": false } }, + "required": ["preset", "name"], "presets": [] }, "description": "Create application in an empty workspace.", diff --git a/e2e/detox/src/detox.test.ts b/e2e/detox/src/detox.test.ts index e4b3f29850ae20..187be10a4904fb 100644 --- a/e2e/detox/src/detox.test.ts +++ b/e2e/detox/src/detox.test.ts @@ -1,11 +1,9 @@ import { checkFilesExist, - isOSX, newProject, runCLI, runCLIAsync, uniq, - killPorts, cleanupProject, } from '@nrwl/e2e/utils'; @@ -46,18 +44,4 @@ describe('Detox', () => { const lintResults = await runCLIAsync(`lint ${expoAppName}-e2e`); expect(lintResults.combinedOutput).toContain('All files pass linting'); }); - - describe('React Native Detox MACOS-Tests', () => { - if (isOSX()) { - it('should test ios MACOS-Tests', async () => { - expect( - runCLI( - `test-ios ${appName}-e2e --prod --debugSynchronization=true --loglevel=trace` - ) - ).toContain('Successfully ran target test-ios'); - - await killPorts(8081); // kill the port for the serve command - }, 3000000); - } - }); }); diff --git a/e2e/nx-plugin/src/nx-plugin.test.ts b/e2e/nx-plugin/src/nx-plugin.test.ts index 6988acc304e742..a1c2c3a06be4a8 100644 --- a/e2e/nx-plugin/src/nx-plugin.test.ts +++ b/e2e/nx-plugin/src/nx-plugin.test.ts @@ -2,7 +2,6 @@ import { ProjectConfiguration } from '@nrwl/devkit'; import { checkFilesExist, expectTestsPass, - isNotWindows, killPorts, newProject, readJson, @@ -12,8 +11,6 @@ import { uniq, updateFile, createFile, - readFile, - removeFile, cleanupProject, runCommand, getPackageManagerCommand, @@ -411,4 +408,22 @@ describe('Nx Plugin', () => { expect(pluginProject.tags).toEqual(['e2etag', 'e2ePackage']); }, 90000); }); + + it('should be able to generate a create-package plugin ', async () => { + const plugin = uniq('plugin'); + const createAppName = `create-${plugin}-app`; + runCLI(`generate @nrwl/nx-plugin:plugin ${plugin}`); + runCLI( + `generate @nrwl/nx-plugin:create-package ${createAppName} --project=${plugin}` + ); + + const buildResults = runCLI(`build ${createAppName}`); + expect(buildResults).toContain('Done compiling TypeScript files'); + + checkFilesExist( + `libs/${plugin}/src/generators/preset`, + `libs/${createAppName}`, + `dist/libs/${createAppName}/bin/index.js` + ); + }); }); diff --git a/packages/create-nx-workspace/bin/create-nx-workspace.ts b/packages/create-nx-workspace/bin/create-nx-workspace.ts index cedb373b58bd21..dbc4d4c0ad7840 100644 --- a/packages/create-nx-workspace/bin/create-nx-workspace.ts +++ b/packages/create-nx-workspace/bin/create-nx-workspace.ts @@ -222,6 +222,7 @@ async function normalizeArgsMiddleware( } else if (monorepoStyle === 'node-standalone') { preset = Preset.NodeStandalone; } else { + // when choose integrated monorepo, further prompt for preset preset = await determinePreset(argv); } } else if (argv.preset === 'react') { diff --git a/packages/create-nx-workspace/index.ts b/packages/create-nx-workspace/index.ts index 285217767a4876..44ba5f0cc1aa1e 100644 --- a/packages/create-nx-workspace/index.ts +++ b/packages/create-nx-workspace/index.ts @@ -1 +1,2 @@ export * from './src/create-workspace'; +export * from './src/create-workspace-options'; diff --git a/packages/create-nx-workspace/package.json b/packages/create-nx-workspace/package.json index f1d06d338f7bb4..4fdf734b9879df 100644 --- a/packages/create-nx-workspace/package.json +++ b/packages/create-nx-workspace/package.json @@ -27,8 +27,6 @@ "bugs": { "url": "https://github.com/nrwl/nx/issues" }, - "main": "./index.js", - "typings": "./index.d.ts", "homepage": "https://nx.dev", "dependencies": { "chalk": "^4.1.0", diff --git a/packages/create-nx-workspace/project.json b/packages/create-nx-workspace/project.json index aca86797a0e238..c47979257ee8a2 100644 --- a/packages/create-nx-workspace/project.json +++ b/packages/create-nx-workspace/project.json @@ -8,7 +8,7 @@ "build-base": { "executor": "@nrwl/js:tsc", "options": { - "main": "packages/create-nx-workspace/bin/create-nx-workspace.ts", + "main": "packages/create-nx-workspace/index.ts", "assets": [ { "input": "packages/create-nx-workspace", diff --git a/packages/create-nx-workspace/src/create-empty-workspace.ts b/packages/create-nx-workspace/src/create-empty-workspace.ts index ab673a60f036fa..1451511de9f5d9 100644 --- a/packages/create-nx-workspace/src/create-empty-workspace.ts +++ b/packages/create-nx-workspace/src/create-empty-workspace.ts @@ -24,7 +24,8 @@ export async function createEmptyWorkspace( tmpDir: string, name: string, packageManager: PackageManager, - options: T + options: T, + preset: string ): Promise { // Ensure to use packageManager for args // if it's not already passed in from previous process @@ -33,6 +34,7 @@ export async function createEmptyWorkspace( } const args = unparse({ + preset, ...options, }).join(' '); diff --git a/packages/create-nx-workspace/src/create-workspace-options.d.ts b/packages/create-nx-workspace/src/create-workspace-options.ts similarity index 95% rename from packages/create-nx-workspace/src/create-workspace-options.d.ts rename to packages/create-nx-workspace/src/create-workspace-options.ts index c0ecfe190c974c..e225b19c7ca3fb 100644 --- a/packages/create-nx-workspace/src/create-workspace-options.d.ts +++ b/packages/create-nx-workspace/src/create-workspace-options.ts @@ -1,4 +1,5 @@ import { PackageManager } from './utils/package-manager'; +import { CI } from './utils/ci/ci-list'; export interface CreateWorkspaceOptions { name: string; // Workspace name (e.g. org name) diff --git a/packages/create-nx-workspace/src/create-workspace.ts b/packages/create-nx-workspace/src/create-workspace.ts index 41676f263d5733..fd4ce4b0f4cca9 100644 --- a/packages/create-nx-workspace/src/create-workspace.ts +++ b/packages/create-nx-workspace/src/create-workspace.ts @@ -40,7 +40,8 @@ export async function createWorkspace( tmpDir, name, packageManager, - options + options, + preset ); // If the preset is a third-party preset, we need to call createPreset to install it @@ -63,7 +64,7 @@ export async function createWorkspace( nxCloud && nxCloudInstallRes?.code === 0 ); } - if (!skipGit) { + if (!skipGit && commit) { try { await initializeGitRepo(directory, { defaultBase, commit }); } catch (e) { diff --git a/packages/nx-plugin/generators.json b/packages/nx-plugin/generators.json index b889b233cf3b86..73ff2f0dff9fbe 100644 --- a/packages/nx-plugin/generators.json +++ b/packages/nx-plugin/generators.json @@ -8,6 +8,11 @@ "schema": "./src/generators/plugin/schema.json", "description": "Create a Nx Plugin." }, + "create-package": { + "factory": "./src/generators/create-package/create-package", + "schema": "./src/generators/create-package/schema.json", + "description": "Create a framework package that uses Nx CLI" + }, "e2e-project": { "factory": "./src/generators/e2e-project/e2e", "schema": "./src/generators/e2e-project/schema.json", @@ -41,6 +46,11 @@ "schema": "./src/generators/plugin/schema.json", "description": "Create a Nx Plugin." }, + "create-package": { + "factory": "./src/generators/create-package/create-package#createPackageSchematic", + "schema": "./src/generators/create-package/schema.json", + "description": "Create a framework package that uses Nx CLI" + }, "e2e-project": { "factory": "./src/generators/e2e-project/e2e#e2eProjectSchematic", "schema": "./src/generators/e2e-project/schema.json", diff --git a/packages/nx-plugin/generators.ts b/packages/nx-plugin/generators.ts index a5c482e0a95801..d2d89360fe8f4b 100644 --- a/packages/nx-plugin/generators.ts +++ b/packages/nx-plugin/generators.ts @@ -1,3 +1,4 @@ +export * from './src/generators/create-package/create-package'; export * from './src/generators/e2e-project/e2e'; export * from './src/generators/executor/executor'; export * from './src/generators/generator/generator'; diff --git a/packages/nx-plugin/src/generators/create-package/create-package.spec.ts b/packages/nx-plugin/src/generators/create-package/create-package.spec.ts new file mode 100644 index 00000000000000..1a4abb2c4071fd --- /dev/null +++ b/packages/nx-plugin/src/generators/create-package/create-package.spec.ts @@ -0,0 +1,124 @@ +import { + joinPathFragments, + readJson, + readProjectConfiguration, + Tree, +} from '@nrwl/devkit'; +import { createTreeWithEmptyWorkspace } from '@nrwl/devkit/testing'; +import { Linter } from '@nrwl/linter'; +import pluginGenerator from '../plugin/plugin'; +import { createPackageGenerator } from './create-package'; +import { CreatePackageSchema } from './schema'; + +const getSchema: ( + overrides?: Partial +) => CreatePackageSchema = (overrides = {}) => ({ + name: 'create-package', + project: 'my-plugin', + compiler: 'tsc', + skipTsConfig: false, + skipFormat: false, + skipLintChecks: false, + linter: Linter.EsLint, + unitTestRunner: 'jest', + minimal: true, + ...overrides, +}); + +describe('NxPlugin Create Package Generator', () => { + let tree: Tree; + + beforeEach(async () => { + tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' }); + await pluginGenerator(tree, { + name: 'my-plugin', + compiler: 'tsc', + skipTsConfig: false, + skipFormat: false, + skipLintChecks: false, + linter: Linter.EsLint, + unitTestRunner: 'jest', + }); + }); + + it('should update the project.json file', async () => { + await createPackageGenerator(tree, getSchema()); + const project = readProjectConfiguration(tree, 'create-package'); + expect(project.root).toEqual('libs/create-package'); + expect(project.sourceRoot).toEqual('libs/create-package/bin'); + expect(project.targets.build).toEqual({ + executor: '@nrwl/js:tsc', + outputs: ['{options.outputPath}'], + options: { + outputPath: 'dist/libs/create-package', + tsConfig: 'libs/create-package/tsconfig.lib.json', + main: 'libs/create-package/bin/index.ts', + assets: [], + }, + }); + }); + + it('should place the create-package plugin in a directory', async () => { + await createPackageGenerator( + tree, + getSchema({ + directory: 'plugins', + } as Partial) + ); + const project = readProjectConfiguration(tree, 'plugins-create-package'); + expect(project.root).toEqual('libs/plugins/create-package'); + }); + + it('should specify tsc as compiler', async () => { + await createPackageGenerator( + tree, + getSchema({ + compiler: 'tsc', + }) + ); + + const { build } = readProjectConfiguration(tree, 'create-package').targets; + + expect(build.executor).toEqual('@nrwl/js:tsc'); + }); + + it('should specify swc as compiler', async () => { + await createPackageGenerator( + tree, + getSchema({ + compiler: 'swc', + }) + ); + + const { build } = readProjectConfiguration(tree, 'create-package').targets; + + expect(build.executor).toEqual('@nrwl/js:swc'); + }); + + it("should use name as default for the package.json's name", async () => { + await createPackageGenerator(tree, getSchema()); + + const { root } = readProjectConfiguration(tree, 'create-package'); + const { name } = readJson<{ name: string }>( + tree, + joinPathFragments(root, 'package.json') + ); + + expect(name).toEqual('create-package'); + }); + + it('should use importPath as the package.json name', async () => { + await createPackageGenerator( + tree, + getSchema({ importPath: '@my-company/create-package' }) + ); + + const { root } = readProjectConfiguration(tree, 'create-package'); + const { name } = readJson<{ name: string }>( + tree, + joinPathFragments(root, 'package.json') + ); + + expect(name).toEqual('@my-company/create-package'); + }); +}); diff --git a/packages/nx-plugin/src/generators/create-package/create-package.ts b/packages/nx-plugin/src/generators/create-package/create-package.ts new file mode 100644 index 00000000000000..e1af7a2030b806 --- /dev/null +++ b/packages/nx-plugin/src/generators/create-package/create-package.ts @@ -0,0 +1,118 @@ +import { + addDependenciesToPackageJson, + readProjectConfiguration, + Tree, + generateFiles, + readJson, + convertNxGenerator, + formatFiles, + updateProjectConfiguration, +} from '@nrwl/devkit'; +import { libraryGenerator as jsLibraryGenerator } from '@nrwl/js'; +import { join } from 'path'; +import { nxVersion } from '../../utils/versions'; +import generatorGenerator from '../generator/generator'; +import { CreatePackageSchema } from './schema'; +import { NormalizedSchema, normalizeSchema } from './utils/normalize-schema'; + +const enquirerVersion = '~2.3.6'; +const yargsVersion = '~16.2.0'; + +export async function createPackageGenerator( + host: Tree, + schema: CreatePackageSchema +) { + const options = normalizeSchema(host, schema); + const pluginPackageName = await addPresetGenerator(host, options); + + const installTask = addDependenciesToPackageJson( + host, + {}, + { + 'create-nx-workspace': nxVersion, + enquirer: enquirerVersion, + yargs: yargsVersion, + } + ); + + await createCliPackage(host, options, pluginPackageName); + + await formatFiles(host); + + return installTask; +} + +async function addPresetGenerator( + host: Tree, + schema: NormalizedSchema +): Promise { + const { root: projectRoot } = readProjectConfiguration(host, schema.project); + if (!host.exists(`${projectRoot}/src/generators/preset`)) { + await generatorGenerator(host, { + name: 'preset', + project: schema.project, + unitTestRunner: schema.unitTestRunner, + }); + } + + return readJson(host, join(projectRoot, 'package.json'))?.name; +} + +async function createCliPackage( + host: Tree, + options: NormalizedSchema, + pluginPackageName: string +) { + await jsLibraryGenerator(host, { + ...options, + config: 'project', + buildable: true, + publishable: true, + bundler: options.bundler, + importPath: options.importPath, + }); + + host.delete(join(options.projectRoot, 'src')); + + // Add the bin entry to the package.json + const packageJsonPath = join(options.projectRoot, 'package.json'); + const packageJson = readJson(host, packageJsonPath); + packageJson.bin = { + [options.name]: './bin/index.js', + }; + host.write(packageJsonPath, JSON.stringify(packageJson)); + + // update project build target to use the bin entry + const projectConfiguration = readProjectConfiguration( + host, + options.projectName + ); + projectConfiguration.sourceRoot = join(options.projectRoot, 'bin'); + projectConfiguration.targets.build.options.main = join( + options.projectRoot, + 'bin/index.ts' + ); + updateProjectConfiguration(host, options.projectName, projectConfiguration); + + // Add bin files to tsconfg.lib.json + const tsConfigPath = join(options.projectRoot, 'tsconfig.lib.json'); + const tsConfig = readJson(host, tsConfigPath); + tsConfig.include.push('bin/**/*.ts'); + host.write(tsConfigPath, JSON.stringify(tsConfig)); + + generateFiles( + host, + join(__dirname, './files/create-framework-app'), + options.projectRoot, + { + ...options, + preset: pluginPackageName, + tmpl: '', + } + ); +} + +export default createPackageGenerator; +export const createPackageSchematic = convertNxGenerator( + createPackageGenerator +); diff --git a/packages/nx-plugin/src/generators/create-package/files/create-framework-app/bin/__name__.ts__tmpl__ b/packages/nx-plugin/src/generators/create-package/files/create-framework-app/bin/__name__.ts__tmpl__ new file mode 100644 index 00000000000000..f2c84707196796 --- /dev/null +++ b/packages/nx-plugin/src/generators/create-package/files/create-framework-app/bin/__name__.ts__tmpl__ @@ -0,0 +1,91 @@ +import * as enquirer from 'enquirer'; +import * as yargs from 'yargs'; +import { createWorkspace, CreateWorkspaceOptions } from 'create-nx-workspace'; + +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface Options extends CreateWorkspaceOptions {} + +export const commandsObject: yargs.Argv = 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 workspace', + (yargs) => + yargs + .option('name', { + describe: 'What is the name of your workspace?', + type: 'string', + }) + .option('packageManager', { + alias: 'pm', + describe: 'Package manager to use', + choices: ['npm', 'yarn', 'pnpm'], + defaultDescription: 'npm', + type: 'string', + }) + .option('nxCloud', { + describe: 'Enable distributed caching to make your CI faster?', + type: 'boolean', + }), + async (argv: yargs.ArgumentsCamelCase) => { + await main(argv).catch((error) => { + throw error; + }); + }, + [normalizeArgsMiddleware] + ) as yargs.Argv; + +async function normalizeArgsMiddleware(argv: yargs.Arguments) { + if (!argv.name) { + const results = await enquirer.prompt<{ name: string }>({ + type: 'input', + name: 'name', + message: 'What is the name of your workspace?', + }); + argv.name = results.name; + } + if (!argv.packageManager) { + const results = await enquirer.prompt<{ packageManager: string }>({ + 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' }, + ], + }); + argv.packageManager = results.packageManager as any; + } + if (argv.nxCloud === undefined) { + const results = await enquirer.prompt<{ nxCloud: 'Yes' | 'No' }>({ + name: 'NxCloud', + message: 'Enable distributed caching to make your CI faster?', + type: 'autocomplete', + choices: [ + { + name: 'Yes', + hint: 'I want faster builds', + }, + + { + name: 'No', + }, + ], + initial: 'Yes' as any, + }); + argv.nxCloud = results.nxCloud === 'Yes'; + } +} + +async function main(options: Options) { + await createWorkspace('<%= preset %>', options); + + console.log(`Successfully created the <%= preset %> workspace: ${options.name}.`); +} diff --git a/packages/nx-plugin/src/generators/create-package/files/create-framework-app/bin/index.ts__tmpl__ b/packages/nx-plugin/src/generators/create-package/files/create-framework-app/bin/index.ts__tmpl__ new file mode 100644 index 00000000000000..0f8d283abcb939 --- /dev/null +++ b/packages/nx-plugin/src/generators/create-package/files/create-framework-app/bin/index.ts__tmpl__ @@ -0,0 +1,5 @@ +#!/usr/bin/env node + +import { commandsObject } from './<%= name %>'; + +commandsObject.argv; diff --git a/packages/nx-plugin/src/generators/create-package/files/e2e/__name__.spec.ts__tmpl__ b/packages/nx-plugin/src/generators/create-package/files/e2e/__name__.spec.ts__tmpl__ new file mode 100644 index 00000000000000..e69de29bb2d1d6 diff --git a/packages/nx-plugin/src/generators/create-package/schema.d.ts b/packages/nx-plugin/src/generators/create-package/schema.d.ts new file mode 100644 index 00000000000000..e852eacd65a766 --- /dev/null +++ b/packages/nx-plugin/src/generators/create-package/schema.d.ts @@ -0,0 +1,15 @@ +export interface CreatePackageSchema { + name: string; + project: string; + + // options to create cli package, passed to js library generator + directory?: string; + skipTsConfig: boolean; + skipFormat: boolean; + tags?: string; + unitTestRunner: 'jest' | 'none'; + linter: Linter; + setParserOptionsProject?: boolean; + compiler: 'swc' | 'tsc'; + importPath?: string; +} diff --git a/packages/nx-plugin/src/generators/create-package/schema.json b/packages/nx-plugin/src/generators/create-package/schema.json new file mode 100644 index 00000000000000..79936d0a5c0f00 --- /dev/null +++ b/packages/nx-plugin/src/generators/create-package/schema.json @@ -0,0 +1,78 @@ +{ + "$schema": "http://json-schema.org/schema", + "cli": "nx", + "$id": "NxPluginCreatePackage", + "title": "Create a framework package", + "description": "Create a framework package that uses Nx CLI.", + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The package name, like `create-framework-app`. Note this must be a valid NPM name to be published.", + "$default": { + "$source": "argv", + "index": 0 + }, + "x-priority": "important" + }, + "project": { + "type": "string", + "description": "The name of the generator project.", + "alias": "p", + "$default": { + "$source": "projectName" + }, + "x-prompt": "What is the name of the project for the generator?", + "x-priority": "important" + }, + "unitTestRunner": { + "type": "string", + "enum": ["jest", "none"], + "description": "Test runner to use for unit tests.", + "default": "jest" + }, + "directory": { + "type": "string", + "description": "A directory where the app is placed." + }, + "linter": { + "description": "The tool to use for running lint checks.", + "type": "string", + "enum": ["eslint"], + "default": "eslint" + }, + "tags": { + "type": "string", + "description": "Add tags to the library (used for linting).", + "alias": "t" + }, + "skipFormat": { + "description": "Skip formatting files.", + "type": "boolean", + "default": false, + "x-priority": "internal" + }, + "skipTsConfig": { + "type": "boolean", + "default": false, + "description": "Do not update tsconfig.json for development experience.", + "x-priority": "internal" + }, + "setParserOptionsProject": { + "type": "boolean", + "description": "Whether or not to configure the ESLint `parserOptions.project` option. We do not do this by default for lint performance reasons.", + "default": false + }, + "compiler": { + "type": "string", + "enum": ["tsc", "swc"], + "default": "tsc", + "description": "The compiler used by the build and test targets." + }, + "importPath": { + "type": "string", + "description": "How the plugin will be published, like `create-framework-app`. Note this must be a valid NPM name. Will use name if not provided." + } + }, + "required": ["name", "project"] +} diff --git a/packages/nx-plugin/src/generators/create-package/utils/normalize-schema.ts b/packages/nx-plugin/src/generators/create-package/utils/normalize-schema.ts new file mode 100644 index 00000000000000..d3e613af350b69 --- /dev/null +++ b/packages/nx-plugin/src/generators/create-package/utils/normalize-schema.ts @@ -0,0 +1,46 @@ +import { + extractLayoutDirectory, + getWorkspaceLayout, + joinPathFragments, + names, + Tree, +} from '@nrwl/devkit'; +import { CreatePackageSchema } from '../schema'; + +export interface NormalizedSchema extends CreatePackageSchema { + bundler: 'swc' | 'tsc'; + npmScope: string; + libsDir: string; + projectName: string; + projectRoot: string; + projectDirectory: string; +} + +export function normalizeSchema( + host: Tree, + schema: CreatePackageSchema +): NormalizedSchema { + const { layoutDirectory, projectDirectory } = extractLayoutDirectory( + schema.directory + ); + const { npmScope, libsDir: defaultLibsDir } = getWorkspaceLayout(host); + const libsDir = layoutDirectory ?? defaultLibsDir; + const name = names(schema.name).fileName; + const fullProjectDirectory = projectDirectory + ? `${names(projectDirectory).fileName}/${name}` + : name; + const projectName = fullProjectDirectory.replace(new RegExp('/', 'g'), '-'); + const projectRoot = joinPathFragments(libsDir, fullProjectDirectory); + const importPath = schema.importPath ?? name; + return { + ...schema, + bundler: schema.compiler ?? 'tsc', + npmScope, + libsDir, + projectName, + projectRoot, + name, + projectDirectory: fullProjectDirectory, + importPath, + }; +} diff --git a/packages/nx-plugin/src/generators/plugin/plugin.spec.ts b/packages/nx-plugin/src/generators/plugin/plugin.spec.ts index 1c46f9dc17ff85..6068df6d88b23b 100644 --- a/packages/nx-plugin/src/generators/plugin/plugin.spec.ts +++ b/packages/nx-plugin/src/generators/plugin/plugin.spec.ts @@ -30,7 +30,7 @@ describe('NxPlugin Plugin Generator', () => { tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' }); }); - it('should update the workspace.json file', async () => { + it('should update the project.json file', async () => { await pluginGenerator(tree, getSchema()); const project = readProjectConfiguration(tree, 'my-plugin'); expect(project.root).toEqual('libs/my-plugin'); diff --git a/packages/nx-plugin/src/generators/plugin/plugin.ts b/packages/nx-plugin/src/generators/plugin/plugin.ts index 5defa1c21b76cb..b8fd1e4b0e0488 100644 --- a/packages/nx-plugin/src/generators/plugin/plugin.ts +++ b/packages/nx-plugin/src/generators/plugin/plugin.ts @@ -53,7 +53,7 @@ async function addFiles(host: Tree, options: NormalizedSchema) { }); } -function updateWorkspaceJson(host: Tree, options: NormalizedSchema) { +function updateProjectJson(host: Tree, options: NormalizedSchema) { const project = readProjectConfiguration(host, options.name); if (project.targets.build) { @@ -114,7 +114,7 @@ export async function pluginGenerator(host: Tree, schema: Schema) { addSwcDependencies(host); await addFiles(host, options); - updateWorkspaceJson(host, options); + updateProjectJson(host, options); if (options.e2eTestRunner !== 'none') { await e2eProjectGenerator(host, { diff --git a/packages/workspace/src/generators/preset/schema.json b/packages/workspace/src/generators/preset/schema.json index ca6f3c5830df8d..fe8bd6cc9d51eb 100644 --- a/packages/workspace/src/generators/preset/schema.json +++ b/packages/workspace/src/generators/preset/schema.json @@ -88,5 +88,6 @@ "type": "boolean", "default": false } - } + }, + "required": ["preset", "name"] }