diff --git a/docs/generated/manifests/menus.json b/docs/generated/manifests/menus.json index d1db7f63c8..25f2135f18 100644 --- a/docs/generated/manifests/menus.json +++ b/docs/generated/manifests/menus.json @@ -5579,6 +5579,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 ee41dd079c..e1e9127390 100644 --- a/docs/generated/manifests/packages.json +++ b/docs/generated/manifests/packages.json @@ -1810,6 +1810,15 @@ "path": "/packages/nx-plugin/generators/plugin", "type": "generator" }, + "/packages/nx-plugin/generators/create-package": { + "description": "Create a package which can be used by npx to create a new workspace", + "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 eea408b7ad..00179da821 100644 --- a/docs/generated/packages-metadata.json +++ b/docs/generated/packages-metadata.json @@ -1788,6 +1788,15 @@ "path": "nx-plugin/generators/plugin", "type": "generator" }, + { + "description": "Create a package which can be used by npx to create a new workspace", + "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/executors/e2e.json b/docs/generated/packages/nx-plugin/executors/e2e.json index 4edf67f6d5..8924234821 100644 --- a/docs/generated/packages/nx-plugin/executors/e2e.json +++ b/docs/generated/packages/nx-plugin/executors/e2e.json @@ -117,7 +117,8 @@ "runInBand": { "alias": "i", "description": "Run all tests serially in the current process (rather than creating a worker pool of child processes that run tests). This is sometimes useful for debugging, but such use cases are pretty rare. Useful for CI. (https://jestjs.io/docs/cli#--runinband)", - "type": "boolean" + "type": "boolean", + "default": true }, "showConfig": { "description": "Print your Jest config and then exits. (https://jestjs.io/docs/en/cli#--showconfig)", 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 0000000000..bd20a069a2 --- /dev/null +++ b/docs/generated/packages/nx-plugin/generators/create-package.json @@ -0,0 +1,75 @@ +{ + "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 of cli, e.g. `create-framework-package`. 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" + }, + "compiler": { + "type": "string", + "enum": ["tsc", "swc"], + "default": "tsc", + "description": "The compiler used by the build and test targets." + }, + "e2eTestRunner": { + "type": "string", + "enum": ["jest", "none"], + "description": "Test runner to use for end to end (E2E) tests.", + "default": "jest" + } + }, + "required": ["name", "project"], + "presets": [] + }, + "description": "Create a package which can be used by npx to create a new workspace", + "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/nx-plugin/generators/executor.json b/docs/generated/packages/nx-plugin/generators/executor.json index bf88b95a5a..65b68e9538 100644 --- a/docs/generated/packages/nx-plugin/generators/executor.json +++ b/docs/generated/packages/nx-plugin/generators/executor.json @@ -49,6 +49,12 @@ "type": "boolean", "default": false, "description": "Do not add an eslint configuration for plugin json files." + }, + "skipFormat": { + "type": "boolean", + "description": "Skip formatting files.", + "default": false, + "x-priority": "internal" } }, "required": ["project", "name"], diff --git a/docs/generated/packages/nx-plugin/generators/preset.json b/docs/generated/packages/nx-plugin/generators/preset.json index 2253d562ca..082117d91b 100644 --- a/docs/generated/packages/nx-plugin/generators/preset.json +++ b/docs/generated/packages/nx-plugin/generators/preset.json @@ -13,6 +13,10 @@ "type": "string", "description": "Plugin name", "aliases": ["name"] + }, + "createPackageName": { + "type": "string", + "description": "Name of package which creates a workspace" } }, "required": ["pluginName"], diff --git a/docs/generated/packages/workspace/generators/preset.json b/docs/generated/packages/workspace/generators/preset.json index 6b00eace00..9fa8429b55 100644 --- a/docs/generated/packages/workspace/generators/preset.json +++ b/docs/generated/packages/workspace/generators/preset.json @@ -92,6 +92,7 @@ "enum": ["cypress", "jest", "detox", "none"] } }, + "required": ["preset", "name"], "presets": [] }, "description": "Create application in an empty workspace.", diff --git a/e2e/nx-plugin/src/nx-plugin.test.ts b/e2e/nx-plugin/src/nx-plugin.test.ts index 139b767ae3..c5ec429bd3 100644 --- a/e2e/nx-plugin/src/nx-plugin.test.ts +++ b/e2e/nx-plugin/src/nx-plugin.test.ts @@ -5,7 +5,6 @@ import { createFile, expectTestsPass, getPackageManagerCommand, - killPorts, newProject, readJson, readProjectConfig, @@ -410,4 +409,31 @@ 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` + ); + }); + + it('should throw an error when run create-package for an invalid plugin ', async () => { + const plugin = uniq('plugin'); + expect(() => + runCLI( + `generate @nrwl/nx-plugin:create-package ${plugin} --project=invalid-plugin` + ) + ).toThrow(); + }); }); diff --git a/e2e/workspace-create/src/create-nx-plugin.test.ts b/e2e/workspace-create/src/create-nx-plugin.test.ts index 54686dc769..e3758b65c1 100644 --- a/e2e/workspace-create/src/create-nx-plugin.test.ts +++ b/e2e/workspace-create/src/create-nx-plugin.test.ts @@ -21,6 +21,7 @@ describe('create-nx-plugin', () => { runCreatePlugin(pluginName, { packageManager, + extraArgs: `--createPackageName='false'`, }); checkFilesExist( @@ -31,7 +32,7 @@ describe('create-nx-plugin', () => { runCLI(`build ${pluginName}`); - checkFilesExist(`dist/package.json`); + checkFilesExist(`dist/package.json`, `dist/src/index.js`); runCLI( `generate @nrwl/nx-plugin:generator ${generatorName} --project=${pluginName}` @@ -48,4 +49,22 @@ describe('create-nx-plugin', () => { `dist/executors.json` ); }); + + it('should be able to create a repo with create workspace cli', () => { + const pluginName = uniq('plugin'); + + runCreatePlugin(pluginName, { + packageManager, + extraArgs: `--createPackageName=create-${pluginName}-package`, + }); + + runCLI(`build ${pluginName}`); + checkFilesExist(`dist/package.json`, `dist/generators.json`); + + runCLI(`build create-${pluginName}-package`); + checkFilesExist(`dist/create-${pluginName}-package/bin/index.js`); + + expect(() => runCLI(`e2e e2e`)).not.toThrow(); + expect(() => runCLI(`e2e create-${pluginName}-package-e2e`)).not.toThrow(); + }); }); diff --git a/packages/create-nx-plugin/bin/create-nx-plugin.ts b/packages/create-nx-plugin/bin/create-nx-plugin.ts index dc0e7f1497..4638b8ab98 100644 --- a/packages/create-nx-plugin/bin/create-nx-plugin.ts +++ b/packages/create-nx-plugin/bin/create-nx-plugin.ts @@ -43,34 +43,44 @@ export const yargsDecorator = { const nxVersion = require('../package.json').version; -function determinePluginName(parsedArgs: CreateNxPluginArguments) { +async function determinePluginName( + parsedArgs: CreateNxPluginArguments +): Promise { if (parsedArgs.pluginName) { - return Promise.resolve(parsedArgs.pluginName); + return parsedArgs.pluginName; } - return enquirer - .prompt([ - { - name: 'pluginName', - message: `Plugin name `, - type: 'input', - validate: (s) => (s.length ? true : 'Name cannot be empty'), - }, - ]) - .then((a: { pluginName: string }) => { - if (!a.pluginName) { - output.error({ - title: 'Invalid name', - bodyLines: [`Name cannot be empty`], - }); - process.exit(1); - } - return a.pluginName; - }); + const results = await enquirer.prompt<{ pluginName: string }>([ + { + name: 'pluginName', + message: `Plugin name `, + type: 'input', + validate: (s_1) => (s_1.length ? true : 'Plugin name cannot be empty'), + }, + ]); + return results.pluginName; +} + +async function determineCreatePackageName( + parsedArgs: CreateNxPluginArguments +): Promise { + if (parsedArgs.createPackageName) { + return parsedArgs.createPackageName; + } + + const results = await enquirer.prompt<{ createPackageName: string }>([ + { + name: 'createPackageName', + message: `Create a package which can be used by npx to create a new workspace (Leave blank to not create this package)`, + type: 'input', + }, + ]); + return results.createPackageName; } interface CreateNxPluginArguments { pluginName: string; + createPackageName?: string; packageManager: PackageManager; ci: CI; allPrompts: boolean; @@ -89,11 +99,16 @@ export const commandsObject: yargs.Argv = yargs 'Create a new Nx plugin workspace', (yargs) => withOptions( - yargs.positional('pluginName', { - describe: chalk.dim`Plugin name`, - type: 'string', - alias: ['name'], - }), + yargs + .positional('pluginName', { + describe: chalk.dim`Plugin name`, + type: 'string', + alias: ['name'], + }) + .option('createPackageName', { + describe: 'Name of the CLI package to create workspace with plugin', + type: 'string', + }), withNxCloud, withCI, withAllPrompts, @@ -164,19 +179,21 @@ async function normalizeArgsMiddleware( argv: yargs.Arguments ): Promise { try { - const name = await determinePluginName(argv); + const pluginName = await determinePluginName(argv); + const createPackageName = await determineCreatePackageName(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, + pluginName, + createPackageName, nxCloud, packageManager, defaultBase, ci, - }); + } as Partial); } catch (e) { console.error(e); process.exit(1); diff --git a/packages/create-nx-workspace/bin/create-nx-workspace.ts b/packages/create-nx-workspace/bin/create-nx-workspace.ts index 59ae99baf5..644f2a3983 100644 --- a/packages/create-nx-workspace/bin/create-nx-workspace.ts +++ b/packages/create-nx-workspace/bin/create-nx-workspace.ts @@ -221,6 +221,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/package.json b/packages/create-nx-workspace/package.json index f1d06d338f..4fdf734b98 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 aca86797a0..c47979257e 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-preset.ts b/packages/create-nx-workspace/src/create-preset.ts index fa108393f7..8d9722b864 100644 --- a/packages/create-nx-workspace/src/create-preset.ts +++ b/packages/create-nx-workspace/src/create-preset.ts @@ -39,7 +39,9 @@ export async function createPreset( } } - if (process.env.NX_VERBOSE_LOGGING !== 'true') { + if ( + !(process.env.NX_VERBOSE_LOGGING === 'true' || args.includes('--verbose')) + ) { args = '--quiet ' + args; } const command = `g ${preset}:preset ${args}`; 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 91% rename from packages/create-nx-workspace/src/create-workspace-options.d.ts rename to packages/create-nx-workspace/src/create-workspace-options.ts index 1b14c13ac0..54284061d7 100644 --- a/packages/create-nx-workspace/src/create-workspace-options.d.ts +++ b/packages/create-nx-workspace/src/create-workspace-options.ts @@ -1,9 +1,11 @@ import { PackageManager } from './utils/package-manager'; +import { CI } from './utils/ci/ci-list'; export interface CreateWorkspaceOptions { name: string; // Workspace name (e.g. org name) packageManager: PackageManager; // Package manager to use nxCloud: boolean; // Enable Nx Cloud + presetVersion?: string; // Version of the preset to use /** * @description Enable interactive mode with presets * @default true diff --git a/packages/create-nx-workspace/src/create-workspace.ts b/packages/create-nx-workspace/src/create-workspace.ts index cb9a0bccf0..d312213110 100644 --- a/packages/create-nx-workspace/src/create-workspace.ts +++ b/packages/create-nx-workspace/src/create-workspace.ts @@ -60,7 +60,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/create-nx-workspace/src/utils/preset/get-third-party-preset.ts b/packages/create-nx-workspace/src/utils/preset/get-third-party-preset.ts index 182bbf30c7..4b5aa13a6b 100644 --- a/packages/create-nx-workspace/src/utils/preset/get-third-party-preset.ts +++ b/packages/create-nx-workspace/src/utils/preset/get-third-party-preset.ts @@ -5,18 +5,19 @@ import { isKnownPreset } from './preset'; /** * This function is used to check if a preset is a third party preset. * @param preset - * @returns null if the preset is a known Nx preset or preset does not exist, the normalized preset otherwise. + * @returns null if the preset is a known Nx preset or preset does not exist, the package name of preset otherwise. */ export async function getThirdPartyPreset( preset?: string ): Promise { if (preset && !isKnownPreset(preset)) { + // extract the package name from the preset const packageName = preset.match(/.+@/) ? preset[0] + preset.substring(1).split('@')[0] : preset; const validateResult = validateNpmPackage(packageName); if (validateResult.validForNewPackages) { - return Promise.resolve(preset); + return Promise.resolve(packageName); } else { //! Error here output.error({ diff --git a/packages/js/src/utils/typescript/add-tslib-dependencies.ts b/packages/js/src/utils/typescript/add-tslib-dependencies.ts index 1c7ef65bbd..366018f67e 100644 --- a/packages/js/src/utils/typescript/add-tslib-dependencies.ts +++ b/packages/js/src/utils/typescript/add-tslib-dependencies.ts @@ -2,7 +2,7 @@ import { addDependenciesToPackageJson, Tree } from '@nx/devkit'; import { tsLibVersion } from '../versions'; export function addTsLibDependencies(tree: Tree) { - addDependenciesToPackageJson( + return addDependenciesToPackageJson( tree, { tslib: tsLibVersion, diff --git a/packages/nx-plugin/generators.json b/packages/nx-plugin/generators.json index 3d13c99a32..365be5458f 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 package which can be used by npx to create a new workspace" + }, "e2e-project": { "factory": "./src/generators/e2e-project/e2e", "schema": "./src/generators/e2e-project/schema.json", @@ -47,6 +52,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 package which can be used by npx to create a new workspace" + }, "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 a5c482e0a9..d2d89360fe 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/executors/e2e/schema.json b/packages/nx-plugin/src/executors/e2e/schema.json index 78581cc672..d830e71d9d 100644 --- a/packages/nx-plugin/src/executors/e2e/schema.json +++ b/packages/nx-plugin/src/executors/e2e/schema.json @@ -128,7 +128,8 @@ "runInBand": { "alias": "i", "description": "Run all tests serially in the current process (rather than creating a worker pool of child processes that run tests). This is sometimes useful for debugging, but such use cases are pretty rare. Useful for CI. (https://jestjs.io/docs/cli#--runinband)", - "type": "boolean" + "type": "boolean", + "default": true }, "showConfig": { "description": "Print your Jest config and then exits. (https://jestjs.io/docs/en/cli#--showconfig)", 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 0000000000..0b8a1f36c6 --- /dev/null +++ b/packages/nx-plugin/src/generators/create-package/create-package.spec.ts @@ -0,0 +1,119 @@ +import { + joinPathFragments, + readJson, + readProjectConfiguration, + Tree, +} from '@nx/devkit'; +import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing'; +import { Linter } from '@nx/linter'; +import { PackageJson } from 'nx/src/utils/package-json'; +import pluginGenerator from '../plugin/plugin'; +import { createPackageGenerator } from './create-package'; +import { CreatePackageSchema } from './schema'; + +const getSchema: ( + overrides?: Partial +) => CreatePackageSchema = (overrides = {}) => ({ + name: 'create-a-workspace', + project: 'my-plugin', + compiler: 'tsc', + skipTsConfig: false, + skipFormat: false, + skipLintChecks: false, + linter: Linter.EsLint, + unitTestRunner: 'jest', + ...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-a-workspace'); + expect(project.root).toEqual('libs/create-a-workspace'); + expect(project.sourceRoot).toEqual('libs/create-a-workspace/bin'); + expect(project.targets.build).toEqual({ + executor: '@nx/js:tsc', + outputs: ['{options.outputPath}'], + options: { + outputPath: 'dist/libs/create-a-workspace', + tsConfig: 'libs/create-a-workspace/tsconfig.lib.json', + main: 'libs/create-a-workspace/bin/index.ts', + assets: ['libs/create-a-workspace/*.md'], + updateBuildableProjectDepsInPackageJson: false, + }, + }); + }); + + 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-a-workspace' + ); + expect(project.root).toEqual('libs/plugins/create-a-workspace'); + }); + + it('should specify tsc as compiler', async () => { + await createPackageGenerator( + tree, + getSchema({ + compiler: 'tsc', + }) + ); + + const { build } = readProjectConfiguration( + tree, + 'create-a-workspace' + ).targets; + + expect(build.executor).toEqual('@nx/js:tsc'); + }); + + it('should specify swc as compiler', async () => { + await createPackageGenerator( + tree, + getSchema({ + compiler: 'swc', + }) + ); + + const { build } = readProjectConfiguration( + tree, + 'create-a-workspace' + ).targets; + + expect(build.executor).toEqual('@nx/js:swc'); + }); + + it("should use name as default for the package.json's name", async () => { + await createPackageGenerator(tree, getSchema()); + + const { root } = readProjectConfiguration(tree, 'create-a-workspace'); + const { name } = readJson( + tree, + joinPathFragments(root, 'package.json') + ); + + expect(name).toEqual('create-a-workspace'); + }); +}); 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 0000000000..0163b4e0c7 --- /dev/null +++ b/packages/nx-plugin/src/generators/create-package/create-package.ts @@ -0,0 +1,211 @@ +import { + addDependenciesToPackageJson, + readProjectConfiguration, + Tree, + generateFiles, + readJson, + convertNxGenerator, + formatFiles, + updateProjectConfiguration, + updateJson, + GeneratorCallback, + runTasksInSerial, + joinPathFragments, +} from '@nx/devkit'; +import { libraryGenerator as jsLibraryGenerator } from '@nx/js'; +import { nxVersion } from 'nx/src/utils/versions'; +import generatorGenerator from '../generator/generator'; +import { CreatePackageSchema } from './schema'; +import { NormalizedSchema, normalizeSchema } from './utils/normalize-schema'; +import e2eProjectGenerator from '../e2e-project/e2e'; +import { hasGenerator } from '../../utils/has-generator'; + +export async function createPackageGenerator( + host: Tree, + schema: CreatePackageSchema +) { + const tasks: GeneratorCallback[] = []; + + const options = normalizeSchema(host, schema); + const pluginPackageName = await addPresetGenerator(host, options); + + const installTask = addDependenciesToPackageJson( + host, + { + 'create-nx-workspace': nxVersion, + }, + {} + ); + tasks.push(installTask); + + await createCliPackage(host, options, pluginPackageName); + if (options.e2eTestRunner !== 'none') { + tasks.push(await addE2eProject(host, options)); + } + + if (!options.skipFormat) { + await formatFiles(host); + } + + return runTasksInSerial(...tasks); +} + +/** + * Add a preset generator to the plugin if it doesn't exist + * @param host + * @param schema + * @returns package name of the plugin + */ +async function addPresetGenerator( + host: Tree, + schema: NormalizedSchema +): Promise { + const { root: projectRoot } = readProjectConfiguration(host, schema.project); + if (!hasGenerator(host, schema.project, 'preset')) { + await generatorGenerator(host, { + name: 'preset', + project: schema.project, + unitTestRunner: schema.unitTestRunner, + skipFormat: true, + }); + } + + return readJson(host, joinPathFragments(projectRoot, 'package.json'))?.name; +} + +async function createCliPackage( + host: Tree, + options: NormalizedSchema, + pluginPackageName: string +) { + await jsLibraryGenerator(host, { + ...options, + rootProject: false, + config: 'project', + publishable: true, + bundler: options.bundler, + importPath: options.name, + skipFormat: true, + skipTsConfig: true, + }); + + host.delete(joinPathFragments(options.projectRoot, 'src')); + + // Add the bin entry to the package.json + updateJson( + host, + joinPathFragments(options.projectRoot, 'package.json'), + (packageJson) => { + packageJson.bin = { + [options.name]: './bin/index.js', + }; + packageJson.dependencies = { + 'create-nx-workspace': nxVersion, + }; + return packageJson; + } + ); + + // update project build target to use the bin entry + const projectConfiguration = readProjectConfiguration( + host, + options.projectName + ); + projectConfiguration.sourceRoot = joinPathFragments( + options.projectRoot, + 'bin' + ); + projectConfiguration.targets.build.options.main = joinPathFragments( + options.projectRoot, + 'bin/index.ts' + ); + projectConfiguration.targets.build.options.updateBuildableProjectDepsInPackageJson = + false; + projectConfiguration.implicitDependencies = [options.project]; + updateProjectConfiguration(host, options.projectName, projectConfiguration); + + // Add bin files to tsconfg.lib.json + updateJson( + host, + joinPathFragments(options.projectRoot, 'tsconfig.lib.json'), + (tsConfig) => { + tsConfig.include.push('bin/**/*.ts'); + return tsConfig; + } + ); + + generateFiles( + host, + joinPathFragments(__dirname, './files/create-framework-package'), + options.projectRoot, + { + ...options, + preset: pluginPackageName, + tmpl: '', + } + ); +} + +/** + * Add a test file to plugin e2e project + * @param host + * @param options + * @returns + */ +async function addE2eProject(host: Tree, options: NormalizedSchema) { + const pluginProjectConfiguration = readProjectConfiguration( + host, + options.project + ); + const pluginOutputPath = + pluginProjectConfiguration.targets.build.options.outputPath; + + const cliProjectConfiguration = readProjectConfiguration( + host, + options.projectName + ); + const cliOutputPath = + cliProjectConfiguration.targets.build.options.outputPath; + + const e2eTask = await e2eProjectGenerator(host, { + pluginName: options.projectName, + projectDirectory: options.projectDirectory, + pluginOutputPath, + npmPackageName: options.name, + skipFormat: true, + rootProject: false, + }); + + const e2eProjectConfiguration = readProjectConfiguration( + host, + `${options.projectName}-e2e` + ); + e2eProjectConfiguration.targets.e2e.dependsOn = ['^build']; + updateProjectConfiguration( + host, + e2eProjectConfiguration.name, + e2eProjectConfiguration + ); + + // delete the default e2e test file + host.delete(e2eProjectConfiguration.sourceRoot); + + generateFiles( + host, + joinPathFragments(__dirname, './files/e2e'), + e2eProjectConfiguration.sourceRoot, + { + ...options, + pluginOutputPath, + cliOutputPath, + tmpl: '', + } + ); + + return e2eTask; +} + +export default createPackageGenerator; +export const createPackageSchematic = convertNxGenerator( + createPackageGenerator +); diff --git a/packages/nx-plugin/src/generators/create-package/files/create-framework-package/bin/index.ts__tmpl__ b/packages/nx-plugin/src/generators/create-package/files/create-framework-package/bin/index.ts__tmpl__ new file mode 100644 index 0000000000..e76aec671a --- /dev/null +++ b/packages/nx-plugin/src/generators/create-package/files/create-framework-package/bin/index.ts__tmpl__ @@ -0,0 +1,26 @@ +#!/usr/bin/env node + +import { createWorkspace } from 'create-nx-workspace'; + +async function main() { + const name = process.argv[2]; // TODO: use libraries like yargs or enquirer to set your workspace name + if (!name) { + throw new Error('Please provide a name for the workspace'); + } + + console.log(`Creating the workspace: ${name}`); + + // TODO: update below to customize the workspace + const { directory } = await createWorkspace('<%= preset %>', { + name, + nxCloud: false, + packageManager: 'npm', + // This assumes "<%= preset %>" and "<%= projectName %>" are at the same version + // eslint-disable-next-line @typescript-eslint/no-var-requires + presetVersion: require('../package.json').version, + }); + + console.log(`Successfully created the workspace: ${directory}.`); +} + +main(); \ No newline at end of file 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 0000000000..e0e0f6adda --- /dev/null +++ b/packages/nx-plugin/src/generators/create-package/files/e2e/__name__.spec.ts__tmpl__ @@ -0,0 +1,30 @@ +import { + uniq, + runCreatePackageCli, + removeTmpProject, +} from '@nx/nx-plugin/testing'; + +describe('<%= name %> e2e', () => { + const project = uniq('<%= name %>'); + let createPackageResult; + + beforeAll(async () => { + // Create project using CLI command + createPackageResult = await runCreatePackageCli( + project, + { + pluginLibraryBuildPath: '<%= pluginOutputPath %>', + createPackageLibraryBuildPath: '<%= cliOutputPath %>', + } + ); + }, 240_000); + + afterAll(() => { + // Remove the generated project from the file system + removeTmpProject(project); + }); + + it('should create project using <%= name %>', () => { + expect(createPackageResult).toContain('Successfully created'); + }); +}); 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 0000000000..6acf4c1f77 --- /dev/null +++ b/packages/nx-plugin/src/generators/create-package/schema.d.ts @@ -0,0 +1,17 @@ +import type { Linter } from '@nx/linter'; + +export interface CreatePackageSchema { + name: string; + project: string; + + // options to create cli package, passed to js library generator + directory?: string; + skipFormat: boolean; + tags?: string; + unitTestRunner: 'jest' | 'none'; + linter: Linter; + compiler: 'swc' | 'tsc'; + + // options to create e2e project, passed to e2e project generator + e2eTestRunner?: 'jest' | 'none'; +} 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 0000000000..3892775419 --- /dev/null +++ b/packages/nx-plugin/src/generators/create-package/schema.json @@ -0,0 +1,69 @@ +{ + "$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 of cli, e.g. `create-framework-package`. 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" + }, + "compiler": { + "type": "string", + "enum": ["tsc", "swc"], + "default": "tsc", + "description": "The compiler used by the build and test targets." + }, + "e2eTestRunner": { + "type": "string", + "enum": ["jest", "none"], + "description": "Test runner to use for end to end (E2E) tests.", + "default": "jest" + } + }, + "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 0000000000..cb85d7f0f8 --- /dev/null +++ b/packages/nx-plugin/src/generators/create-package/utils/normalize-schema.ts @@ -0,0 +1,42 @@ +import { + extractLayoutDirectory, + getWorkspaceLayout, + joinPathFragments, + names, + Tree, +} from '@nx/devkit'; +import { CreatePackageSchema } from '../schema'; + +export interface NormalizedSchema extends CreatePackageSchema { + bundler: 'swc' | 'tsc'; + libsDir: string; + projectName: string; + projectRoot: string; + projectDirectory: string; +} + +export function normalizeSchema( + host: Tree, + schema: CreatePackageSchema +): NormalizedSchema { + const { layoutDirectory, projectDirectory } = extractLayoutDirectory( + schema.directory + ); + const { 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); + return { + ...schema, + bundler: schema.compiler ?? 'tsc', + libsDir, + projectName, + projectRoot, + name, + projectDirectory: fullProjectDirectory, + }; +} diff --git a/packages/nx-plugin/src/generators/e2e-project/e2e.ts b/packages/nx-plugin/src/generators/e2e-project/e2e.ts index 63784a4b3c..c2bea829b5 100644 --- a/packages/nx-plugin/src/generators/e2e-project/e2e.ts +++ b/packages/nx-plugin/src/generators/e2e-project/e2e.ts @@ -74,7 +74,7 @@ function updateWorkspaceConfiguration(host: Tree, options: NormalizedSchema) { addProjectConfiguration(host, options.projectName, { root: options.projectRoot, projectType: 'application', - sourceRoot: `${options.projectRoot}/src`, + sourceRoot: `${options.projectRoot}/tests`, targets: { e2e: { executor: '@nx/nx-plugin:e2e', diff --git a/packages/nx-plugin/src/generators/e2e-project/files/tests/__pluginName__.spec.ts__tmpl__ b/packages/nx-plugin/src/generators/e2e-project/files/tests/__pluginName__.spec.ts__tmpl__ index cd101be787..8f5570f4e0 100644 --- a/packages/nx-plugin/src/generators/e2e-project/files/tests/__pluginName__.spec.ts__tmpl__ +++ b/packages/nx-plugin/src/generators/e2e-project/files/tests/__pluginName__.spec.ts__tmpl__ @@ -32,6 +32,6 @@ describe('<%= pluginName %> e2e', () => { const generator = 'PLACEHOLDER'; await runNxCommandAsync(`generate <%= npmPackageName %>:${generator} --name ${name}`); expect(() => runNxCommand('build ${proj}')).not.toThrow(); - expect(() => checkFilesExist([`dist/${name}/index.js`])).not.toThrow(); + expect(() => checkFilesExist(`dist/${name}/index.js`)).not.toThrow(); }); }); diff --git a/packages/nx-plugin/src/generators/executor/executor.ts b/packages/nx-plugin/src/generators/executor/executor.ts index f24c6ece4b..d461931b3a 100644 --- a/packages/nx-plugin/src/generators/executor/executor.ts +++ b/packages/nx-plugin/src/generators/executor/executor.ts @@ -9,6 +9,7 @@ import { writeJson, readJson, ExecutorsJson, + formatFiles, } from '@nx/devkit'; import type { Tree } from '@nx/devkit'; import type { Schema } from './schema'; @@ -176,6 +177,10 @@ export async function executorGenerator(host: Tree, schema: Schema) { } await updateExecutorJson(host, options); + + if (!schema.skipFormat) { + await formatFiles(host); + } } export default executorGenerator; diff --git a/packages/nx-plugin/src/generators/executor/schema.d.ts b/packages/nx-plugin/src/generators/executor/schema.d.ts index 6946c85d5b..4e896a3f90 100644 --- a/packages/nx-plugin/src/generators/executor/schema.d.ts +++ b/packages/nx-plugin/src/generators/executor/schema.d.ts @@ -5,4 +5,5 @@ export interface Schema { unitTestRunner: 'jest' | 'none'; includeHasher: boolean; skipLintChecks?: boolean; + skipFormat?: boolean; } diff --git a/packages/nx-plugin/src/generators/executor/schema.json b/packages/nx-plugin/src/generators/executor/schema.json index e7d3f1b946..4337938fa6 100644 --- a/packages/nx-plugin/src/generators/executor/schema.json +++ b/packages/nx-plugin/src/generators/executor/schema.json @@ -51,6 +51,12 @@ "type": "boolean", "default": false, "description": "Do not add an eslint configuration for plugin json files." + }, + "skipFormat": { + "type": "boolean", + "description": "Skip formatting files.", + "default": false, + "x-priority": "internal" } }, "required": ["project", "name"], diff --git a/packages/nx-plugin/src/generators/generator/files/generator/__fileName__/generator.ts__tmpl__ b/packages/nx-plugin/src/generators/generator/files/generator/__fileName__/generator.ts__tmpl__ index ed05aae07e..f7a2c889f1 100644 --- a/packages/nx-plugin/src/generators/generator/files/generator/__fileName__/generator.ts__tmpl__ +++ b/packages/nx-plugin/src/generators/generator/files/generator/__fileName__/generator.ts__tmpl__ @@ -23,7 +23,10 @@ function normalizeOptions(tree: Tree, options: <%= className %>GeneratorSchema): ? `${names(options.directory).fileName}/${name}` : name; const projectName = projectDirectory.replace(new RegExp('/', 'g'), '-'); - const projectRoot = `${getWorkspaceLayout(tree).libsDir}/${projectDirectory}`; + const projectRoot = + getWorkspaceLayout(tree).libsDir === '.' + ? '.' + : `${getWorkspaceLayout(tree).libsDir}/${projectDirectory}`; const parsedTags = options.tags ? options.tags.split(',').map((s) => s.trim()) : []; diff --git a/packages/nx-plugin/src/generators/generator/generator.ts b/packages/nx-plugin/src/generators/generator/generator.ts index a54670b6d1..23936b80dd 100644 --- a/packages/nx-plugin/src/generators/generator/generator.ts +++ b/packages/nx-plugin/src/generators/generator/generator.ts @@ -4,8 +4,6 @@ import { joinPathFragments, Tree, writeJson, -} from '@nx/devkit'; -import { convertNxGenerator, generateFiles, getWorkspaceLayout, diff --git a/packages/nx-plugin/src/generators/plugin/plugin.spec.ts b/packages/nx-plugin/src/generators/plugin/plugin.spec.ts index 6abb31d39c..92f9a67866 100644 --- a/packages/nx-plugin/src/generators/plugin/plugin.spec.ts +++ b/packages/nx-plugin/src/generators/plugin/plugin.spec.ts @@ -7,6 +7,7 @@ import { } from '@nx/devkit'; import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing'; import { Linter } from '@nx/linter'; +import { PackageJson } from 'nx/src/utils/package-json'; import { pluginGenerator } from './plugin'; import { Schema } from './schema'; @@ -161,7 +162,7 @@ describe('NxPlugin Plugin Generator', () => { await pluginGenerator(tree, getSchema()); const { root } = readProjectConfiguration(tree, 'my-plugin'); - const { name } = readJson<{ name: string }>( + const { name } = readJson( tree, joinPathFragments(root, 'package.json') ); @@ -178,7 +179,7 @@ describe('NxPlugin Plugin Generator', () => { await pluginGenerator(tree, getSchema()); const { root } = readProjectConfiguration(tree, 'my-plugin'); - const { name } = readJson<{ name: string }>( + const { name } = readJson( tree, joinPathFragments(root, 'package.json') ); @@ -193,7 +194,7 @@ describe('NxPlugin Plugin Generator', () => { ); const { root } = readProjectConfiguration(tree, 'my-plugin'); - const { name } = readJson<{ name: string }>( + const { name } = readJson( tree, joinPathFragments(root, 'package.json') ); diff --git a/packages/nx-plugin/src/generators/plugin/plugin.ts b/packages/nx-plugin/src/generators/plugin/plugin.ts index fcb2bde66e..765ee97fd0 100644 --- a/packages/nx-plugin/src/generators/plugin/plugin.ts +++ b/packages/nx-plugin/src/generators/plugin/plugin.ts @@ -3,9 +3,10 @@ import { convertNxGenerator, formatFiles, generateFiles, - installPackagesTask, + GeneratorCallback, normalizePath, readProjectConfiguration, + runTasksInSerial, Tree, updateProjectConfiguration, } from '@nx/devkit'; @@ -73,55 +74,65 @@ function updatePluginConfig(host: Tree, options: NormalizedSchema) { export async function pluginGenerator(host: Tree, schema: Schema) { const options = normalizeOptions(host, schema); + const tasks: GeneratorCallback[] = []; - await jsLibraryGenerator(host, { - ...schema, - config: 'project', - bundler: options.bundler, - importPath: options.npmPackageName, - skipFormat: true, - }); + tasks.push( + await jsLibraryGenerator(host, { + ...schema, + config: 'project', + bundler: options.bundler, + publishable: true, + importPath: options.npmPackageName, + skipFormat: true, + }) + ); - addTsLibDependencies(host); + tasks.push(addTsLibDependencies(host)); - addDependenciesToPackageJson( - host, - { - '@nx/devkit': nxVersion, - }, - { - '@nx/jest': nxVersion, - '@nx/js': nxVersion, - '@nx/nx-plugin': nxVersion, - } + tasks.push( + addDependenciesToPackageJson( + host, + { + '@nx/devkit': nxVersion, + }, + { + '@nx/jest': nxVersion, + '@nx/js': nxVersion, + '@nx/nx-plugin': nxVersion, + } + ) ); // Ensures Swc Deps are installed to handle running // local plugin generators and executors - addSwcDependencies(host); - addSwcRegisterDependencies(host); + tasks.push(addSwcDependencies(host)); + tasks.push(addSwcRegisterDependencies(host)); await addFiles(host, options); updatePluginConfig(host, options); if (options.e2eTestRunner !== 'none') { - await e2eProjectGenerator(host, { - pluginName: options.name, - projectDirectory: options.projectDirectory, - pluginOutputPath: `dist/${options.libsDir}/${options.projectDirectory}`, - npmPackageName: options.npmPackageName, - skipFormat: true, - rootProject: options.rootProject, - }); + tasks.push( + await e2eProjectGenerator(host, { + pluginName: options.name, + projectDirectory: options.projectDirectory, + pluginOutputPath: `dist/${options.libsDir}/${options.projectDirectory}`, + npmPackageName: options.npmPackageName, + skipFormat: true, + rootProject: options.rootProject, + }) + ); } if (options.linter === Linter.EsLint && !options.skipLintChecks) { await pluginLintCheckGenerator(host, { projectName: options.name }); } - await formatFiles(host); + if (!options.skipFormat) { + await formatFiles(host); + } - return () => installPackagesTask(host); + return runTasksInSerial(...tasks); } export default pluginGenerator; diff --git a/packages/nx-plugin/src/generators/plugin/schema.d.ts b/packages/nx-plugin/src/generators/plugin/schema.d.ts index 5de5ce4613..294249c817 100644 --- a/packages/nx-plugin/src/generators/plugin/schema.d.ts +++ b/packages/nx-plugin/src/generators/plugin/schema.d.ts @@ -4,9 +4,9 @@ export interface Schema { name: string; directory?: string; importPath?: string; - skipTsConfig: boolean; - skipFormat: boolean; - skipLintChecks: boolean; + skipTsConfig?: boolean; // default is false + skipFormat?: boolean; // default is false + skipLintChecks?: boolean; // default is false e2eTestRunner?: 'jest' | 'none'; tags?: string; unitTestRunner: 'jest' | 'none'; diff --git a/packages/nx-plugin/src/generators/preset/generator.ts b/packages/nx-plugin/src/generators/preset/generator.ts index b374968a45..533cc9c86c 100644 --- a/packages/nx-plugin/src/generators/preset/generator.ts +++ b/packages/nx-plugin/src/generators/preset/generator.ts @@ -1,34 +1,58 @@ -import { Tree, updateJson, updateNxJson, readNxJson } from '@nx/devkit'; +import { + Tree, + updateJson, + updateNxJson, + readNxJson, + formatFiles, + runTasksInSerial, + GeneratorCallback, +} from '@nx/devkit'; import { Linter } from '@nx/linter'; import { PackageJson } from 'nx/src/utils/package-json'; import { pluginGenerator } from '../plugin/plugin'; import { PresetGeneratorSchema } from './schema'; +import createPackageGenerator from '../create-package/create-package'; export default async function (tree: Tree, options: PresetGeneratorSchema) { - const task = await pluginGenerator(tree, { + const tasks: GeneratorCallback[] = []; + const pluginTask = await pluginGenerator(tree, { compiler: 'tsc', linter: Linter.EsLint, name: options.pluginName.includes('/') ? options.pluginName.split('/')[1] : options.pluginName, - skipFormat: false, - skipLintChecks: false, - skipTsConfig: false, + skipFormat: true, unitTestRunner: 'jest', importPath: options.pluginName, rootProject: true, e2eTestRunner: 'jest', }); + tasks.push(pluginTask); removeNpmScope(tree); moveNxPluginToDevDeps(tree); - return task; + if (options.createPackageName) { + const cliTask = await createPackageGenerator(tree, { + name: options.createPackageName, + project: options.pluginName, + skipFormat: true, + unitTestRunner: 'jest', + linter: Linter.EsLint, + compiler: 'tsc', + }); + tasks.push(cliTask); + } + + await formatFiles(tree); + + return runTasksInSerial(...tasks); } function removeNpmScope(tree: Tree) { updateNxJson(tree, { ...readNxJson(tree), npmScope: undefined }); } + function moveNxPluginToDevDeps(tree: Tree) { updateJson(tree, 'package.json', (json) => { const nxPluginEntry = json.dependencies['@nx/nx-plugin']; diff --git a/packages/nx-plugin/src/generators/preset/schema.d.ts b/packages/nx-plugin/src/generators/preset/schema.d.ts index bff8df3c05..b7ed9dfbb5 100644 --- a/packages/nx-plugin/src/generators/preset/schema.d.ts +++ b/packages/nx-plugin/src/generators/preset/schema.d.ts @@ -1,3 +1,4 @@ export interface PresetGeneratorSchema { pluginName: string; + createPackageName?: string; } diff --git a/packages/nx-plugin/src/generators/preset/schema.json b/packages/nx-plugin/src/generators/preset/schema.json index bc224d8091..b59a3f317c 100644 --- a/packages/nx-plugin/src/generators/preset/schema.json +++ b/packages/nx-plugin/src/generators/preset/schema.json @@ -10,6 +10,10 @@ "type": "string", "description": "Plugin name", "aliases": ["name"] + }, + "createPackageName": { + "type": "string", + "description": "Name of package which creates a workspace" } }, "required": ["pluginName"] diff --git a/packages/nx-plugin/src/utils/testing-utils/async-commands.ts b/packages/nx-plugin/src/utils/testing-utils/async-commands.ts index 2eaa0977fe..718b061df5 100644 --- a/packages/nx-plugin/src/utils/testing-utils/async-commands.ts +++ b/packages/nx-plugin/src/utils/testing-utils/async-commands.ts @@ -11,7 +11,7 @@ import { fileExists } from './utils'; */ export function runCommandAsync( command: string, - opts: { silenceError?: boolean; env?: NodeJS.ProcessEnv } = { + opts: { silenceError?: boolean; env?: NodeJS.ProcessEnv; cwd?: string } = { silenceError: false, } ): Promise<{ stdout: string; stderr: string }> { @@ -19,7 +19,7 @@ export function runCommandAsync( exec( command, { - cwd: tmpProjPath(), + cwd: opts.cwd ?? tmpProjPath(), env: { ...process.env, ...opts.env }, }, (err, stdout, stderr) => { @@ -39,7 +39,7 @@ export function runCommandAsync( */ export function runNxCommandAsync( command: string, - opts: { silenceError?: boolean; env?: NodeJS.ProcessEnv } = { + opts: { silenceError?: boolean; env?: NodeJS.ProcessEnv; cwd?: string } = { silenceError: false, } ): Promise<{ stdout: string; stderr: string }> { diff --git a/packages/nx-plugin/src/utils/testing-utils/commands.ts b/packages/nx-plugin/src/utils/testing-utils/commands.ts index 62602a4221..1cbbee3b97 100644 --- a/packages/nx-plugin/src/utils/testing-utils/commands.ts +++ b/packages/nx-plugin/src/utils/testing-utils/commands.ts @@ -12,13 +12,13 @@ import { fileExists } from './utils'; */ export function runNxCommand( command?: string, - opts: { silenceError?: boolean; env?: NodeJS.ProcessEnv } = { + opts: { silenceError?: boolean; env?: NodeJS.ProcessEnv; cwd?: string } = { silenceError: false, } ): string { function _runNxCommand(c) { const execSyncOptions: ExecOptions = { - cwd: tmpProjPath(), + cwd: opts.cwd, env: { ...process.env, ...opts.env }, }; if (fileExists(tmpProjPath('package.json'))) { @@ -50,11 +50,11 @@ export function runNxCommand( export function runCommand( command: string, - opts?: { env?: NodeJS.ProcessEnv } + opts: { env?: NodeJS.ProcessEnv; cwd?: string } ): string { try { return execSync(command, { - cwd: tmpProjPath(), + cwd: opts.cwd ?? tmpProjPath(), stdio: ['pipe', 'pipe', 'pipe'], env: { ...process.env, ...opts?.env }, }).toString(); diff --git a/packages/nx-plugin/src/utils/testing-utils/create-package-cli.ts b/packages/nx-plugin/src/utils/testing-utils/create-package-cli.ts new file mode 100644 index 0000000000..b983fe590f --- /dev/null +++ b/packages/nx-plugin/src/utils/testing-utils/create-package-cli.ts @@ -0,0 +1,72 @@ +import { workspaceRoot } from '@nx/devkit'; +import { tmpFolder } from './paths'; +import { fork } from 'child_process'; + +/** + * This function is used to run the create package CLI command. + * It builds the plugin library and the create package library and run the create package command with for the plugin library. + * It needs to be ran inside an Nx project. It would assume that an Nx project already exists. + * @param projectToBeCreated project name to be created using the cli + * @param pluginLibraryBuildPath e.g. dist/packages/my-plugin + * @param createPackageLibraryBuildPath e.g. dist/packages/create-my-plugin-package + * @param extraArguments extra arguments to be passed to the create package command + * @param verbose if true, NX_VERBOSE_LOGGING will be set to true + * @returns results for the create package command + */ +export function runCreatePackageCli( + projectToBeCreated: string, + { + pluginLibraryBuildPath, + createPackageLibraryBuildPath, + extraArguments, + verbose, + }: { + pluginLibraryBuildPath: string; + createPackageLibraryBuildPath: string; + extraArguments?: string[]; + verbose?: boolean; + } +): Promise { + return new Promise((resolve, reject) => { + const childProcess = fork( + `${workspaceRoot}/${createPackageLibraryBuildPath}/bin/index.js`, + [projectToBeCreated, ...(extraArguments || [])], + { + stdio: ['pipe', 'pipe', 'pipe', 'ipc'], + env: { + ...process.env, + [`NX_E2E_PRESET_VERSION`]: `file:${workspaceRoot}/${pluginLibraryBuildPath}`, + // only add NX_VERBOSE_LOGGING if verbose is true + ...(verbose && { NX_VERBOSE_LOGGING: 'true' }), + }, + cwd: tmpFolder(), + } + ); + + // Ensure the child process is killed when the parent exits + process.on('exit', () => childProcess.kill()); + process.on('SIGTERM', () => childProcess.kill()); + + let allMessages = ''; + childProcess.on('message', (message) => { + allMessages += message; + }); + childProcess.stdout.on('data', (data) => { + allMessages += data; + }); + childProcess.on('error', (error) => { + reject(error); + }); + childProcess.on('exit', (code) => { + if (code === 0) { + resolve(allMessages); + } else { + reject(allMessages); + } + }); + }); +} + +export function generatedPackagePath(projectToBeCreated: string) { + return `${tmpFolder()}/${projectToBeCreated}`; +} diff --git a/packages/nx-plugin/src/utils/testing-utils/index.ts b/packages/nx-plugin/src/utils/testing-utils/index.ts index 81414ad685..2541b07012 100644 --- a/packages/nx-plugin/src/utils/testing-utils/index.ts +++ b/packages/nx-plugin/src/utils/testing-utils/index.ts @@ -1,5 +1,6 @@ export * from './async-commands'; export * from './commands'; +export * from './create-package-cli'; export * from './paths'; export * from './nx-project'; export * from './utils'; diff --git a/packages/nx-plugin/src/utils/testing-utils/paths.ts b/packages/nx-plugin/src/utils/testing-utils/paths.ts index a743052272..0cd239f9ac 100644 --- a/packages/nx-plugin/src/utils/testing-utils/paths.ts +++ b/packages/nx-plugin/src/utils/testing-utils/paths.ts @@ -1,3 +1,9 @@ +import { workspaceRoot } from '@nx/devkit'; + +export function tmpFolder() { + return `${workspaceRoot}/tmp`; +} + /** * The directory where the e2e workspace resides in. * @@ -6,8 +12,8 @@ */ export function tmpProjPath(path?: string) { return path - ? `${process.cwd()}/tmp/nx-e2e/proj/${path}` - : `${process.cwd()}/tmp/nx-e2e/proj`; + ? `${tmpFolder()}/nx-e2e/proj/${path}` + : `${tmpFolder()}/nx-e2e/proj`; } /** @@ -18,6 +24,6 @@ export function tmpProjPath(path?: string) { */ export function tmpBackupProjPath(path?: string) { return path - ? `${process.cwd()}/tmp/nx-e2e/proj-backup/${path}` - : `${process.cwd()}/tmp/nx-e2e/proj-backup`; + ? `${workspaceRoot}/tmp/nx-e2e/proj-backup/${path}` + : `${workspaceRoot}/tmp/nx-e2e/proj-backup`; } diff --git a/packages/nx-plugin/src/utils/testing-utils/utils.ts b/packages/nx-plugin/src/utils/testing-utils/utils.ts index f6a9b5741c..33e1c1a341 100644 --- a/packages/nx-plugin/src/utils/testing-utils/utils.ts +++ b/packages/nx-plugin/src/utils/testing-utils/utils.ts @@ -9,7 +9,7 @@ import { writeFileSync, } from 'fs-extra'; import { dirname, isAbsolute } from 'path'; -import { tmpProjPath } from './paths'; +import { tmpFolder, tmpProjPath } from './paths'; import { parseJson } from '@nx/devkit'; import type { JsonParseOptions } from '@nx/devkit'; import { directoryExists, fileExists } from 'nx/src/utils/fileutils'; @@ -135,6 +135,10 @@ export function rmDist(): void { removeSync(`${tmpProjPath()}/dist`); } +export function removeTmpProject(project: string): void { + removeSync(`${tmpFolder()}/${project}`); +} + /** * Get the currend `cwd` in the process */ diff --git a/packages/react/src/module-federation/with-module-federation.ts b/packages/react/src/module-federation/with-module-federation.ts index 28a341d4f8..f1c1e185a6 100644 --- a/packages/react/src/module-federation/with-module-federation.ts +++ b/packages/react/src/module-federation/with-module-federation.ts @@ -2,7 +2,7 @@ import { ModuleFederationConfig } from '@nx/devkit'; import { readCachedProjectConfiguration } from 'nx/src/project-graph/project-graph'; import { getModuleFederationConfig } from './utils'; import ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin'); -import type { AsyncNxWebpackPlugin, NxWebpackPlugin } from '@nx/webpack'; +import type { AsyncNxWebpackPlugin } from '@nx/webpack'; function determineRemoteUrl(remote: string) { const remoteConfiguration = readCachedProjectConfiguration(remote); diff --git a/packages/workspace/src/generators/preset/schema.json b/packages/workspace/src/generators/preset/schema.json index 9e775f8dde..f1af8260d0 100644 --- a/packages/workspace/src/generators/preset/schema.json +++ b/packages/workspace/src/generators/preset/schema.json @@ -94,5 +94,6 @@ "type": "string", "enum": ["cypress", "jest", "detox", "none"] } - } + }, + "required": ["preset", "name"] }