Skip to content

Commit

Permalink
feat(react): use helper to determine project name and root directory …
Browse files Browse the repository at this point in the history
…in project generators (#18615)
  • Loading branch information
leosvelperez authored Aug 18, 2023
1 parent 71d2994 commit eb9caa1
Show file tree
Hide file tree
Showing 28 changed files with 346 additions and 117 deletions.
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
84 changes: 84 additions & 0 deletions packages/devkit/src/generators/project-name-and-root-utils.spec.ts
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
7 changes: 5 additions & 2 deletions packages/devkit/src/generators/project-name-and-root-utils.ts
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

1 comment on commit eb9caa1

@vercel
Copy link

@vercel vercel bot commented on eb9caa1 Aug 18, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

nx-dev – ./

nx-dev-git-master-nrwl.vercel.app
nx-five.vercel.app
nx-dev-nrwl.vercel.app
nx.dev

Please sign in to comment.