diff --git a/packages/angular/src/generators/remote/remote.spec.ts b/packages/angular/src/generators/remote/remote.spec.ts index 1c2458d0b0fa4..6d66de8dd5aa2 100644 --- a/packages/angular/src/generators/remote/remote.spec.ts +++ b/packages/angular/src/generators/remote/remote.spec.ts @@ -443,4 +443,19 @@ describe('MF Remote App Generator', () => { const packageJson = readJson(tree, 'package.json'); expect(packageJson).toEqual(initialPackageJson); }); + + it('should error when an invalid remote name is passed to the remote generator', async () => { + const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' }); + + await expect( + generateTestRemoteApplication(tree, { + directory: 'test/my-remote', + }) + ).rejects.toMatchInlineSnapshot(` + [Error: Invalid remote name: my-remote. Remote project names must: + - Start with a letter, dollar sign ($) or underscore (_) + - Followed by any valid character (letters, digits, underscores, or dollar signs) + The regular expression used is /^[a-zA-Z_$][a-zA-Z_$0-9]*$/.] + `); + }); }); diff --git a/packages/angular/src/generators/remote/remote.ts b/packages/angular/src/generators/remote/remote.ts index b400a1be0fb2e..723f4b4143e0f 100644 --- a/packages/angular/src/generators/remote/remote.ts +++ b/packages/angular/src/generators/remote/remote.ts @@ -3,6 +3,7 @@ import { formatFiles, getProjects, runTasksInSerial, + stripIndents, Tree, } from '@nx/devkit'; import { @@ -39,9 +40,14 @@ export async function remote(tree: Tree, schema: Schema) { directory: options.directory, }); - if (remoteProjectName.includes('-')) { + const REMOTE_NAME_REGEX = '^[a-zA-Z_$][a-zA-Z_$0-9]*$'; + const remoteNameRegex = new RegExp(REMOTE_NAME_REGEX); + if (!remoteNameRegex.test(remoteProjectName)) { throw new Error( - `Remote projects cannot contain '-' in their name. Invalid remote name: ${remoteProjectName}` + stripIndents`Invalid remote name: ${remoteProjectName}. Remote project names must: + - Start with a letter, dollar sign ($) or underscore (_) + - Followed by any valid character (letters, digits, underscores, or dollar signs) + The regular expression used is ${REMOTE_NAME_REGEX}.` ); } const port = options.port ?? findNextAvailablePort(tree); diff --git a/packages/react/src/generators/remote/__snapshots__/remote.rspack.spec.ts.snap b/packages/react/src/generators/remote/__snapshots__/remote.rspack.spec.ts.snap new file mode 100644 index 0000000000000..31c7933533f6d --- /dev/null +++ b/packages/react/src/generators/remote/__snapshots__/remote.rspack.spec.ts.snap @@ -0,0 +1,179 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`remote generator bundler=rspack should create the remote with the correct config files 1`] = ` +"const { composePlugins, withNx, withReact } = require('@nx/rspack'); +const { withModuleFederation } = require('@nx/rspack/module-federation'); + +const baseConfig = require('./module-federation.config'); + +const config = { + ...baseConfig, +}; + +// Nx plugins for rspack to build config object from Nx options and context. +/** + * DTS Plugin is disabled in Nx Workspaces as Nx already provides Typing support Module Federation + * The DTS Plugin can be enabled by setting dts: true + * Learn more about the DTS Plugin here: https://module-federation.io/configure/dts.html + */ +module.exports = composePlugins(withNx(), withReact(), withModuleFederation(config, { dts: false })); +" +`; + +exports[`remote generator bundler=rspack should create the remote with the correct config files 2`] = `"module.exports = require('./rspack.config');"`; + +exports[`remote generator bundler=rspack should create the remote with the correct config files 3`] = ` +"module.exports = { + name: 'test', + + exposes: { + './Module': './src/remote-entry.ts', + }, +}; +" +`; + +exports[`remote generator bundler=rspack should create the remote with the correct config files when --js=true 1`] = ` +"const { composePlugins, withNx, withReact } = require('@nx/rspack'); +const { withModuleFederation } = require('@nx/rspack/module-federation'); + +const baseConfig = require('./module-federation.config'); + +const config = { + ...baseConfig, +}; + +// Nx plugins for rspack to build config object from Nx options and context. +/** + * DTS Plugin is disabled in Nx Workspaces as Nx already provides Typing support Module Federation + * The DTS Plugin can be enabled by setting dts: true + * Learn more about the DTS Plugin here: https://module-federation.io/configure/dts.html + */ +module.exports = composePlugins(withNx(), withReact(), withModuleFederation(config, { dts: false })); +" +`; + +exports[`remote generator bundler=rspack should create the remote with the correct config files when --js=true 2`] = `"module.exports = require('./rspack.config');"`; + +exports[`remote generator bundler=rspack should create the remote with the correct config files when --js=true 3`] = ` +"module.exports = { + name: 'test', + + exposes: { + './Module': './src/remote-entry.js', + }, +}; +" +`; + +exports[`remote generator bundler=rspack should create the remote with the correct config files when --typescriptConfiguration=true 1`] = ` +"import { composePlugins, withNx, withReact } from '@nx/rspack'; +import { withModuleFederation } from '@nx/rspack/module-federation'; + +import baseConfig from './module-federation.config'; + +const config = { + ...baseConfig, +}; + +// Nx plugins for rspack to build config object from Nx options and context. +/** + * DTS Plugin is disabled in Nx Workspaces as Nx already provides Typing support Module Federation + * The DTS Plugin can be enabled by setting dts: true + * Learn more about the DTS Plugin here: https://module-federation.io/configure/dts.html + */ +export default composePlugins( + withNx(), + withReact(), + withModuleFederation(config, { dts: false }) +); +" +`; + +exports[`remote generator bundler=rspack should create the remote with the correct config files when --typescriptConfiguration=true 2`] = ` +"export default require('./rspack.config'); +" +`; + +exports[`remote generator bundler=rspack should create the remote with the correct config files when --typescriptConfiguration=true 3`] = ` +"import { ModuleFederationConfig } from '@nx/rspack/module-federation'; + +const config: ModuleFederationConfig = { + name: 'test', + + exposes: { + './Module': './src/remote-entry.ts', + }, +}; + +export default config; +" +`; + +exports[`remote generator bundler=rspack should generate correct remote with config files when using --ssr 1`] = ` +"const {composePlugins, withNx, withReact} = require('@nx/rspack'); +const {withModuleFederationForSSR} = require('@nx/rspack/module-federation'); + +const baseConfig = require("./module-federation.server.config"); + +const defaultConfig = { + ...baseConfig, +}; + +// Nx plugins for rspack to build config object from Nx options and context. +/** + * DTS Plugin is disabled in Nx Workspaces as Nx already provides Typing support Module Federation + * The DTS Plugin can be enabled by setting dts: true + * Learn more about the DTS Plugin here: https://module-federation.io/configure/dts.html + */ +module.exports = composePlugins(withNx(), withReact({ssr: true}), withModuleFederationForSSR(defaultConfig, { dts: false })); +" +`; + +exports[`remote generator bundler=rspack should generate correct remote with config files when using --ssr 2`] = ` +"module.exports = { + name: 'test', + exposes: { + './Module': './src/remote-entry.ts', + }, +}; +" +`; + +exports[`remote generator bundler=rspack should generate correct remote with config files when using --ssr and --typescriptConfiguration=true 1`] = ` +"import { composePlugins, withNx, withReact } from '@nx/rspack'; +import { withModuleFederationForSSR } from '@nx/rspack/module-federation'; + +import baseConfig from './module-federation.server.config'; + +const defaultConfig = { + ...baseConfig, +}; + +// Nx plugins for rspack to build config object from Nx options and context. +/** + * DTS Plugin is disabled in Nx Workspaces as Nx already provides Typing support Module Federation + * The DTS Plugin can be enabled by setting dts: true + * Learn more about the DTS Plugin here: https://module-federation.io/configure/dts.html + */ +export default composePlugins( + withNx(), + withReact({ ssr: true }), + withModuleFederationForSSR(defaultConfig, { dts: false }) +); +" +`; + +exports[`remote generator bundler=rspack should generate correct remote with config files when using --ssr and --typescriptConfiguration=true 2`] = ` +"import { ModuleFederationConfig } from '@nx/rspack/module-federation'; + +const config: ModuleFederationConfig = { + name: 'test', + exposes: { + './Module': './src/remote-entry.ts', + }, +}; + +export default config; +" +`; diff --git a/packages/react/src/generators/remote/remote.rspack.spec.ts b/packages/react/src/generators/remote/remote.rspack.spec.ts index 5d439679334ca..f7a7444dd9f03 100644 --- a/packages/react/src/generators/remote/remote.rspack.spec.ts +++ b/packages/react/src/generators/remote/remote.rspack.spec.ts @@ -86,8 +86,7 @@ jest.mock('@nx/devkit', () => { }; }); -// TODO(colum): turn these on when rspack is moved into the main repo -xdescribe('remote generator', () => { +describe('remote generator', () => { // TODO(@jaysoo): Turn this back to adding the plugin let originalEnv: string; @@ -324,5 +323,28 @@ xdescribe('remote generator', () => { }) ).rejects.toThrowError(`Invalid remote name provided: ${name}.`); }); + + it('should throw an error when an invalid remote name is used', async () => { + const tree = createTreeWithEmptyWorkspace(); + await expect( + remote(tree, { + directory: 'test/my-app', + devServerPort: 4209, + e2eTestRunner: 'cypress', + linter: Linter.EsLint, + skipFormat: false, + style: 'css', + unitTestRunner: 'jest', + ssr: true, + typescriptConfiguration: true, + bundler: 'rspack', + }) + ).rejects.toMatchInlineSnapshot(` + [Error: Invalid remote name: my-app. Remote project names must: + - Start with a letter, dollar sign ($) or underscore (_) + - Followed by any valid character (letters, digits, underscores, or dollar signs) + The regular expression used is ^[a-zA-Z_$][a-zA-Z_$0-9]*$.] + `); + }); }); }); diff --git a/packages/react/src/generators/remote/remote.ts b/packages/react/src/generators/remote/remote.ts index e22275765fb08..4d6549715c207 100644 --- a/packages/react/src/generators/remote/remote.ts +++ b/packages/react/src/generators/remote/remote.ts @@ -8,6 +8,7 @@ import { names, readProjectConfiguration, runTasksInSerial, + stripIndents, Tree, updateProjectConfiguration, } from '@nx/devkit'; @@ -25,7 +26,10 @@ import { addRemoteToDynamicHost } from './lib/add-remote-to-dynamic-host'; import { addMfEnvToTargetDefaultInputs } from '../../utils/add-mf-env-to-inputs'; import { maybeJs } from '../../utils/maybe-js'; import { isValidVariable } from '@nx/js'; -import { moduleFederationEnhancedVersion } from '../../utils/versions'; +import { + moduleFederationEnhancedVersion, + nxVersion, +} from '../../utils/versions'; import { ensureProjectName } from '@nx/devkit/src/generators/project-name-and-root-utils'; import { assertNotUsingTsSolutionSetup } from '@nx/js/src/utils/typescript/ts-solution-setup'; @@ -119,9 +123,14 @@ export async function remoteGenerator(host: Tree, schema: Schema) { } await ensureProjectName(host, options, 'application'); - if (options.projectName.includes('-')) { + const REMOTE_NAME_REGEX = '^[a-zA-Z_$][a-zA-Z_$0-9]*$'; + const remoteNameRegex = new RegExp(REMOTE_NAME_REGEX); + if (!remoteNameRegex.test(options.projectName)) { throw new Error( - `Remote projects cannot contain '-' in their name. Invalid remote name: ${options.projectName}` + stripIndents`Invalid remote name: ${options.projectName}. Remote project names must: + - Start with a letter, dollar sign ($) or underscore (_) + - Followed by any valid character (letters, digits, underscores, or dollar signs) + The regular expression used is ${REMOTE_NAME_REGEX}.` ); } const initAppTask = await applicationGenerator(host, { @@ -216,7 +225,10 @@ export async function remoteGenerator(host: Tree, schema: Schema) { const installTask = addDependenciesToPackageJson( host, {}, - { '@module-federation/enhanced': moduleFederationEnhancedVersion } + { + '@module-federation/enhanced': moduleFederationEnhancedVersion, + '@nx/web': nxVersion, + } ); tasks.push(installTask);