From 6b79866cc8d2fae08b16cbd983250fc4d8c5ec61 Mon Sep 17 00:00:00 2001 From: Nicholas Cunningham Date: Thu, 4 Jan 2024 12:13:24 -0700 Subject: [PATCH] feat(nextjs): Standalone projects now default to src --- docs/generated/cli/create-nx-workspace.md | 6 ++ .../packages/next/generators/application.json | 6 ++ .../nx/documents/create-nx-workspace.md | 6 ++ .../packages/workspace/generators/new.json | 5 ++ .../packages/workspace/generators/preset.json | 5 ++ e2e/next-core/src/next-pcv3.test.ts | 2 +- e2e/next-core/src/next-structure.test.ts | 4 +- e2e/next-core/src/next.test.ts | 6 +- .../src/next-component-tests.test.ts | 2 +- e2e/next-extensions/src/next-styles.test.ts | 2 +- e2e/utils/create-project-utils.ts | 6 ++ .../src/create-nx-workspace.test.ts | 8 ++- .../bin/create-nx-workspace.ts | 40 +++++++++++- .../application/application.spec.ts | 64 ++++++++++--------- .../specs/__fileName__.spec.tsx__tmpl__ | 6 +- .../lib/create-application-files.ts | 20 +++--- .../application/lib/normalize-options.ts | 2 + .../src/generators/application/schema.d.ts | 1 + .../src/generators/application/schema.json | 6 ++ .../convert-to-monorepo.spec.ts | 3 +- .../src/generators/new/generate-preset.ts | 1 + packages/workspace/src/generators/new/new.ts | 1 + .../workspace/src/generators/new/schema.json | 5 ++ .../src/generators/preset/preset.spec.ts | 2 +- .../workspace/src/generators/preset/preset.ts | 2 + .../src/generators/preset/schema.d.ts | 1 + .../src/generators/preset/schema.json | 5 ++ 27 files changed, 162 insertions(+), 55 deletions(-) diff --git a/docs/generated/cli/create-nx-workspace.md b/docs/generated/cli/create-nx-workspace.md index 202c214eda2232..b27439a06406b6 100644 --- a/docs/generated/cli/create-nx-workspace.md +++ b/docs/generated/cli/create-nx-workspace.md @@ -119,6 +119,12 @@ Type: `boolean` Enable the App Router for Next.js +### nextSrcDir + +Type: `boolean` + +Generate a 'src/' directory for Next.js + ### nxCloud Type: `boolean` diff --git a/docs/generated/packages/next/generators/application.json b/docs/generated/packages/next/generators/application.json index 652508f2ebb37b..ec6cee09510aa4 100644 --- a/docs/generated/packages/next/generators/application.json +++ b/docs/generated/packages/next/generators/application.json @@ -124,6 +124,12 @@ "description": "Enable the App Router for this project.", "x-prompt": "Would you like to use the App Router (recommended)?" }, + "src": { + "type": "boolean", + "default": true, + "description": "Generate a `src` directory for the project.", + "x-prompt": "Would you like to use `src/` directory?" + }, "rootProject": { "description": "Create an application at the root of the workspace.", "type": "boolean", diff --git a/docs/generated/packages/nx/documents/create-nx-workspace.md b/docs/generated/packages/nx/documents/create-nx-workspace.md index 202c214eda2232..b27439a06406b6 100644 --- a/docs/generated/packages/nx/documents/create-nx-workspace.md +++ b/docs/generated/packages/nx/documents/create-nx-workspace.md @@ -119,6 +119,12 @@ Type: `boolean` Enable the App Router for Next.js +### nextSrcDir + +Type: `boolean` + +Generate a 'src/' directory for Next.js + ### nxCloud Type: `boolean` diff --git a/docs/generated/packages/workspace/generators/new.json b/docs/generated/packages/workspace/generators/new.json index ead20a62830d00..64b534f755feed 100644 --- a/docs/generated/packages/workspace/generators/new.json +++ b/docs/generated/packages/workspace/generators/new.json @@ -65,6 +65,11 @@ "type": "boolean", "default": true }, + "nextSrcDir": { + "description": "Generate a `src` directory for this project.", + "type": "boolean", + "default": true + }, "e2eTestRunner": { "description": "The tool to use for running e2e tests.", "type": "string", diff --git a/docs/generated/packages/workspace/generators/preset.json b/docs/generated/packages/workspace/generators/preset.json index 4c8e09ab65d32a..35a7d51a28a0db 100644 --- a/docs/generated/packages/workspace/generators/preset.json +++ b/docs/generated/packages/workspace/generators/preset.json @@ -82,6 +82,11 @@ "type": "boolean", "default": true }, + "nextSrcDir": { + "description": "Generate a `src` directory for this project.", + "type": "boolean", + "default": true + }, "e2eTestRunner": { "description": "The tool to use for running e2e tests.", "type": "string", diff --git a/e2e/next-core/src/next-pcv3.test.ts b/e2e/next-core/src/next-pcv3.test.ts index 0ee191908ed3bd..95ff838a8cace0 100644 --- a/e2e/next-core/src/next-pcv3.test.ts +++ b/e2e/next-core/src/next-pcv3.test.ts @@ -34,7 +34,7 @@ describe('@nx/next/plugin', () => { }); }); - afterAll(() => cleanupProject()); + // afterAll(() => cleanupProject()); it('nx.json should contain plugin configuration', () => { const nxJson = readJson('nx.json'); diff --git a/e2e/next-core/src/next-structure.test.ts b/e2e/next-core/src/next-structure.test.ts index 1b5c34f3b7399c..95de4159c2f998 100644 --- a/e2e/next-core/src/next-structure.test.ts +++ b/e2e/next-core/src/next-structure.test.ts @@ -100,11 +100,11 @@ describe('Next.js Apps Libs', () => { ` ); - const mainPath = `packages/${appName}/pages/index.tsx`; + const mainPath = `packages/${appName}/src/pages/index.tsx`; const content = readFile(mainPath); updateFile( - `packages/${appName}/pages/api/hello.ts`, + `packages/${appName}/src/pages/api/hello.ts`, ` import { jsLibAsync } from '@${proj}/${jsLib}'; diff --git a/e2e/next-core/src/next.test.ts b/e2e/next-core/src/next.test.ts index fe6353f5ffd31e..0db2b35ede34ce 100644 --- a/e2e/next-core/src/next.test.ts +++ b/e2e/next-core/src/next.test.ts @@ -42,7 +42,7 @@ describe('Next.js Applications', () => { // 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`); + checkFilesExist(`${appName}/src/app/page.tsx`); // check build works expect(runCLI(`build ${appName}`)).toContain( `Successfully ran target build for project ${appName}` @@ -179,7 +179,7 @@ describe('Next.js Applications', () => { `generate @nx/next:app ${appName} --no-interactive --js --appDir=false` ); - checkFilesExist(`apps/${appName}/pages/index.js`); + checkFilesExist(`apps/${appName}/src/pages/index.js`); await checkApp(appName, { checkUnitTest: true, @@ -195,7 +195,7 @@ describe('Next.js Applications', () => { `generate @nx/next:lib ${libName} --no-interactive --style=none --js` ); - const mainPath = `apps/${appName}/pages/index.js`; + const mainPath = `apps/${appName}/src/pages/index.js`; updateFile( mainPath, `import '@${proj}/${libName}';\n` + readFile(mainPath) diff --git a/e2e/next-extensions/src/next-component-tests.test.ts b/e2e/next-extensions/src/next-component-tests.test.ts index 3f3f10f6b05f72..616df85786bcc2 100644 --- a/e2e/next-extensions/src/next-component-tests.test.ts +++ b/e2e/next-extensions/src/next-component-tests.test.ts @@ -152,7 +152,7 @@ function addTailwindToApp(appName: string) { ); updateFile(`apps/${appName}/cypress/support/component.ts`, (content) => { return `${content} -import '../../pages/styles.css'`; +import '../../src/pages/styles.css'`; }); updateFile(`apps/${appName}/components/button.cy.tsx`, (content) => { diff --git a/e2e/next-extensions/src/next-styles.test.ts b/e2e/next-extensions/src/next-styles.test.ts index 2f6e0e74e2fe3c..e9e4a88ab6755c 100644 --- a/e2e/next-extensions/src/next-styles.test.ts +++ b/e2e/next-extensions/src/next-styles.test.ts @@ -22,7 +22,7 @@ describe('Next.js Styles', () => { const lessApp = uniq('app'); runCLI( - `generate @nx/next:app ${lessApp} --no-interactive --style=less --appDir=false` + `generate @nx/next:app ${lessApp} --no-interactive --style=less --appDir=false --src=false` ); await checkApp(lessApp, { diff --git a/e2e/utils/create-project-utils.ts b/e2e/utils/create-project-utils.ts index 4722f14af9ef5c..0a557c678c7b93 100644 --- a/e2e/utils/create-project-utils.ts +++ b/e2e/utils/create-project-utils.ts @@ -229,6 +229,7 @@ export function runCreateWorkspace( standaloneApi, docker, nextAppDir, + nextSrcDir, e2eTestRunner, ssr, framework, @@ -247,6 +248,7 @@ export function runCreateWorkspace( routing?: boolean; docker?: boolean; nextAppDir?: boolean; + nextSrcDir?: boolean; e2eTestRunner?: 'cypress' | 'playwright' | 'jest' | 'detox' | 'none'; ssr?: boolean; framework?: string; @@ -275,6 +277,10 @@ export function runCreateWorkspace( command += ` --nextAppDir=${nextAppDir}`; } + if (nextSrcDir !== undefined) { + command += ` --nextSrcDir=${nextSrcDir}`; + } + if (docker !== undefined) { command += ` --docker=${docker}`; } diff --git a/e2e/workspace-create/src/create-nx-workspace.test.ts b/e2e/workspace-create/src/create-nx-workspace.test.ts index 07f564a0aa772e..a62abdbabf8808 100644 --- a/e2e/workspace-create/src/create-nx-workspace.test.ts +++ b/e2e/workspace-create/src/create-nx-workspace.test.ts @@ -227,7 +227,7 @@ describe('create-nx-workspace', () => { e2eTestRunner: 'none', }); - checkFilesExist(`apps/${appName}/pages/index.tsx`); + checkFilesExist(`apps/${appName}/src/pages/index.tsx`); expectNoAngularDevkit(); expectCodeIsFormatted(); @@ -240,12 +240,13 @@ describe('create-nx-workspace', () => { preset: 'nextjs-standalone', style: 'css', nextAppDir: true, + nextSrcDir: true, appName, packageManager, e2eTestRunner: 'none', }); - checkFilesExist('app/page.tsx'); + checkFilesExist('src/app/page.tsx'); expectNoAngularDevkit(); expectCodeIsFormatted(); @@ -258,12 +259,13 @@ describe('create-nx-workspace', () => { preset: 'nextjs-standalone', style: 'css', nextAppDir: false, + nextSrcDir: true, appName, packageManager, e2eTestRunner: 'none', }); - checkFilesExist('pages/index.tsx'); + checkFilesExist('src/pages/index.tsx'); expectNoAngularDevkit(); expectCodeIsFormatted(); diff --git a/packages/create-nx-workspace/bin/create-nx-workspace.ts b/packages/create-nx-workspace/bin/create-nx-workspace.ts index 3942ba7f22ca0c..c513910ba706db 100644 --- a/packages/create-nx-workspace/bin/create-nx-workspace.ts +++ b/packages/create-nx-workspace/bin/create-nx-workspace.ts @@ -49,6 +49,7 @@ interface ReactArguments extends BaseArguments { style: string; bundler: 'webpack' | 'vite' | 'rspack'; nextAppDir: boolean; + nextSrcDir: boolean; e2eTestRunner: 'none' | 'cypress' | 'playwright'; } @@ -164,6 +165,10 @@ export const commandsObject: yargs.Argv = yargs describe: chalk.dim`Enable the App Router for Next.js`, type: 'boolean', }) + .option('nextSrcDir', { + describe: chalk.dim`Generate a 'src/' directory for Next.js`, + type: 'boolean', + }) .option('e2eTestRunner', { describe: chalk.dim`Test runner to use for end to end (E2E) tests.`, choices: ['cypress', 'playwright', 'none'], @@ -512,6 +517,7 @@ async function determineReactOptions( let bundler: undefined | 'webpack' | 'vite' | 'rspack' = undefined; let e2eTestRunner: undefined | 'none' | 'cypress' | 'playwright' = undefined; let nextAppDir = false; + let nextSrcDir = false; if (parsedArgs.preset && parsedArgs.preset !== Preset.React) { preset = parsedArgs.preset; @@ -563,6 +569,7 @@ async function determineReactOptions( e2eTestRunner = await determineE2eTestRunner(parsedArgs); } else if (preset === Preset.NextJs || preset === Preset.NextJsStandalone) { nextAppDir = await determineNextAppDir(parsedArgs); + nextSrcDir = await determineNextSrcDir(parsedArgs); e2eTestRunner = await determineE2eTestRunner(parsedArgs); } @@ -614,7 +621,15 @@ async function determineReactOptions( style = reply.style; } - return { preset, style, appName, bundler, nextAppDir, e2eTestRunner }; + return { + preset, + style, + appName, + bundler, + nextAppDir, + nextSrcDir, + e2eTestRunner, + }; } async function determineVueOptions( @@ -1066,6 +1081,29 @@ async function determineNextAppDir( return reply.nextAppDir === 'Yes'; } +async function determineNextSrcDir( + parsedArgs: yargs.Arguments +): Promise { + if (parsedArgs.nextSrcDir !== undefined) return parsedArgs.nextSrcDir; + const reply = await enquirer.prompt<{ nextSrcDir: 'Yes' | 'No' }>([ + { + name: 'nextSrcDir', + message: 'Would you like to use the src/ directory?', + type: 'autocomplete', + choices: [ + { + name: 'Yes', + }, + { + name: 'No', + }, + ], + initial: 'Yes' as any, + }, + ]); + return reply.nextSrcDir === 'Yes'; +} + async function determineVueFramework( parsedArgs: yargs.Arguments ): Promise<'none' | 'nuxt'> { diff --git a/packages/next/src/generators/application/application.spec.ts b/packages/next/src/generators/application/application.spec.ts index 0d8569d018139f..6d836461db4846 100644 --- a/packages/next/src/generators/application/application.spec.ts +++ b/packages/next/src/generators/application/application.spec.ts @@ -82,12 +82,12 @@ describe('app', () => { `../dist/${name}/.next/types/**/*.ts`, 'next-env.d.ts', ]); - expect(tree.exists(`${name}/pages/styles.css`)).toBeFalsy(); - expect(tree.exists(`${name}/app/global.css`)).toBeTruthy(); - expect(tree.exists(`${name}/app/page.tsx`)).toBeTruthy(); - expect(tree.exists(`${name}/app/layout.tsx`)).toBeTruthy(); - expect(tree.exists(`${name}/app/api/hello/route.ts`)).toBeTruthy(); - expect(tree.exists(`${name}/app/page.module.css`)).toBeTruthy(); + expect(tree.exists(`${name}/src/pages/styles.css`)).toBeFalsy(); + expect(tree.exists(`${name}/src/app/global.css`)).toBeTruthy(); + expect(tree.exists(`${name}/src/app/page.tsx`)).toBeTruthy(); + expect(tree.exists(`${name}/src/app/layout.tsx`)).toBeTruthy(); + expect(tree.exists(`${name}/src/app/api/hello/route.ts`)).toBeTruthy(); + expect(tree.exists(`${name}/src/app/page.module.css`)).toBeTruthy(); expect(tree.exists(`${name}/public/favicon.ico`)).toBeTruthy(); }); @@ -123,7 +123,7 @@ describe('app', () => { projectNameAndRootFormat: 'as-provided', }); - const content = tree.read('app/page.tsx').toString(); + const content = tree.read('src/app/page.tsx').toString(); expect(content).not.toContain('import styles from'); expect(content).not.toContain('const StyledPage'); @@ -138,6 +138,7 @@ describe('app', () => { name, style: 'css', appDir: false, + src: false, projectNameAndRootFormat: 'as-provided', }); expect(tree.exists(`${name}/tsconfig.json`)).toBeTruthy(); @@ -166,6 +167,7 @@ describe('app', () => { name, style: 'none', appDir: false, + src: false, projectNameAndRootFormat: 'as-provided', }); @@ -186,12 +188,12 @@ describe('app', () => { projectNameAndRootFormat: 'as-provided', }); - expect(tree.exists(`${name}/app/page.module.scss`)).toBeTruthy(); - expect(tree.exists(`${name}/app/global.css`)).toBeTruthy(); + expect(tree.exists(`${name}/src/app/page.module.scss`)).toBeTruthy(); + expect(tree.exists(`${name}/src/app/global.css`)).toBeTruthy(); - const indexContent = tree.read(`${name}/app/page.tsx`, 'utf-8'); + const indexContent = tree.read(`${name}/src/app/page.tsx`, 'utf-8'); expect(indexContent).toContain(`import styles from './page.module.scss'`); - expect(tree.read(`${name}/app/layout.tsx`, 'utf-8')) + expect(tree.read(`${name}/src/app/layout.tsx`, 'utf-8')) .toMatchInlineSnapshot(` "import './global.css'; @@ -225,12 +227,12 @@ describe('app', () => { projectNameAndRootFormat: 'as-provided', }); - expect(tree.exists(`${name}/app/page.module.less`)).toBeTruthy(); - expect(tree.exists(`${name}/app/global.less`)).toBeTruthy(); + expect(tree.exists(`${name}/src/app/page.module.less`)).toBeTruthy(); + expect(tree.exists(`${name}/src/app/global.less`)).toBeTruthy(); - const indexContent = tree.read(`${name}/app/page.tsx`, 'utf-8'); + const indexContent = tree.read(`${name}/src/app/page.tsx`, 'utf-8'); expect(indexContent).toContain(`import styles from './page.module.less'`); - expect(tree.read(`${name}/app/layout.tsx`, 'utf-8')) + expect(tree.read(`${name}/src/app/layout.tsx`, 'utf-8')) .toMatchInlineSnapshot(` "import './global.less'; @@ -265,14 +267,14 @@ describe('app', () => { }); expect( - tree.exists(`${name}/app/page.module.styled-components`) + tree.exists(`${name}/src/app/page.module.styled-components`) ).toBeFalsy(); - expect(tree.exists(`${name}/app/global.css`)).toBeTruthy(); + expect(tree.exists(`${name}/src/app/global.css`)).toBeTruthy(); - const indexContent = tree.read(`${name}/app/page.tsx`, 'utf-8'); + const indexContent = tree.read(`${name}/src/app/page.tsx`, 'utf-8'); expect(indexContent).not.toContain(`import styles from './page.module`); expect(indexContent).toContain(`import styled from 'styled-components'`); - expect(tree.read(`${name}/app/layout.tsx`, 'utf-8')) + expect(tree.read(`${name}/src/app/layout.tsx`, 'utf-8')) .toMatchInlineSnapshot(` "import './global.css'; import { StyledComponentsRegistry } from './registry'; @@ -297,7 +299,7 @@ describe('app', () => { } " `); - expect(tree.read(`${name}/app/registry.tsx`, 'utf-8')) + expect(tree.read(`${name}/src/app/registry.tsx`, 'utf-8')) .toMatchInlineSnapshot(` "'use client'; @@ -348,14 +350,14 @@ describe('app', () => { }); expect( - tree.exists(`${name}/app/page.module.styled-components`) + tree.exists(`${name}/src/app/page.module.styled-components`) ).toBeFalsy(); - expect(tree.exists(`${name}/app/global.css`)).toBeTruthy(); + expect(tree.exists(`${name}/src/app/global.css`)).toBeTruthy(); - const indexContent = tree.read(`${name}/app/page.tsx`, 'utf-8'); + const indexContent = tree.read(`${name}/src/app/page.tsx`, 'utf-8'); expect(indexContent).not.toContain(`import styles from './page.module`); expect(indexContent).toContain(`import styled from '@emotion/styled'`); - expect(tree.read(`${name}/app/layout.tsx`, 'utf-8')) + expect(tree.read(`${name}/src/app/layout.tsx`, 'utf-8')) .toMatchInlineSnapshot(` "import './global.css'; @@ -406,17 +408,17 @@ describe('app', () => { projectNameAndRootFormat: 'as-provided', }); - const indexContent = tree.read(`${name}/app/page.tsx`, 'utf-8'); + const indexContent = tree.read(`${name}/src/app/page.tsx`, 'utf-8'); expect(indexContent).toMatchSnapshot(); - expect(tree.exists(`${name}/app/page.module.styled-jsx`)).toBeFalsy(); - expect(tree.exists(`${name}/app/global.css`)).toBeTruthy(); + expect(tree.exists(`${name}/src/app/page.module.styled-jsx`)).toBeFalsy(); + expect(tree.exists(`${name}/src/app/global.css`)).toBeTruthy(); expect(indexContent).not.toContain(`import styles from './page.module`); expect(indexContent).not.toContain( `import styled from 'styled-components'` ); - expect(tree.read(`${name}/app/layout.tsx`, 'utf-8')) + expect(tree.read(`${name}/src/app/layout.tsx`, 'utf-8')) .toMatchInlineSnapshot(` "import './global.css'; import { StyledJsxRegistry } from './registry'; @@ -436,7 +438,7 @@ describe('app', () => { } " `); - expect(tree.read(`${name}/app/registry.tsx`, 'utf-8')) + expect(tree.read(`${name}/src/app/registry.tsx`, 'utf-8')) .toMatchInlineSnapshot(` "'use client'; @@ -588,7 +590,7 @@ describe('app', () => { projectNameAndRootFormat: 'as-provided', }); - const appContent = tree.read(`${name}/app/page.tsx`, 'utf-8'); + const appContent = tree.read(`${name}/src/app/page.tsx`, 'utf-8'); expect(appContent).not.toMatch(/extends Component/); }); @@ -709,7 +711,7 @@ describe('app', () => { js: true, }); - expect(tree.exists(`${name}/app/page.js`)).toBeTruthy(); + expect(tree.exists(`${name}/src/app/page.js`)).toBeTruthy(); expect(tree.exists(`${name}/specs/index.spec.js`)).toBeTruthy(); expect(tree.exists(`${name}/index.d.js`)).toBeFalsy(); expect(tree.exists(`${name}/index.d.ts`)).toBeFalsy(); diff --git a/packages/next/src/generators/application/files/common/specs/__fileName__.spec.tsx__tmpl__ b/packages/next/src/generators/application/files/common/specs/__fileName__.spec.tsx__tmpl__ index 42c94022afd1c1..f364a500a600e9 100644 --- a/packages/next/src/generators/application/files/common/specs/__fileName__.spec.tsx__tmpl__ +++ b/packages/next/src/generators/application/files/common/specs/__fileName__.spec.tsx__tmpl__ @@ -1,8 +1,10 @@ import React from 'react'; import { render } from '@testing-library/react'; - +<% if (src) { %> +import Index from '../src/pages/index'; +<% } else { %> import Index from '../pages/index'; - +<% } %> describe('Index', () => { it('should render successfully', () => { const { baseElement } = render(); diff --git a/packages/next/src/generators/application/lib/create-application-files.ts b/packages/next/src/generators/application/lib/create-application-files.ts index ddb80ad0930e80..06a580655711da 100644 --- a/packages/next/src/generators/application/lib/create-application-files.ts +++ b/packages/next/src/generators/application/lib/create-application-files.ts @@ -48,6 +48,10 @@ export function createApplicationFiles(host: Tree, options: NormalizedSchema) { stylesExt: options.style === 'less' ? options.style : 'css', }; + const generatedAppFilePath = options.src + ? join(options.appProjectRoot, 'src') + : options.appProjectRoot; + generateFiles( host, join(__dirname, '../files/common'), @@ -59,7 +63,7 @@ export function createApplicationFiles(host: Tree, options: NormalizedSchema) { generateFiles( host, join(__dirname, '../files/app'), - join(options.appProjectRoot, 'app'), + join(generatedAppFilePath, 'app'), templateVariables ); @@ -76,21 +80,21 @@ export function createApplicationFiles(host: Tree, options: NormalizedSchema) { generateFiles( host, join(__dirname, '../files/app-styled-components'), - join(options.appProjectRoot, 'app'), + join(generatedAppFilePath, 'app'), templateVariables ); } else if (options.style === 'styled-jsx') { generateFiles( host, join(__dirname, '../files/app-styled-jsx'), - join(options.appProjectRoot, 'app'), + join(generatedAppFilePath, 'app'), templateVariables ); } else { generateFiles( host, join(__dirname, '../files/app-default-layout'), - join(options.appProjectRoot, 'app'), + join(generatedAppFilePath, 'app'), templateVariables ); } @@ -98,7 +102,7 @@ export function createApplicationFiles(host: Tree, options: NormalizedSchema) { generateFiles( host, join(__dirname, '../files/pages'), - join(options.appProjectRoot, 'pages'), + join(generatedAppFilePath, 'pages'), templateVariables ); } @@ -152,16 +156,16 @@ export function createApplicationFiles(host: Tree, options: NormalizedSchema) { if (options.styledModule) { if (options.appDir) { - host.delete(`${options.appProjectRoot}/app/page.module.${options.style}`); + host.delete(`${generatedAppFilePath}/app/page.module.${options.style}`); } else { host.delete( - `${options.appProjectRoot}/pages/${options.fileName}.module.${options.style}` + `${generatedAppFilePath}/pages/${options.fileName}.module.${options.style}` ); } } if (options.style !== 'styled-components') { - host.delete(`${options.appProjectRoot}/pages/_document.tsx`); + host.delete(`${generatedAppFilePath}/pages/_document.tsx`); } if (options.js) { diff --git a/packages/next/src/generators/application/lib/normalize-options.ts b/packages/next/src/generators/application/lib/normalize-options.ts index d4b1850293e3d3..b956a15f2964c5 100644 --- a/packages/next/src/generators/application/lib/normalize-options.ts +++ b/packages/next/src/generators/application/lib/normalize-options.ts @@ -53,6 +53,7 @@ export async function normalizeOptions( const fileName = 'index'; const appDir = options.appDir ?? true; + const src = options.src ?? true; const styledModule = /^(css|scss|less)$/.test(options.style) ? null @@ -63,6 +64,7 @@ export async function normalizeOptions( return { ...options, appDir, + src, appProjectRoot, e2eProjectName, e2eProjectRoot, diff --git a/packages/next/src/generators/application/schema.d.ts b/packages/next/src/generators/application/schema.d.ts index a83160ae6065d3..8174bc9f2c6dbd 100644 --- a/packages/next/src/generators/application/schema.d.ts +++ b/packages/next/src/generators/application/schema.d.ts @@ -18,5 +18,6 @@ export interface Schema { customServer?: boolean; skipPackageJson?: boolean; appDir?: boolean; + src?: boolean; rootProject?: boolean; } diff --git a/packages/next/src/generators/application/schema.json b/packages/next/src/generators/application/schema.json index 2c3a51b74d0d4b..242e9fed49ee9e 100644 --- a/packages/next/src/generators/application/schema.json +++ b/packages/next/src/generators/application/schema.json @@ -127,6 +127,12 @@ "description": "Enable the App Router for this project.", "x-prompt": "Would you like to use the App Router (recommended)?" }, + "src": { + "type": "boolean", + "default": true, + "description": "Generate a `src` directory for the project.", + "x-prompt": "Would you like to use `src/` directory?" + }, "rootProject": { "description": "Create an application at the root of the workspace.", "type": "boolean", diff --git a/packages/workspace/src/generators/convert-to-monorepo/convert-to-monorepo.spec.ts b/packages/workspace/src/generators/convert-to-monorepo/convert-to-monorepo.spec.ts index 78653e9a83a996..79f3a3807076c4 100644 --- a/packages/workspace/src/generators/convert-to-monorepo/convert-to-monorepo.spec.ts +++ b/packages/workspace/src/generators/convert-to-monorepo/convert-to-monorepo.spec.ts @@ -111,6 +111,7 @@ describe('monorepo generator', () => { unitTestRunner: 'jest', e2eTestRunner: 'none', appDir: true, + src: true, linter: 'eslint', rootProject: true, }); @@ -121,7 +122,7 @@ describe('monorepo generator', () => { expect(readProjectConfiguration(tree, 'demo')).toMatchObject({ sourceRoot: 'apps/demo', }); - expect(tree.read('apps/demo/app/page.tsx', 'utf-8')).toContain('demo'); + expect(tree.read('apps/demo/src/app/page.tsx', 'utf-8')).toContain('demo'); expect(readProjectConfiguration(tree, 'util')).toMatchObject({ sourceRoot: 'libs/util/src', }); diff --git a/packages/workspace/src/generators/new/generate-preset.ts b/packages/workspace/src/generators/new/generate-preset.ts index 51681b0db4e495..5095515c1868a5 100644 --- a/packages/workspace/src/generators/new/generate-preset.ts +++ b/packages/workspace/src/generators/new/generate-preset.ts @@ -70,6 +70,7 @@ export function generatePreset(host: Tree, opts: NormalizedSchema) { opts.docker ? `--docker=${opts.docker}` : null, opts.js ? `--js` : null, opts.nextAppDir ? '--nextAppDir=true' : '--nextAppDir=false', + opts.nextSrcDir ? '--nextSrcDir=true' : '--nextSrcDir=false', opts.packageManager ? `--packageManager=${opts.packageManager}` : null, opts.standaloneApi !== undefined ? `--standaloneApi=${opts.standaloneApi}` diff --git a/packages/workspace/src/generators/new/new.ts b/packages/workspace/src/generators/new/new.ts index fb45215e7a44fd..77f9b425f19425 100644 --- a/packages/workspace/src/generators/new/new.ts +++ b/packages/workspace/src/generators/new/new.ts @@ -26,6 +26,7 @@ interface Schema { docker?: boolean; js?: boolean; nextAppDir?: boolean; + nextSrcDir?: boolean; linter?: Linter; bundler?: 'vite' | 'webpack'; standaloneApi?: boolean; diff --git a/packages/workspace/src/generators/new/schema.json b/packages/workspace/src/generators/new/schema.json index 3794e31911c506..59ba64cf25ba6f 100644 --- a/packages/workspace/src/generators/new/schema.json +++ b/packages/workspace/src/generators/new/schema.json @@ -68,6 +68,11 @@ "type": "boolean", "default": true }, + "nextSrcDir": { + "description": "Generate a `src` directory for this project.", + "type": "boolean", + "default": true + }, "e2eTestRunner": { "description": "The tool to use for running e2e tests.", "type": "string", diff --git a/packages/workspace/src/generators/preset/preset.spec.ts b/packages/workspace/src/generators/preset/preset.spec.ts index 868c42211e74fc..00d1d788b3dda8 100644 --- a/packages/workspace/src/generators/preset/preset.spec.ts +++ b/packages/workspace/src/generators/preset/preset.spec.ts @@ -70,7 +70,7 @@ describe('preset', () => { style: 'css', linter: 'eslint', }); - expect(tree.exists('/apps/proj/app/page.tsx')).toBe(true); + expect(tree.exists('/apps/proj/src/app/page.tsx')).toBe(true); }); it(`should create files (preset = ${Preset.Express})`, async () => { diff --git a/packages/workspace/src/generators/preset/preset.ts b/packages/workspace/src/generators/preset/preset.ts index 196d6357db9335..835e579f783746 100644 --- a/packages/workspace/src/generators/preset/preset.ts +++ b/packages/workspace/src/generators/preset/preset.ts @@ -143,6 +143,7 @@ async function createPreset(tree: Tree, options: Schema) { style: options.style, linter: options.linter, appDir: options.nextAppDir, + src: options.nextSrcDir, e2eTestRunner: options.e2eTestRunner ?? 'cypress', }); } else if (options.preset === Preset.NextJsStandalone) { @@ -155,6 +156,7 @@ async function createPreset(tree: Tree, options: Schema) { style: options.style, linter: options.linter, appDir: options.nextAppDir, + src: options.nextSrcDir, e2eTestRunner: options.e2eTestRunner ?? 'cypress', rootProject: true, }); diff --git a/packages/workspace/src/generators/preset/schema.d.ts b/packages/workspace/src/generators/preset/schema.d.ts index e609314a4da791..13fbbfbc61ded6 100644 --- a/packages/workspace/src/generators/preset/schema.d.ts +++ b/packages/workspace/src/generators/preset/schema.d.ts @@ -12,6 +12,7 @@ export interface Schema { bundler?: 'vite' | 'webpack' | 'rspack' | 'esbuild'; docker?: boolean; nextAppDir?: boolean; + nextSrcDir?: boolean; routing?: boolean; standaloneApi?: boolean; e2eTestRunner?: 'cypress' | 'playwright' | 'jest' | 'detox' | 'none'; diff --git a/packages/workspace/src/generators/preset/schema.json b/packages/workspace/src/generators/preset/schema.json index 2fa4d19605268b..8c556d53212d10 100644 --- a/packages/workspace/src/generators/preset/schema.json +++ b/packages/workspace/src/generators/preset/schema.json @@ -85,6 +85,11 @@ "type": "boolean", "default": true }, + "nextSrcDir": { + "description": "Generate a `src` directory for this project.", + "type": "boolean", + "default": true + }, "e2eTestRunner": { "description": "The tool to use for running e2e tests.", "type": "string",