From 94b38fadf663b64e86005c7c0298fa033d2ffdb8 Mon Sep 17 00:00:00 2001 From: Katerina Skroumpelou Date: Tue, 12 Sep 2023 11:19:16 +0300 Subject: [PATCH] feat(vue): add vue preset to create-nx-workspace --- docs/generated/cli/create-nx-workspace.md | 2 +- .../nx/documents/create-nx-workspace.md | 2 +- .../bin/create-nx-workspace.ts | 98 ++++++++++++++++++- .../src/utils/preset/preset-options.ts | 4 + .../src/utils/preset/preset.ts | 3 + .../src/generators/new/generate-preset.ts | 13 +++ .../new/generate-workspace-files.spec.ts | 2 + .../new/generate-workspace-files.ts | 3 + .../workspace/src/generators/new/new.spec.ts | 19 ++++ .../src/generators/preset/preset.spec.ts | 24 +++++ .../workspace/src/generators/preset/preset.ts | 26 +++++ .../workspace/src/generators/utils/presets.ts | 2 + 12 files changed, 192 insertions(+), 6 deletions(-) diff --git a/docs/generated/cli/create-nx-workspace.md b/docs/generated/cli/create-nx-workspace.md index 1d129d85d048c8..ca39cba7b804bc 100644 --- a/docs/generated/cli/create-nx-workspace.md +++ b/docs/generated/cli/create-nx-workspace.md @@ -139,7 +139,7 @@ Package manager to use 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", "next", "nextjs-standalone", "react-native", "expo", "nest", "express", "react", "angular", "node-standalone", "node-monorepo", "ts-standalone"]. To build your own see https://nx.dev/extending-nx/recipes/create-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", "vue-monorepo", "vue-standalone", "next", "nextjs-standalone", "react-native", "expo", "nest", "express", "react", "angular", "vue", "node-standalone", "node-monorepo", "ts-standalone"]. To build your own see https://nx.dev/extending-nx/recipes/create-preset ### routing diff --git a/docs/generated/packages/nx/documents/create-nx-workspace.md b/docs/generated/packages/nx/documents/create-nx-workspace.md index 1d129d85d048c8..ca39cba7b804bc 100644 --- a/docs/generated/packages/nx/documents/create-nx-workspace.md +++ b/docs/generated/packages/nx/documents/create-nx-workspace.md @@ -139,7 +139,7 @@ Package manager to use 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", "next", "nextjs-standalone", "react-native", "expo", "nest", "express", "react", "angular", "node-standalone", "node-monorepo", "ts-standalone"]. To build your own see https://nx.dev/extending-nx/recipes/create-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", "vue-monorepo", "vue-standalone", "next", "nextjs-standalone", "react-native", "expo", "nest", "express", "react", "angular", "vue", "node-standalone", "node-monorepo", "ts-standalone"]. To build your own see https://nx.dev/extending-nx/recipes/create-preset ### routing diff --git a/packages/create-nx-workspace/bin/create-nx-workspace.ts b/packages/create-nx-workspace/bin/create-nx-workspace.ts index 025aca3f012061..94d93134e075d7 100644 --- a/packages/create-nx-workspace/bin/create-nx-workspace.ts +++ b/packages/create-nx-workspace/bin/create-nx-workspace.ts @@ -62,6 +62,16 @@ interface AngularArguments extends BaseArguments { e2eTestRunner: 'none' | 'cypress' | 'playwright'; } +interface VueArguments extends BaseArguments { + stack: 'vue'; + workspaceType: 'standalone' | 'integrated'; + appName: string; + // framework: 'none' | 'nuxt'; + style: string; + // nextAppDir: boolean; + e2eTestRunner: 'none' | 'cypress' | 'playwright'; +} + interface NodeArguments extends BaseArguments { stack: 'node'; workspaceType: 'standalone' | 'integrated'; @@ -78,6 +88,7 @@ type Arguments = | NoneArguments | ReactArguments | AngularArguments + | VueArguments | NodeArguments | UnknownStackArguments; @@ -347,7 +358,7 @@ async function determineFolder( async function determineStack( parsedArgs: yargs.Arguments -): Promise<'none' | 'react' | 'angular' | 'node' | 'unknown'> { +): Promise<'none' | 'react' | 'angular' | 'vue' | 'node' | 'unknown'> { if (parsedArgs.preset) { switch (parsedArgs.preset) { case Preset.Angular: @@ -360,7 +371,10 @@ async function determineStack( case Preset.NextJs: case Preset.NextJsStandalone: return 'react'; - + case Preset.Vue: + case Preset.VueStandalone: + case Preset.VueMonorepo: + return 'vue'; case Preset.Nest: case Preset.NodeStandalone: case Preset.Express: @@ -379,7 +393,7 @@ async function determineStack( } const { stack } = await enquirer.prompt<{ - stack: 'none' | 'react' | 'angular' | 'node'; + stack: 'none' | 'react' | 'angular' | 'node' | 'vue'; }>([ { name: 'stack', @@ -394,6 +408,10 @@ async function determineStack( name: `react`, message: `React: Configures a React application with your framework of choice.`, }, + { + name: `vue`, + message: `Vue: Configures a Vue application with modern tooling.`, + }, { name: `angular`, message: `Angular: Configures a Angular application with modern tooling.`, @@ -419,6 +437,8 @@ async function determinePresetOptions( return determineReactOptions(parsedArgs); case 'angular': return determineAngularOptions(parsedArgs); + case 'vue': + return determineVueOptions(parsedArgs); case 'node': return determineNodeOptions(parsedArgs); default: @@ -589,6 +609,74 @@ async function determineReactOptions( return { preset, style, appName, bundler, nextAppDir, e2eTestRunner }; } +async function determineVueOptions( + parsedArgs: yargs.Arguments +): Promise> { + let preset: Preset; + let style: undefined | string = undefined; + let appName: string; + let e2eTestRunner: undefined | 'none' | 'cypress' | 'playwright' = undefined; + + if (parsedArgs.preset && parsedArgs.preset !== Preset.Vue) { + preset = parsedArgs.preset; + if (preset === Preset.VueStandalone || preset === Preset.VueMonorepo) { + appName = parsedArgs.appName ?? parsedArgs.name; + } else { + appName = await determineAppName(parsedArgs); + } + } else { + const workspaceType = await determineStandaloneOrMonorepo(); + + if (workspaceType === 'standalone') { + appName = parsedArgs.name; + } else { + appName = await determineAppName(parsedArgs); + } + + if (workspaceType === 'standalone') { + preset = Preset.VueStandalone; + } else { + preset = Preset.VueMonorepo; + } + } + + e2eTestRunner = await determineE2eTestRunner(parsedArgs); + + if (parsedArgs.style) { + style = parsedArgs.style; + } else if (preset === Preset.VueMonorepo || preset === Preset.VueStandalone) { + const reply = await enquirer.prompt<{ style: string }>([ + { + name: 'style', + message: `Default stylesheet format`, + initial: 'css' as any, + type: 'autocomplete', + choices: [ + { + name: 'css', + message: 'CSS', + }, + { + name: 'scss', + message: 'SASS(.scss) [ http://sass-lang.com ]', + }, + { + name: 'less', + message: 'LESS [ http://lesscss.org ]', + }, + { + name: 'none', + message: 'None', + }, + ], + }, + ]); + style = reply.style; + } + + return { preset, style, appName, e2eTestRunner }; +} + async function determineAngularOptions( parsedArgs: yargs.Arguments ): Promise> { @@ -847,7 +935,9 @@ async function determineStandaloneOrMonorepo(): Promise< } async function determineAppName( - parsedArgs: yargs.Arguments + parsedArgs: yargs.Arguments< + ReactArguments | AngularArguments | NodeArguments | VueArguments + > ): Promise { if (parsedArgs.appName) return parsedArgs.appName; diff --git a/packages/create-nx-workspace/src/utils/preset/preset-options.ts b/packages/create-nx-workspace/src/utils/preset/preset-options.ts index 3ebfc1fbdfde8a..b79c5020db9271 100644 --- a/packages/create-nx-workspace/src/utils/preset/preset-options.ts +++ b/packages/create-nx-workspace/src/utils/preset/preset-options.ts @@ -19,6 +19,10 @@ export const presetOptions: { name: Preset; message: string }[] = [ name: Preset.AngularMonorepo, message: 'angular [a monorepo with a single Angular application]', }, + { + name: Preset.VueMonorepo, + message: 'vue [a monorepo with a single Vue application]', + }, { name: Preset.NextJs, message: 'next.js [a monorepo with a single Next.js application]', diff --git a/packages/create-nx-workspace/src/utils/preset/preset.ts b/packages/create-nx-workspace/src/utils/preset/preset.ts index 301b782c4a49b5..62dd0c45ba60f2 100644 --- a/packages/create-nx-workspace/src/utils/preset/preset.ts +++ b/packages/create-nx-workspace/src/utils/preset/preset.ts @@ -9,6 +9,8 @@ export enum Preset { AngularStandalone = 'angular-standalone', ReactMonorepo = 'react-monorepo', ReactStandalone = 'react-standalone', + VueMonorepo = 'vue-monorepo', + VueStandalone = 'vue-standalone', NextJs = 'next', NextJsStandalone = 'nextjs-standalone', ReactNative = 'react-native', @@ -17,6 +19,7 @@ export enum Preset { Express = 'express', React = 'react', Angular = 'angular', + Vue = 'vue', NodeStandalone = 'node-standalone', NodeMonorepo = 'node-monorepo', TsStandalone = 'ts-standalone', diff --git a/packages/workspace/src/generators/new/generate-preset.ts b/packages/workspace/src/generators/new/generate-preset.ts index 578655c195376f..ba89673a28a7cd 100644 --- a/packages/workspace/src/generators/new/generate-preset.ts +++ b/packages/workspace/src/generators/new/generate-preset.ts @@ -119,6 +119,19 @@ function getPresetDependencies({ case Preset.NextJsStandalone: return { dependencies: { '@nx/next': nxVersion }, dev: {} }; + case Preset.VueMonorepo: + case Preset.VueStandalone: + return { + dependencies: {}, + dev: { + '@nx/vue': nxVersion, + '@nx/cypress': e2eTestRunner === 'cypress' ? nxVersion : undefined, + '@nx/playwright': + e2eTestRunner === 'playwright' ? nxVersion : undefined, + '@nx/vite': nxVersion, + }, + }; + case Preset.ReactMonorepo: case Preset.ReactStandalone: return { diff --git a/packages/workspace/src/generators/new/generate-workspace-files.spec.ts b/packages/workspace/src/generators/new/generate-workspace-files.spec.ts index 66e5a9e2ef007d..41b6e84f7e245e 100644 --- a/packages/workspace/src/generators/new/generate-workspace-files.spec.ts +++ b/packages/workspace/src/generators/new/generate-workspace-files.spec.ts @@ -36,6 +36,8 @@ describe('@nx/workspace:generateWorkspaceFiles', () => { [ Preset.ReactMonorepo, Preset.ReactStandalone, + Preset.VueMonorepo, + Preset.VueStandalone, Preset.AngularMonorepo, Preset.AngularStandalone, Preset.Nest, diff --git a/packages/workspace/src/generators/new/generate-workspace-files.ts b/packages/workspace/src/generators/new/generate-workspace-files.ts index 9c2e1eda71a60f..5090f3b25ddff2 100644 --- a/packages/workspace/src/generators/new/generate-workspace-files.ts +++ b/packages/workspace/src/generators/new/generate-workspace-files.ts @@ -67,6 +67,7 @@ function createAppsAndLibsFolders(tree: Tree, options: NormalizedSchema) { } else if ( options.preset === Preset.AngularStandalone || options.preset === Preset.ReactStandalone || + options.preset === Preset.VueStandalone || options.preset === Preset.NodeStandalone || options.preset === Preset.NextJsStandalone || options.preset === Preset.TsStandalone || @@ -127,6 +128,7 @@ function createFiles(tree: Tree, options: NormalizedSchema) { const filesDirName = options.preset === Preset.AngularStandalone || options.preset === Preset.ReactStandalone || + options.preset === Preset.VueStandalone || options.preset === Preset.NodeStandalone || options.preset === Preset.NextJsStandalone || options.preset === Preset.TsStandalone @@ -181,6 +183,7 @@ function addNpmScripts(tree: Tree, options: NormalizedSchema) { if ( options.preset === Preset.AngularStandalone || options.preset === Preset.ReactStandalone || + options.preset === Preset.VueStandalone || options.preset === Preset.NodeStandalone || options.preset === Preset.NextJsStandalone ) { diff --git a/packages/workspace/src/generators/new/new.spec.ts b/packages/workspace/src/generators/new/new.spec.ts index 8a1287f466b33f..c09346f85879ed 100644 --- a/packages/workspace/src/generators/new/new.spec.ts +++ b/packages/workspace/src/generators/new/new.spec.ts @@ -79,6 +79,25 @@ describe('new', () => { }); }); + it('should generate necessary npm dependencies for vue preset', async () => { + await newGenerator(tree, { + ...defaultOptions, + name: 'my-workspace', + directory: 'my-workspace', + appName: 'app', + preset: Preset.VueMonorepo, + }); + + const { devDependencies } = readJson(tree, 'my-workspace/package.json'); + expect(devDependencies).toStrictEqual({ + '@nx/vue': nxVersion, + '@nx/cypress': nxVersion, + '@nx/vite': nxVersion, + '@nx/workspace': nxVersion, + nx: nxVersion, + }); + }); + it('should generate necessary npm dependencies for angular preset', async () => { await newGenerator(tree, { ...defaultOptions, diff --git a/packages/workspace/src/generators/preset/preset.spec.ts b/packages/workspace/src/generators/preset/preset.spec.ts index 14cc8dda8ad417..e599fc83a05784 100644 --- a/packages/workspace/src/generators/preset/preset.spec.ts +++ b/packages/workspace/src/generators/preset/preset.spec.ts @@ -41,6 +41,17 @@ describe('preset', () => { expect(readProjectConfiguration(tree, 'proj').targets.serve).toBeDefined(); }); + it(`should create files (preset = ${Preset.VueMonorepo})`, async () => { + await presetGenerator(tree, { + name: 'proj', + preset: Preset.VueMonorepo, + style: 'css', + linter: 'eslint', + }); + expect(tree.exists('apps/proj/src/main.ts')).toBe(true); + expect(readProjectConfiguration(tree, 'proj').targets.serve).toBeDefined(); + }); + it(`should create files (preset = ${Preset.NextJs})`, async () => { await presetGenerator(tree, { name: 'proj', @@ -99,4 +110,17 @@ describe('preset', () => { readProjectConfiguration(tree, 'proj').targets.serve ).toMatchSnapshot(); }); + + it(`should create files (preset = ${Preset.VueStandalone})`, async () => { + await presetGenerator(tree, { + name: 'proj', + preset: Preset.VueStandalone, + style: 'css', + e2eTestRunner: 'cypress', + }); + expect(tree.exists('vite.config.ts')).toBe(true); + expect( + readProjectConfiguration(tree, 'proj').targets.serve + ).toMatchSnapshot(); + }); }); diff --git a/packages/workspace/src/generators/preset/preset.ts b/packages/workspace/src/generators/preset/preset.ts index 66793666550533..f37e14bf1e349c 100644 --- a/packages/workspace/src/generators/preset/preset.ts +++ b/packages/workspace/src/generators/preset/preset.ts @@ -76,6 +76,32 @@ async function createPreset(tree: Tree, options: Schema) { e2eTestRunner: options.e2eTestRunner ?? 'cypress', unitTestRunner: options.bundler === 'vite' ? 'vitest' : 'jest', }); + } else if (options.preset === Preset.VueMonorepo) { + const { applicationGenerator: vueApplicationGenerator } = require('@nx' + + '/vue'); + + return vueApplicationGenerator(tree, { + name: options.name, + directory: join('apps', options.name), + projectNameAndRootFormat: 'as-provided', + style: options.style, + linter: options.linter, + e2eTestRunner: options.e2eTestRunner ?? 'cypress', + }); + } else if (options.preset === Preset.VueStandalone) { + const { applicationGenerator: vueApplicationGenerator } = require('@nx' + + '/vue'); + + return vueApplicationGenerator(tree, { + name: options.name, + directory: '.', + projectNameAndRootFormat: 'as-provided', + style: options.style, + linter: options.linter, + rootProject: true, + e2eTestRunner: options.e2eTestRunner ?? 'cypress', + unitTestRunner: 'vitest', + }); } else if (options.preset === Preset.NextJs) { const { applicationGenerator: nextApplicationGenerator } = require('@nx' + '/next'); diff --git a/packages/workspace/src/generators/utils/presets.ts b/packages/workspace/src/generators/utils/presets.ts index 258379c73e59c3..dabae9824849ae 100644 --- a/packages/workspace/src/generators/utils/presets.ts +++ b/packages/workspace/src/generators/utils/presets.ts @@ -14,6 +14,8 @@ export enum Preset { AngularStandalone = 'angular-standalone', ReactMonorepo = 'react-monorepo', ReactStandalone = 'react-standalone', + VueMonorepo = 'vue-monorepo', + VueStandalone = 'vue-standalone', NextJsStandalone = 'nextjs-standalone', ReactNative = 'react-native', Expo = 'expo',