From 7485b16fc91301d5ada35fb21ce76900dc94f8b8 Mon Sep 17 00:00:00 2001 From: Colum Ferry Date: Fri, 15 Mar 2024 16:54:55 +0000 Subject: [PATCH] feat(remix): add option to create-nx-workspace --- docs/generated/cli/create-nx-workspace.md | 2 +- .../nx/documents/create-nx-workspace.md | 2 +- .../bin/create-nx-workspace.ts | 26 ++- .../src/utils/preset/preset.ts | 2 + .../application.impl.spec.ts.snap | 3 +- .../application/application.impl.ts | 23 +++ .../remix/src/utils/testing-config-utils.ts | 27 +++ .../generate-workspace-files.spec.ts.snap | 160 ++++++++++++++++++ .../src/generators/new/generate-preset.ts | 4 + .../new/generate-workspace-files.ts | 1 + .../workspace/src/generators/preset/preset.ts | 30 +++- .../workspace/src/generators/utils/presets.ts | 2 + 12 files changed, 271 insertions(+), 11 deletions(-) diff --git a/docs/generated/cli/create-nx-workspace.md b/docs/generated/cli/create-nx-workspace.md index 4b5447bddd455..4292def25a1e3 100644 --- a/docs/generated/cli/create-nx-workspace.md +++ b/docs/generated/cli/create-nx-workspace.md @@ -145,7 +145,7 @@ Prefix to use for Angular component and directive selectors. 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", "vue-monorepo", "vue-standalone", "nuxt", "nuxt-standalone", "next", "nextjs-standalone", "react-native", "expo", "nest", "express", "react", "vue", "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", "nuxt", "nuxt-standalone", "next", "nextjs-standalone", "remix-monorepo", "remix-standalone", "react-native", "expo", "nest", "express", "react", "vue", "angular", "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 4b5447bddd455..4292def25a1e3 100644 --- a/docs/generated/packages/nx/documents/create-nx-workspace.md +++ b/docs/generated/packages/nx/documents/create-nx-workspace.md @@ -145,7 +145,7 @@ Prefix to use for Angular component and directive selectors. 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", "vue-monorepo", "vue-standalone", "nuxt", "nuxt-standalone", "next", "nextjs-standalone", "react-native", "expo", "nest", "express", "react", "vue", "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", "nuxt", "nuxt-standalone", "next", "nextjs-standalone", "remix-monorepo", "remix-standalone", "react-native", "expo", "nest", "express", "react", "vue", "angular", "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 d4be78643d0e2..f64d1ff6321d7 100644 --- a/packages/create-nx-workspace/bin/create-nx-workspace.ts +++ b/packages/create-nx-workspace/bin/create-nx-workspace.ts @@ -43,7 +43,7 @@ interface ReactArguments extends BaseArguments { stack: 'react'; workspaceType: 'standalone' | 'integrated'; appName: string; - framework: 'none' | 'next'; + framework: 'none' | 'next' | 'remix'; style: string; bundler: 'webpack' | 'vite' | 'rspack'; nextAppDir: boolean; @@ -378,6 +378,8 @@ async function determineStack( case Preset.ReactMonorepo: case Preset.NextJs: case Preset.NextJsStandalone: + case Preset.RemixStandalone: + case Preset.RemixMonorepo: return 'react'; case Preset.Vue: case Preset.VueStandalone: @@ -520,7 +522,8 @@ async function determineReactOptions( preset = parsedArgs.preset; if ( preset === Preset.ReactStandalone || - preset === Preset.NextJsStandalone + preset === Preset.NextJsStandalone || + preset === Preset.RemixStandalone ) { appName = parsedArgs.appName ?? parsedArgs.name; } else { @@ -548,6 +551,12 @@ async function determineReactOptions( } else { preset = Preset.NextJs; } + } else if (framework === 'remix') { + if (workspaceType === 'standalone') { + preset = Preset.RemixStandalone; + } else { + preset = Preset.RemixMonorepo; + } } else if (framework === 'react-native') { preset = Preset.ReactNative; } else if (framework === 'expo') { @@ -568,6 +577,11 @@ async function determineReactOptions( nextAppDir = await determineNextAppDir(parsedArgs); nextSrcDir = await determineNextSrcDir(parsedArgs); e2eTestRunner = await determineE2eTestRunner(parsedArgs); + } else if ( + preset === Preset.RemixMonorepo || + preset === Preset.RemixStandalone + ) { + e2eTestRunner = await determineE2eTestRunner(parsedArgs); } if (parsedArgs.style) { @@ -1017,9 +1031,9 @@ async function determineAppName( async function determineReactFramework( parsedArgs: yargs.Arguments -): Promise<'none' | 'nextjs' | 'expo' | 'react-native'> { +): Promise<'none' | 'nextjs' | 'remix' | 'expo' | 'react-native'> { const reply = await enquirer.prompt<{ - framework: 'none' | 'nextjs' | 'expo' | 'react-native'; + framework: 'none' | 'nextjs' | 'remix' | 'expo' | 'react-native'; }>([ { name: 'framework', @@ -1035,6 +1049,10 @@ async function determineReactFramework( name: 'nextjs', message: 'Next.js [ https://nextjs.org/ ]', }, + { + name: 'remix', + message: 'Remix [ https://remix.run/ ]', + }, { name: 'expo', message: 'Expo [ https://expo.io/ ]', diff --git a/packages/create-nx-workspace/src/utils/preset/preset.ts b/packages/create-nx-workspace/src/utils/preset/preset.ts index 35e6ba7f1d52f..433f7e6a0e2e6 100644 --- a/packages/create-nx-workspace/src/utils/preset/preset.ts +++ b/packages/create-nx-workspace/src/utils/preset/preset.ts @@ -15,6 +15,8 @@ export enum Preset { NuxtStandalone = 'nuxt-standalone', NextJs = 'next', NextJsStandalone = 'nextjs-standalone', + RemixMonorepo = 'remix-monorepo', + RemixStandalone = 'remix-standalone', ReactNative = 'react-native', Expo = 'expo', Nest = 'nest', diff --git a/packages/remix/src/generators/application/__snapshots__/application.impl.spec.ts.snap b/packages/remix/src/generators/application/__snapshots__/application.impl.spec.ts.snap index e7155b51fa42c..bfff5a8801fef 100644 --- a/packages/remix/src/generators/application/__snapshots__/application.impl.spec.ts.snap +++ b/packages/remix/src/generators/application/__snapshots__/application.impl.spec.ts.snap @@ -1147,8 +1147,7 @@ export default { moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'], coverageDirectory: './coverage/test', testMatch: [ - '/src/**/__tests__/**/*.[jt]s?(x)', - '/src/**/*(*.)@(spec|test).[jt]s?(x)', + '/tests/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}', ], }; " diff --git a/packages/remix/src/generators/application/application.impl.ts b/packages/remix/src/generators/application/application.impl.ts index c0450ea4a9fb0..a1495d302a772 100644 --- a/packages/remix/src/generators/application/application.impl.ts +++ b/packages/remix/src/generators/application/application.impl.ts @@ -35,6 +35,7 @@ import initGenerator from '../init/init'; import { initGenerator as jsInitGenerator } from '@nx/js'; import { addBuildTargetDefaults } from '@nx/devkit/src/generators/add-build-target-defaults'; import { logShowProjectCommand } from '@nx/devkit/src/utils/log-show-project-command'; +import { updateJestTestMatch } from '../../utils/testing-config-utils'; export function remixApplicationGenerator( tree: Tree, @@ -267,6 +268,28 @@ export async function remixApplicationGeneratorInternal( extractTsConfigBase(tree); } + if (options.rootProject) { + updateJson(tree, `package.json`, (json) => { + json.type = 'module'; + return json; + }); + + if (options.unitTestRunner === 'jest') { + tree.write( + 'jest.preset.js', + `import { nxPreset } from '@nx/jest/preset/jest-preset.js'; +export default {...nxPreset}; +` + ); + + updateJestTestMatch( + tree, + 'jest.config.ts', + '/tests/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}' + ); + } + } + tasks.push(await addE2E(tree, options)); if (!options.skipFormat) { diff --git a/packages/remix/src/utils/testing-config-utils.ts b/packages/remix/src/utils/testing-config-utils.ts index 02a358bb8aac7..4381fe9d39735 100644 --- a/packages/remix/src/utils/testing-config-utils.ts +++ b/packages/remix/src/utils/testing-config-utils.ts @@ -100,6 +100,33 @@ export function updateJestTestSetup( } } +export function updateJestTestMatch( + tree: Tree, + pathToJestConfig: string, + includesString: string +) { + if (!tsModule) { + tsModule = ensureTypescript(); + } + const { tsquery } = require('@phenomnomnominal/tsquery'); + const fileContents = tree.read(pathToJestConfig, 'utf-8'); + + const ast = tsquery.ast(fileContents); + + const TEST_MATCH_SELECTOR = + 'PropertyAssignment:has(Identifier[name=testMatch])'; + const nodes = tsquery(ast, TEST_MATCH_SELECTOR, { visitAllChildren: true }); + + if (nodes.length !== 0) { + const updatedFileContents = stripIndents`${fileContents.slice( + 0, + nodes[0].getStart() + )}testMatch: ["${includesString}"]${fileContents.slice(nodes[0].getEnd())}`; + + tree.write(pathToJestConfig, updatedFileContents); + } +} + export function updateVitestTestIncludes( tree: Tree, pathToVitestConfig: string, diff --git a/packages/workspace/src/generators/new/__snapshots__/generate-workspace-files.spec.ts.snap b/packages/workspace/src/generators/new/__snapshots__/generate-workspace-files.spec.ts.snap index e51b4672bc3c5..b27f4cf97286e 100644 --- a/packages/workspace/src/generators/new/__snapshots__/generate-workspace-files.spec.ts.snap +++ b/packages/workspace/src/generators/new/__snapshots__/generate-workspace-files.spec.ts.snap @@ -1286,6 +1286,166 @@ It will show tasks that you can run with Nx. " `; +exports[`@nx/workspace:generateWorkspaceFiles README.md should be created for RemixMonorepo preset 1`] = ` +"# Proj + + + +✨ **This workspace has been generated by [Nx, Smart Monorepos · Fast CI.](https://nx.dev)** ✨ + +## Integrate with editors + +Enhance your Nx experience by installing [Nx Console](https://nx.dev/nx-console) for your favorite editor. Nx Console +provides an interactive UI to view your projects, run tasks, generate code, and more! Available for VSCode, IntelliJ and +comes with a LSP for Vim users. + +## Nx plugins and code generators + +Add Nx plugins to leverage their code generators and automated, inferred tasks. + +\`\`\` +# Add plugin +npx nx add @nx/react + +# Use code generator +npx nx generate @nx/react:app demo + +# Run development server +npx nx serve demo + +# View project details +npx nx show project demo --web +\`\`\` + +Run \`npx nx list\` to get a list of available plugins and whether they have generators. Then run \`npx nx list \` to see what generators are available. + +Learn more about [code generators](https://nx.dev/features/generate-code) and [inferred tasks](https://nx.dev/concepts/inferred-tasks) in the docs. + +## Running tasks + +To execute tasks with Nx use the following syntax: + +\`\`\` +npx nx <...options> +\`\`\` + +You can also run multiple targets: + +\`\`\` +npx nx run-many -t +\`\`\` + +..or add \`-p\` to filter specific projects + +\`\`\` +npx nx run-many -t -p +\`\`\` + +Targets can be defined in the \`package.json\` or \`projects.json\`. Learn more [in the docs](https://nx.dev/features/run-tasks). + +## Set up CI! + +Nx comes with local caching already built-in (check your \`nx.json\`). On CI you might want to go a step further. + +- [Set up remote caching](https://nx.dev/features/share-your-cache) +- [Set up task distribution across multiple machines](https://nx.dev/nx-cloud/features/distribute-task-execution) +- [Learn more how to setup CI](https://nx.dev/recipes/ci) + +## Explore the project graph + +Run \`npx nx graph\` to show the graph of the workspace. +It will show tasks that you can run with Nx. + +- [Learn more about Exploring the Project Graph](https://nx.dev/core-features/explore-graph) + +## Connect with us! + +- [Join the community](https://nx.dev/community) +- [Subscribe to the Nx Youtube Channel](https://www.youtube.com/@nxdevtools) +- [Follow us on Twitter](https://twitter.com/nxdevtools) +" +`; + +exports[`@nx/workspace:generateWorkspaceFiles README.md should be created for RemixStandalone preset 1`] = ` +"# Proj + + + +✨ **This workspace has been generated by [Nx, Smart Monorepos · Fast CI.](https://nx.dev)** ✨ + +## Integrate with editors + +Enhance your Nx experience by installing [Nx Console](https://nx.dev/nx-console) for your favorite editor. Nx Console +provides an interactive UI to view your projects, run tasks, generate code, and more! Available for VSCode, IntelliJ and +comes with a LSP for Vim users. + +## Nx plugins and code generators + +Add Nx plugins to leverage their code generators and automated, inferred tasks. + +\`\`\` +# Add plugin +npx nx add @nx/react + +# Use code generator +npx nx generate @nx/react:app demo + +# Run development server +npx nx serve demo + +# View project details +npx nx show project demo --web +\`\`\` + +Run \`npx nx list\` to get a list of available plugins and whether they have generators. Then run \`npx nx list \` to see what generators are available. + +Learn more about [code generators](https://nx.dev/features/generate-code) and [inferred tasks](https://nx.dev/concepts/inferred-tasks) in the docs. + +## Running tasks + +To execute tasks with Nx use the following syntax: + +\`\`\` +npx nx <...options> +\`\`\` + +You can also run multiple targets: + +\`\`\` +npx nx run-many -t +\`\`\` + +..or add \`-p\` to filter specific projects + +\`\`\` +npx nx run-many -t -p +\`\`\` + +Targets can be defined in the \`package.json\` or \`projects.json\`. Learn more [in the docs](https://nx.dev/features/run-tasks). + +## Set up CI! + +Nx comes with local caching already built-in (check your \`nx.json\`). On CI you might want to go a step further. + +- [Set up remote caching](https://nx.dev/features/share-your-cache) +- [Set up task distribution across multiple machines](https://nx.dev/nx-cloud/features/distribute-task-execution) +- [Learn more how to setup CI](https://nx.dev/recipes/ci) + +## Explore the project graph + +Run \`npx nx graph\` to show the graph of the workspace. +It will show tasks that you can run with Nx. + +- [Learn more about Exploring the Project Graph](https://nx.dev/core-features/explore-graph) + +## Connect with us! + +- [Join the community](https://nx.dev/community) +- [Subscribe to the Nx Youtube Channel](https://www.youtube.com/@nxdevtools) +- [Follow us on Twitter](https://twitter.com/nxdevtools) +" +`; + exports[`@nx/workspace:generateWorkspaceFiles README.md should be created for TS preset 1`] = ` "# Proj diff --git a/packages/workspace/src/generators/new/generate-preset.ts b/packages/workspace/src/generators/new/generate-preset.ts index 81d865a40c88c..e2a1457355109 100644 --- a/packages/workspace/src/generators/new/generate-preset.ts +++ b/packages/workspace/src/generators/new/generate-preset.ts @@ -122,6 +122,10 @@ function getPresetDependencies({ case Preset.NextJsStandalone: return { dependencies: { '@nx/next': nxVersion }, dev: {} }; + case Preset.RemixStandalone: + case Preset.RemixMonorepo: + return { dependencies: { '@nx/remix': nxVersion }, dev: {} }; + case Preset.VueMonorepo: case Preset.VueStandalone: return { diff --git a/packages/workspace/src/generators/new/generate-workspace-files.ts b/packages/workspace/src/generators/new/generate-workspace-files.ts index 916001092f8e1..566fa4a7d471b 100644 --- a/packages/workspace/src/generators/new/generate-workspace-files.ts +++ b/packages/workspace/src/generators/new/generate-workspace-files.ts @@ -107,6 +107,7 @@ function createFiles(tree: Tree, options: NormalizedSchema) { options.preset === Preset.NuxtStandalone || options.preset === Preset.NodeStandalone || options.preset === Preset.NextJsStandalone || + options.preset === Preset.RemixStandalone || options.preset === Preset.TsStandalone ? './files-root-app' : options.preset === Preset.NPM diff --git a/packages/workspace/src/generators/preset/preset.ts b/packages/workspace/src/generators/preset/preset.ts index 1b2ee6c5232f0..df573568a0e8b 100644 --- a/packages/workspace/src/generators/preset/preset.ts +++ b/packages/workspace/src/generators/preset/preset.ts @@ -21,9 +21,6 @@ async function createPreset(tree: Tree, options: Schema) { process.env.NX_ADD_PLUGINS !== 'false' && nxJson.useInferencePlugins !== false; - console.log('Add plugin', addPlugin); - console.log(options.preset, Preset.ReactNative); - if (options.preset === Preset.Apps) { return; } else if (options.preset === Preset.AngularMonorepo) { @@ -93,6 +90,33 @@ async function createPreset(tree: Tree, options: Schema) { unitTestRunner: options.bundler === 'vite' ? 'vitest' : 'jest', addPlugin, }); + } else if (options.preset === Preset.RemixMonorepo) { + const { applicationGenerator: remixApplicationGenerator } = require('@nx' + + '/remix/generators'); + + return remixApplicationGenerator(tree, { + name: options.name, + directory: join('apps', options.name), + projectNameAndRootFormat: 'as-provided', + linter: options.linter, + e2eTestRunner: options.e2eTestRunner ?? 'cypress', + unitTestRunner: 'vitest', + addPlugin, + }); + } else if (options.preset === Preset.RemixStandalone) { + const { applicationGenerator: remixApplicationGenerator } = require('@nx' + + '/remix/generators'); + + return remixApplicationGenerator(tree, { + name: options.name, + directory: '.', + projectNameAndRootFormat: 'as-provided', + linter: options.linter, + e2eTestRunner: options.e2eTestRunner ?? 'cypress', + rootProject: true, + unitTestRunner: 'vitest', + addPlugin, + }); } else if (options.preset === Preset.VueMonorepo) { const { applicationGenerator: vueApplicationGenerator } = require('@nx' + '/vue'); diff --git a/packages/workspace/src/generators/utils/presets.ts b/packages/workspace/src/generators/utils/presets.ts index be82ad7a1d9c6..4139c3d505957 100644 --- a/packages/workspace/src/generators/utils/presets.ts +++ b/packages/workspace/src/generators/utils/presets.ts @@ -15,6 +15,8 @@ export enum Preset { ReactMonorepo = 'react-monorepo', ReactStandalone = 'react-standalone', NextJsStandalone = 'nextjs-standalone', + RemixMonorepo = 'remix-monorepo', + RemixStandalone = 'remix-standalone', ReactNative = 'react-native', VueMonorepo = 'vue-monorepo', VueStandalone = 'vue-standalone',