feat(node): update CNW to support generating a node server with a framework (#14313)

This commit is contained in:
Nicholas Cunningham 2023-01-13 11:39:38 -07:00 committed by GitHub
parent 5faef5d972
commit 00caf6ae5e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 210 additions and 26 deletions

View File

@ -75,6 +75,12 @@ Default: `main`
Default base to use for new projects Default base to use for new projects
### framework
Type: `string`
Framework option to be used when the node-server preset is selected
### help ### help
Type: `boolean` Type: `boolean`
@ -113,7 +119,7 @@ Package manager to use
Type: `string` Type: `string`
Customizes the initial content of your workspace. Default presets include: ["apps", "empty", "core", "npm", "ts", "web-components", "angular-monorepo", "angular-standalone", "react-monorepo", "react-standalone", "react-native", "expo", "next", "nest", "express", "react", "angular"]. To build your own see https://nx.dev/packages/nx-plugin#preset Customizes the initial content of your workspace. Default presets include: ["apps", "empty", "core", "npm", "ts", "web-components", "angular-monorepo", "angular-standalone", "react-monorepo", "react-standalone", "react-native", "expo", "next", "nest", "express", "react", "angular", "node-server"]. To build your own see https://nx.dev/packages/nx-plugin#preset
### skipGit ### skipGit

View File

@ -89,6 +89,12 @@
"description": "The port which the server will be run on", "description": "The port which the server will be run on",
"type": "number", "type": "number",
"default": 3000 "default": 3000
},
"rootProject": {
"description": "Create node application at the root of the workspace",
"type": "boolean",
"default": false,
"hidden": true
} }
}, },
"required": [], "required": [],

View File

@ -75,6 +75,12 @@ Default: `main`
Default base to use for new projects Default base to use for new projects
### framework
Type: `string`
Framework option to be used when the node-server preset is selected
### help ### help
Type: `boolean` Type: `boolean`
@ -113,7 +119,7 @@ Package manager to use
Type: `string` Type: `string`
Customizes the initial content of your workspace. Default presets include: ["apps", "empty", "core", "npm", "ts", "web-components", "angular-monorepo", "angular-standalone", "react-monorepo", "react-standalone", "react-native", "expo", "next", "nest", "express", "react", "angular"]. To build your own see https://nx.dev/packages/nx-plugin#preset Customizes the initial content of your workspace. Default presets include: ["apps", "empty", "core", "npm", "ts", "web-components", "angular-monorepo", "angular-standalone", "react-monorepo", "react-standalone", "react-native", "expo", "next", "nest", "express", "react", "angular", "node-server"]. To build your own see https://nx.dev/packages/nx-plugin#preset
### skipGit ### skipGit

View File

@ -59,6 +59,11 @@
"description": "The package manager used to install dependencies.", "description": "The package manager used to install dependencies.",
"type": "string", "type": "string",
"enum": ["npm", "yarn", "pnpm"] "enum": ["npm", "yarn", "pnpm"]
},
"framework": {
"description": "The framework which the application is using",
"type": "string",
"enum": ["express", "koa", "fastify", "connect"]
} }
}, },
"additionalProperties": true, "additionalProperties": true,

View File

@ -57,6 +57,11 @@
"description": "The package manager used to install dependencies.", "description": "The package manager used to install dependencies.",
"type": "string", "type": "string",
"enum": ["npm", "yarn", "pnpm"] "enum": ["npm", "yarn", "pnpm"]
},
"framework": {
"description": "The framework which the application is using",
"type": "string",
"enum": ["express", "koa", "fastify", "connect"]
} }
}, },
"presets": [] "presets": []

View File

@ -126,9 +126,9 @@ describe('Node Applications', () => {
it('should be able to generate an express application', async () => { it('should be able to generate an express application', async () => {
const nodeapp = uniq('nodeapp'); const nodeapp = uniq('nodeapp');
const originalEnvPort = process.env.port; const originalEnvPort = process.env.PORT;
const port = 3333; const port = 3333;
process.env.port = `${port}`; process.env.PORT = `${port}`;
runCLI(`generate @nrwl/express:app ${nodeapp} --linter=eslint`); runCLI(`generate @nrwl/express:app ${nodeapp} --linter=eslint`);
const lintResults = runCLI(`lint ${nodeapp}`); const lintResults = runCLI(`lint ${nodeapp}`);

View File

@ -31,6 +31,7 @@ type Arguments = {
appName: string; appName: string;
cli: string; cli: string;
style: string; style: string;
framework: string;
nxCloud: boolean; nxCloud: boolean;
allPrompts: boolean; allPrompts: boolean;
packageManager: PackageManager; packageManager: PackageManager;
@ -62,6 +63,7 @@ enum Preset {
Express = 'express', Express = 'express',
React = 'react', React = 'react',
Angular = 'angular', Angular = 'angular',
NodeServer = 'node-server',
} }
const presetOptions: { name: Preset; message: string }[] = [ const presetOptions: { name: Preset; message: string }[] = [
@ -96,6 +98,11 @@ const presetOptions: { name: Preset; message: string }[] = [
message: message:
'react-native [a monorepo with a single React Native application]', 'react-native [a monorepo with a single React Native application]',
}, },
{
name: Preset.NodeServer,
message:
'node [a standalone repo with a single Node Server e.g. Express]',
},
]; ];
const nxVersion = require('../package.json').version; const nxVersion = require('../package.json').version;
@ -145,6 +152,10 @@ export const commandsObject: yargs.Argv<Arguments> = yargs
describe: chalk.dim`Style option to be used when a preset with pregenerated app is selected`, describe: chalk.dim`Style option to be used when a preset with pregenerated app is selected`,
type: 'string', type: 'string',
}) })
.option('framework', {
describe: chalk.dim`Framework option to be used when the node-server preset is selected`,
type: 'string',
})
.option('nxCloud', { .option('nxCloud', {
describe: chalk.dim(messages.getPromptMessage('nxCloudCreation')), describe: chalk.dim(messages.getPromptMessage('nxCloudCreation')),
type: 'boolean', type: 'boolean',
@ -224,6 +235,7 @@ async function main(parsedArgs: yargs.Arguments<Arguments>) {
ci, ci,
skipGit, skipGit,
commit, commit,
framework,
} = parsedArgs; } = parsedArgs;
output.log({ output.log({
@ -248,6 +260,7 @@ async function main(parsedArgs: yargs.Arguments<Arguments>) {
style, style,
nxCloud, nxCloud,
defaultBase, defaultBase,
framework,
} }
); );
@ -300,7 +313,7 @@ async function getConfiguration(
argv: yargs.Arguments<Arguments> argv: yargs.Arguments<Arguments>
): Promise<void> { ): Promise<void> {
try { try {
let name, appName, style, preset; let name, appName, style, preset, framework;
output.log({ output.log({
title: title:
@ -343,6 +356,9 @@ async function getConfiguration(
} else { } else {
name = await determineRepoName(argv); name = await determineRepoName(argv);
appName = await determineAppName(preset, argv); appName = await determineAppName(preset, argv);
if (preset === Preset.NodeServer) {
framework = await determineFramework(preset, argv);
}
} }
style = await determineStyle(preset, argv); style = await determineStyle(preset, argv);
} }
@ -358,6 +374,7 @@ async function getConfiguration(
preset, preset,
appName, appName,
style, style,
framework,
cli, cli,
nxCloud, nxCloud,
packageManager, packageManager,
@ -643,6 +660,47 @@ async function determineAppName(
}); });
} }
async function determineFramework(
preset: Preset,
parsedArgs: yargs.Arguments<Arguments>
): Promise<string> {
if (preset !== Preset.NodeServer) {
return Promise.resolve('');
}
const frameworkChoices = ['express', 'koa', 'fastify', 'connect'];
if (!parsedArgs.framework) {
return enquirer
.prompt([
{
message: 'What framework should be used?',
type: 'select',
name: 'framework',
choices: frameworkChoices,
},
])
.then((a: { framework: string }) => a.framework);
}
const foundFramework = frameworkChoices.indexOf(parsedArgs.framework);
if (foundFramework < 0) {
output.error({
title: 'Invalid framwork',
bodyLines: [
`It must be one of the following:`,
'',
...frameworkChoices.map((choice) => choice),
],
});
process.exit(1);
}
return Promise.resolve(parsedArgs.framework);
}
function isValidCli(cli: string): cli is 'angular' | 'nx' { function isValidCli(cli: string): cli is 'angular' | 'nx' {
return ['nx', 'angular'].indexOf(cli) !== -1; return ['nx', 'angular'].indexOf(cli) !== -1;
} }
@ -685,7 +743,8 @@ async function determineStyle(
preset === Preset.Nest || preset === Preset.Nest ||
preset === Preset.Express || preset === Preset.Express ||
preset === Preset.ReactNative || preset === Preset.ReactNative ||
preset === Preset.Expo preset === Preset.Expo ||
preset === Preset.NodeServer
) { ) {
return Promise.resolve(null); return Promise.resolve(null);
} }
@ -1182,6 +1241,7 @@ function pointToTutorialAndCourse(preset: Preset) {
}); });
break; break;
case Preset.Express: case Preset.Express:
case Preset.NodeServer:
output.addVerticalSeparator(); output.addVerticalSeparator();
output.note({ output.note({
title, title,

View File

@ -16,11 +16,14 @@ describe('app', () => {
} as Schema); } as Schema);
const mainFile = appTree.read('apps/my-node-app/src/main.ts').toString(); const mainFile = appTree.read('apps/my-node-app/src/main.ts').toString();
expect(mainFile).toContain(`import * as express from 'express';`); expect(mainFile).toContain(`import express from 'express';`);
const tsconfig = readJson(appTree, 'apps/my-node-app/tsconfig.json'); const tsconfig = readJson(appTree, 'apps/my-node-app/tsconfig.json');
expect(tsconfig).toMatchInlineSnapshot(` expect(tsconfig).toMatchInlineSnapshot(`
Object { Object {
"compilerOptions": Object {
"esModuleInterop": true,
},
"extends": "../../tsconfig.base.json", "extends": "../../tsconfig.base.json",
"files": Array [], "files": Array [],
"include": Array [], "include": Array [],
@ -111,12 +114,13 @@ Object {
expect(appTree.exists('apps/my-node-app/src/main.js')).toBeTruthy(); expect(appTree.exists('apps/my-node-app/src/main.js')).toBeTruthy();
expect(appTree.read('apps/my-node-app/src/main.js').toString()).toContain( expect(appTree.read('apps/my-node-app/src/main.js').toString()).toContain(
`import * as express from 'express';` `import express from 'express';`
); );
const tsConfig = readJson(appTree, 'apps/my-node-app/tsconfig.json'); const tsConfig = readJson(appTree, 'apps/my-node-app/tsconfig.json');
expect(tsConfig.compilerOptions).toEqual({ expect(tsConfig.compilerOptions).toEqual({
allowJs: true, allowJs: true,
esModuleInterop: true,
}); });
const tsConfigApp = readJson( const tsConfigApp = readJson(

View File

@ -37,7 +37,7 @@ function addMainFile(tree: Tree, options: NormalizedSchema) {
* This is only a minimal backend to get started. * This is only a minimal backend to get started.
*/ */
import * as express from 'express'; import express from 'express';
import * as path from 'path'; import * as path from 'path';
const app = express(); const app = express();
@ -48,7 +48,7 @@ app.get('/api', (req, res) => {
res.send({ message: 'Welcome to ${options.name}!' }); res.send({ message: 'Welcome to ${options.name}!' });
}); });
const port = process.env.port || 3333; const port = process.env.PORT || 3333;
const server = app.listen(port, () => { const server = app.listen(port, () => {
console.log(\`Listening at http://localhost:\${port}/api\`); console.log(\`Listening at http://localhost:\${port}/api\`);
}); });

View File

@ -41,6 +41,7 @@ describe('app', () => {
await applicationGenerator(tree, { await applicationGenerator(tree, {
name: 'myNodeApp', name: 'myNodeApp',
standaloneConfig: false, standaloneConfig: false,
bundler: 'webpack',
}); });
const project = readProjectConfiguration(tree, 'my-node-app'); const project = readProjectConfiguration(tree, 'my-node-app');
expect(project.root).toEqual('my-node-app'); expect(project.root).toEqual('my-node-app');
@ -116,6 +117,9 @@ describe('app', () => {
const tsconfig = readJson(tree, 'my-node-app/tsconfig.json'); const tsconfig = readJson(tree, 'my-node-app/tsconfig.json');
expect(tsconfig).toMatchInlineSnapshot(` expect(tsconfig).toMatchInlineSnapshot(`
Object { Object {
"compilerOptions": Object {
"esModuleInterop": true,
},
"extends": "../tsconfig.base.json", "extends": "../tsconfig.base.json",
"files": Array [], "files": Array [],
"include": Array [], "include": Array [],
@ -401,6 +405,7 @@ describe('app', () => {
const tsConfig = readJson(tree, 'my-node-app/tsconfig.json'); const tsConfig = readJson(tree, 'my-node-app/tsconfig.json');
expect(tsConfig.compilerOptions).toEqual({ expect(tsConfig.compilerOptions).toEqual({
allowJs: true, allowJs: true,
esModuleInterop: true,
}); });
const tsConfigApp = readJson(tree, 'my-node-app/tsconfig.app.json'); const tsConfigApp = readJson(tree, 'my-node-app/tsconfig.app.json');

View File

@ -27,7 +27,7 @@ import { Linter, lintProjectGenerator } from '@nrwl/linter';
import { jestProjectGenerator } from '@nrwl/jest'; import { jestProjectGenerator } from '@nrwl/jest';
import { runTasksInSerial } from '@nrwl/workspace/src/utilities/run-tasks-in-serial'; import { runTasksInSerial } from '@nrwl/workspace/src/utilities/run-tasks-in-serial';
import { Schema } from './schema'; import { NodeJsFrameWorks, Schema } from './schema';
import { initGenerator } from '../init/init'; import { initGenerator } from '../init/init';
import { getRelativePathToRootTsConfig } from '@nrwl/workspace/src/utilities/typescript'; import { getRelativePathToRootTsConfig } from '@nrwl/workspace/src/utilities/typescript';
import { import {
@ -43,6 +43,8 @@ import {
} from '../../utils/versions'; } from '../../utils/versions';
import { prompt } from 'enquirer'; import { prompt } from 'enquirer';
import * as shared from '@nrwl/workspace/src/utils/create-ts-config';
export interface NormalizedSchema extends Schema { export interface NormalizedSchema extends Schema {
appProjectRoot: string; appProjectRoot: string;
parsedTags: string[]; parsedTags: string[];
@ -290,14 +292,27 @@ function addProjectDependencies(
} }
function updateTsConfigOptions(tree: Tree, options: NormalizedSchema) { function updateTsConfigOptions(tree: Tree, options: NormalizedSchema) {
// updatae tsconfig.app.json to typecheck default exports https://www.typescriptlang.org/tsconfig#esModuleInterop updateJson(tree, `${options.appProjectRoot}/tsconfig.json`, (json) => {
updateJson(tree, `${options.appProjectRoot}/tsconfig.app.json`, (json) => ({ if (options.rootProject) {
return {
compilerOptions: {
...shared.tsConfigBaseOptions,
...json.compilerOptions,
},
...json,
extends: undefined,
exclude: ['node_modules', 'tmp'],
};
} else {
return {
...json, ...json,
compilerOptions: { compilerOptions: {
...json.compilerOptions, ...json.compilerOptions,
esModuleInterop: true, esModuleInterop: true,
}, },
})); };
}
});
} }
export async function applicationGenerator(tree: Tree, schema: Schema) { export async function applicationGenerator(tree: Tree, schema: Schema) {
@ -313,9 +328,8 @@ export async function applicationGenerator(tree: Tree, schema: Schema) {
addProjectDependencies(tree, options); addProjectDependencies(tree, options);
addAppFiles(tree, options); addAppFiles(tree, options);
addProject(tree, options); addProject(tree, options);
if (options.framework && options?.bundler === 'esbuild') {
updateTsConfigOptions(tree, options); updateTsConfigOptions(tree, options);
}
if (options.linter !== Linter.None) { if (options.linter !== Linter.None) {
const lintTask = await addLintingToApplication(tree, { const lintTask = await addLintingToApplication(tree, {
@ -365,7 +379,11 @@ function normalizeOptions(host: Tree, options: Schema): NormalizedSchema {
const appProjectName = appDirectory.replace(new RegExp('/', 'g'), '-'); const appProjectName = appDirectory.replace(new RegExp('/', 'g'), '-');
const appProjectRoot = joinPathFragments(appsDir, appDirectory); const appProjectRoot = options.rootProject
? '.'
: joinPathFragments(appsDir, appDirectory);
options.bundler = options.bundler ?? 'esbuild';
const parsedTags = options.tags const parsedTags = options.tags
? options.tags.split(',').map((s) => s.trim()) ? options.tags.split(',').map((s) => s.trim())

View File

@ -17,6 +17,7 @@ export interface Schema {
bundler?: 'esbuild' | 'webpack'; bundler?: 'esbuild' | 'webpack';
framework?: NodeJsFrameWorks; framework?: NodeJsFrameWorks;
port?: number; port?: number;
rootProject?: boolean;
} }
export type NodeJsFrameWorks = 'express' | 'koa' | 'fastify' | 'connect'; export type NodeJsFrameWorks = 'express' | 'koa' | 'fastify' | 'connect';

View File

@ -89,6 +89,12 @@
"description": "The port which the server will be run on", "description": "The port which the server will be run on",
"type": "number", "type": "number",
"default": 3000 "default": 3000
},
"rootProject": {
"description": "Create node application at the root of the workspace",
"type": "boolean",
"default": false,
"hidden": true
} }
}, },
"required": [] "required": []

View File

@ -230,6 +230,31 @@ Visit the [Nx Documentation](https://nx.dev) to learn more.
" "
`; `;
exports[`@nrwl/workspace:generateWorkspaceFiles README.md should be created for NodeServer preset 1`] = `
"# Proj
<a href=\\"https://nx.dev\\" target=\\"_blank\\" rel=\\"noreferrer\\"><img src=\\"https://raw.githubusercontent.com/nrwl/nx/master/images/nx-logo.png\\" width=\\"45\\"></a>
✨ **This workspace has been generated by [Nx, a Smart, fast and extensible build system.](https://nx.dev)** ✨
## Development server
Run \`nx serve app1\` for a dev server. Navigate to http://localhost:4200/. The app will automatically reload if you change any of the source files.
## Understand this workspace
Run \`nx graph\` to see a diagram of the dependencies of the projects.
## Remote caching
Run \`npx nx connect-to-nx-cloud\` to enable [remote caching](https://nx.app) and make CI faster.
## Further help
Visit the [Nx Documentation](https://nx.dev) to learn more.
"
`;
exports[`@nrwl/workspace:generateWorkspaceFiles README.md should be created for ReactMonorepo preset 1`] = ` exports[`@nrwl/workspace:generateWorkspaceFiles README.md should be created for ReactMonorepo preset 1`] = `
"# Proj "# Proj

View File

@ -73,6 +73,7 @@ export function generatePreset(host: Tree, opts: NormalizedSchema) {
opts.linter ? `--linter=${opts.linter}` : null, opts.linter ? `--linter=${opts.linter}` : null,
opts.npmScope ? `--npmScope=${opts.npmScope}` : `--npmScope=${opts.name}`, opts.npmScope ? `--npmScope=${opts.npmScope}` : `--npmScope=${opts.name}`,
opts.preset ? `--preset=${opts.preset}` : null, opts.preset ? `--preset=${opts.preset}` : null,
opts.framework ? `--framework=${opts.framework}` : null,
opts.packageManager ? `--packageManager=${opts.packageManager}` : null, opts.packageManager ? `--packageManager=${opts.packageManager}` : null,
parsedArgs.interactive ? '--interactive=true' : '--interactive=false', parsedArgs.interactive ? '--interactive=true' : '--interactive=false',
].filter((e) => !!e); ].filter((e) => !!e);
@ -112,6 +113,9 @@ function getPresetDependencies(preset: string, version?: string) {
case Preset.WebComponents: case Preset.WebComponents:
return { dependencies: {}, dev: { '@nrwl/web': nxVersion } }; return { dependencies: {}, dev: { '@nrwl/web': nxVersion } };
case Preset.NodeServer:
return { dependencies: {}, dev: { '@nrwl/node': nxVersion } };
default: { default: {
return { return {
dev: {}, dev: {},

View File

@ -42,6 +42,7 @@ describe('@nrwl/workspace:generateWorkspaceFiles', () => {
Preset.NextJs, Preset.NextJs,
Preset.WebComponents, Preset.WebComponents,
Preset.Express, Preset.Express,
Preset.NodeServer,
].includes(Preset[preset]) ].includes(Preset[preset])
) { ) {
appName = 'app1'; appName = 'app1';

View File

@ -76,7 +76,8 @@ function createAppsAndLibsFolders(tree: Tree, options: NormalizedSchema) {
tree.write(join(options.directory, 'packages/.gitkeep'), ''); tree.write(join(options.directory, 'packages/.gitkeep'), '');
} else if ( } else if (
options.preset === Preset.AngularStandalone || options.preset === Preset.AngularStandalone ||
options.preset === Preset.ReactStandalone options.preset === Preset.ReactStandalone ||
options.preset === Preset.NodeServer
) { ) {
// don't generate any folders // don't generate any folders
} else { } else {
@ -134,7 +135,8 @@ function createFiles(tree: Tree, options: NormalizedSchema) {
const formattedNames = names(options.name); const formattedNames = names(options.name);
const filesDirName = const filesDirName =
options.preset === Preset.AngularStandalone || options.preset === Preset.AngularStandalone ||
options.preset === Preset.ReactStandalone options.preset === Preset.ReactStandalone ||
options.preset === Preset.NodeServer
? './files-root-app' ? './files-root-app'
: options.preset === Preset.NPM || options.preset === Preset.Core : options.preset === Preset.NPM || options.preset === Preset.Core
? './files-package-based-repo' ? './files-package-based-repo'
@ -196,7 +198,8 @@ function createYarnrcYml(tree: Tree, options: NormalizedSchema) {
function addNpmScripts(tree: Tree, options: NormalizedSchema) { function addNpmScripts(tree: Tree, options: NormalizedSchema) {
if ( if (
options.preset === Preset.AngularStandalone || options.preset === Preset.AngularStandalone ||
options.preset === Preset.ReactStandalone options.preset === Preset.ReactStandalone ||
options.preset === Preset.NodeServer
) { ) {
updateJson(tree, join(options.directory, 'package.json'), (json) => { updateJson(tree, join(options.directory, 'package.json'), (json) => {
Object.assign(json.scripts, { Object.assign(json.scripts, {

View File

@ -24,6 +24,7 @@ interface Schema {
nxCloud?: boolean; nxCloud?: boolean;
preset: string; preset: string;
defaultBase: string; defaultBase: string;
framework?: string;
linter?: Linter; linter?: Linter;
packageManager?: PackageManager; packageManager?: PackageManager;
} }
@ -74,6 +75,12 @@ function validateOptions(options: Schema, host: Tree) {
throw new Error(`Cannot select nxCloud when skipInstall is set to true.`); throw new Error(`Cannot select nxCloud when skipInstall is set to true.`);
} }
if (options.preset === Preset.NodeServer && !options.framework) {
throw new Error(
`Cannot generate ${options.preset} without selecting a framework`
);
}
if (devkitGetWorkspacePath(host)) { if (devkitGetWorkspacePath(host)) {
throw new Error( throw new Error(
'Cannot generate a new workspace within an existing workspace' 'Cannot generate a new workspace within an existing workspace'

View File

@ -62,6 +62,11 @@
"description": "The package manager used to install dependencies.", "description": "The package manager used to install dependencies.",
"type": "string", "type": "string",
"enum": ["npm", "yarn", "pnpm"] "enum": ["npm", "yarn", "pnpm"]
},
"framework": {
"description": "The framework which the application is using",
"type": "string",
"enum": ["express", "koa", "fastify", "connect"]
} }
}, },
"additionalProperties": true "additionalProperties": true

View File

@ -134,6 +134,16 @@ async function createPreset(tree: Tree, options: Schema) {
libsDir: 'packages', libsDir: 'packages',
}; };
updateNxJson(tree, c); updateNxJson(tree, c);
} else if (options.preset === Preset.NodeServer) {
const { applicationGenerator: nodeApplicationGenerator } = require('@nrwl' +
'/node');
await nodeApplicationGenerator(tree, {
name: options.name,
linter: options.linter,
standaloneConfig: options.standaloneConfig,
framework: options.framework,
rootProject: true,
});
} else { } else {
throw new Error(`Invalid preset ${options.preset}`); throw new Error(`Invalid preset ${options.preset}`);
} }

View File

@ -9,5 +9,6 @@ export interface Schema {
linter?: string; linter?: string;
preset: Preset; preset: Preset;
standaloneConfig?: boolean; standaloneConfig?: boolean;
framework?: string;
packageManager?: PackageManager; packageManager?: PackageManager;
} }

View File

@ -63,6 +63,11 @@
"description": "The package manager used to install dependencies.", "description": "The package manager used to install dependencies.",
"type": "string", "type": "string",
"enum": ["npm", "yarn", "pnpm"] "enum": ["npm", "yarn", "pnpm"]
},
"framework": {
"description": "The framework which the application is using",
"type": "string",
"enum": ["express", "koa", "fastify", "connect"]
} }
} }
} }

View File

@ -14,4 +14,5 @@ export enum Preset {
NextJs = 'next', NextJs = 'next',
Nest = 'nest', Nest = 'nest',
Express = 'express', Express = 'express',
NodeServer = 'node-server',
} }