feat(nx-plugin): use helper to determine project name and root in project generators (#18739)

This commit is contained in:
Leosvel Pérez Espinosa 2023-08-22 17:28:23 +01:00 committed by GitHub
parent 1b0439b55c
commit 016c89fed6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 210 additions and 89 deletions

View File

@ -1,6 +1,6 @@
{
"name": "create-package",
"factory": "./src/generators/create-package/create-package",
"factory": "./src/generators/create-package/create-package#createPackageGeneratorInternal",
"schema": {
"$schema": "http://json-schema.org/schema",
"cli": "nx",
@ -35,6 +35,11 @@
"type": "string",
"description": "A directory where the app is placed."
},
"projectNameAndRootFormat": {
"description": "Whether to generate the project name and root directory as provided (`as-provided`) or generate them composing their values and taking the configured layout into account (`derived`).",
"type": "string",
"enum": ["as-provided", "derived"]
},
"linter": {
"description": "The tool to use for running lint checks.",
"type": "string",
@ -70,7 +75,7 @@
"presets": []
},
"description": "Create a package which can be used by npx to create a new workspace",
"implementation": "/packages/plugin/src/generators/create-package/create-package.ts",
"implementation": "/packages/plugin/src/generators/create-package/create-package#createPackageGeneratorInternal.ts",
"aliases": [],
"hidden": false,
"path": "/packages/plugin/src/generators/create-package/schema.json",

View File

@ -1,6 +1,6 @@
{
"name": "e2e-project",
"factory": "./src/generators/e2e-project/e2e",
"factory": "./src/generators/e2e-project/e2e#e2eProjectGeneratorInternal",
"schema": {
"$schema": "http://json-schema.org/schema",
"cli": "nx",
@ -24,6 +24,11 @@
"type": "string",
"description": "the directory where the plugin is placed."
},
"projectNameAndRootFormat": {
"description": "Whether to generate the project name and root directory as provided (`as-provided`) or generate them composing their values and taking the configured layout into account (`derived`).",
"type": "string",
"enum": ["as-provided", "derived"]
},
"pluginOutputPath": {
"type": "string",
"description": "the output path of the plugin after it builds.",
@ -52,7 +57,7 @@
"presets": []
},
"description": "Create a E2E application for a Nx Plugin.",
"implementation": "/packages/plugin/src/generators/e2e-project/e2e.ts",
"implementation": "/packages/plugin/src/generators/e2e-project/e2e#e2eProjectGeneratorInternal.ts",
"aliases": [],
"hidden": false,
"path": "/packages/plugin/src/generators/e2e-project/schema.json",

View File

@ -1,6 +1,6 @@
{
"name": "plugin",
"factory": "./src/generators/plugin/plugin",
"factory": "./src/generators/plugin/plugin#pluginGeneratorInternal",
"schema": {
"$schema": "http://json-schema.org/schema",
"cli": "nx",
@ -20,12 +20,18 @@
"description": "Plugin name",
"$default": { "$source": "argv", "index": 0 },
"x-prompt": "What name would you like to use for the plugin?",
"x-priority": "important"
"x-priority": "important",
"pattern": "(?:^@[a-zA-Z0-9-*~][a-zA-Z0-9-*._~]*\\/[a-zA-Z0-9-~][a-zA-Z0-9-._~]*|^[a-zA-Z][^:]*)$"
},
"directory": {
"type": "string",
"description": "A directory where the plugin is placed."
},
"projectNameAndRootFormat": {
"description": "Whether to generate the project name and root directory as provided (`as-provided`) or generate them composing their values and taking the configured layout into account (`derived`).",
"type": "string",
"enum": ["as-provided", "derived"]
},
"importPath": {
"type": "string",
"description": "How the plugin will be published, like `@myorg/my-awesome-plugin`. Note this must be a valid NPM name.",
@ -98,7 +104,7 @@
"presets": []
},
"description": "Create a Nx Plugin.",
"implementation": "/packages/plugin/src/generators/plugin/plugin.ts",
"implementation": "/packages/plugin/src/generators/plugin/plugin#pluginGeneratorInternal.ts",
"aliases": [],
"hidden": false,
"path": "/packages/plugin/src/generators/plugin/schema.json",

View File

@ -434,4 +434,43 @@ describe('Nx Plugin', () => {
)
).toThrow();
});
it('should support the new name and root format', async () => {
const plugin = uniq('plugin');
const createAppName = `create-${plugin}-app`;
runCLI(
`generate @nx/plugin:plugin ${plugin} --e2eTestRunner jest --publishable --project-name-and-root-format=as-provided`
);
// check files are generated without the layout directory ("libs/") and
// using the project name as the directory when no directory is provided
checkFilesExist(`${plugin}/src/index.ts`);
// check build works
expect(runCLI(`build ${plugin}`)).toContain(
`Successfully ran target build for project ${plugin}`
);
// check tests pass
const appTestResult = runCLI(`test ${plugin}`);
expect(appTestResult).toContain(
`Successfully ran target test for project ${plugin}`
);
runCLI(
`generate @nx/plugin:create-package ${createAppName} --project=${plugin} --e2eProject=${plugin}-e2e --project-name-and-root-format=as-provided`
);
// check files are generated without the layout directory ("libs/") and
// using the project name as the directory when no directory is provided
checkFilesExist(`${plugin}/src/generators/preset`, `${createAppName}`);
// check build works
expect(runCLI(`build ${createAppName}`)).toContain(
`Successfully ran target build for project ${createAppName}`
);
// check tests pass
const libTestResult = runCLI(`test ${createAppName}`);
expect(libTestResult).toContain(
`Successfully ran target test for project ${createAppName}`
);
});
});

View File

@ -4,17 +4,17 @@
"extends": ["@nx/workspace"],
"generators": {
"plugin": {
"factory": "./src/generators/plugin/plugin",
"factory": "./src/generators/plugin/plugin#pluginGeneratorInternal",
"schema": "./src/generators/plugin/schema.json",
"description": "Create a Nx Plugin."
},
"create-package": {
"factory": "./src/generators/create-package/create-package",
"factory": "./src/generators/create-package/create-package#createPackageGeneratorInternal",
"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",
"factory": "./src/generators/e2e-project/e2e#e2eProjectGeneratorInternal",
"schema": "./src/generators/e2e-project/schema.json",
"description": "Create a E2E application for a Nx Plugin."
},

View File

@ -26,10 +26,20 @@ import { join } from 'path';
export async function createPackageGenerator(
host: Tree,
schema: CreatePackageSchema
) {
return await createPackageGeneratorInternal(host, {
projectNameAndRootFormat: 'derived',
...schema,
});
}
export async function createPackageGeneratorInternal(
host: Tree,
schema: CreatePackageSchema
) {
const tasks: GeneratorCallback[] = [];
const options = normalizeSchema(host, schema);
const options = await normalizeSchema(host, schema);
const pluginPackageName = await addPresetGenerator(host, options);
const installTask = addDependenciesToPackageJson(

View File

@ -1,3 +1,4 @@
import type { ProjectNameAndRootFormat } from '@nx/devkit/src/generators/project-name-directory-utils';
import type { Linter } from '@nx/linter';
export interface CreatePackageSchema {
@ -6,6 +7,7 @@ export interface CreatePackageSchema {
// options to create cli package, passed to js library generator
directory?: string;
projectNameAndRootFormat?: ProjectNameAndRootFormat;
skipFormat: boolean;
tags?: string;
unitTestRunner: 'jest' | 'none';

View File

@ -37,6 +37,11 @@
"type": "string",
"description": "A directory where the app is placed."
},
"projectNameAndRootFormat": {
"description": "Whether to generate the project name and root directory as provided (`as-provided`) or generate them composing their values and taking the configured layout into account (`derived`).",
"type": "string",
"enum": ["as-provided", "derived"]
},
"linter": {
"description": "The tool to use for running lint checks.",
"type": "string",

View File

@ -1,42 +1,36 @@
import {
extractLayoutDirectory,
getWorkspaceLayout,
joinPathFragments,
names,
Tree,
} from '@nx/devkit';
import { Tree } from '@nx/devkit';
import { determineProjectNameAndRootOptions } from '@nx/devkit/src/generators/project-name-and-root-utils';
import { CreatePackageSchema } from '../schema';
export interface NormalizedSchema extends CreatePackageSchema {
bundler: 'swc' | 'tsc';
libsDir: string;
projectName: string;
projectRoot: string;
projectDirectory: string;
}
export function normalizeSchema(
export async 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);
): Promise<NormalizedSchema> {
const {
projectName,
names: projectNames,
projectRoot,
projectNameAndRootFormat,
} = await determineProjectNameAndRootOptions(host, {
name: schema.name,
projectType: 'library',
directory: schema.directory,
projectNameAndRootFormat: schema.projectNameAndRootFormat,
callingGenerator: '@nx/plugin:create-package',
});
schema.projectNameAndRootFormat = projectNameAndRootFormat;
return {
...schema,
bundler: schema.compiler ?? 'tsc',
libsDir,
projectName,
projectRoot,
name,
projectDirectory: fullProjectDirectory,
name: projectNames.projectSimpleName,
};
}

View File

@ -16,13 +16,13 @@ import {
runTasksInSerial,
updateProjectConfiguration,
} from '@nx/devkit';
import { determineProjectNameAndRootOptions } from '@nx/devkit/src/generators/project-name-and-root-utils';
import { addPropertyToJestConfig, configurationGenerator } from '@nx/jest';
import { getRelativePathToRootTsConfig } from '@nx/js';
import { setupVerdaccio } from '@nx/js/src/generators/setup-verdaccio/generator';
import { addLocalRegistryScripts } from '@nx/js/src/utils/add-local-registry-scripts';
import { join } from 'path';
import { Linter, lintProjectGenerator } from '@nx/linter';
import { join } from 'path';
import type { Schema } from './schema';
interface NormalizedSchema extends Schema {
@ -32,20 +32,42 @@ interface NormalizedSchema extends Schema {
linter: Linter;
}
function normalizeOptions(host: Tree, options: Schema): NormalizedSchema {
const { layoutDirectory, projectDirectory } = extractLayoutDirectory(
options.projectDirectory
);
const { appsDir: defaultAppsDir } = getWorkspaceLayout(host);
const appsDir = layoutDirectory ?? defaultAppsDir;
async function normalizeOptions(
host: Tree,
options: Schema
): Promise<NormalizedSchema> {
const projectName = options.rootProject ? 'e2e' : `${options.pluginName}-e2e`;
const projectRoot =
projectDirectory && !options.rootProject
? joinPathFragments(appsDir, `${projectDirectory}-e2e`)
: options.rootProject
let projectRoot: string;
if (options.projectNameAndRootFormat === 'as-provided') {
const projectNameAndRootOptions = await determineProjectNameAndRootOptions(
host,
{
name: projectName,
projectType: 'application',
directory:
options.rootProject || !options.projectDirectory
? projectName
: `${options.projectDirectory}-e2e`,
projectNameAndRootFormat: `as-provided`,
callingGenerator: '@nx/plugin:e2e-project',
}
);
projectRoot = projectNameAndRootOptions.projectRoot;
} else {
const { layoutDirectory, projectDirectory } = extractLayoutDirectory(
options.projectDirectory
);
const { appsDir: defaultAppsDir } = getWorkspaceLayout(host);
const appsDir = layoutDirectory ?? defaultAppsDir;
projectRoot = options.rootProject
? projectName
: projectDirectory
? joinPathFragments(appsDir, `${projectDirectory}-e2e`)
: joinPathFragments(appsDir, projectName);
}
const pluginPropertyName = names(options.pluginName).propertyName;
return {
@ -158,10 +180,17 @@ async function addLintingToApplication(
}
export async function e2eProjectGenerator(host: Tree, schema: Schema) {
return await e2eProjectGeneratorInternal(host, {
projectNameAndRootFormat: 'derived',
...schema,
});
}
export async function e2eProjectGeneratorInternal(host: Tree, schema: Schema) {
const tasks: GeneratorCallback[] = [];
validatePlugin(host, schema.pluginName);
const options = normalizeOptions(host, schema);
const options = await normalizeOptions(host, schema);
addFiles(host, options);
tasks.push(
await setupVerdaccio(host, {

View File

@ -1,9 +1,11 @@
import { Linter } from '@nx/linter';
import type { ProjectNameAndRootFormat } from '@nx/devkit/src/generators/project-name-directory-utils';
import type { Linter } from '@nx/linter';
export interface Schema {
pluginName: string;
npmPackageName: string;
projectDirectory?: string;
projectNameAndRootFormat?: ProjectNameAndRootFormat;
pluginOutputPath?: string;
jestConfig?: string;
linter?: Linter;

View File

@ -21,6 +21,11 @@
"type": "string",
"description": "the directory where the plugin is placed."
},
"projectNameAndRootFormat": {
"description": "Whether to generate the project name and root directory as provided (`as-provided`) or generate them composing their values and taking the configured layout into account (`derived`).",
"type": "string",
"enum": ["as-provided", "derived"]
},
"pluginOutputPath": {
"type": "string",
"description": "the output path of the plugin after it builds.",

View File

@ -4,6 +4,7 @@ import {
formatFiles,
generateFiles,
GeneratorCallback,
joinPathFragments,
normalizePath,
readProjectConfiguration,
runTasksInSerial,
@ -74,7 +75,14 @@ function updatePluginConfig(host: Tree, options: NormalizedSchema) {
}
export async function pluginGenerator(host: Tree, schema: Schema) {
const options = normalizeOptions(host, schema);
return await pluginGeneratorInternal(host, {
projectNameAndRootFormat: 'derived',
...schema,
});
}
export async function pluginGeneratorInternal(host: Tree, schema: Schema) {
const options = await normalizeOptions(host, schema);
const tasks: GeneratorCallback[] = [];
tasks.push(
@ -119,10 +127,14 @@ export async function pluginGenerator(host: Tree, schema: Schema) {
await e2eProjectGenerator(host, {
pluginName: options.name,
projectDirectory: options.projectDirectory,
pluginOutputPath: `dist/${options.libsDir}/${options.projectDirectory}`,
pluginOutputPath: joinPathFragments(
'dist',
options.rootProject ? options.name : options.projectRoot
),
npmPackageName: options.npmPackageName,
skipFormat: true,
rootProject: options.rootProject,
projectNameAndRootFormat: options.projectNameAndRootFormat,
})
);
}

View File

@ -1,8 +1,10 @@
import { Linter } from '@nx/linter';
import type { ProjectNameAndRootFormat } from '@nx/devkit/src/generators/project-name-directory-utils';
import type { Linter } from '@nx/linter';
export interface Schema {
name: string;
directory?: string;
projectNameAndRootFormat?: ProjectNameAndRootFormat;
importPath?: string;
skipTsConfig?: boolean; // default is false
skipFormat?: boolean; // default is false

View File

@ -20,12 +20,18 @@
"index": 0
},
"x-prompt": "What name would you like to use for the plugin?",
"x-priority": "important"
"x-priority": "important",
"pattern": "(?:^@[a-zA-Z0-9-*~][a-zA-Z0-9-*._~]*\\/[a-zA-Z0-9-~][a-zA-Z0-9-._~]*|^[a-zA-Z][^:]*)$"
},
"directory": {
"type": "string",
"description": "A directory where the plugin is placed."
},
"projectNameAndRootFormat": {
"description": "Whether to generate the project name and root directory as provided (`as-provided`) or generate them composing their values and taking the configured layout into account (`derived`).",
"type": "string",
"enum": ["as-provided", "derived"]
},
"importPath": {
"type": "string",
"description": "How the plugin will be published, like `@myorg/my-awesome-plugin`. Note this must be a valid NPM name.",

View File

@ -1,17 +1,10 @@
import {
extractLayoutDirectory,
getWorkspaceLayout,
joinPathFragments,
names,
Tree,
} from '@nx/devkit';
import { Tree, extractLayoutDirectory, getWorkspaceLayout } from '@nx/devkit';
import { determineProjectNameAndRootOptions } from '@nx/devkit/src/generators/project-name-and-root-utils';
import { Schema } from '../schema';
import { getImportPath } from '@nx/js/src/utils/get-import-path';
export interface NormalizedSchema extends Schema {
name: string;
fileName: string;
libsDir: string;
projectRoot: string;
projectDirectory: string;
parsedTags: string[];
@ -19,44 +12,50 @@ export interface NormalizedSchema extends Schema {
bundler: 'swc' | 'tsc';
publishable: boolean;
}
export function normalizeOptions(
export async function normalizeOptions(
host: Tree,
options: Schema
): NormalizedSchema {
const { layoutDirectory, projectDirectory } = extractLayoutDirectory(
options.directory
);
const { libsDir: defaultLibsDir } = getWorkspaceLayout(host);
const libsDir = layoutDirectory ?? defaultLibsDir;
const name = names(options.name).fileName;
const fullProjectDirectory = projectDirectory
? `${names(projectDirectory).fileName}/${name}`
: options.rootProject
? '.'
: name;
): Promise<NormalizedSchema> {
const {
projectName,
projectRoot,
importPath: npmPackageName,
projectNameAndRootFormat,
} = await determineProjectNameAndRootOptions(host, {
name: options.name,
projectType: 'library',
directory: options.directory,
importPath: options.importPath,
projectNameAndRootFormat: options.projectNameAndRootFormat,
rootProject: options.rootProject,
callingGenerator: '@nx/plugin:plugin',
});
options.projectNameAndRootFormat = projectNameAndRootFormat;
options.rootProject = projectRoot === '.';
const projectName = options.rootProject
? name
: fullProjectDirectory.replace(new RegExp('/', 'g'), '-');
const fileName = projectName;
const projectRoot = options.rootProject
? fullProjectDirectory
: joinPathFragments(libsDir, fullProjectDirectory);
let projectDirectory = projectRoot;
if (options.projectNameAndRootFormat === 'derived') {
let { layoutDirectory } = extractLayoutDirectory(options.directory);
if (!layoutDirectory) {
const { libsDir } = getWorkspaceLayout(host);
layoutDirectory = libsDir;
}
if (projectRoot.startsWith(`${layoutDirectory}/`)) {
projectDirectory = projectRoot.replace(`${layoutDirectory}/`, '');
}
}
const parsedTags = options.tags
? options.tags.split(',').map((s) => s.trim())
: [];
const npmPackageName = options.importPath || getImportPath(host, name);
return {
...options,
bundler: options.compiler ?? 'tsc',
fileName,
libsDir,
fileName: projectName,
name: projectName,
projectRoot,
projectDirectory: fullProjectDirectory,
projectDirectory,
parsedTags,
npmPackageName,
publishable: options.publishable ?? false,