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(nextjs): use helper to determine project name and root in projects generators #18733

Merged
merged 3 commits into from
Aug 21, 2023
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/next/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 All @@ -14,7 +14,7 @@
"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][^:]*$",
"x-priority": "important"
},
"directory": {
Expand All @@ -23,6 +23,11 @@
"alias": "d",
"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 @@ -138,7 +143,7 @@
"aliases": ["app"],
"x-type": "application",
"description": "Create an application.",
"implementation": "/packages/next/src/generators/application/application#applicationGenerator.ts",
"implementation": "/packages/next/src/generators/application/application#applicationGeneratorInternal.ts",
"hidden": false,
"path": "/packages/next/src/generators/application/schema.json",
"type": "generator"
Expand Down
11 changes: 8 additions & 3 deletions docs/generated/packages/next/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 @@ -14,7 +14,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 @@ -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 @@ -154,7 +159,7 @@
"aliases": ["lib"],
"x-type": "library",
"description": "Create a library.",
"implementation": "/packages/next/src/generators/library/library#libraryGenerator.ts",
"implementation": "/packages/next/src/generators/library/library#libraryGeneratorInternal.ts",
"hidden": false,
"path": "/packages/next/src/generators/library/schema.json",
"type": "generator"
Expand Down
41 changes: 41 additions & 0 deletions e2e/next/src/next.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,47 @@ describe('Next.js Applications', () => {
await killPort(selfContainedPort);
}, 600_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/next:app ${appName} --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}/app/page.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/next:lib ${libName} --buildable --project-name-and-root-format=derived --no-interactive`
)
).toThrow();

runCLI(
`generate @nx/next:lib ${libName} --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}`
);
}, 600_000);

it('should build and install pruned lock file', () => {
const appName = uniq('app');
runCLI(`generate @nx/next:app ${appName} --no-interactive --style=css`);
Expand Down
4 changes: 2 additions & 2 deletions packages/next/generators.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
"hidden": true
},
"application": {
"factory": "./src/generators/application/application#applicationGenerator",
"factory": "./src/generators/application/application#applicationGeneratorInternal",
"schema": "./src/generators/application/schema.json",
"aliases": ["app"],
"x-type": "application",
Expand All @@ -57,7 +57,7 @@
"description": "Create a component."
},
"library": {
"factory": "./src/generators/library/library#libraryGenerator",
"factory": "./src/generators/library/library#libraryGeneratorInternal",
"schema": "./src/generators/library/schema.json",
"aliases": ["lib"],
"x-type": "library",
Expand Down
11 changes: 9 additions & 2 deletions packages/next/src/generators/application/application.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,15 @@ import { updateCypressTsConfig } from './lib/update-cypress-tsconfig';
import { showPossibleWarnings } from './lib/show-possible-warnings';

export async function applicationGenerator(host: Tree, schema: Schema) {
return await applicationGeneratorInternal(host, {
projectNameAndRootFormat: 'derived',
...schema,
});
}

export async function applicationGeneratorInternal(host: Tree, schema: Schema) {
const tasks: GeneratorCallback[] = [];
const options = normalizeOptions(host, schema);
const options = await normalizeOptions(host, schema);

showPossibleWarnings(host, options);

Expand Down Expand Up @@ -58,7 +65,7 @@ export async function applicationGenerator(host: Tree, schema: Schema) {

if (options.customServer) {
await customServerGenerator(host, {
project: options.name,
project: options.projectName,
compiler: options.swc ? 'swc' : 'tsc',
});
}
Expand Down
6 changes: 4 additions & 2 deletions packages/next/src/generators/application/lib/add-e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ export async function addE2e(host: Tree, options: NormalizedSchema) {
...options,
linter: Linter.EsLint,
name: options.e2eProjectName,
directory: options.directory,
directory: options.e2eProjectRoot,
// the name and root are already normalized, instruct the generator to use them as is
projectNameAndRootFormat: 'as-provided',
project: options.projectName,
skipFormat: true,
});
Expand All @@ -43,7 +45,7 @@ export async function addE2e(host: Tree, options: NormalizedSchema) {
setParserOptionsProject: options.setParserOptionsProject,
webServerAddress: 'http://127.0.0.1:4200',
webServerCommand: `${getPackageManagerCommand().exec} nx serve ${
options.name
options.projectName
}`,
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export function createApplicationFiles(host: Tree, options: NormalizedSchema) {
host,
options.appProjectRoot
),
appContent: createAppJsx(options.name),
appContent: createAppJsx(options.projectName),
styleContent: createStyleRules(),
pageStyleContent: `.page {}`,

Expand Down Expand Up @@ -133,7 +133,7 @@ export function createApplicationFiles(host: Tree, options: NormalizedSchema) {
...(updatedJson.exclude || []),
...(appJSON.exclude || []),
'**e2e/**/*',
`dist/${options.name}/**/*`,
`dist/${options.projectName}/**/*`,
]),
],
};
Expand Down
64 changes: 34 additions & 30 deletions packages/next/src/generators/application/lib/normalize-options.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,7 @@
import { assertValidStyle } from '@nx/react/src/utils/assertion';
import {
extractLayoutDirectory,
getWorkspaceLayout,
joinPathFragments,
names,
Tree,
} from '@nx/devkit';
import { joinPathFragments, names, Tree } from '@nx/devkit';
import { determineProjectNameAndRootOptions } from '@nx/devkit/src/generators/project-name-and-root-utils';
import { Linter } from '@nx/linter';

import { assertValidStyle } from '@nx/react/src/utils/assertion';
import { Schema } from '../schema';

export interface NormalizedSchema extends Schema {
Expand All @@ -22,31 +16,41 @@ export interface NormalizedSchema extends Schema {
js?: boolean;
}

export function normalizeOptions(
export async function normalizeOptions(
host: Tree,
options: Schema
): NormalizedSchema {
const { layoutDirectory, projectDirectory } = extractLayoutDirectory(
options.directory
);
const name = names(options.name).fileName;

const appDirectory = projectDirectory
? `${names(projectDirectory).fileName}/${names(options.name).fileName}`
: names(options.name).fileName;

const appsDir = layoutDirectory ?? getWorkspaceLayout(host).appsDir;

const appProjectName = appDirectory.replace(new RegExp('/', 'g'), '-');
const e2eProjectName = options.rootProject ? 'e2e' : `${appProjectName}-e2e`;
): Promise<NormalizedSchema> {
const {
projectName: appProjectName,
projectRoot: appProjectRoot,
projectNameAndRootFormat,
} = await determineProjectNameAndRootOptions(host, {
name: options.name,
projectType: 'application',
directory: options.directory,
projectNameAndRootFormat: options.projectNameAndRootFormat,
rootProject: options.rootProject,
callingGenerator: '@nx/next:application',
});
options.rootProject = appProjectRoot === '.';
options.projectNameAndRootFormat = projectNameAndRootFormat;

const appProjectRoot = options.rootProject
? '.'
: joinPathFragments(appsDir, appDirectory);
let e2eProjectName = 'e2e';
let e2eProjectRoot = 'e2e';
if (!options.rootProject) {
const projectNameAndRoot = await determineProjectNameAndRootOptions(host, {
name: `${options.name}-e2e`,
projectType: 'application',
directory: options.directory,
projectNameAndRootFormat: options.projectNameAndRootFormat,
rootProject: options.rootProject,
callingGenerator: '@nx/next:application',
});
e2eProjectName = projectNameAndRoot.projectName;
e2eProjectRoot = projectNameAndRoot.projectRoot;
}

const e2eProjectRoot = options.rootProject
? 'e2e'
: joinPathFragments(appsDir, `${appDirectory}-e2e`);
const name = names(options.name).fileName;

const outputPath = joinPathFragments(
'dist',
Expand Down
6 changes: 4 additions & 2 deletions packages/next/src/generators/application/schema.d.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import { Linter } from '@nx/linter';
import { SupportedStyles } from '@nx/react';
import type { ProjectNameAndRootFormat } from '@nx/devkit/src/generators/project-name-and-root-utils';
import type { Linter } from '@nx/linter';
import type { SupportedStyles } from '@nx/react';

export interface Schema {
name: string;
style?: SupportedStyles;
skipFormat?: boolean;
directory?: string;
projectNameAndRootFormat?: ProjectNameAndRootFormat;
tags?: string;
unitTestRunner?: 'jest' | 'none';
e2eTestRunner?: 'cypress' | 'playwright' | 'none';
Expand Down
7 changes: 6 additions & 1 deletion packages/next/src/generators/application/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
"index": 0
},
"x-prompt": "What name would you like to use for the application?",
"pattern": "^[a-zA-Z].*$",
"pattern": "^[a-zA-Z][^:]*$",
"x-priority": "important"
},
"directory": {
Expand All @@ -23,6 +23,11 @@
"alias": "d",
"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
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ describe('normalizeOptions', () => {
tree = createTreeWithEmptyWorkspace();
});

it('should set importPath and projectRoot', () => {
const options = normalizeOptions(tree, {
it('should set importPath and projectRoot', async () => {
const options = await normalizeOptions(tree, {
name: 'my-lib',
style: 'css',
linter: Linter.None,
Expand Down
26 changes: 15 additions & 11 deletions packages/next/src/generators/library/lib/normalize-options.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,30 @@
import { getWorkspaceLayout, joinPathFragments, names, Tree } from '@nx/devkit';
import { getImportPath } from '@nx/js/src/utils/get-import-path';
import { Tree } from '@nx/devkit';
import { determineProjectNameAndRootOptions } from '@nx/devkit/src/generators/project-name-and-root-utils';
import { Schema } from '../schema';

export interface NormalizedSchema extends Schema {
importPath: string;
projectRoot: string;
}

export function normalizeOptions(
export async function normalizeOptions(
host: Tree,
options: Schema
): NormalizedSchema {
const name = names(options.name).fileName;
const projectDirectory = options.directory
? `${names(options.directory).fileName}/${name}`
: name;
): Promise<NormalizedSchema> {
const { projectRoot, importPath, projectNameAndRootFormat } =
await determineProjectNameAndRootOptions(host, {
name: options.name,
projectType: 'library',
directory: options.directory,
importPath: options.importPath,
projectNameAndRootFormat: options.projectNameAndRootFormat,
callingGenerator: '@nx/next:library',
});
options.projectNameAndRootFormat = projectNameAndRootFormat;

const { libsDir } = getWorkspaceLayout(host);
const projectRoot = joinPathFragments(libsDir, projectDirectory);
return {
...options,
importPath: options.importPath ?? getImportPath(host, projectDirectory),
importPath,
projectRoot,
};
}
9 changes: 8 additions & 1 deletion packages/next/src/generators/library/library.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,14 @@ import { Schema } from './schema';
import { normalizeOptions } from './lib/normalize-options';

export async function libraryGenerator(host: Tree, rawOptions: Schema) {
const options = normalizeOptions(host, rawOptions);
return await libraryGeneratorInternal(host, {
projectNameAndRootFormat: 'derived',
...rawOptions,
});
}

export async function libraryGeneratorInternal(host: Tree, rawOptions: Schema) {
const options = await normalizeOptions(host, rawOptions);
const tasks: GeneratorCallback[] = [];
const initTask = await nextInitGenerator(host, {
...options,
Expand Down
Loading