diff --git a/.claude/skills/run-nx-generator/SKILL.md b/.claude/skills/run-nx-generator/SKILL.md index 29d91175fb5..0feccb595bf 100644 --- a/.claude/skills/run-nx-generator/SKILL.md +++ b/.claude/skills/run-nx-generator/SKILL.md @@ -67,15 +67,6 @@ nx generate @nx/workspace-plugin:bump-maven-version \ This automates all the version bumping instead of manual file edits. -### Creating a New Plugin - -For creating a new create-nodes plugin: - -```bash -nx generate @nx/workspace-plugin:create-nodes-plugin \ - --name my-custom-plugin -``` - ## When to Use This Skill Use this skill when you need to: diff --git a/packages/devkit/src/migrations/update-23-0-0/update-deep-imports.md b/packages/devkit/src/migrations/update-23-0-0/update-deep-imports.md index dcc72ffa1b6..35fd65708fb 100644 --- a/packages/devkit/src/migrations/update-23-0-0/update-deep-imports.md +++ b/packages/devkit/src/migrations/update-23-0-0/update-deep-imports.md @@ -30,7 +30,7 @@ import { dasherize, addPlugin } from '@nx/devkit/internal'; #### Fallback for non-named imports -For deep-import shapes that can't be split by symbol — default imports, namespace imports, side-effect imports, `require(...)` calls, and dynamic `import(...)` — the migration rewrites the specifier to `@nx/devkit/internal` as a best guess, since most symbols that previously lived under `@nx/devkit/src/...` ended up there. +For deep-import shapes that can't be split by symbol — default imports, namespace imports, side-effect imports, `require(...)` calls, dynamic `import(...)`, and `jest.mock(...)` / `vi.mock(...)`-style mock-helper calls — the migration rewrites the specifier to `@nx/devkit/internal` as a best guess, since most symbols that previously lived under `@nx/devkit/src/...` ended up there. ```ts // Before @@ -40,4 +40,4 @@ const { dasherize } = require('@nx/devkit/src/utils/string-utils'); const { dasherize } = require('@nx/devkit/internal'); ``` -If the symbol you're after is part of the stable public API instead, the rewritten import will fail to resolve against `@nx/devkit/internal` — switch it to `@nx/devkit` by hand. +If the symbol you're after is part of the stable public API instead, the rewritten import will fail to resolve against `@nx/devkit/internal` — switch it to `@nx/devkit` by hand. The migration also leaves `typeof import('@nx/devkit/src/...')` type queries and any deep-import strings inside template literals or comments untouched, so you'll need to update those by hand. diff --git a/packages/devkit/src/migrations/update-23-0-0/update-deep-imports.spec.ts b/packages/devkit/src/migrations/update-23-0-0/update-deep-imports.spec.ts index ca83fea046d..dc05cac145b 100644 --- a/packages/devkit/src/migrations/update-23-0-0/update-deep-imports.spec.ts +++ b/packages/devkit/src/migrations/update-23-0-0/update-deep-imports.spec.ts @@ -140,6 +140,80 @@ describe('update-deep-imports migration', () => { }); }); + // These tests guard against an earlier implementation that string-replaced + // every `'@nx/devkit/src/...'` literal in the file. That over-eager rewrite + // mangled test fixtures inside template literals, type queries, comments, + // and other non-runtime usages. The current AST-based pass must leave them + // alone. + describe('non-runtime string literals', () => { + it('does not rewrite deep-import paths inside template literals', () => { + const input = `const fixture = \`import { dasherize } from '@nx/devkit/src/utils/string-utils';\\n\`;\n`; + expect(rewriteDevkitDeepImports(input)).toBe(input); + }); + + it('does not rewrite deep-import paths inside `typeof import(...)` type queries', () => { + const input = `type Devkit = typeof import('@nx/devkit/src/executors/parse-target-string');\n`; + expect(rewriteDevkitDeepImports(input)).toBe(input); + }); + + it('does not rewrite deep-import paths inside block comments', () => { + const input = `/* see @nx/devkit/src/utils/foo */\nconst x = 1;\n`; + expect(rewriteDevkitDeepImports(input)).toBe(input); + }); + + it('does not rewrite deep-import paths inside line comments', () => { + const input = `// see '@nx/devkit/src/utils/foo'\nconst x = 1;\n`; + expect(rewriteDevkitDeepImports(input)).toBe(input); + }); + + it('does not rewrite arbitrary string-literal arguments to unrelated calls', () => { + const input = `someUnrelatedFn('@nx/devkit/src/utils/foo');\n`; + expect(rewriteDevkitDeepImports(input)).toBe(input); + }); + }); + + describe('mock helper calls', () => { + it('rewrites jest.mock(...) targets', () => { + const input = `jest.mock('@nx/devkit/src/utils/foo');\n`; + expect(rewriteDevkitDeepImports(input)).toBe( + `jest.mock('@nx/devkit/internal');\n` + ); + }); + + it('rewrites jest.requireActual(...) targets', () => { + const input = `const real = jest.requireActual('@nx/devkit/src/utils/foo');\n`; + expect(rewriteDevkitDeepImports(input)).toBe( + `const real = jest.requireActual('@nx/devkit/internal');\n` + ); + }); + + it('rewrites vi.mock(...) targets', () => { + const input = `vi.mock('@nx/devkit/src/utils/foo');\n`; + expect(rewriteDevkitDeepImports(input)).toBe( + `vi.mock('@nx/devkit/internal');\n` + ); + }); + + it('rewrites vi.importActual(...) targets', () => { + const input = `const real = await vi.importActual('@nx/devkit/src/utils/foo');\n`; + expect(rewriteDevkitDeepImports(input)).toBe( + `const real = await vi.importActual('@nx/devkit/internal');\n` + ); + }); + + it('rewrites paired import + jest.mock together', () => { + const input = + `import * as cfg from '@nx/devkit/src/utils/config-utils';\n` + + `jest.mock('@nx/devkit/src/utils/config-utils', () => ({\n` + + ` ...jest.requireActual('@nx/devkit/src/utils/config-utils'),\n` + + `}));\n`; + const output = rewriteDevkitDeepImports(input); + expect(output).toContain(`import * as cfg from '@nx/devkit/internal';`); + expect(output).toContain(`jest.mock('@nx/devkit/internal',`); + expect(output).toContain(`jest.requireActual('@nx/devkit/internal')`); + }); + }); + describe('migration runner', () => { it('rewrites deep imports across .ts/.tsx/.cts/.mts files', async () => { tree.write( diff --git a/packages/devkit/src/migrations/update-23-0-0/update-deep-imports.ts b/packages/devkit/src/migrations/update-23-0-0/update-deep-imports.ts index 1bfc96e75c7..5ee600d1f7c 100644 --- a/packages/devkit/src/migrations/update-23-0-0/update-deep-imports.ts +++ b/packages/devkit/src/migrations/update-23-0-0/update-deep-imports.ts @@ -1,8 +1,10 @@ import { logger, type Tree } from 'nx/src/devkit-exports'; import type { + CallExpression, ImportDeclaration, ImportSpecifier, NamedImports, + Node, SourceFile, } from 'typescript'; import { formatFiles } from '../../generators/format-files'; @@ -69,7 +71,19 @@ export const DEVKIT_INTERNAL_SYMBOLS: ReadonlySet = new Set([ 'dasherize', ]); -const FALLBACK_RE = /(['"])@nx\/devkit\/src\/[^'"\n]+?\1/g; +// Methods on `jest` and `vi` that take a module specifier as their first +// argument. Calls like `jest.mock('@nx/devkit/src/...')` are rewritten so the +// mock target lines up with the rewritten import. +const MOCK_HELPER_METHODS: ReadonlySet = new Set([ + 'mock', + 'unmock', + 'doMock', + 'dontMock', + 'requireActual', + 'requireMock', + 'importActual', + 'importMock', +]); let ts: typeof import('typescript') | undefined; @@ -134,19 +148,17 @@ export function rewriteDevkitDeepImports(source: string): string { ); } + // Pass 2: rewrite `require('@nx/devkit/src/...')`, dynamic + // `import('@nx/devkit/src/...')`, and `jest.mock(...)` / `vi.mock(...)`-style + // calls. We can't bucket these by symbol (no named binding to inspect), so + // we route them at `/internal` as a best guess. Walking the AST instead of + // string-replacing keeps us out of unrelated string literals — template + // strings, `typeof import('...')` type queries, comments, etc. + collectCallExpressionRewrites(sourceFile, changes); + let updated = changes.length > 0 ? applyChangesToString(source, changes) : source; - // Fallback: any remaining `@nx/devkit/src/...` specifiers (default imports, - // namespace imports, side-effect imports, dynamic `import(...)`, `require(...)` - // calls, etc.) get rewritten to `/internal`. We can't bucket by symbol for - // those forms, so `/internal` is the safe default since it re-exports every - // symbol that was deep-importable. - updated = updated.replace( - FALLBACK_RE, - (_match, quote: string) => `${quote}${INTERNAL_SPECIFIER}${quote}` - ); - // Final pass: collapse any duplicate `@nx/devkit` and `@nx/devkit/internal` // named-only imports into a single declaration. This handles both the // imports we just emitted AND any that the user already had, so we never @@ -341,3 +353,60 @@ function renderImport( function source(decl: ImportDeclaration, sourceFile: SourceFile): string { return sourceFile.text.slice(decl.getStart(sourceFile), decl.getEnd()); } + +function collectCallExpressionRewrites( + sourceFile: SourceFile, + changes: StringChange[] +): void { + const visit = (node: Node): void => { + if ( + ts!.isCallExpression(node) && + shouldRewriteCallExpression(node) && + node.arguments.length >= 1 && + ts!.isStringLiteral(node.arguments[0]) && + node.arguments[0].text.startsWith(DEEP_IMPORT_PREFIX) + ) { + const arg = node.arguments[0]; + const start = arg.getStart(sourceFile); + const end = arg.getEnd(); + const quote = sourceFile.text.charAt(start); + changes.push( + { + type: ChangeType.Delete, + start, + length: end - start, + }, + { + type: ChangeType.Insert, + index: start, + text: `${quote}${INTERNAL_SPECIFIER}${quote}`, + } + ); + } + ts!.forEachChild(node, visit); + }; + visit(sourceFile); +} + +function shouldRewriteCallExpression(call: CallExpression): boolean { + const callee = call.expression; + // `require('...')` + if (ts!.isIdentifier(callee) && callee.text === 'require') return true; + // dynamic `import('...')` — the runtime form parses as a CallExpression + // whose callee is the `import` keyword. The type-position form + // (`typeof import('...')`) parses as `ImportTypeNode`, not a CallExpression, + // so we don't touch it. + if (callee.kind === ts!.SyntaxKind.ImportKeyword) return true; + // `jest.mock(...)` / `vi.mock(...)` and friends. + if (ts!.isPropertyAccessExpression(callee)) { + const obj = callee.expression; + if ( + ts!.isIdentifier(obj) && + (obj.text === 'jest' || obj.text === 'vi') && + MOCK_HELPER_METHODS.has(callee.name.text) + ) { + return true; + } + } + return false; +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index de274e2a41e..c24c8a619f7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -4182,9 +4182,6 @@ importers: '@nx/js': specifier: 23.0.0-beta.4 version: 23.0.0-beta.4(@babel/traverse@7.29.0)(@swc-node/register@1.11.1(@swc/core@1.15.10(@swc/helpers@0.5.18))(@swc/types@0.1.25)(typescript@5.9.2))(@swc/core@1.15.10(@swc/helpers@0.5.18))(nx@23.0.0-beta.4(@swc-node/register@1.11.1(@swc/core@1.15.10(@swc/helpers@0.5.18))(@swc/types@0.1.25)(typescript@5.9.2))(@swc/core@1.15.10(@swc/helpers@0.5.18)))(verdaccio@6.3.2(encoding@0.1.13)(typanion@3.14.0)) - '@nx/plugin': - specifier: 23.0.0-beta.4 - version: 23.0.0-beta.4(@babel/traverse@7.29.0)(@swc-node/register@1.11.1(@swc/core@1.15.10(@swc/helpers@0.5.18))(@swc/types@0.1.25)(typescript@5.9.2))(@swc/core@1.15.10(@swc/helpers@0.5.18))(@types/node@20.19.19)(@zkochan/js-yaml@0.0.7)(babel-plugin-macros@3.1.0)(esbuild-register@3.6.0(esbuild@0.25.0))(eslint@9.39.4(jiti@2.6.1))(nx@23.0.0-beta.4(@swc-node/register@1.11.1(@swc/core@1.15.10(@swc/helpers@0.5.18))(@swc/types@0.1.25)(typescript@5.9.2))(@swc/core@1.15.10(@swc/helpers@0.5.18)))(ts-node@10.9.1(@swc/core@1.15.10(@swc/helpers@0.5.18))(@types/node@20.19.19)(typescript@5.9.2))(typescript@5.9.2)(verdaccio@6.3.2(encoding@0.1.13)(typanion@3.14.0)) '@xmldom/xmldom': specifier: ^0.8.10 version: 0.8.11 diff --git a/tools/workspace-plugin/generators.json b/tools/workspace-plugin/generators.json index cab7ad923fe..221591f02ab 100644 --- a/tools/workspace-plugin/generators.json +++ b/tools/workspace-plugin/generators.json @@ -5,11 +5,6 @@ "schema": "./src/generators/remove-migrations/schema.json", "description": "remove-migrations generator" }, - "create-nodes-plugin": { - "factory": "./src/generators/create-nodes-plugin/generator", - "schema": "./src/generators/create-nodes-plugin/schema.json", - "description": "Workspace Generator to create a create-nodes-plugin" - }, "bump-maven-version": { "factory": "./src/generators/bump-maven-version/generator", "schema": "./src/generators/bump-maven-version/schema.json", diff --git a/tools/workspace-plugin/package.json b/tools/workspace-plugin/package.json index 001cba5947e..ada4e553ebf 100644 --- a/tools/workspace-plugin/package.json +++ b/tools/workspace-plugin/package.json @@ -8,7 +8,6 @@ "@nx/conformance": "5.0.4", "@nx/devkit": "23.0.0-beta.4", "@nx/js": "23.0.0-beta.4", - "@nx/plugin": "23.0.0-beta.4", "@xmldom/xmldom": "^0.8.10", "glob": "7.1.4", "semver": "catalog:", diff --git a/tools/workspace-plugin/src/generators/create-nodes-plugin/__snapshots__/generator.spec.ts.snap b/tools/workspace-plugin/src/generators/create-nodes-plugin/__snapshots__/generator.spec.ts.snap deleted file mode 100644 index e31ebbd6cae..00000000000 --- a/tools/workspace-plugin/src/generators/create-nodes-plugin/__snapshots__/generator.spec.ts.snap +++ /dev/null @@ -1,277 +0,0 @@ -// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing - -exports[`create-nodes-plugin/generator generator should add the plugin path to package.json exports 1`] = ` -"{ - "name": "@nx/eslint", - "version": "0.0.1", - "private": false, - "description": "Some description", - "repository": { - "type": "git", - "url": "https://github.com/nrwl/nx.git", - "directory": "packages/eslint" - }, - "keywords": [ - "Monorepo", - "eslint", - "Web", - "CLI" - ], - "main": "./index", - "typings": "./index.d.ts", - "author": "Victor Savkin", - "license": "MIT", - "bugs": { - "url": "https://github.com/nrwl/nx/issues" - }, - "homepage": "https://nx.dev", - "generators": "./generators.json", - "executors": "./executors.json", - "ng-update": { - "requirements": {}, - "migrations": "./migrations.json" - }, - "dependencies": { - "@nx/devkit": "file:../devkit" - }, - "peerDependencies": {}, - "publishConfig": { - "access": "public" - }, - "exports": { - ".": "./index.js", - "./package.json": "./package.json", - "./migrations.json": "./migrations.json", - "./generators.json": "./generators.json", - "./executors.json": "./executors.json", - "./executors": "./executors.js", - "./src/executors/*/schema.json": "./src/executors/*/schema.json", - "./src/executors/*.impl": "./src/executors/*.impl.js", - "./src/executors/*/compat": "./src/executors/*/compat.js", - "./plugin": "./plugin.js" - } -} -" -`; - -exports[`create-nodes-plugin/generator generator should run successfully 1`] = ` -"import { - CreateNodes, - CreateNodesContext, - TargetConfiguration, -} from '@nx/devkit'; -import { basename, dirname, extname, join, resolve } from 'path'; -import { registerTsProject } from '@nx/js/src/internal'; - -import { getRootTsConfigPath } from '@nx/js'; - -import { readTargetDefaultsForTarget } from 'nx/src/project-graph/utils/project-configuration-utils'; -import { getNamedInputs } from '@nx/devkit/src/utils/get-named-inputs'; -import { readdirSync } from 'fs'; - -export interface EslintPluginOptions { - targetName?: string; -} - -export const createNodes: CreateNodes = [ - '**/TODO', - (configFilePath, options, context) => { - const projectRoot = dirname(configFilePath); - - // Do not create a project if package.json and project.json isn't there. - const siblingFiles = readdirSync(join(context.workspaceRoot, projectRoot)); - if ( - !siblingFiles.includes('package.json') && - !siblingFiles.includes('project.json') - ) { - return {}; - } - - options = normalizeOptions(options); - - return { - projects: { - [projectRoot]: { - root: projectRoot, - projectType: 'library', - targets: buildEslintTargets( - configFilePath, - projectRoot, - options, - context, - ), - }, - }, - }; - }, -]; - -function buildEslintTargets( - configFilePath: string, - projectRoot: string, - options: EslintPluginOptions, - context: CreateNodesContext, -) { - const eslintConfig = getEslintConfig(configFilePath, context); - - const targetDefaults = readTargetDefaultsForTarget( - options.targetName, - context.nxJsonConfiguration.targetDefaults, - 'executorName', - ); - - const namedInputs = getNamedInputs(projectRoot, context); - - const targets: Record> = {}; - - const baseTargetConfig: TargetConfiguration = { - executor: 'executorName', - options: { - config: configFilePath, - ...targetDefaults?.options, - }, - }; - - targets[options.targetName] = { - ...baseTargetConfig, - cache: targetDefaults?.cache ?? true, - inputs: - (targetDefaults?.inputs ?? 'production' in namedInputs) - ? ['default', '^production'] - : ['default', '^default'], - outputs: targetDefaults?.outputs ?? getOutputs(projectRoot), - options: { - ...baseTargetConfig.options, - }, - }; - - return targets; -} - -function getEslintConfig( - configFilePath: string, - context: CreateNodesContext, -): any { - const resolvedPath = join(context.workspaceRoot, configFilePath); - - let module: any; - if (extname(configFilePath) === '.ts') { - const tsConfigPath = getRootTsConfigPath(); - - if (tsConfigPath) { - const unregisterTsProject = registerTsProject(tsConfigPath); - try { - module = require(resolvedPath); - } finally { - unregisterTsProject(); - } - } else { - module = require(resolvedPath); - } - } else { - module = require(resolvedPath); - } - return module.default ?? module; -} - -function getOutputs(projectRoot: string): string[] { - function getOutput(path: string): string { - if (path.startsWith('..')) { - return join('{workspaceRoot}', join(projectRoot, path)); - } else { - return join('{projectRoot}', path); - } - } - - const outputs = []; - - return outputs; -} - -function normalizeOptions(options: EslintPluginOptions): EslintPluginOptions { - options ??= {}; - options.targetName ??= 'TODO'; - return options; -} -" -`; - -exports[`create-nodes-plugin/generator generator should run successfully 2`] = ` -"import { CreateNodesContext } from '@nx/devkit'; - -import { createNodes } from './plugin'; - -describe('@nx/eslint/plugin', () => { - let createNodesFunction = createNodes[1]; - let context: CreateNodesContext; - - beforeEach(async () => { - context = { - nxJsonConfiguration: { - namedInputs: { - default: ['{projectRoot}/**/*'], - production: ['!{projectRoot}/**/*.spec.ts'], - }, - }, - workspaceRoot: '', - }; - }); - - afterEach(() => { - jest.resetModules(); - }); - - it('should create nodes', () => { - mockEslintConfig({}); - const nodes = createNodesFunction( - 'TODO', - { - targetName: 'target', - }, - context, - ); - - expect(nodes).toMatchInlineSnapshot(); - }); -}); - -function mockEslintConfig(config: any) { - jest.mock( - 'TODO', - () => ({ - default: config, - }), - { - virtual: true, - }, - ); -} -" -`; - -exports[`create-nodes-plugin/generator generator should run successfully 3`] = ` -"import { formatFiles, getProjects, Tree } from '@nx/devkit'; -import { createNodes } from '../../plugins/plugin'; - -import { createProjectRootMappingsFromProjectConfigurations } from 'nx/src/project-graph/utils/find-project-for-path'; -import { replaceProjectConfigurationsWithPlugin } from '@nx/devkit/src/utils/replace-project-configuration-with-plugin'; - -export default async function update(tree: Tree) { - const proj = Object.fromEntries(getProjects(tree).entries()); - - const rootMappings = createProjectRootMappingsFromProjectConfigurations(proj); - - await replaceProjectConfigurationsWithPlugin( - tree, - rootMappings, - '@nx/eslint/plugin', - createNodes, - { - targetName: 'TODO', - }, - ); - - await formatFiles(tree); -} -" -`; diff --git a/tools/workspace-plugin/src/generators/create-nodes-plugin/files/plugin.ts.template b/tools/workspace-plugin/src/generators/create-nodes-plugin/files/plugin.ts.template deleted file mode 100644 index 948d9ddbb50..00000000000 --- a/tools/workspace-plugin/src/generators/create-nodes-plugin/files/plugin.ts.template +++ /dev/null @@ -1 +0,0 @@ -export { createNodes, <%= className %>PluginOptions } from './src/plugins/plugin'; diff --git a/tools/workspace-plugin/src/generators/create-nodes-plugin/files/src/migrations/update-17-2-0/add-__dirName__-plugin.ts b/tools/workspace-plugin/src/generators/create-nodes-plugin/files/src/migrations/update-17-2-0/add-__dirName__-plugin.ts deleted file mode 100644 index 4eedfae7f2b..00000000000 --- a/tools/workspace-plugin/src/generators/create-nodes-plugin/files/src/migrations/update-17-2-0/add-__dirName__-plugin.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { formatFiles, getProjects, Tree } from '@nx/devkit'; -import { createNodes } from '../../plugins/plugin'; - -import { createProjectRootMappingsFromProjectConfigurations } from 'nx/src/project-graph/utils/find-project-for-path'; -import { replaceProjectConfigurationsWithPlugin } from '@nx/devkit/src/utils/replace-project-configuration-with-plugin'; - -export default async function update(tree: Tree) { - const proj = Object.fromEntries(getProjects(tree).entries()); - - const rootMappings = createProjectRootMappingsFromProjectConfigurations(proj); - - await replaceProjectConfigurationsWithPlugin( - tree, - rootMappings, - '@nx/<%= dirName %>/plugin', - createNodes, - { - targetName: 'TODO', - } - ); - - await formatFiles(tree); -} diff --git a/tools/workspace-plugin/src/generators/create-nodes-plugin/files/src/plugins/plugin.spec.ts.template b/tools/workspace-plugin/src/generators/create-nodes-plugin/files/src/plugins/plugin.spec.ts.template deleted file mode 100644 index 3056f2f29ff..00000000000 --- a/tools/workspace-plugin/src/generators/create-nodes-plugin/files/src/plugins/plugin.spec.ts.template +++ /dev/null @@ -1,52 +0,0 @@ -import { CreateNodesContext } from '@nx/devkit'; - -import { createNodes } from './plugin'; - -describe('@nx/<%= dirName %>/plugin', () => { - let createNodesFunction = createNodes[1]; - let context: CreateNodesContext; - - beforeEach(async () => { - context = { - nxJsonConfiguration: { - namedInputs: { - default: ['{projectRoot}/**/*'], - production: ['!{projectRoot}/**/*.spec.ts'], - }, - }, - workspaceRoot: '', - }; - }); - - afterEach(() => { - jest.resetModules(); - }); - - it('should create nodes', () => { - mock<%= className %>Config({}) - const nodes = createNodesFunction( - 'TODO', - { - targetName: 'target', - }, - context - ); - - expect(nodes).toMatchInlineSnapshot(); - }); -}); - - -function mock<%= className %>Config( - config: any -) { - jest.mock( - 'TODO', - () => ({ - default: config, - }), - { - virtual: true, - } - ); -} diff --git a/tools/workspace-plugin/src/generators/create-nodes-plugin/files/src/plugins/plugin.ts.template b/tools/workspace-plugin/src/generators/create-nodes-plugin/files/src/plugins/plugin.ts.template deleted file mode 100644 index 3c39caa7441..00000000000 --- a/tools/workspace-plugin/src/generators/create-nodes-plugin/files/src/plugins/plugin.ts.template +++ /dev/null @@ -1,148 +0,0 @@ -import { - CreateNodes, - CreateNodesContext, - TargetConfiguration, -} from '@nx/devkit'; -import { basename, dirname, extname, join, resolve } from "path"; -import { registerTsProject } from '@nx/js/src/internal'; - -import { getRootTsConfigPath } from '@nx/js'; - -import { readTargetDefaultsForTarget } from 'nx/src/project-graph/utils/project-configuration-utils'; -import { getNamedInputs } from '@nx/devkit/src/utils/get-named-inputs'; -import { readdirSync } from 'fs'; - -export interface <%= className %>PluginOptions { - targetName?: string; -} - -export const createNodes: CreateNodes<<%= className %>PluginOptions> = [ - '**/TODO', - (configFilePath, options, context) => { - const projectRoot = dirname(configFilePath); - - // Do not create a project if package.json and project.json isn't there. - const siblingFiles = readdirSync(join(context.workspaceRoot, projectRoot)); - if ( - !siblingFiles.includes('package.json') && - !siblingFiles.includes('project.json') - ) { - return {}; - } - - options = normalizeOptions(options); - - return { - projects: { - [projectRoot]: { - root: projectRoot, - projectType: 'library', - targets: build<%= className %>Targets( - configFilePath, - projectRoot, - options, - context, - ), - }, - }, - }; - }, -]; - -function build<%= className %>Targets( - configFilePath: string, - projectRoot: string, - options: <%= className %>PluginOptions, - context: CreateNodesContext, -) { - const <%= propertyName %>Config = get<%= className %>Config( - configFilePath, - context - ); - - const targetDefaults = readTargetDefaultsForTarget( - options.targetName, - context.nxJsonConfiguration.targetDefaults, - 'executorName' - ); - - const namedInputs = getNamedInputs(projectRoot, context); - - const targets: Record< - string, - TargetConfiguration - > = {}; - - const baseTargetConfig: TargetConfiguration = { - executor: 'executorName', - options: { - config: configFilePath, - ...targetDefaults?.options, - }, - }; - - targets[options.targetName] = { - ...baseTargetConfig, - cache: targetDefaults?.cache ?? true, - inputs: - targetDefaults?.inputs ?? 'production' in namedInputs - ? ['default', '^production'] - : ['default', '^default'], - outputs: - targetDefaults?.outputs ?? - getOutputs(projectRoot), - options: { - ...baseTargetConfig.options, - }, - }; - - return targets; -} - -function get<%= className %>Config( - configFilePath: string, - context: CreateNodesContext -): any { - const resolvedPath = join(context.workspaceRoot, configFilePath); - - let module: any; - if (extname(configFilePath) === '.ts') { - const tsConfigPath = getRootTsConfigPath(); - - if (tsConfigPath) { - const unregisterTsProject = registerTsProject(tsConfigPath); - try { - module = require(resolvedPath); - } finally { - unregisterTsProject(); - } - } else { - module = require(resolvedPath); - } - } else { - module = require(resolvedPath); - } - return module.default ?? module; -} - -function getOutputs( - projectRoot: string, -): string[] { - function getOutput(path: string): string { - if (path.startsWith('..')) { - return join('{workspaceRoot}', join(projectRoot, path)); - } else { - return join('{projectRoot}', path); - } - } - - const outputs = []; - - return outputs; -} - -function normalizeOptions(options: <%= className %>PluginOptions): <%= className %>PluginOptions { - options ??= {}; - options.targetName ??= 'TODO'; - return options; -} diff --git a/tools/workspace-plugin/src/generators/create-nodes-plugin/generator.spec.ts b/tools/workspace-plugin/src/generators/create-nodes-plugin/generator.spec.ts deleted file mode 100644 index 2d8b16def3f..00000000000 --- a/tools/workspace-plugin/src/generators/create-nodes-plugin/generator.spec.ts +++ /dev/null @@ -1,98 +0,0 @@ -import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing'; -import { - addProjectConfiguration, - Tree, - workspaceRoot, - writeJson, -} from '@nx/devkit'; -import { join } from 'node:path'; - -import { generatorGenerator } from './generator'; - -describe('create-nodes-plugin/generator generator', () => { - let tree: Tree; - - beforeEach(() => { - tree = createTreeWithEmptyWorkspace(); - - addProjectConfiguration(tree, 'eslint', { - root: 'packages/eslint', - targets: { - build: {}, - }, - }); - - jest.spyOn(process, 'cwd').mockReturnValue('/virtual/packages/eslint'); - - process.env.INIT_CWD = join(workspaceRoot, 'packages/eslint'); - }); - - it('should run successfully', async () => { - writeJson(tree, 'packages/eslint/package.json', {}); - await generatorGenerator(tree); - expect( - tree.read('packages/eslint/src/plugins/plugin.ts').toString() - ).toMatchSnapshot(); - expect( - tree.read('packages/eslint/src/plugins/plugin.spec.ts').toString() - ).toMatchSnapshot(); - expect( - tree - .read( - 'packages/eslint/src/migrations/update-17-2-0/add-eslint-plugin.ts' - ) - .toString() - ).toMatchSnapshot(); - }); - - it('should add the plugin path to package.json exports', async () => { - writeJson(tree, 'packages/eslint/package.json', { - name: '@nx/eslint', - version: '0.0.1', - private: false, - description: 'Some description', - repository: { - type: 'git', - url: 'https://github.com/nrwl/nx.git', - directory: 'packages/eslint', - }, - keywords: ['Monorepo', 'eslint', 'Web', 'CLI'], - main: './index', - typings: './index.d.ts', - author: 'Victor Savkin', - license: 'MIT', - bugs: { - url: 'https://github.com/nrwl/nx/issues', - }, - homepage: 'https://nx.dev', - generators: './generators.json', - executors: './executors.json', - 'ng-update': { - requirements: {}, - migrations: './migrations.json', - }, - dependencies: { - '@nx/devkit': 'file:../devkit', - }, - peerDependencies: {}, - publishConfig: { - access: 'public', - }, - exports: { - '.': './index.js', - './package.json': './package.json', - './migrations.json': './migrations.json', - './generators.json': './generators.json', - './executors.json': './executors.json', - './executors': './executors.js', - './src/executors/*/schema.json': './src/executors/*/schema.json', - './src/executors/*.impl': './src/executors/*.impl.js', - './src/executors/*/compat': './src/executors/*/compat.js', - }, - }); - await generatorGenerator(tree); - expect( - tree.read('packages/eslint/package.json', 'utf-8') - ).toMatchSnapshot(); - }); -}); diff --git a/tools/workspace-plugin/src/generators/create-nodes-plugin/generator.ts b/tools/workspace-plugin/src/generators/create-nodes-plugin/generator.ts deleted file mode 100644 index da1ae2cc856..00000000000 --- a/tools/workspace-plugin/src/generators/create-nodes-plugin/generator.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { - formatFiles, - generateFiles, - joinPathFragments, - names, - Tree, - updateJson, -} from '@nx/devkit'; -import { basename, join, relative } from 'path'; -import migrationGenerator from '@nx/plugin/src/generators/migration/migration'; - -export async function generatorGenerator(tree: Tree) { - const cwd = process.cwd(); - const { className, propertyName } = names(basename(cwd)); - - await migrationGenerator(tree, { - name: `add-${basename(cwd)}-plugin`, - packageVersion: '17.2.0-beta.0', - description: `Add @nx/${basename(cwd)}/plugin`, - path: `src/migrations/update-17-2-0`, - skipFormat: true, - }); - - generateFiles(tree, join(__dirname, 'files'), relative(tree.root, cwd), { - dirName: basename(cwd), - className, - propertyName, - }); - - updateJson( - tree, - joinPathFragments(relative(tree.root, cwd), 'package.json'), - (json) => { - if (json['exports']) { - json['exports']['./plugin'] = './plugin.js'; - } - return json; - } - ); - - await formatFiles(tree); -} - -export default generatorGenerator; diff --git a/tools/workspace-plugin/src/generators/create-nodes-plugin/schema.json b/tools/workspace-plugin/src/generators/create-nodes-plugin/schema.json deleted file mode 100644 index 8802ea1fc57..00000000000 --- a/tools/workspace-plugin/src/generators/create-nodes-plugin/schema.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "$schema": "https://json-schema.org/schema", - "$id": "Generator", - "title": "", - "type": "object", - "description": "A generator for creating a new nodes plugin.", - "properties": {}, - "required": [] -}