From b52b649fcbc8fdf259703939823969c5e8c4eead Mon Sep 17 00:00:00 2001 From: Katerina Skroumpelou Date: Fri, 16 Dec 2022 15:35:52 +0200 Subject: [PATCH] feat(vite): allow user to set their custom target for transform (#13691) --- docs/generated/packages/vite.json | 19 +- e2e/react/src/react-package.test.ts | 2 +- packages/vite/package.json | 3 +- .../__snapshots__/configuration.spec.ts.snap | 70 ++++++ .../configuration/configuration.spec.ts | 166 +++++++++++- .../generators/configuration/configuration.ts | 90 ++++++- .../src/generators/configuration/schema.d.ts | 3 + .../src/generators/configuration/schema.json | 14 +- .../vite/src/generators/vitest/schema.d.ts | 1 + .../vite/src/generators/vitest/schema.json | 5 + .../src/generators/vitest/vitest-generator.ts | 9 +- .../vite/src/utils/generator-util.spec.ts | 218 ++++++++++++++++ .../vite/src/utils/generator-util.test.ts | 111 -------- packages/vite/src/utils/generator-utils.ts | 236 ++++++++++++++++-- .../test-files/angular-project.config.json | 89 +++++++ .../react-mixed-project.config.json | 52 ++++ .../test-files/unknown-project.config.json | 72 ++++++ packages/vite/src/utils/test-utils.ts | 147 +++++++++++ 18 files changed, 1148 insertions(+), 159 deletions(-) create mode 100644 packages/vite/src/utils/generator-util.spec.ts delete mode 100644 packages/vite/src/utils/generator-util.test.ts create mode 100644 packages/vite/src/utils/test-files/angular-project.config.json create mode 100644 packages/vite/src/utils/test-files/react-mixed-project.config.json create mode 100644 packages/vite/src/utils/test-files/unknown-project.config.json diff --git a/docs/generated/packages/vite.json b/docs/generated/packages/vite.json index 66b524012889c..793d8f2ef3bc0 100644 --- a/docs/generated/packages/vite.json +++ b/docs/generated/packages/vite.json @@ -63,7 +63,7 @@ "$default": { "$source": "argv", "index": 0 }, "aliases": ["name", "projectName"], "x-dropdown": "project", - "x-prompt": "What is the name of the project to set up a webpack for?" + "x-prompt": "What is the name of the project to set up Vite for?" }, "includeLib": { "type": "boolean", @@ -82,6 +82,18 @@ "description": "Is this a new project?", "default": false, "hidden": true + }, + "buildTarget": { + "type": "string", + "description": "The build target of the project to be transformed to use the @nrwl/vite:build executor." + }, + "serveTarget": { + "type": "string", + "description": "The serve target of the project to be transformed to use the @nrwl/vite:dev-server executor." + }, + "testTarget": { + "type": "string", + "description": "The test target of the project to be transformed to use the @nrwl/vite:test executor." } }, "examplesFile": "This is a generator for setting up Vite configuration for an existing React or Web application. It will change the build and serve targets to use the `@nrwl/vite` executors for serving and building the application. This generator will modify your code, so make sure to commit your changes before running it.\n\n```bash\nnx g @nrwl/vite:configuration\n```\n\nWhen running this generator, you will be prompted to provide the following:\n\n- The `project`, as the name of the project you want to generate the configuration for.\n- The `uiFramework` you want to use. Supported values are: `react` and `none`.\n\nYou must provide a `project` and a `uiFramework` for the generator to work.\n\nYou can read more about how this generator works, in the [Vite package overview page](/packages/vite).\n\n## Examples\n\n### Change a React app to use Vite\n\n```bash\nnx g @nrwl/vite:configuration --project=my-app --uiFramework=react\n```\n\nThis will change the `my-app` project to use Vite instead of the default Webpack configuration. The changes this generator will do are described in the [Vite package overview page](/packages/vite).\n\n### Change a Web app to use Vite\n\n```bash\nnx g @nrwl/vite:configuration --project=my-app --uiFramework=none\n```\n\nThis will change the `my-app` project to use Vite instead of the existing bundler configuration.\n", @@ -130,6 +142,11 @@ "enum": ["c8", "istanbul"], "default": "c8", "description": "Coverage provider to use." + }, + "testTarget": { + "type": "string", + "description": "The test target of the project to be transformed to use the @nrwl/vite:test executor.", + "hidden": true } }, "required": ["project"], diff --git a/e2e/react/src/react-package.test.ts b/e2e/react/src/react-package.test.ts index 500d74c169d7d..6ce3c14dc6b70 100644 --- a/e2e/react/src/react-package.test.ts +++ b/e2e/react/src/react-package.test.ts @@ -377,7 +377,7 @@ describe('Build React applications and libraries with Vite', () => { // Convert non-buildable lib to buildable one const nonBuildableLib = uniq('nonbuildablelib'); runCLI( - `generate @nrwl/react:lib ${nonBuildableLib} --no-interactive --unitTestRunner=none` + `generate @nrwl/react:lib ${nonBuildableLib} --no-interactive --unitTestRunner=jest` ); runCLI( `generate @nrwl/vite:configuration ${nonBuildableLib} --uiFramework=react --no-interactive` diff --git a/packages/vite/package.json b/packages/vite/package.json index fcea064b347ab..3cb499b0499a3 100644 --- a/packages/vite/package.json +++ b/packages/vite/package.json @@ -34,7 +34,8 @@ "@nrwl/js": "file:../js", "@swc/helpers": "^0.4.11", "chalk": "4.1.0", - "dotenv": "~10.0.0" + "dotenv": "~10.0.0", + "enquirer": "~2.3.6" }, "peerDependencies": { "vite": "^4.0.1", diff --git a/packages/vite/src/generators/configuration/__snapshots__/configuration.spec.ts.snap b/packages/vite/src/generators/configuration/__snapshots__/configuration.spec.ts.snap index d05470eba0add..239513c8718c6 100644 --- a/packages/vite/src/generators/configuration/__snapshots__/configuration.spec.ts.snap +++ b/packages/vite/src/generators/configuration/__snapshots__/configuration.spec.ts.snap @@ -1,5 +1,75 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`@nrwl/vite:configuration transform React app to use Vite by providing custom targets transform React app if supported executor is provided should transform workspace.json project config 1`] = ` +"{ + \\"projects\\": { + \\"my-test-mixed-react-app\\": { + \\"$schema\\": \\"../../node_modules/nx/schemas/project-schema.json\\", + \\"root\\": \\"apps/my-test-mixed-react-app\\", + \\"sourceRoot\\": \\"apps/my-test-mixed-react-app/src\\", + \\"projectType\\": \\"application\\", + \\"architect\\": { + \\"invalid-build\\": { + \\"builder\\": \\"@nrwl/js:tsc\\", + \\"outputs\\": [ + \\"{options.outputPath}\\" + ] + }, + \\"valid-build\\": { + \\"builder\\": \\"@nrwl/vite:build\\", + \\"outputs\\": [ + \\"{options.outputPath}\\" + ], + \\"options\\": { + \\"outputPath\\": \\"dist/apps/my-test-mixed-react-app\\" + } + }, + \\"serve\\": { + \\"builder\\": \\"@nrwl/vite:dev-server\\", + \\"defaultConfiguration\\": \\"development\\", + \\"options\\": { + \\"buildTarget\\": \\"my-test-mixed-react-app:build\\" + }, + \\"configurations\\": { + \\"development\\": { + \\"buildTarget\\": \\"my-test-mixed-react-app:build:development\\" + }, + \\"production\\": { + \\"buildTarget\\": \\"my-test-mixed-react-app:build:production\\", + \\"hmr\\": false + } + } + }, + \\"lint\\": { + \\"builder\\": \\"@nrwl/linter:eslint\\", + \\"outputs\\": [ + \\"{options.outputFile}\\" + ], + \\"options\\": { + \\"lintFilePatterns\\": [ + \\"apps/my-test-mixed-react-app/**/*.{ts,tsx,js,jsx}\\" + ] + } + }, + \\"test\\": { + \\"builder\\": \\"@nrwl/jest:jest\\", + \\"outputs\\": [ + \\"{workspaceRoot}/coverage/{projectRoot}\\" + ], + \\"options\\": { + \\"jestConfig\\": \\"apps/my-test-mixed-react-app/jest.config.ts\\", + \\"passWithNoTests\\": true + } + } + }, + \\"tags\\": [] + } + }, + \\"version\\": 1 +} +" +`; + exports[`@nrwl/vite:configuration transform React app to use Vite should transform workspace.json project config 1`] = ` "{ \\"projects\\": { diff --git a/packages/vite/src/generators/configuration/configuration.spec.ts b/packages/vite/src/generators/configuration/configuration.spec.ts index 4059990f38014..592f78caf2cda 100644 --- a/packages/vite/src/generators/configuration/configuration.spec.ts +++ b/packages/vite/src/generators/configuration/configuration.spec.ts @@ -9,7 +9,10 @@ import { nxVersion } from '../../utils/versions'; import { viteConfigurationGenerator } from './configuration'; import { + mockAngularAppGenerator, mockReactAppGenerator, + mockReactMixedAppGenerator, + mockUnknownAppGenerator, mockWebAppGenerator, } from '../../utils/test-utils'; @@ -19,7 +22,7 @@ describe('@nrwl/vite:configuration', () => { describe('transform React app to use Vite', () => { beforeAll(async () => { tree = createTreeWithEmptyV1Workspace(); - await mockReactAppGenerator(tree); + mockReactAppGenerator(tree); const existing = 'existing'; const existingVersion = '1.0.0'; addDependenciesToPackageJson( @@ -65,7 +68,7 @@ describe('@nrwl/vite:configuration', () => { describe('transform Web app to use Vite', () => { beforeAll(async () => { tree = createTreeWithEmptyV1Workspace(); - await mockWebAppGenerator(tree); + mockWebAppGenerator(tree); const existing = 'existing'; const existingVersion = '1.0.0'; addDependenciesToPackageJson( @@ -105,6 +108,165 @@ describe('@nrwl/vite:configuration', () => { }); }); + describe('do not transform Angular app to use Vite', () => { + beforeAll(async () => { + tree = createTreeWithEmptyV1Workspace(); + mockAngularAppGenerator(tree); + }); + it('should throw when trying to convert', async () => { + expect.assertions(2); + + try { + await viteConfigurationGenerator(tree, { + uiFramework: 'none', + project: 'my-test-angular-app', + }); + } catch (e) { + expect(e).toBeDefined(); + expect(e.toString()).toContain( + 'The project my-test-angular-app cannot be converted to use the @nrwl/vite executors' + ); + } + }); + }); + + describe('inform user of unknown targets when converting', () => { + beforeAll(async () => { + tree = createTreeWithEmptyV1Workspace(); + mockUnknownAppGenerator(tree); + }); + + it('should throw when trying to convert something unknown', async () => { + const { Confirm } = require('enquirer'); + const confirmSpy = jest.spyOn(Confirm.prototype, 'run'); + confirmSpy.mockResolvedValue(true); + expect.assertions(2); + + try { + await viteConfigurationGenerator(tree, { + uiFramework: 'none', + project: 'my-test-random-app', + }); + } catch (e) { + expect(e).toBeDefined(); + expect(e.toString()).toContain( + 'Error: Cannot find apps/my-test-random-app/tsconfig.json' + ); + } + }); + + it('should throw when trying to convert something unknown and user denies conversion', async () => { + const { Confirm } = require('enquirer'); + const confirmSpy = jest.spyOn(Confirm.prototype, 'run'); + confirmSpy.mockResolvedValue(false); + + expect.assertions(2); + + try { + await viteConfigurationGenerator(tree, { + uiFramework: 'none', + project: 'my-test-random-app', + }); + } catch (e) { + expect(e).toBeDefined(); + expect(e.toString()).toContain( + 'Nx could not verify that the executors you are using can be converted to the @nrwl/vite executors.' + ); + } + }); + }); + + describe('transform React app to use Vite by providing custom targets', () => { + describe('transform React app if supported executor is provided', () => { + beforeEach(async () => { + tree = createTreeWithEmptyV1Workspace(); + mockReactMixedAppGenerator(tree); + const existing = 'existing'; + const existingVersion = '1.0.0'; + addDependenciesToPackageJson( + tree, + { '@nrwl/vite': nxVersion, [existing]: existingVersion }, + { [existing]: existingVersion } + ); + await viteConfigurationGenerator(tree, { + uiFramework: 'react', + project: 'my-test-mixed-react-app', + buildTarget: 'valid-build', + }); + }); + it('should add vite packages and react-related dependencies for vite', async () => { + const packageJson = readJson(tree, '/package.json'); + expect(packageJson.devDependencies).toMatchObject({ + vite: expect.any(String), + '@vitejs/plugin-react': expect.any(String), + }); + }); + + it('should create vite.config file at the root of the app', () => { + expect(tree.exists('apps/my-test-mixed-react-app/vite.config.ts')).toBe( + true + ); + }); + + it('should transform workspace.json project config', () => { + expect(tree.read('workspace.json', 'utf-8')).toMatchSnapshot(); + }); + }); + + describe('do NOT transform React app if unsupported executor is provided', () => { + beforeEach(async () => { + tree = createTreeWithEmptyV1Workspace(); + mockReactMixedAppGenerator(tree); + const existing = 'existing'; + const existingVersion = '1.0.0'; + addDependenciesToPackageJson( + tree, + { '@nrwl/vite': nxVersion, [existing]: existingVersion }, + { [existing]: existingVersion } + ); + }); + it('should throw when trying to convert and user denies', async () => { + const { Confirm } = require('enquirer'); + const confirmSpy = jest.spyOn(Confirm.prototype, 'run'); + confirmSpy.mockResolvedValue(false); + expect.assertions(2); + + try { + await viteConfigurationGenerator(tree, { + uiFramework: 'none', + project: 'my-test-mixed-react-app', + buildTarget: 'invalid-build', + }); + } catch (e) { + expect(e).toBeDefined(); + expect(e.toString()).toContain( + 'The build target invalid-build cannot be converted to use the @nrwl/vite:build executor' + ); + } + }); + + it('should NOT throw error when trying to convert and user confirms', async () => { + const { Confirm } = require('enquirer'); + const confirmSpy = jest.spyOn(Confirm.prototype, 'run'); + confirmSpy.mockResolvedValue(true); + expect.assertions(1); + + try { + await viteConfigurationGenerator(tree, { + uiFramework: 'none', + project: 'my-test-mixed-react-app', + buildTarget: 'invalid-build', + }); + expect( + tree.exists('apps/my-test-mixed-react-app/vite.config.ts') + ).toBe(true); + } catch (e) { + throw new Error('Should not throw error'); + } + }); + }); + }); + describe('vitest', () => { beforeAll(async () => { tree = createTreeWithEmptyV1Workspace(); diff --git a/packages/vite/src/generators/configuration/configuration.ts b/packages/vite/src/generators/configuration/configuration.ts index abbf8b98992bd..d4fa52020f3be 100644 --- a/packages/vite/src/generators/configuration/configuration.ts +++ b/packages/vite/src/generators/configuration/configuration.ts @@ -7,12 +7,15 @@ import { } from '@nrwl/devkit'; import { runTasksInSerial } from '@nrwl/workspace/src/utilities/run-tasks-in-serial'; import { - findExistingTargets, + findExistingTargetsInProject, addOrChangeBuildTarget, addOrChangeServeTarget, editTsConfig, moveAndEditIndexHtml, writeViteConfig, + handleUnsupportedUserProvidedTargets, + handleUnknownExecutors, + UserProvidedTargetName, } from '../../utils/generator-utils'; import initGenerator from '../init/init'; @@ -28,29 +31,89 @@ export async function viteConfigurationGenerator(tree: Tree, schema: Schema) { ); let buildTargetName = 'build'; let serveTargetName = 'serve'; + let testTargetName = 'test'; schema.includeLib ??= projectType === 'library'; + /** + * This is for when we are convering an existing project + * to use the vite executors. + * */ if (!schema.newProject) { - const { buildTarget, serveTarget, unsuppored } = - findExistingTargets(targets); + const userProvidedTargetName: UserProvidedTargetName = { + build: schema.buildTarget, + serve: schema.serveTarget, + test: schema.testTarget, + }; + + const { + validFoundTargetName, + projectContainsUnsupportedExecutor, + userProvidedTargetIsUnsupported, + } = findExistingTargetsInProject(targets, userProvidedTargetName); /** - * Here, we make sure that the project has a build target - * and it is unsupported, before throwing the error. - * The reason is that the project could not have a build - * target at all, in which case we don't want to throw. - * Or the project can have multiple build targets, one of which unsupported. - * In that case, we convert the supported one. - */ - if (!buildTarget && unsuppored) { + * This means that we only found unsupported build targets in that project. + * The only way that buildTarget is defined, means that it is supported. + * + * If the `unsupported` flag was false, it would mean that we did not find + * a build target at all, so we can create a new one. + * + * So we only throw if we found a target, but it is unsupported. + * + * */ + if (!validFoundTargetName.build && projectContainsUnsupportedExecutor) { throw new Error( `The project ${schema.project} cannot be converted to use the @nrwl/vite executors.` ); } - buildTargetName = buildTarget ?? buildTargetName; - serveTargetName = serveTarget ?? serveTargetName; + /** + * This means that we did not find any supported executors + * so we don't have any valid target names. + * + * However, the executors that we may have found are not in the + * list of the specifically unsupported executors either. + * + * So, we should warn the user about it. + */ + + if ( + !projectContainsUnsupportedExecutor && + !validFoundTargetName.build && + !validFoundTargetName.serve && + !validFoundTargetName.test + ) { + await handleUnknownExecutors(); + } + + /** + * There is a possibility at this stage that the user has provided + * targets with unsupported executors. + * We keep track here of which of the targets that the user provided + * are unsupported. + * We do this with the `userProvidedTargetIsUnsupported` object, + * which contains flags for each target (whether it is supported or not). + * + * We also keep track of the targets that we found in the project, + * through the findExistingTargetsInProject function, which returns + * targets for build/serve/test that use supported executors, and + * can be coverted to use the vite executors. These are the + * kept in the validFoundTargetName object. + * + */ + await handleUnsupportedUserProvidedTargets( + userProvidedTargetIsUnsupported, + userProvidedTargetName, + validFoundTargetName + ); + + /** + * Once the user is at this stage, then they can go ahead and convert. + */ + + buildTargetName = validFoundTargetName.build ?? buildTargetName; + serveTargetName = validFoundTargetName.serve ?? serveTargetName; if (projectType === 'application') { moveAndEditIndexHtml(tree, schema, buildTargetName); @@ -79,6 +142,7 @@ export async function viteConfigurationGenerator(tree: Tree, schema: Schema) { inSourceTests: schema.inSourceTests, coverageProvider: 'c8', skipViteConfig: true, + testTarget: testTargetName, }); tasks.push(vitestTask); } diff --git a/packages/vite/src/generators/configuration/schema.d.ts b/packages/vite/src/generators/configuration/schema.d.ts index 9533d025cff33..a18e5bf3da0e0 100644 --- a/packages/vite/src/generators/configuration/schema.d.ts +++ b/packages/vite/src/generators/configuration/schema.d.ts @@ -5,4 +5,7 @@ export interface Schema { includeVitest?: boolean; inSourceTests?: boolean; includeLib?: boolean; + buildTarget?: string; + serveTarget?: string; + testTarget?: string; } diff --git a/packages/vite/src/generators/configuration/schema.json b/packages/vite/src/generators/configuration/schema.json index c506bd7691629..569b50934eb0d 100644 --- a/packages/vite/src/generators/configuration/schema.json +++ b/packages/vite/src/generators/configuration/schema.json @@ -14,7 +14,7 @@ }, "aliases": ["name", "projectName"], "x-dropdown": "project", - "x-prompt": "What is the name of the project to set up a webpack for?" + "x-prompt": "What is the name of the project to set up Vite for?" }, "includeLib": { "type": "boolean", @@ -33,6 +33,18 @@ "description": "Is this a new project?", "default": false, "hidden": true + }, + "buildTarget": { + "type": "string", + "description": "The build target of the project to be transformed to use the @nrwl/vite:build executor." + }, + "serveTarget": { + "type": "string", + "description": "The serve target of the project to be transformed to use the @nrwl/vite:dev-server executor." + }, + "testTarget": { + "type": "string", + "description": "The test target of the project to be transformed to use the @nrwl/vite:test executor." } }, "examplesFile": "../../../docs/configuration-examples.md" diff --git a/packages/vite/src/generators/vitest/schema.d.ts b/packages/vite/src/generators/vitest/schema.d.ts index 647f1d9619e69..fd2bcdd72c306 100644 --- a/packages/vite/src/generators/vitest/schema.d.ts +++ b/packages/vite/src/generators/vitest/schema.d.ts @@ -4,4 +4,5 @@ export interface VitestGeneratorSchema { coverageProvider: 'c8' | 'istanbul'; inSourceTests?: boolean; skipViteConfig?: boolean; + testTarget?: string; } diff --git a/packages/vite/src/generators/vitest/schema.json b/packages/vite/src/generators/vitest/schema.json index 8c8103bb01752..e6add2bfc7506 100644 --- a/packages/vite/src/generators/vitest/schema.json +++ b/packages/vite/src/generators/vitest/schema.json @@ -32,6 +32,11 @@ "enum": ["c8", "istanbul"], "default": "c8", "description": "Coverage provider to use." + }, + "testTarget": { + "type": "string", + "description": "The test target of the project to be transformed to use the @nrwl/vite:test executor.", + "hidden": true } }, "required": ["project"] diff --git a/packages/vite/src/generators/vitest/vitest-generator.ts b/packages/vite/src/generators/vitest/vitest-generator.ts index a24af0d21d421..50e8f9bc6ed01 100644 --- a/packages/vite/src/generators/vitest/vitest-generator.ts +++ b/packages/vite/src/generators/vitest/vitest-generator.ts @@ -12,7 +12,7 @@ import { } from '@nrwl/devkit'; import { addOrChangeTestTarget, - findExistingTargets, + findExistingTargetsInProject, writeViteConfig, } from '../../utils/generator-utils'; import { VitestGeneratorSchema } from './schema'; @@ -31,9 +31,12 @@ export async function vitestGenerator( const tasks: GeneratorCallback[] = []; const { targets, root } = readProjectConfiguration(tree, schema.project); - let testTarget = findExistingTargets(targets).testTarget; + let testTarget = + schema.testTarget ?? + findExistingTargetsInProject(targets)?.validFoundTargetName?.test ?? + 'test'; - addOrChangeTestTarget(tree, schema, testTarget ?? 'test'); + addOrChangeTestTarget(tree, schema, testTarget); const initTask = await initGenerator(tree, { uiFramework: schema.uiFramework, diff --git a/packages/vite/src/utils/generator-util.spec.ts b/packages/vite/src/utils/generator-util.spec.ts new file mode 100644 index 0000000000000..729f95ef3fb16 --- /dev/null +++ b/packages/vite/src/utils/generator-util.spec.ts @@ -0,0 +1,218 @@ +import { + readProjectConfiguration, + Tree, + updateProjectConfiguration, +} from '@nrwl/devkit'; +import { createTreeWithEmptyV1Workspace } from '@nrwl/devkit/testing'; +import { + findExistingTargetsInProject, + getViteConfigPathForProject, + handleUnsupportedUserProvidedTargets, +} from './generator-utils'; +import { + mockReactAppGenerator, + mockViteReactAppGenerator, + mockAngularAppGenerator, +} from './test-utils'; +describe('generator utils', () => { + let tree: Tree; + + beforeEach(() => { + tree = createTreeWithEmptyV1Workspace(); + }); + + describe('getViteConfigPathForProject', () => { + beforeEach(() => { + mockViteReactAppGenerator(tree); + }); + it('should return correct path for vite.config file if no configFile is set', () => { + const viteConfigPath = getViteConfigPathForProject( + tree, + 'my-test-react-vite-app' + ); + expect(viteConfigPath).toEqual( + 'apps/my-test-react-vite-app/vite.config.ts' + ); + }); + + it('should return correct path for vite.config file if custom configFile is set', () => { + const projectConfig = readProjectConfiguration( + tree, + 'my-test-react-vite-app' + ); + updateProjectConfiguration(tree, 'my-test-react-vite-app', { + ...projectConfig, + targets: { + ...projectConfig.targets, + build: { + ...projectConfig.targets.build, + options: { + ...projectConfig.targets.build.options, + configFile: 'apps/my-test-react-vite-app/vite.config.custom.ts', + }, + }, + }, + }); + + tree.write(`apps/my-test-react-vite-app/vite.config.custom.ts`, ''); + + const viteConfigPath = getViteConfigPathForProject( + tree, + 'my-test-react-vite-app' + ); + expect(viteConfigPath).toEqual( + 'apps/my-test-react-vite-app/vite.config.custom.ts' + ); + }); + + it('should return correct path for vite.config file given a target name', () => { + const projectConfig = readProjectConfiguration( + tree, + 'my-test-react-vite-app' + ); + updateProjectConfiguration(tree, 'my-test-react-vite-app', { + ...projectConfig, + targets: { + ...projectConfig.targets, + 'other-build': { + ...projectConfig.targets.build, + options: { + ...projectConfig.targets.build.options, + configFile: 'apps/my-test-react-vite-app/vite.other.custom.ts', + }, + }, + }, + }); + + tree.write(`apps/my-test-react-vite-app/vite.other.custom.ts`, ''); + + const viteConfigPath = getViteConfigPathForProject( + tree, + 'my-test-react-vite-app', + 'other-build' + ); + expect(viteConfigPath).toEqual( + 'apps/my-test-react-vite-app/vite.other.custom.ts' + ); + }); + }); + + describe('findExistingTargetsInProject', () => { + it('should return the correct targets', () => { + mockReactAppGenerator(tree); + const { targets } = readProjectConfiguration(tree, 'my-test-react-app'); + + const existingTargets = findExistingTargetsInProject(targets); + expect(existingTargets).toMatchObject({ + userProvidedTargetIsUnsupported: { + build: undefined, + serve: undefined, + test: undefined, + }, + validFoundTargetName: { + build: 'build', + serve: 'serve', + test: 'test', + }, + projectContainsUnsupportedExecutor: undefined, + }); + }); + + it('should return the correct - undefined - targets for Angular apps', () => { + mockAngularAppGenerator(tree); + const { targets } = readProjectConfiguration(tree, 'my-test-angular-app'); + const existingTargets = findExistingTargetsInProject(targets); + expect(existingTargets).toMatchObject({ + userProvidedTargetIsUnsupported: { + build: undefined, + serve: undefined, + test: undefined, + }, + validFoundTargetName: { + build: undefined, + serve: undefined, + test: 'test', + }, + projectContainsUnsupportedExecutor: true, + }); + }); + }); + + describe('handleUnsupportedUserProvidedTargets', () => { + afterEach(() => { + jest.clearAllMocks(); + }); + it('should throw error if unsupported and user does not want to confirm', async () => { + const { Confirm } = require('enquirer'); + const confirmSpy = jest.spyOn(Confirm.prototype, 'run'); + confirmSpy.mockResolvedValueOnce(false); + expect.assertions(2); + const object = { + unsupportedUserProvidedTarget: { + build: true, + }, + userProvidedTargets: { + build: 'my-build', + serve: 'my-serve', + test: 'my-test', + }, + targets: { + build: 'build', + serve: 'serve', + test: 'test', + }, + }; + + try { + await handleUnsupportedUserProvidedTargets( + object.unsupportedUserProvidedTarget, + object.userProvidedTargets, + object.targets + ); + throw new Error('should not reach here'); + } catch (e) { + expect(e).toBeDefined(); + expect(e.toString()).toContain( + 'The build target my-build cannot be converted to use the @nrwl/vite:build executor' + ); + } + }); + + it('should NOT throw error if unsupported and user confirms', async () => { + const { Confirm } = require('enquirer'); + const confirmSpy = jest.spyOn(Confirm.prototype, 'run'); + confirmSpy.mockResolvedValue(true); + expect.assertions(2); + const object = { + unsupportedUserProvidedTarget: { + build: true, + }, + userProvidedTargets: { + build: 'my-build', + serve: 'my-serve', + test: 'my-test', + }, + targets: { + build: 'build', + serve: 'serve', + test: 'test', + }, + }; + + expect( + handleUnsupportedUserProvidedTargets( + object.unsupportedUserProvidedTarget, + object.userProvidedTargets, + object.targets + ) + ).resolves.toBeUndefined(); + + const response = await handleUnsupportedUserProvidedTargets( + object.unsupportedUserProvidedTarget, + object.userProvidedTargets, + object.targets + ); + expect(response).toBeUndefined(); + }); + }); +}); diff --git a/packages/vite/src/utils/generator-util.test.ts b/packages/vite/src/utils/generator-util.test.ts deleted file mode 100644 index 82ba55b15c32f..0000000000000 --- a/packages/vite/src/utils/generator-util.test.ts +++ /dev/null @@ -1,111 +0,0 @@ -import { - readProjectConfiguration, - Tree, - updateProjectConfiguration, -} from '@nrwl/devkit'; -import { createTreeWithEmptyV1Workspace } from '@nrwl/devkit/testing'; -import { - findExistingTargets, - getViteConfigPathForProject, -} from './generator-utils'; -import { mockReactAppGenerator, mockViteReactAppGenerator } from './test-utils'; -describe('generator utils', () => { - let tree: Tree; - - beforeEach(() => { - tree = createTreeWithEmptyV1Workspace(); - }); - - describe('getViteConfigPathForProject', () => { - beforeEach(() => { - mockViteReactAppGenerator(tree); - }); - it('should return correct path for vite.config file if no configFile is set', () => { - const viteConfigPath = getViteConfigPathForProject( - tree, - 'my-test-react-vite-app' - ); - expect(viteConfigPath).toEqual( - 'apps/my-test-react-vite-app/vite.config.ts' - ); - }); - - it('should return correct path for vite.config file if custom configFile is set', () => { - const projectConfig = readProjectConfiguration( - tree, - 'my-test-react-vite-app' - ); - updateProjectConfiguration(tree, 'my-test-react-vite-app', { - ...projectConfig, - targets: { - ...projectConfig.targets, - build: { - ...projectConfig.targets.build, - options: { - ...projectConfig.targets.build.options, - configFile: 'apps/my-test-react-vite-app/vite.config.custom.ts', - }, - }, - }, - }); - - tree.write(`apps/my-test-react-vite-app/vite.config.custom.ts`, ''); - - const viteConfigPath = getViteConfigPathForProject( - tree, - 'my-test-react-vite-app' - ); - expect(viteConfigPath).toEqual( - 'apps/my-test-react-vite-app/vite.config.custom.ts' - ); - }); - - it('should return correct path for vite.config file given a target name', () => { - const projectConfig = readProjectConfiguration( - tree, - 'my-test-react-vite-app' - ); - updateProjectConfiguration(tree, 'my-test-react-vite-app', { - ...projectConfig, - targets: { - ...projectConfig.targets, - 'other-build': { - ...projectConfig.targets.build, - options: { - ...projectConfig.targets.build.options, - configFile: 'apps/my-test-react-vite-app/vite.other.custom.ts', - }, - }, - }, - }); - - tree.write(`apps/my-test-react-vite-app/vite.other.custom.ts`, ''); - - const viteConfigPath = getViteConfigPathForProject( - tree, - 'my-test-react-vite-app', - 'other-build' - ); - expect(viteConfigPath).toEqual( - 'apps/my-test-react-vite-app/vite.other.custom.ts' - ); - }); - }); - - describe('findExistingTargets', () => { - beforeEach(() => { - mockReactAppGenerator(tree); - }); - it('should return the correct targets', () => { - const { targets } = readProjectConfiguration(tree, 'my-test-react-app'); - - const existingTargets = findExistingTargets(targets); - expect(existingTargets).toMatchObject({ - buildTarget: 'build', - serveTarget: 'serve', - testTarget: 'test', - unsuppored: undefined, - }); - }); - }); -}); diff --git a/packages/vite/src/utils/generator-utils.ts b/packages/vite/src/utils/generator-utils.ts index 6f6aa0e5ed998..d10a0ece3efa3 100644 --- a/packages/vite/src/utils/generator-utils.ts +++ b/packages/vite/src/utils/generator-utils.ts @@ -14,17 +14,43 @@ import { ViteDevServerExecutorOptions } from '../executors/dev-server/schema'; import { VitestExecutorOptions } from '../executors/test/schema'; import { Schema } from '../generators/configuration/schema'; -export function findExistingTargets(targets: { - [targetName: string]: TargetConfiguration; -}): { - buildTarget?: string; - serveTarget?: string; - testTarget?: string; - unsuppored?: boolean; -} { - let buildTarget, serveTarget, testTarget, unsuppored; +export interface UserProvidedTargetIsUnsupported { + build?: boolean; + serve?: boolean; + test?: boolean; +} + +export interface UserProvidedTargetName { + build?: string; + serve?: string; + test?: string; +} - const arrayOfBuilders = [ +export interface ValidFoundTargetName { + build?: string; + serve?: string; + test?: string; +} + +export function findExistingTargetsInProject( + targets: { + [targetName: string]: TargetConfiguration; + }, + userProvidedTargets?: UserProvidedTargetName +): { + validFoundTargetName: ValidFoundTargetName; + projectContainsUnsupportedExecutor?: boolean; + userProvidedTargetIsUnsupported?: UserProvidedTargetIsUnsupported; +} { + let validFoundBuildTarget: string | undefined, + validFoundServeTarget: string | undefined, + validFoundTestTarget: string | undefined, + projectContainsUnsupportedExecutor: boolean | undefined, + unsupportedUserProvidedTargetBuild: boolean | undefined, + unsupportedUserProvidedTargetServe: boolean | undefined, + unsupportedUserProvidedTargetTest: boolean | undefined; + + const arrayOfSupportedBuilders = [ '@nxext/vite:build', '@nrwl/js:babel', '@nrwl/js:swc', @@ -33,11 +59,14 @@ export function findExistingTargets(targets: { '@nrwl/web:rollup', ]; - const arrayOfServers = ['@nxext/vite:dev', '@nrwl/webpack:dev-server']; + const arrayOfSupportedServers = [ + '@nxext/vite:dev', + '@nrwl/webpack:dev-server', + ]; - const arrayOfTesters = ['@nrwl/jest:jest', '@nxext/vitest:vitest']; + const arrayOfSupportedTesters = ['@nrwl/jest:jest', '@nxext/vitest:vitest']; - const arrayofUnsupported = [ + const arrayofUnsupportedExecutors = [ '@nrwl/angular:ng-packagr-lite', '@angular-devkit/build-angular:browser', '@nrwl/angular:package', @@ -52,29 +81,93 @@ export function findExistingTargets(targets: { '@nrwl/js:tsc', ]; + // First, we check if the user has provided a target + // If they have, we check if the executor the target is using is supported + // If it's not supported, then we set the unsupported flag to true for that target + + if (userProvidedTargets?.build) { + if ( + arrayOfSupportedBuilders.includes( + targets[userProvidedTargets.build]?.executor + ) + ) { + validFoundBuildTarget = userProvidedTargets.build; + } else { + unsupportedUserProvidedTargetBuild = true; + } + } + + if (userProvidedTargets?.serve) { + if ( + arrayOfSupportedServers.includes( + targets[userProvidedTargets.serve]?.executor + ) + ) { + validFoundServeTarget = userProvidedTargets.serve; + } else { + unsupportedUserProvidedTargetServe = true; + } + } + + if (userProvidedTargets?.test) { + if ( + arrayOfSupportedServers.includes( + targets[userProvidedTargets.test]?.executor + ) + ) { + validFoundTestTarget = userProvidedTargets.test; + } else { + unsupportedUserProvidedTargetTest = true; + } + } + + // Then, we try to find the targets that are using the supported executors + // for build, serve and test, since these are the ones we will be converting + for (const target in targets) { - if (buildTarget && serveTarget && testTarget) { + // If we have a value for each one of the targets, we can break out of the loop + if ( + validFoundBuildTarget && + validFoundServeTarget && + validFoundTestTarget + ) { break; } - if (arrayOfBuilders.includes(targets[target].executor)) { - buildTarget = target; + if ( + !validFoundBuildTarget && + arrayOfSupportedBuilders.includes(targets[target].executor) + ) { + validFoundBuildTarget = target; } - if (arrayOfServers.includes(targets[target].executor)) { - serveTarget = target; + if ( + !validFoundServeTarget && + arrayOfSupportedServers.includes(targets[target].executor) + ) { + validFoundServeTarget = target; } - if (arrayOfTesters.includes(targets[target].executor)) { - testTarget = target; + if ( + !validFoundTestTarget && + arrayOfSupportedTesters.includes(targets[target].executor) + ) { + validFoundTestTarget = target; } - if (arrayofUnsupported.includes(targets[target].executor)) { - unsuppored = true; + if (arrayofUnsupportedExecutors.includes(targets[target].executor)) { + projectContainsUnsupportedExecutor = true; } } return { - buildTarget, - serveTarget, - testTarget, - unsuppored, + validFoundTargetName: { + build: validFoundBuildTarget, + serve: validFoundServeTarget, + test: validFoundTestTarget, + }, + projectContainsUnsupportedExecutor, + userProvidedTargetIsUnsupported: { + build: unsupportedUserProvidedTargetBuild, + serve: unsupportedUserProvidedTargetServe, + test: unsupportedUserProvidedTargetTest, + }, }; } @@ -501,3 +594,94 @@ export function getViteConfigPathForProject( return normalizeViteConfigFilePathWithTree(tree, root, viteConfigPath); } + +export async function handleUnsupportedUserProvidedTargets( + userProvidedTargetIsUnsupported: UserProvidedTargetIsUnsupported, + userProvidedTargetName: UserProvidedTargetName, + validFoundTargetName: ValidFoundTargetName +) { + if (userProvidedTargetIsUnsupported.build && validFoundTargetName.build) { + await handleUnsupportedUserProvidedTargetsErrors( + userProvidedTargetName.build, + validFoundTargetName.build, + 'build', + 'build' + ); + } + + if (userProvidedTargetIsUnsupported.serve && validFoundTargetName.serve) { + await handleUnsupportedUserProvidedTargetsErrors( + userProvidedTargetName.serve, + validFoundTargetName.serve, + 'serve', + 'dev-server' + ); + } + + if (userProvidedTargetIsUnsupported.test && validFoundTargetName.test) { + await handleUnsupportedUserProvidedTargetsErrors( + userProvidedTargetName.test, + validFoundTargetName.test, + 'test', + 'test' + ); + } +} + +async function handleUnsupportedUserProvidedTargetsErrors( + userProvidedTargetName: string, + validFoundTargetName: string, + action: 'build' | 'serve' | 'test', + executor: 'build' | 'dev-server' | 'test' +) { + logger.warn( + `The custom ${action} target you provided (${userProvidedTargetName}) cannot be converted to use the @nrwl/vite:${executor} executor. + However, we found the following ${action} target in your project that can be converted: ${validFoundTargetName} + + Please note that converting a potentially non-compatible project to use Vite.js may result in unexpected behavior. Always commit + your changes before converting a project to use Vite.js, and test the converted project thoroughly before deploying it. + ` + ); + const { Confirm } = require('enquirer'); + const prompt = new Confirm({ + name: 'question', + message: `Should we convert the ${validFoundTargetName} target to use the @nrwl/vite:${executor} executor?`, + initial: true, + }); + const shouldConvert = await prompt.run(); + if (!shouldConvert) { + throw new Error( + `The ${action} target ${userProvidedTargetName} cannot be converted to use the @nrwl/vite:${executor} executor. + Please try again, either by providing a different ${action} target or by not providing a target at all (Nx will + convert the first one it finds, most probably this one: ${validFoundTargetName}) + + Please note that converting a potentially non-compatible project to use Vite.js may result in unexpected behavior. Always commit + your changes before converting a project to use Vite.js, and test the converted project thoroughly before deploying it. + ` + ); + } +} + +export async function handleUnknownExecutors() { + logger.warn( + ` + We cound not find any targets in your project that are using the supported executors for build, serve and test. + However, we could not verify that the executors you are using are not supported. + Please make sure to commit your changes before running this generator. + ` + ); + + const { Confirm } = require('enquirer'); + const prompt = new Confirm({ + name: 'question', + message: `Should Nx convert your project to use the @nrwl/vite executors?`, + initial: true, + }); + const shouldConvert = await prompt.run(); + if (!shouldConvert) { + throw new Error(` + Nx could not verify that the executors you are using can be converted to the @nrwl/vite executors. + Please try again with a different project. + `); + } +} diff --git a/packages/vite/src/utils/test-files/angular-project.config.json b/packages/vite/src/utils/test-files/angular-project.config.json new file mode 100644 index 0000000000000..81793fa0a3f28 --- /dev/null +++ b/packages/vite/src/utils/test-files/angular-project.config.json @@ -0,0 +1,89 @@ +{ + "name": "my-test-angular-app", + "$schema": "../../node_modules/nx/schemas/project-schema.json", + "root": "apps/my-test-angular-app", + "sourceRoot": "apps/my-test-angular-app/src", + "projectType": "application", + "targets": { + "build": { + "executor": "@angular-devkit/build-angular:browser", + "outputs": ["{options.outputPath}"], + "options": { + "outputPath": "dist/apps/my-test-angular-app", + "index": "apps/my-test-angular-app/src/index.html", + "main": "apps/my-test-angular-app/src/main.ts", + "polyfills": ["zone.js"], + "tsConfig": "apps/my-test-angular-app/tsconfig.app.json", + "assets": [ + "apps/my-test-angular-app/src/favicon.ico", + "apps/my-test-angular-app/src/assets" + ], + "styles": ["apps/my-test-angular-app/src/styles.css"], + "scripts": [] + }, + "configurations": { + "production": { + "budgets": [ + { + "type": "initial", + "maximumWarning": "500kb", + "maximumError": "1mb" + }, + { + "type": "anyComponentStyle", + "maximumWarning": "2kb", + "maximumError": "4kb" + } + ], + "outputHashing": "all" + }, + "development": { + "buildOptimizer": false, + "optimization": false, + "vendorChunk": true, + "extractLicenses": false, + "sourceMap": true, + "namedChunks": true + } + }, + "defaultConfiguration": "production" + }, + "serve": { + "executor": "@angular-devkit/build-angular:dev-server", + "configurations": { + "production": { + "browserTarget": "my-test-angular-app:build:production" + }, + "development": { + "browserTarget": "my-test-angular-app:build:development" + } + }, + "defaultConfiguration": "development" + }, + "extract-i18n": { + "executor": "@angular-devkit/build-angular:extract-i18n", + "options": { + "browserTarget": "my-test-angular-app:build" + } + }, + "lint": { + "executor": "@nrwl/linter:eslint", + "outputs": ["{options.outputFile}"], + "options": { + "lintFilePatterns": [ + "apps/my-test-angular-app/**/*.ts", + "apps/my-test-angular-app/**/*.html" + ] + } + }, + "test": { + "executor": "@nrwl/jest:jest", + "outputs": ["{workspaceRoot}/coverage/{projectRoot}"], + "options": { + "jestConfig": "apps/my-test-angular-app/jest.config.ts", + "passWithNoTests": true + } + } + }, + "tags": [] +} diff --git a/packages/vite/src/utils/test-files/react-mixed-project.config.json b/packages/vite/src/utils/test-files/react-mixed-project.config.json new file mode 100644 index 0000000000000..f05026c7bec8b --- /dev/null +++ b/packages/vite/src/utils/test-files/react-mixed-project.config.json @@ -0,0 +1,52 @@ +{ + "name": "my-test-mixed-react-app", + "$schema": "../../node_modules/nx/schemas/project-schema.json", + "root": "apps/my-test-mixed-react-app", + "sourceRoot": "apps/my-test-mixed-react-app/src", + "projectType": "application", + "targets": { + "invalid-build": { + "executor": "@nrwl/js:tsc", + "outputs": ["{options.outputPath}"] + }, + "valid-build": { + "executor": "@nrwl/webpack:webpack", + "outputs": ["{options.outputPath}"] + }, + "serve": { + "executor": "@nrwl/webpack:dev-server", + "defaultConfiguration": "development", + "options": { + "buildTarget": "my-test-mixed-react-app:build", + "hmr": true + }, + "configurations": { + "development": { + "buildTarget": "my-test-mixed-react-app:build:development" + }, + "production": { + "buildTarget": "my-test-mixed-react-app:build:production", + "hmr": false + } + } + }, + "lint": { + "executor": "@nrwl/linter:eslint", + "outputs": ["{options.outputFile}"], + "options": { + "lintFilePatterns": [ + "apps/my-test-mixed-react-app/**/*.{ts,tsx,js,jsx}" + ] + } + }, + "test": { + "executor": "@nrwl/jest:jest", + "outputs": ["{workspaceRoot}/coverage/{projectRoot}"], + "options": { + "jestConfig": "apps/my-test-mixed-react-app/jest.config.ts", + "passWithNoTests": true + } + } + }, + "tags": [] +} diff --git a/packages/vite/src/utils/test-files/unknown-project.config.json b/packages/vite/src/utils/test-files/unknown-project.config.json new file mode 100644 index 0000000000000..f87f53c3787e4 --- /dev/null +++ b/packages/vite/src/utils/test-files/unknown-project.config.json @@ -0,0 +1,72 @@ +{ + "name": "my-test-random-app", + "$schema": "../../node_modules/nx/schemas/project-schema.json", + "projectType": "application", + "root": "apps/my-test-random-app", + "sourceRoot": "apps/my-test-random-app/src", + "tags": [], + "targets": { + "build": { + "executor": "@random/something:build", + "outputs": ["{options.outputPath}"], + "defaultConfiguration": "production", + "options": { + "outputPath": "dist/apps/my-test-random-app", + "compiler": "babel", + "main": "apps/my-test-random-app/src/main.ts", + "tsConfig": "apps/my-test-random-app/tsconfig.app.json", + "assets": [ + "apps/my-test-random-app/src/favicon.ico", + "apps/my-test-random-app/src/assets" + ], + "index": "apps/my-test-random-app/src/index.html", + "baseHref": "/", + "polyfills": "apps/my-test-random-app/src/polyfills.ts", + "styles": ["apps/my-test-random-app/src/styles.css"], + "scripts": [] + }, + "configurations": { + "production": { + "optimization": true, + "outputHashing": "all", + "sourceMap": false, + "namedChunks": false, + "extractLicenses": true, + "vendorChunk": false, + "fileReplacements": [ + { + "replace": "apps/my-test-random-app/src/environments/environment.ts", + "with": "apps/my-test-random-app/src/environments/environment.prod.ts" + } + ] + } + } + }, + "serve": { + "executor": "@random/something:serve", + "options": { + "buildTarget": "my-test-random-app:build" + }, + "configurations": { + "production": { + "buildTarget": "my-test-random-app:build:production" + } + } + }, + "lint": { + "executor": "@random/something:test", + "outputs": ["{options.outputFile}"], + "options": { + "lintFilePatterns": ["apps/my-test-random-app/**/*.ts"] + } + }, + "test": { + "executor": "@random/something:test", + "outputs": ["{workspaceRoot}/coverage/{projectRoot}"], + "options": { + "jestConfig": "apps/my-test-random-app/jest.config.ts", + "passWithNoTests": true + } + } + } +} diff --git a/packages/vite/src/utils/test-utils.ts b/packages/vite/src/utils/test-utils.ts index e7148104bbd62..f77a9a0fae1b2 100644 --- a/packages/vite/src/utils/test-utils.ts +++ b/packages/vite/src/utils/test-utils.ts @@ -2,6 +2,9 @@ import { parseJson, Tree, writeJson } from '@nrwl/devkit'; import * as reactAppConfig from './test-files/react-project.config.json'; import * as reactViteConfig from './test-files/react-vite-project.config.json'; import * as webAppConfig from './test-files/web-project.config.json'; +import * as angularAppConfig from './test-files/angular-project.config.json'; +import * as randomAppConfig from './test-files/unknown-project.config.json'; +import * as mixedAppConfig from './test-files/react-mixed-project.config.json'; export function mockViteReactAppGenerator(tree: Tree): Tree { const appName = 'my-test-react-vite-app'; @@ -234,6 +237,106 @@ export function mockReactAppGenerator(tree: Tree): Tree { return tree; } +export function mockReactMixedAppGenerator(tree: Tree): Tree { + const appName = 'my-test-mixed-react-app'; + + tree.write( + `apps/${appName}/src/main.tsx`, + `import ReactDOM from 'react-dom';\n` + ); + + tree.write( + `apps/${appName}/tsconfig.json`, + `{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "jsx": "react-jsx", + "allowJs": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.app.json" + }, + { + "path": "./tsconfig.spec.json" + } + ] + } + ` + ); + tree.write( + `apps/${appName}/tsconfig.app.json`, + `{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc" + }, + "files": [ + "../../node_modules/@nrwl/react/typings/cssmodule.d.ts", + "../../node_modules/@nrwl/react/typings/image.d.ts" + ], + "exclude": [ + "jest.config.ts", + "**/*.spec.ts", + "**/*.test.ts", + "**/*.spec.tsx", + "**/*.test.tsx", + "**/*.spec.js", + "**/*.test.js", + "**/*.spec.jsx", + "**/*.test.jsx" + ], + "include": ["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx"] + } + ` + ); + + tree.write( + `apps/${appName}/src/index.html`, + ` + + + + My Test React App + + + + + + +
+ + ` + ); + + writeJson(tree, 'workspace.json', { + projects: { + 'my-test-mixed-react-app': { + ...mixedAppConfig, + root: `apps/${appName}`, + projectType: 'application', + }, + }, + }); + + writeJson(tree, `apps/${appName}/project.json`, { + ...mixedAppConfig, + root: `apps/${appName}`, + projectType: 'application', + }); + + return tree; +} export function mockWebAppGenerator(tree: Tree): Tree { const appName = 'my-test-web-app'; @@ -299,3 +402,47 @@ export function mockWebAppGenerator(tree: Tree): Tree { }); return tree; } + +export function mockAngularAppGenerator(tree: Tree): Tree { + const appName = 'my-test-angular-app'; + + writeJson(tree, 'workspace.json', { + projects: { + 'my-test-angular-app': { + ...angularAppConfig, + root: `apps/${appName}`, + projectType: 'application', + }, + }, + }); + + writeJson(tree, `apps/${appName}/project.json`, { + ...angularAppConfig, + root: `apps/${appName}`, + projectType: 'application', + }); + + return tree; +} + +export function mockUnknownAppGenerator(tree: Tree): Tree { + const appName = 'my-test-random-app'; + + writeJson(tree, 'workspace.json', { + projects: { + 'my-test-random-app': { + ...randomAppConfig, + root: `apps/${appName}`, + projectType: 'application', + }, + }, + }); + + writeJson(tree, `apps/${appName}/project.json`, { + ...randomAppConfig, + root: `apps/${appName}`, + projectType: 'application', + }); + + return tree; +}