Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(react): use helper to determine project name and root directory in project generators #18615

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 8 additions & 3 deletions docs/generated/packages/react/generators/application.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "application",
"factory": "./src/generators/application/application#applicationGenerator",
"factory": "./src/generators/application/application#applicationGeneratorInternal",
"schema": {
"$schema": "http://json-schema.org/schema",
"cli": "nx",
Expand Down Expand Up @@ -28,14 +28,19 @@
"type": "string",
"$default": { "$source": "argv", "index": 0 },
"x-prompt": "What name would you like to use for the application?",
"pattern": "^[a-zA-Z].*$"
"pattern": "^[a-zA-Z][^:]*$"
},
"directory": {
"description": "The directory of the new application.",
"type": "string",
"alias": "dir",
"x-priority": "important"
},
"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"]
},
"style": {
"description": "The file extension to be used for style files.",
"type": "string",
Expand Down Expand Up @@ -192,7 +197,7 @@
"aliases": ["app"],
"x-type": "application",
"description": "Create a React application.",
"implementation": "/packages/react/src/generators/application/application#applicationGenerator.ts",
"implementation": "/packages/react/src/generators/application/application#applicationGeneratorInternal.ts",
"hidden": false,
"path": "/packages/react/src/generators/application/schema.json",
"type": "generator"
Expand Down
11 changes: 8 additions & 3 deletions docs/generated/packages/react/generators/host.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "host",
"factory": "./src/generators/host/host#hostGenerator",
"factory": "./src/generators/host/host#hostGeneratorInternal",
"schema": {
"$schema": "http://json-schema.org/schema",
"$id": "GeneratorReactHost",
Expand All @@ -14,7 +14,7 @@
"description": "The name of the host application to generate the Module Federation configuration",
"$default": { "$source": "argv", "index": 0 },
"x-prompt": "What name would you like to use as the host application?",
"pattern": "^[a-zA-Z].*$",
"pattern": "^[a-zA-Z][^:]*$",
"x-priority": "important"
},
"directory": {
Expand All @@ -23,6 +23,11 @@
"alias": "dir",
"x-priority": "important"
},
"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"]
},
"style": {
"description": "The file extension to be used for style files.",
"type": "string",
Expand Down Expand Up @@ -163,7 +168,7 @@
},
"x-type": "application",
"description": "Generate a host react application",
"implementation": "/packages/react/src/generators/host/host#hostGenerator.ts",
"implementation": "/packages/react/src/generators/host/host#hostGeneratorInternal.ts",
"aliases": [],
"hidden": false,
"path": "/packages/react/src/generators/host/schema.json",
Expand Down
11 changes: 8 additions & 3 deletions docs/generated/packages/react/generators/library.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "library",
"factory": "./src/generators/library/library#libraryGenerator",
"factory": "./src/generators/library/library#libraryGeneratorInternal",
"schema": {
"$schema": "http://json-schema.org/schema",
"cli": "nx",
Expand All @@ -24,7 +24,7 @@
"description": "Library name",
"$default": { "$source": "argv", "index": 0 },
"x-prompt": "What name would you like to use for the library?",
"pattern": "^[a-zA-Z].*$",
"pattern": "(?:^@[a-zA-Z0-9-*~][a-zA-Z0-9-*._~]*\\/[a-zA-Z0-9-~][a-zA-Z0-9-._~]*|^[a-zA-Z][^:]*)$",
"x-priority": "important"
},
"directory": {
Expand All @@ -33,6 +33,11 @@
"alias": "dir",
"x-priority": "important"
},
"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"]
},
"style": {
"description": "The file extension to be used for style files.",
"type": "string",
Expand Down Expand Up @@ -196,7 +201,7 @@
"aliases": ["lib"],
"x-type": "library",
"description": "Create a React library.",
"implementation": "/packages/react/src/generators/library/library#libraryGenerator.ts",
"implementation": "/packages/react/src/generators/library/library#libraryGeneratorInternal.ts",
"hidden": false,
"path": "/packages/react/src/generators/library/schema.json",
"type": "generator"
Expand Down
11 changes: 8 additions & 3 deletions docs/generated/packages/react/generators/remote.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "remote",
"factory": "./src/generators/remote/remote#remoteGenerator",
"factory": "./src/generators/remote/remote#remoteGeneratorInternal",
"schema": {
"$schema": "http://json-schema.org/schema",
"$id": "GeneratorReactRemote",
Expand All @@ -14,7 +14,7 @@
"description": "The name of the remote application to generate the Module Federation configuration",
"$default": { "$source": "argv", "index": 0 },
"x-prompt": "What name would you like to use as the remote application?",
"pattern": "^[a-zA-Z].*$",
"pattern": "^[a-zA-Z][^:]*$",
"x-priority": "important"
},
"directory": {
Expand All @@ -23,6 +23,11 @@
"alias": "dir",
"x-priority": "important"
},
"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"]
},
"style": {
"description": "The file extension to be used for style files.",
"type": "string",
Expand Down Expand Up @@ -162,7 +167,7 @@
},
"x-type": "application",
"description": "Generate a remote react application",
"implementation": "/packages/react/src/generators/remote/remote#remoteGenerator.ts",
"implementation": "/packages/react/src/generators/remote/remote#remoteGeneratorInternal.ts",
"aliases": [],
"hidden": false,
"path": "/packages/react/src/generators/remote/schema.json",
Expand Down
26 changes: 21 additions & 5 deletions e2e/react-core/src/react-module-federation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { stripIndents } from '@nx/devkit';
import {
checkFilesExist,
cleanupProject,
killPort,
newProject,
readProjectConfig,
runCLI,
Expand Down Expand Up @@ -114,12 +113,29 @@ describe('React Module Federation', () => {
// }
}, 500_000);

it('should should support generating host and remote apps with the new name and root format', async () => {
const shell = uniq('shell');
const remote = uniq('remote');

runCLI(
`generate @nx/react:host ${shell} --project-name-and-root-format=as-provided --no-interactive`
);
runCLI(
`generate @nx/react:remote ${remote} --host=${shell} --project-name-and-root-format=as-provided --no-interactive`
);

// check files are generated without the layout directory ("apps/") and
// using the project name as the directory when no directory is provided
checkFilesExist(`${shell}/module-federation.config.js`);
checkFilesExist(`${remote}/module-federation.config.js`);

// check default generated host is built successfully
const buildOutput = runCLI(`run ${shell}:build:development`);
expect(buildOutput).toContain('Successfully ran target build');
}, 500_000);

async function readPort(appName: string): Promise<number> {
const config = await readProjectConfig(appName);
return config.targets.serve.options.port;
}
});

function killPorts(ports: number[]): Promise<boolean[]> {
return Promise.all(ports.map((p) => killPort(p)));
}
46 changes: 46 additions & 0 deletions e2e/react-core/src/react.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,52 @@ describe('React Applications', () => {
);
}, 250_000);

it('should support generating projects with the new name and root format', () => {
const appName = uniq('app1');
const libName = uniq('@my-org/lib1');

runCLI(
`generate @nx/react:app ${appName} --bundler=webpack --project-name-and-root-format=as-provided --no-interactive`
);

// check files are generated without the layout directory ("apps/") and
// using the project name as the directory when no directory is provided
checkFilesExist(`${appName}/src/main.tsx`);
// check build works
expect(runCLI(`build ${appName}`)).toContain(
`Successfully ran target build for project ${appName}`
);
// check tests pass
const appTestResult = runCLI(`test ${appName}`);
expect(appTestResult).toContain(
`Successfully ran target test for project ${appName}`
);

// assert scoped project names are not supported when --project-name-and-root-format=derived
expect(() =>
runCLI(
`generate @nx/react:lib ${libName} --unit-test-runner=jest --buildable --project-name-and-root-format=derived --no-interactive`
)
).toThrow();

runCLI(
`generate @nx/react:lib ${libName} --unit-test-runner=jest --buildable --project-name-and-root-format=as-provided --no-interactive`
);

// check files are generated without the layout directory ("libs/") and
// using the project name as the directory when no directory is provided
checkFilesExist(`${libName}/src/index.ts`);
// check build works
expect(runCLI(`build ${libName}`)).toContain(
`Successfully ran target build for project ${libName}`
);
// check tests pass
const libTestResult = runCLI(`test ${libName}`);
expect(libTestResult).toContain(
`Successfully ran target test for project ${libName}`
);
}, 500_000);

describe('React Applications: --style option', () => {
it.each`
style
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,27 @@ describe('determineProjectNameAndRootOptions', () => {
});
});

it(`should handle window's style paths correctly when format is "as-provided"`, async () => {
const result = await determineProjectNameAndRootOptions(tree, {
name: 'libName',
directory: 'shared\\libName',
projectType: 'library',
projectNameAndRootFormat: 'as-provided',
callingGenerator: '',
});

expect(result).toStrictEqual({
projectName: 'lib-name',
names: {
projectSimpleName: 'lib-name',
projectFileName: 'lib-name',
},
importPath: '@proj/lib-name',
projectRoot: 'shared/lib-name',
projectNameAndRootFormat: 'as-provided',
});
});

it('should use a scoped package name as the project name and import path when format is "as-provided"', async () => {
const result = await determineProjectNameAndRootOptions(tree, {
name: '@scope/libName',
Expand Down Expand Up @@ -253,6 +274,27 @@ describe('determineProjectNameAndRootOptions', () => {
expect(result.importPath).toBe('@proj/lib-name');
});

it(`should handle window's style paths correctly when format is "derived"`, async () => {
const result = await determineProjectNameAndRootOptions(tree, {
name: 'libName',
directory: 'shared\\sub-dir',
projectType: 'library',
projectNameAndRootFormat: 'derived',
callingGenerator: '',
});

expect(result).toStrictEqual({
projectName: 'shared-sub-dir-lib-name',
names: {
projectSimpleName: 'lib-name',
projectFileName: 'shared-sub-dir-lib-name',
},
importPath: '@proj/shared/sub-dir/lib-name',
projectRoot: 'shared/sub-dir/lib-name',
projectNameAndRootFormat: 'derived',
});
});

it('should prompt for the project name and root format', async () => {
// simulate interactive mode
ensureInteractiveMode();
Expand Down Expand Up @@ -370,6 +412,27 @@ describe('determineProjectNameAndRootOptions', () => {
});
});

it(`should handle window's style paths correctly when format is "as-provided"`, async () => {
const result = await determineProjectNameAndRootOptions(tree, {
name: 'libName',
directory: 'shared\\libName',
projectType: 'library',
projectNameAndRootFormat: 'as-provided',
callingGenerator: '',
});

expect(result).toStrictEqual({
projectName: 'lib-name',
names: {
projectSimpleName: 'lib-name',
projectFileName: 'lib-name',
},
importPath: '@proj/lib-name',
projectRoot: 'shared/lib-name',
projectNameAndRootFormat: 'as-provided',
});
});

it('should use a scoped package name as the project name and import path when format is "as-provided"', async () => {
const result = await determineProjectNameAndRootOptions(tree, {
name: '@scope/libName',
Expand Down Expand Up @@ -514,6 +577,27 @@ describe('determineProjectNameAndRootOptions', () => {
});
});

it(`should handle window's style paths correctly when format is "derived"`, async () => {
const result = await determineProjectNameAndRootOptions(tree, {
name: 'libName',
directory: 'shared\\sub-dir',
projectType: 'library',
projectNameAndRootFormat: 'derived',
callingGenerator: '',
});

expect(result).toStrictEqual({
projectName: 'shared-sub-dir-lib-name',
names: {
projectSimpleName: 'lib-name',
projectFileName: 'shared-sub-dir-lib-name',
},
importPath: '@proj/shared/sub-dir/lib-name',
projectRoot: 'libs/shared/sub-dir/lib-name',
projectNameAndRootFormat: 'derived',
});
});

it('should throw when using a scoped package name as the project name and format is derived', async () => {
await expect(
determineProjectNameAndRootOptions(tree, {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import {
} from '../utils/get-workspace-layout';
import { names } from '../utils/names';

const { joinPathFragments, readJson, readNxJson, updateNxJson } = requireNx();
const { joinPathFragments, normalizePath, readJson, readNxJson, updateNxJson } =
requireNx();

export type ProjectNameAndRootFormat = 'as-provided' | 'derived';
export type ProjectGenerationOptions = {
Expand Down Expand Up @@ -169,7 +170,9 @@ function getProjectNameAndRootFormats(
options: ProjectGenerationOptions
): ProjectNameAndRootFormats {
const name = names(options.name).fileName;
const directory = options.directory?.replace(/^\.?\//, '');
const directory = options.directory
? normalizePath(options.directory.replace(/^\.?\//, ''))
: undefined;

const asProvidedProjectName = name;
const asProvidedProjectDirectory = directory
Expand Down
Loading