feat(nextjs): Standalone projects now default to src (#21010)

This commit is contained in:
Nicholas Cunningham 2024-01-17 11:22:51 -07:00 committed by GitHub
parent 49cff89908
commit c43b22dc88
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
27 changed files with 172 additions and 57 deletions

View File

@ -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`

View File

@ -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",

View File

@ -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`

View File

@ -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",

View File

@ -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",

View File

@ -45,7 +45,7 @@ describe('Next.js Apps Libs', () => {
const buildableLib = uniq('buildablelib');
runCLI(
`generate @nx/next:app ${appName} --no-interactive --style=css --appDir=false --src=false`
`generate @nx/next:app ${appName} --no-interactive --style=css --appDir=false`
);
runCLI(`generate @nx/next:lib ${nextLib} --no-interactive`);
runCLI(`generate @nx/js:lib ${jsLib} --no-interactive`);
@ -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}';

View File

@ -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)

View File

@ -108,7 +108,9 @@ function addBabelSupport(path: string) {
}
function createAppWithCt(appName: string) {
runCLI(`generate @nx/next:app ${appName} --no-interactive --appDir=false`);
runCLI(
`generate @nx/next:app ${appName} --no-interactive --appDir=false --src=false`
);
runCLI(
`generate @nx/next:component button --project=${appName} --directory=components --flat --no-interactive`
);

View File

@ -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, {

View File

@ -12,6 +12,7 @@ import {
getPackageManagerCommand,
getSelectedPackageManager,
runCommand,
runE2ETests,
} from '@nx/e2e/utils';
import { join } from 'path';
@ -41,7 +42,9 @@ describe('@nx/workspace:convert-to-monorepo', () => {
expect(() => runCLI(`test ${reactApp}`)).not.toThrow();
expect(() => runCLI(`lint ${reactApp}`)).not.toThrow();
expect(() => runCLI(`lint e2e`)).not.toThrow();
if (runE2ETests()) {
expect(() => runCLI(`e2e e2e`)).not.toThrow();
}
});
it('should be convert a standalone vite and playwright react project to a monorepo', async () => {
@ -61,7 +64,9 @@ describe('@nx/workspace:convert-to-monorepo', () => {
expect(() => runCLI(`test ${reactApp}`)).not.toThrow();
expect(() => runCLI(`lint ${reactApp}`)).not.toThrow();
expect(() => runCLI(`lint e2e`)).not.toThrow();
if (runE2ETests()) {
expect(() => runCLI(`e2e e2e`)).not.toThrow();
}
});
});

View File

@ -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}`;
}

View File

@ -223,11 +223,12 @@ describe('create-nx-workspace', () => {
style: 'css',
appName,
nextAppDir: false,
nextSrcDir: true,
packageManager,
e2eTestRunner: 'none',
});
checkFilesExist(`apps/${appName}/pages/index.tsx`);
checkFilesExist(`apps/${appName}/src/pages/index.tsx`);
expectNoAngularDevkit();
expectCodeIsFormatted();
@ -240,12 +241,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 +260,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();

View File

@ -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<Arguments> = 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<ReactArguments>
): Promise<boolean> {
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<VueArguments>
): Promise<'none' | 'nuxt'> {

View File

@ -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();

View File

@ -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(<Index />);

View File

@ -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
);
}
@ -151,16 +155,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) {

View File

@ -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,

View File

@ -18,5 +18,6 @@ export interface Schema {
customServer?: boolean;
skipPackageJson?: boolean;
appDir?: boolean;
src?: boolean;
rootProject?: boolean;
}

View File

@ -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",

View File

@ -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',
});

View File

@ -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}`

View File

@ -26,6 +26,7 @@ interface Schema {
docker?: boolean;
js?: boolean;
nextAppDir?: boolean;
nextSrcDir?: boolean;
linter?: Linter;
bundler?: 'vite' | 'webpack';
standaloneApi?: boolean;

View File

@ -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",

View File

@ -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 () => {

View File

@ -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,
});

View File

@ -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';

View File

@ -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",