diff --git a/packages/angular/src/generators/application/lib/normalize-options.ts b/packages/angular/src/generators/application/lib/normalize-options.ts index 48726a12e3..3665412c51 100644 --- a/packages/angular/src/generators/application/lib/normalize-options.ts +++ b/packages/angular/src/generators/application/lib/normalize-options.ts @@ -11,7 +11,7 @@ import { E2eTestRunner, UnitTestRunner } from '../../../utils/test-runners'; import { Linter } from '@nx/linter'; import { normalizeDirectory, - normalizePrefix, + normalizeNewProjectPrefix, normalizeProjectName, } from '../../utils/project'; @@ -45,7 +45,7 @@ export function normalizeOptions( ? options.tags.split(',').map((s) => s.trim()) : []; - const prefix = normalizePrefix(options.prefix, npmScope); + const prefix = normalizeNewProjectPrefix(options.prefix, npmScope, 'app'); options.standaloneConfig = options.standaloneConfig ?? standaloneAsDefault; diff --git a/packages/angular/src/generators/host/host.ts b/packages/angular/src/generators/host/host.ts index b6315aa63e..4691476791 100644 --- a/packages/angular/src/generators/host/host.ts +++ b/packages/angular/src/generators/host/host.ts @@ -64,6 +64,7 @@ export async function host(tree: Tree, options: Schema) { skipFormat: true, skipE2E, e2eProjectName: skipE2E ? undefined : `${appName}-e2e`, + prefix: options.prefix, }); let installTasks = [appInstallTask]; diff --git a/packages/angular/src/generators/library/lib/normalize-options.ts b/packages/angular/src/generators/library/lib/normalize-options.ts index 4b59d1e8d9..40b1d023f8 100644 --- a/packages/angular/src/generators/library/lib/normalize-options.ts +++ b/packages/angular/src/generators/library/lib/normalize-options.ts @@ -10,7 +10,7 @@ import { Schema } from '../schema'; import { NormalizedSchema } from './normalized-schema'; import { Linter } from '@nx/linter'; import { UnitTestRunner } from '../../../utils/test-runners'; -import { normalizePrefix } from '../../utils/project'; +import { normalizeNewProjectPrefix } from '../../utils/project'; export function normalizeOptions(host: Tree, schema: Schema): NormalizedSchema { // Create a schema with populated default values @@ -55,7 +55,7 @@ export function normalizeOptions(host: Tree, schema: Schema): NormalizedSchema { ? options.tags.split(',').map((s) => s.trim()) : []; const modulePath = `${projectRoot}/src/lib/${fileName}.module.ts`; - const prefix = normalizePrefix(options.prefix, npmScope); + const prefix = normalizeNewProjectPrefix(options.prefix, npmScope, 'lib'); options.standaloneConfig = options.standaloneConfig ?? standaloneAsDefault; diff --git a/packages/angular/src/generators/remote/remote.ts b/packages/angular/src/generators/remote/remote.ts index fa8dce2d04..8754fb98f3 100644 --- a/packages/angular/src/generators/remote/remote.ts +++ b/packages/angular/src/generators/remote/remote.ts @@ -57,6 +57,7 @@ export async function remote(tree: Tree, options: Schema) { skipE2E, e2eProjectName: skipE2E ? undefined : `${appName}-e2e`, standalone: options.standalone, + prefix: options.prefix, }); let installTasks = [appInstallTask]; diff --git a/packages/angular/src/generators/setup-mf/lib/add-remote-entry.ts b/packages/angular/src/generators/setup-mf/lib/add-remote-entry.ts index ada4b6b8ea..727e73b54d 100644 --- a/packages/angular/src/generators/setup-mf/lib/add-remote-entry.ts +++ b/packages/angular/src/generators/setup-mf/lib/add-remote-entry.ts @@ -1,12 +1,6 @@ import type { Tree } from '@nx/devkit'; -import { - generateFiles, - joinPathFragments, - readNxJson, - readProjectConfiguration, -} from '@nx/devkit'; +import { generateFiles, joinPathFragments } from '@nx/devkit'; import { addRoute } from '../../../utils/nx-devkit/route-utils'; -import type { AngularProjectConfiguration } from '../../../utils/types'; import type { Schema } from '../schema'; export function addRemoteEntry( @@ -14,11 +8,6 @@ export function addRemoteEntry( { appName, routing, prefix, standalone }: Schema, appRoot: string ) { - prefix = - prefix ?? - (readProjectConfiguration(tree, appName) as AngularProjectConfiguration) - ?.prefix ?? - readNxJson(tree).npmScope; generateFiles( tree, standalone diff --git a/packages/angular/src/generators/setup-mf/lib/index.ts b/packages/angular/src/generators/setup-mf/lib/index.ts index efcb4b37a3..e9e3af1955 100644 --- a/packages/angular/src/generators/setup-mf/lib/index.ts +++ b/packages/angular/src/generators/setup-mf/lib/index.ts @@ -5,6 +5,7 @@ export * from './change-build-target'; export * from './fix-bootstrap'; export * from './generate-config'; export * from './get-remotes-with-ports'; +export * from './normalize-options'; export * from './set-tsconfig-target'; export * from './setup-host-if-dynamic'; export * from './setup-serve-target'; diff --git a/packages/angular/src/generators/setup-mf/lib/normalize-options.ts b/packages/angular/src/generators/setup-mf/lib/normalize-options.ts new file mode 100644 index 0000000000..cafa8510d1 --- /dev/null +++ b/packages/angular/src/generators/setup-mf/lib/normalize-options.ts @@ -0,0 +1,14 @@ +import type { Tree } from '@nx/devkit'; +import { getProjectPrefix } from '../../utils/project'; +import type { NormalizedOptions, Schema } from '../schema'; + +export function normalizeOptions( + tree: Tree, + options: Schema +): NormalizedOptions { + return { + ...options, + federationType: options.federationType ?? 'static', + prefix: options.prefix ?? getProjectPrefix(tree, options.appName), + }; +} diff --git a/packages/angular/src/generators/setup-mf/lib/remove-dead-code-from-remote.ts b/packages/angular/src/generators/setup-mf/lib/remove-dead-code-from-remote.ts index d505a03d2c..a4aa35e315 100644 --- a/packages/angular/src/generators/setup-mf/lib/remove-dead-code-from-remote.ts +++ b/packages/angular/src/generators/setup-mf/lib/remove-dead-code-from-remote.ts @@ -1,8 +1,6 @@ -import { Tree } from 'nx/src/generators/tree'; +import type { Tree } from '@nx/devkit'; +import { joinPathFragments, readProjectConfiguration } from '@nx/devkit'; import { Schema } from '../schema'; -import { readProjectConfiguration } from 'nx/src/generators/utils/project-configuration'; -import { joinPathFragments } from 'nx/src/utils/path'; -import { readNxJson } from '@nx/devkit'; export function removeDeadCodeFromRemote(tree: Tree, options: Schema) { const projectName = options.appName; @@ -73,13 +71,15 @@ export class AppModule {}` } else { tree.delete(pathToAppComponent); - const prefix = options.prefix ?? readNxJson(tree).npmScope; - const remoteEntrySelector = `${prefix}-${projectName}-entry`; - const pathToIndexHtml = project.targets.build.options.index; const indexContents = tree.read(pathToIndexHtml, 'utf-8'); - - const rootSelectorRegex = new RegExp(`${prefix}-root`, 'ig'); + const rootSelectorRegex = new RegExp( + `${options.prefix || 'app'}-root`, + 'ig' + ); + const remoteEntrySelector = `${ + options.prefix || 'app' + }-${projectName}-entry`; const newIndexContents = indexContents.replace( rootSelectorRegex, remoteEntrySelector diff --git a/packages/angular/src/generators/setup-mf/schema.d.ts b/packages/angular/src/generators/setup-mf/schema.d.ts index d4c14728e6..aed4d97280 100644 --- a/packages/angular/src/generators/setup-mf/schema.d.ts +++ b/packages/angular/src/generators/setup-mf/schema.d.ts @@ -1,10 +1,12 @@ +type FederationType = 'static' | 'dynamic'; + export interface Schema { appName: string; mfType: 'host' | 'remote'; port?: number; remotes?: string[]; host?: string; - federationType?: 'static' | 'dynamic'; + federationType?: FederationType; routing?: boolean; skipFormat?: boolean; skipPackageJson?: boolean; @@ -13,3 +15,8 @@ export interface Schema { standalone?: boolean; skipE2E?: boolean; } + +export interface NormalizedOptions extends Schema { + federationType: FederationType; + prefix: string | undefined; +} diff --git a/packages/angular/src/generators/setup-mf/setup-mf.ts b/packages/angular/src/generators/setup-mf/setup-mf.ts index ef5cdfe8cb..d0ad635f12 100644 --- a/packages/angular/src/generators/setup-mf/setup-mf.ts +++ b/packages/angular/src/generators/setup-mf/setup-mf.ts @@ -14,6 +14,7 @@ import { fixBootstrap, generateWebpackConfig, getRemotesWithPorts, + normalizeOptions, removeDeadCodeFromRemote, setupHostIfDynamic, setupServeTarget, @@ -24,17 +25,16 @@ import { getInstalledAngularVersionInfo } from '../utils/version-utils'; import { nxVersion } from '../../utils/versions'; import { lt } from 'semver'; -export async function setupMf(tree: Tree, options: Schema) { +export async function setupMf(tree: Tree, rawOptions: Schema) { const installedAngularInfo = getInstalledAngularVersionInfo(tree); - - if (lt(installedAngularInfo.version, '14.1.0') && options.standalone) { + if (lt(installedAngularInfo.version, '14.1.0') && rawOptions.standalone) { throw new Error( `The --standalone flag is not supported in your current version of Angular (${installedAngularInfo.version}). Please update to a version of Angular that supports Standalone Components (>= 14.1.0).` ); } - const projectConfig = readProjectConfiguration(tree, options.appName); - options.federationType = options.federationType ?? 'static'; + const options = normalizeOptions(tree, rawOptions); + const projectConfig = readProjectConfiguration(tree, options.appName); if (options.mfType === 'host') { setupHostIfDynamic(tree, options); diff --git a/packages/angular/src/generators/utils/project.ts b/packages/angular/src/generators/utils/project.ts index ae3c51401e..6ea32aa764 100644 --- a/packages/angular/src/generators/utils/project.ts +++ b/packages/angular/src/generators/utils/project.ts @@ -1,4 +1,6 @@ -import { names } from '@nx/devkit'; +import type { Tree } from '@nx/devkit'; +import { names, readNxJson, readProjectConfiguration } from '@nx/devkit'; +import type { AngularProjectConfiguration } from '../../utils/types'; export function normalizeDirectory( appName: string, @@ -16,19 +18,27 @@ export function normalizeProjectName( return normalizeDirectory(appName, directoryName).replace(/\//g, '-'); } -export function normalizePrefix( +export function normalizeNewProjectPrefix( prefix: string | undefined, - npmScope: string | undefined + npmScope: string | undefined, + fallbackPrefix: string ): string { - if (prefix) { - return prefix; - } - // Prefix needs to be a valid html selector, if npmScope it's not valid, we don't default // to it and let it fall through to the Angular schematic to handle it // https://github.com/angular/angular-cli/blob/1c634cd327e5a850553b258aa2d5e6a6b2c75c65/packages/schematics/angular/component/index.ts#L130 const htmlSelectorRegex = /^[a-zA-Z][.0-9a-zA-Z]*(:?-[a-zA-Z][.0-9a-zA-Z]*)*$/; + + if (prefix) { + if (!htmlSelectorRegex.test(prefix)) { + throw new Error( + 'The provided "prefix" is invalid. The prefix must start with a letter, and must contain only alphanumeric characters or dashes. When adding a dash the segment after the dash must also start with a letter.' + ); + } + + return prefix; + } + if (npmScope && !htmlSelectorRegex.test(npmScope)) { throw new Error(`The "--prefix" option was not provided, therefore attempted to use the "npmScope" defined in "nx.json" to set the application's selector prefix, but it is invalid. @@ -41,5 +51,15 @@ If you encountered this error when creating a new Nx Workspace, the workspace na Valid selector prefixes must start with a letter, and must contain only alphanumeric characters or dashes. When adding a dash the segment after the dash must also start with a letter.`); } - return npmScope || 'app'; + return npmScope || fallbackPrefix; +} + +export function getProjectPrefix( + tree: Tree, + project: string +): string | undefined { + return ( + (readProjectConfiguration(tree, project) as AngularProjectConfiguration) + .prefix ?? readNxJson(tree).npmScope + ); }