fix(angular): stop using npmScope as a prefix for component and directive selectors (#21828)
This commit is contained in:
parent
fe72ab4c78
commit
cfa0815385
@ -135,6 +135,12 @@ Default: `npm`
|
||||
|
||||
Package manager to use
|
||||
|
||||
### prefix
|
||||
|
||||
Type: `string`
|
||||
|
||||
Prefix to use for Angular component and directive selectors.
|
||||
|
||||
### preset
|
||||
|
||||
Type: `string`
|
||||
|
||||
@ -78,6 +78,7 @@
|
||||
"type": "string",
|
||||
"format": "html-selector",
|
||||
"description": "The prefix to apply to generated selectors.",
|
||||
"default": "app",
|
||||
"alias": "p"
|
||||
},
|
||||
"skipTests": {
|
||||
|
||||
@ -135,6 +135,12 @@ Default: `npm`
|
||||
|
||||
Package manager to use
|
||||
|
||||
### prefix
|
||||
|
||||
Type: `string`
|
||||
|
||||
Prefix to use for Angular component and directive selectors.
|
||||
|
||||
### preset
|
||||
|
||||
Type: `string`
|
||||
|
||||
@ -79,6 +79,10 @@
|
||||
"description": "Enable Server-Side Rendering (SSR) and Static Site Generation (SSG/Prerendering) for the Angular application.",
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
},
|
||||
"prefix": {
|
||||
"description": "The prefix to use for Angular component and directive selectors.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": true,
|
||||
|
||||
@ -96,6 +96,10 @@
|
||||
"description": "Enable Server-Side Rendering (SSR) and Static Site Generation (SSG/Prerendering) for the Angular application.",
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
},
|
||||
"prefix": {
|
||||
"description": "The prefix to use for Angular component and directive selectors.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": ["preset", "name"],
|
||||
|
||||
@ -346,7 +346,7 @@ describe('Angular Module Federation', () => {
|
||||
import { isEven } from '${remote}/${module}';
|
||||
|
||||
@Component({
|
||||
selector: 'proj-root',
|
||||
selector: 'app-root',
|
||||
template: \`<div class="host">{{title}}</div>\`,
|
||||
standalone: true
|
||||
})
|
||||
@ -433,7 +433,7 @@ describe('Angular Module Federation', () => {
|
||||
import { isEven } from '${childRemote}/${module}';
|
||||
|
||||
@Component({
|
||||
selector: 'proj-${remote}-entry',
|
||||
selector: 'app-${remote}-entry',
|
||||
template: \`<div class="childremote">{{title}}</div>\`,
|
||||
standalone: true
|
||||
})
|
||||
|
||||
@ -233,6 +233,7 @@ export function runCreateWorkspace(
|
||||
e2eTestRunner,
|
||||
ssr,
|
||||
framework,
|
||||
prefix,
|
||||
}: {
|
||||
preset: string;
|
||||
appName?: string;
|
||||
@ -251,6 +252,7 @@ export function runCreateWorkspace(
|
||||
e2eTestRunner?: 'cypress' | 'playwright' | 'jest' | 'detox' | 'none';
|
||||
ssr?: boolean;
|
||||
framework?: string;
|
||||
prefix?: string;
|
||||
}
|
||||
) {
|
||||
projName = name;
|
||||
@ -317,6 +319,10 @@ export function runCreateWorkspace(
|
||||
command += ` --ssr=${ssr}`;
|
||||
}
|
||||
|
||||
if (prefix !== undefined) {
|
||||
command += ` --prefix=${prefix}`;
|
||||
}
|
||||
|
||||
try {
|
||||
const create = execSync(`${command}${isVerbose() ? ' --verbose' : ''}`, {
|
||||
cwd,
|
||||
|
||||
@ -156,8 +156,7 @@ describe('create-nx-workspace', () => {
|
||||
|
||||
it('should fail correctly when preset errors', () => {
|
||||
// Using Angular Preset as the example here to test
|
||||
// It will error when npmScope is of form `<char>-<num>-<char>`
|
||||
// Due to a validation error Angular will throw.
|
||||
// It will error when prefix is not valid
|
||||
const wsName = uniq('angular-1-test');
|
||||
const appName = uniq('app');
|
||||
expect(() =>
|
||||
@ -171,6 +170,7 @@ describe('create-nx-workspace', () => {
|
||||
e2eTestRunner: 'none',
|
||||
bundler: 'webpack',
|
||||
ssr: false,
|
||||
prefix: '1-one',
|
||||
})
|
||||
).toThrow();
|
||||
});
|
||||
|
||||
@ -24,7 +24,7 @@ exports[`app --minimal should skip "nx-welcome.component.ts" file and references
|
||||
"import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'proj-root',
|
||||
selector: 'app-root',
|
||||
templateUrl: './app.component.html',
|
||||
styleUrl: './app.component.css',
|
||||
})
|
||||
@ -83,7 +83,7 @@ exports[`app --minimal should skip "nx-welcome.component.ts" file and references
|
||||
"import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'proj-root',
|
||||
selector: 'app-root',
|
||||
templateUrl: './app.component.html',
|
||||
styleUrl: './app.component.css',
|
||||
})
|
||||
@ -127,7 +127,7 @@ import { RouterModule } from '@angular/router';
|
||||
@Component({
|
||||
standalone: true,
|
||||
imports: [RouterModule],
|
||||
selector: 'proj-root',
|
||||
selector: 'app-root',
|
||||
templateUrl: './app.component.html',
|
||||
styleUrl: './app.component.css',
|
||||
})
|
||||
@ -170,7 +170,7 @@ exports[`app --minimal should skip "nx-welcome.component.ts" file and references
|
||||
@Component({
|
||||
standalone: true,
|
||||
imports: [],
|
||||
selector: 'proj-root',
|
||||
selector: 'app-root',
|
||||
templateUrl: './app.component.html',
|
||||
styleUrl: './app.component.css',
|
||||
})
|
||||
@ -210,7 +210,7 @@ exports[`app --project-name-and-root-format=derived should generate correctly wh
|
||||
{
|
||||
"$schema": "../../../node_modules/nx/schemas/project-schema.json",
|
||||
"name": "my-dir-my-app",
|
||||
"prefix": "proj",
|
||||
"prefix": "app",
|
||||
"projectType": "application",
|
||||
"root": "apps/my-dir/my-app",
|
||||
"sourceRoot": "apps/my-dir/my-app/src",
|
||||
@ -409,7 +409,7 @@ exports[`app --project-name-and-root-format=derived should generate correctly wh
|
||||
{
|
||||
"$schema": "../../node_modules/nx/schemas/project-schema.json",
|
||||
"name": "my-app",
|
||||
"prefix": "proj",
|
||||
"prefix": "app",
|
||||
"projectType": "application",
|
||||
"root": "apps/my-app",
|
||||
"sourceRoot": "apps/my-app/src",
|
||||
@ -641,7 +641,7 @@ import { NxWelcomeComponent } from './nx-welcome.component';
|
||||
@Component({
|
||||
standalone: true,
|
||||
imports: [NxWelcomeComponent, RouterModule],
|
||||
selector: 'proj-root',
|
||||
selector: 'app-root',
|
||||
templateUrl: './app.component.html',
|
||||
styleUrl: './app.component.css',
|
||||
})
|
||||
@ -709,7 +709,7 @@ import { NxWelcomeComponent } from './nx-welcome.component';
|
||||
@Component({
|
||||
standalone: true,
|
||||
imports: [NxWelcomeComponent, ],
|
||||
selector: 'proj-root',
|
||||
selector: 'app-root',
|
||||
templateUrl: './app.component.html',
|
||||
styleUrl: './app.component.css',
|
||||
})
|
||||
@ -850,7 +850,7 @@ exports[`app format files should format files 2`] = `
|
||||
"import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'proj-root',
|
||||
selector: 'app-root',
|
||||
templateUrl: './app.component.html',
|
||||
styleUrl: './app.component.css',
|
||||
})
|
||||
@ -903,7 +903,7 @@ exports[`app nested should create project configs 1`] = `
|
||||
{
|
||||
"$schema": "../../node_modules/nx/schemas/project-schema.json",
|
||||
"name": "my-app",
|
||||
"prefix": "proj",
|
||||
"prefix": "app",
|
||||
"projectType": "application",
|
||||
"root": "my-dir/my-app",
|
||||
"sourceRoot": "my-dir/my-app/src",
|
||||
@ -1015,7 +1015,7 @@ exports[`app not nested should create project configs 1`] = `
|
||||
{
|
||||
"$schema": "../node_modules/nx/schemas/project-schema.json",
|
||||
"name": "my-app",
|
||||
"prefix": "proj",
|
||||
"prefix": "app",
|
||||
"projectType": "application",
|
||||
"root": "my-app",
|
||||
"sourceRoot": "my-app/src",
|
||||
|
||||
@ -449,7 +449,7 @@ describe('app', () => {
|
||||
await generateApp(appTree, 'my-app', { directory: 'my-dir/my-app' });
|
||||
expect(
|
||||
appTree.read('my-dir/my-app/src/app/app.component.html', 'utf-8')
|
||||
).toContain('<proj-nx-welcome></proj-nx-welcome>');
|
||||
).toContain('<app-nx-welcome></app-nx-welcome>');
|
||||
});
|
||||
|
||||
it("should update `template`'s property of AppComponent with Nx content", async () => {
|
||||
@ -459,7 +459,7 @@ describe('app', () => {
|
||||
});
|
||||
expect(
|
||||
appTree.read('my-dir/my-app/src/app/app.component.ts', 'utf-8')
|
||||
).toContain('<proj-nx-welcome></proj-nx-welcome>');
|
||||
).toContain('<app-nx-welcome></app-nx-welcome>');
|
||||
});
|
||||
|
||||
it('should create Nx specific `nx-welcome.component.ts` file', async () => {
|
||||
@ -598,7 +598,7 @@ describe('app', () => {
|
||||
"@angular-eslint/component-selector": [
|
||||
"error",
|
||||
{
|
||||
"prefix": "proj",
|
||||
"prefix": "app",
|
||||
"style": "kebab-case",
|
||||
"type": "element",
|
||||
},
|
||||
@ -606,7 +606,7 @@ describe('app', () => {
|
||||
"@angular-eslint/directive-selector": [
|
||||
"error",
|
||||
{
|
||||
"prefix": "proj",
|
||||
"prefix": "app",
|
||||
"style": "camelCase",
|
||||
"type": "attribute",
|
||||
},
|
||||
|
||||
@ -5,6 +5,7 @@ import { getRelativePathToRootTsConfig, getRootTsConfigFileName } from '@nx/js';
|
||||
import { createTsConfig } from '../../utils/create-ts-config';
|
||||
import { UnitTestRunner } from '../../../utils/test-runners';
|
||||
import { getInstalledAngularVersionInfo } from '../../utils/version-utils';
|
||||
import { validateHtmlSelector } from '../../utils/selector';
|
||||
|
||||
export async function createFiles(
|
||||
tree: Tree,
|
||||
@ -15,8 +16,13 @@ export async function createFiles(
|
||||
const isUsingApplicationBuilder =
|
||||
angularMajorVersion >= 17 && options.bundler === 'esbuild';
|
||||
|
||||
const rootSelector = `${options.prefix}-root`;
|
||||
validateHtmlSelector(rootSelector);
|
||||
const nxWelcomeSelector = `${options.prefix}-nx-welcome`;
|
||||
validateHtmlSelector(nxWelcomeSelector);
|
||||
|
||||
const substitutions = {
|
||||
rootSelector: `${options.prefix}-root`,
|
||||
rootSelector,
|
||||
appName: options.name,
|
||||
inlineStyle: options.inlineStyle,
|
||||
inlineTemplate: options.inlineTemplate,
|
||||
@ -25,7 +31,7 @@ export async function createFiles(
|
||||
unitTesting: options.unitTestRunner !== UnitTestRunner.None,
|
||||
routing: options.routing,
|
||||
minimal: options.minimal,
|
||||
nxWelcomeSelector: `${options.prefix}-nx-welcome`,
|
||||
nxWelcomeSelector,
|
||||
rootTsConfig: joinPathFragments(rootOffset, getRootTsConfigFileName(tree)),
|
||||
angularMajorVersion,
|
||||
rootOffset,
|
||||
|
||||
@ -1,9 +1,7 @@
|
||||
import { joinPathFragments, type Tree } from '@nx/devkit';
|
||||
import { determineProjectNameAndRootOptions } from '@nx/devkit/src/generators/project-name-and-root-utils';
|
||||
import { Linter } from '@nx/eslint';
|
||||
import { getNpmScope } from '@nx/js/src/utils/package-json/get-npm-scope';
|
||||
import { E2eTestRunner, UnitTestRunner } from '../../../utils/test-runners';
|
||||
import { normalizeNewProjectPrefix } from '../../utils/project';
|
||||
import type { Schema } from '../schema';
|
||||
import type { NormalizedSchema } from './normalized-schema';
|
||||
import { getInstalledAngularVersionInfo } from '../../utils/version-utils';
|
||||
@ -34,12 +32,6 @@ export async function normalizeOptions(
|
||||
? options.tags.split(',').map((s) => s.trim())
|
||||
: [];
|
||||
|
||||
const prefix = normalizeNewProjectPrefix(
|
||||
options.prefix,
|
||||
getNpmScope(host),
|
||||
'app'
|
||||
);
|
||||
|
||||
let bundler = options.bundler;
|
||||
if (!bundler) {
|
||||
const { major: angularMajorVersion } = getInstalledAngularVersionInfo(host);
|
||||
@ -60,7 +52,7 @@ export async function normalizeOptions(
|
||||
strict: true,
|
||||
standalone: true,
|
||||
...options,
|
||||
prefix,
|
||||
prefix: options.prefix || 'app',
|
||||
name: appProjectName,
|
||||
appProjectRoot,
|
||||
appProjectSourceRoot: `${appProjectRoot}/src`,
|
||||
|
||||
@ -81,6 +81,7 @@
|
||||
"type": "string",
|
||||
"format": "html-selector",
|
||||
"description": "The prefix to apply to generated selectors.",
|
||||
"default": "app",
|
||||
"alias": "p"
|
||||
},
|
||||
"skipTests": {
|
||||
|
||||
@ -4,7 +4,7 @@ exports[`component Generator --flat should create the component correctly and ex
|
||||
"import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'proj-example',
|
||||
selector: 'example',
|
||||
templateUrl: './example.component.html',
|
||||
styleUrl: './example.component.css'
|
||||
})
|
||||
@ -16,7 +16,7 @@ exports[`component Generator --flat should create the component correctly and no
|
||||
"import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'proj-example',
|
||||
selector: 'example',
|
||||
templateUrl: './example.component.html',
|
||||
styleUrl: './example.component.css'
|
||||
})
|
||||
@ -41,7 +41,7 @@ exports[`component Generator --path should create the component correctly and ex
|
||||
"import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'proj-example',
|
||||
selector: 'example',
|
||||
templateUrl: './example.component.html',
|
||||
styleUrl: './example.component.css'
|
||||
})
|
||||
@ -53,7 +53,7 @@ exports[`component Generator compat should inline styles when --inline-style=tru
|
||||
"import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'proj-example',
|
||||
selector: 'example',
|
||||
templateUrl: './example.component.html',
|
||||
styles: \`\`
|
||||
})
|
||||
@ -65,7 +65,7 @@ exports[`component Generator secondary entry points should create the component
|
||||
"import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'proj-example',
|
||||
selector: 'example',
|
||||
templateUrl: './example.component.html',
|
||||
styleUrl: './example.component.css'
|
||||
})
|
||||
@ -82,7 +82,7 @@ exports[`component Generator should create component files correctly: component
|
||||
"import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'proj-example',
|
||||
selector: 'example',
|
||||
templateUrl: './example.component.html',
|
||||
styleUrl: './example.component.css',
|
||||
})
|
||||
@ -131,7 +131,7 @@ exports[`component Generator should create the component correctly and export it
|
||||
"import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'proj-example',
|
||||
selector: 'example',
|
||||
templateUrl: './example.component.html',
|
||||
styleUrl: './example.component.css'
|
||||
})
|
||||
@ -149,7 +149,7 @@ exports[`component Generator should create the component correctly and export it
|
||||
import { CommonModule } from '@angular/common';
|
||||
|
||||
@Component({
|
||||
selector: 'proj-example',
|
||||
selector: 'example',
|
||||
standalone: true,
|
||||
imports: [CommonModule],
|
||||
templateUrl: './example.component.html',
|
||||
@ -163,7 +163,7 @@ exports[`component Generator should create the component correctly and not expor
|
||||
"import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'proj-example',
|
||||
selector: 'example',
|
||||
templateUrl: './example.component.html',
|
||||
styleUrl: './example.component.css'
|
||||
})
|
||||
@ -176,7 +176,7 @@ exports[`component Generator should create the component correctly and not expor
|
||||
import { CommonModule } from '@angular/common';
|
||||
|
||||
@Component({
|
||||
selector: 'proj-example',
|
||||
selector: 'example',
|
||||
standalone: true,
|
||||
imports: [CommonModule],
|
||||
templateUrl: './example.component.html',
|
||||
@ -190,7 +190,7 @@ exports[`component Generator should create the component correctly and not expor
|
||||
"import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'proj-example',
|
||||
selector: 'example',
|
||||
templateUrl: './example.component.html',
|
||||
styleUrl: './example.component.css'
|
||||
})
|
||||
@ -202,7 +202,7 @@ exports[`component Generator should create the component correctly but not expor
|
||||
"import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'proj-example',
|
||||
selector: 'example',
|
||||
templateUrl: './example.component.html',
|
||||
styleUrl: './example.component.css'
|
||||
})
|
||||
@ -214,7 +214,7 @@ exports[`component Generator should inline styles when --inline-style=true 1`] =
|
||||
"import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'proj-example',
|
||||
selector: 'example',
|
||||
templateUrl: './example.component.html',
|
||||
styles: \`\`
|
||||
})
|
||||
@ -226,7 +226,7 @@ exports[`component Generator should inline template when --inline-template=true
|
||||
"import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'proj-example',
|
||||
selector: 'example',
|
||||
template: \`<p>example works!</p>\`,
|
||||
styleUrl: './example.component.css'
|
||||
})
|
||||
|
||||
@ -1,5 +1,12 @@
|
||||
import { addProjectConfiguration, writeJson } from '@nx/devkit';
|
||||
import {
|
||||
Tree,
|
||||
addProjectConfiguration,
|
||||
readProjectConfiguration,
|
||||
updateProjectConfiguration,
|
||||
writeJson,
|
||||
} from '@nx/devkit';
|
||||
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
|
||||
import { AngularProjectConfiguration } from '../../utils/types';
|
||||
import { componentGenerator } from './component';
|
||||
|
||||
describe('component Generator', () => {
|
||||
@ -202,7 +209,7 @@ describe('component Generator', () => {
|
||||
"import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'proj-example',
|
||||
selector: 'example',
|
||||
templateUrl: './example.component.html'
|
||||
})
|
||||
export class ExampleComponent {}
|
||||
@ -885,6 +892,108 @@ export class LibModule {}
|
||||
});
|
||||
});
|
||||
|
||||
describe('prefix & selector', () => {
|
||||
let tree: Tree;
|
||||
|
||||
beforeEach(() => {
|
||||
tree = createTreeWithEmptyWorkspace();
|
||||
addProjectConfiguration(tree, 'lib1', {
|
||||
projectType: 'library',
|
||||
root: 'lib1',
|
||||
});
|
||||
});
|
||||
|
||||
it('should use the prefix', async () => {
|
||||
await componentGenerator(tree, {
|
||||
name: 'lib1/src/lib/example/example',
|
||||
prefix: 'foo',
|
||||
nameAndDirectoryFormat: 'as-provided',
|
||||
});
|
||||
|
||||
const content = tree.read(
|
||||
'lib1/src/lib/example/example.component.ts',
|
||||
'utf-8'
|
||||
);
|
||||
expect(content).toMatch(/selector: 'foo-example'/);
|
||||
});
|
||||
|
||||
it('should error when name starts with a digit', async () => {
|
||||
await expect(
|
||||
componentGenerator(tree, {
|
||||
name: 'lib1/src/lib/1-one/1-one',
|
||||
prefix: 'foo',
|
||||
nameAndDirectoryFormat: 'as-provided',
|
||||
})
|
||||
).rejects.toThrow('The selector "foo-1-one" is invalid.');
|
||||
});
|
||||
|
||||
it('should allow dash in selector before a number', async () => {
|
||||
await componentGenerator(tree, {
|
||||
name: 'lib1/src/lib/one-1/one-1',
|
||||
prefix: 'foo',
|
||||
nameAndDirectoryFormat: 'as-provided',
|
||||
});
|
||||
|
||||
const content = tree.read(
|
||||
'lib1/src/lib/one-1/one-1.component.ts',
|
||||
'utf-8'
|
||||
);
|
||||
expect(content).toMatch(/selector: 'foo-one-1'/);
|
||||
});
|
||||
|
||||
it('should allow dash in selector before a number and without a prefix', async () => {
|
||||
await componentGenerator(tree, {
|
||||
name: 'lib1/src/lib/example/example',
|
||||
selector: 'one-1',
|
||||
nameAndDirectoryFormat: 'as-provided',
|
||||
});
|
||||
|
||||
const content = tree.read(
|
||||
'lib1/src/lib/example/example.component.ts',
|
||||
'utf-8'
|
||||
);
|
||||
expect(content).toMatch(/selector: 'one-1'/);
|
||||
});
|
||||
|
||||
it('should use the default project prefix if none is passed', async () => {
|
||||
const projectConfig = readProjectConfiguration(tree, 'lib1');
|
||||
updateProjectConfiguration(tree, 'lib1', {
|
||||
...projectConfig,
|
||||
prefix: 'bar',
|
||||
} as AngularProjectConfiguration);
|
||||
|
||||
await componentGenerator(tree, {
|
||||
name: 'lib1/src/lib/example/example',
|
||||
nameAndDirectoryFormat: 'as-provided',
|
||||
});
|
||||
|
||||
const content = tree.read(
|
||||
'lib1/src/lib/example/example.component.ts',
|
||||
'utf-8'
|
||||
);
|
||||
expect(content).toMatch(/selector: 'bar-example'/);
|
||||
});
|
||||
|
||||
it('should not use the default project prefix when supplied prefix is ""', async () => {
|
||||
const projectConfig = readProjectConfiguration(tree, 'lib1');
|
||||
updateProjectConfiguration(tree, 'lib1', {
|
||||
...projectConfig,
|
||||
prefix: '',
|
||||
} as AngularProjectConfiguration);
|
||||
|
||||
await componentGenerator(tree, {
|
||||
name: 'lib1/src/lib/example/example',
|
||||
nameAndDirectoryFormat: 'as-provided',
|
||||
});
|
||||
|
||||
const content = tree.read(
|
||||
'lib1/src/lib/example/example.component.ts',
|
||||
'utf-8'
|
||||
);
|
||||
expect(content).toMatch(/selector: 'example'/);
|
||||
});
|
||||
});
|
||||
|
||||
describe('secondary entry points', () => {
|
||||
it('should create the component correctly and export it in the entry point', async () => {
|
||||
// ARRANGE
|
||||
|
||||
@ -2,7 +2,7 @@ import type { Tree } from '@nx/devkit';
|
||||
import { names, readProjectConfiguration } from '@nx/devkit';
|
||||
import { determineArtifactNameAndDirectoryOptions } from '@nx/devkit/src/generators/artifact-name-and-directory-utils';
|
||||
import type { AngularProjectConfiguration } from '../../../utils/types';
|
||||
import { buildSelector } from '../../utils/selector';
|
||||
import { buildSelector, validateHtmlSelector } from '../../utils/selector';
|
||||
import type { NormalizedSchema, Schema } from '../schema';
|
||||
|
||||
export async function normalizeOptions(
|
||||
@ -37,8 +37,8 @@ export async function normalizeOptions(
|
||||
) as AngularProjectConfiguration;
|
||||
|
||||
const selector =
|
||||
options.selector ??
|
||||
buildSelector(tree, name, options.prefix, prefix, 'fileName');
|
||||
options.selector ?? buildSelector(name, options.prefix, prefix, 'fileName');
|
||||
validateHtmlSelector(selector);
|
||||
|
||||
return {
|
||||
...options,
|
||||
|
||||
@ -16,7 +16,7 @@ exports[`directive generator --no-standalone should generate a directive with te
|
||||
"import { Directive } from '@angular/core';
|
||||
|
||||
@Directive({
|
||||
selector: '[projTest]',
|
||||
selector: '[test]',
|
||||
})
|
||||
export class TestDirective {
|
||||
constructor() {}
|
||||
@ -52,7 +52,7 @@ exports[`directive generator --no-standalone should import the directive correct
|
||||
"import { Directive } from '@angular/core';
|
||||
|
||||
@Directive({
|
||||
selector: '[projTest]'
|
||||
selector: '[test]'
|
||||
})
|
||||
export class TestDirective {
|
||||
constructor() {}
|
||||
@ -88,7 +88,7 @@ exports[`directive generator --no-standalone should import the directive correct
|
||||
"import { Directive } from '@angular/core';
|
||||
|
||||
@Directive({
|
||||
selector: '[projTest]'
|
||||
selector: '[test]'
|
||||
})
|
||||
export class TestDirective {
|
||||
constructor() {}
|
||||
@ -124,7 +124,7 @@ exports[`directive generator should generate correctly 1`] = `
|
||||
"import { Directive } from '@angular/core';
|
||||
|
||||
@Directive({
|
||||
selector: '[projTest]',
|
||||
selector: '[test]',
|
||||
standalone: true,
|
||||
})
|
||||
export class TestDirective {
|
||||
|
||||
@ -1,5 +1,11 @@
|
||||
import { addProjectConfiguration, Tree } from '@nx/devkit';
|
||||
import {
|
||||
addProjectConfiguration,
|
||||
readProjectConfiguration,
|
||||
updateProjectConfiguration,
|
||||
type Tree,
|
||||
} from '@nx/devkit';
|
||||
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
|
||||
import type { AngularProjectConfiguration } from '../../utils/types';
|
||||
import { directiveGenerator } from './directive';
|
||||
import type { Schema } from './schema';
|
||||
|
||||
@ -164,6 +170,74 @@ describe('directive generator', () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('prefix & selector', () => {
|
||||
it('should use the prefix', async () => {
|
||||
await directiveGenerator(tree, {
|
||||
name: 'test/src/app/example/example',
|
||||
prefix: 'foo',
|
||||
nameAndDirectoryFormat: 'as-provided',
|
||||
});
|
||||
|
||||
const content = tree.read(
|
||||
'test/src/app/example/example.directive.ts',
|
||||
'utf-8'
|
||||
);
|
||||
expect(content).toMatch(/selector: '\[fooExample\]'/);
|
||||
});
|
||||
|
||||
it('should use the default project prefix if none is passed', async () => {
|
||||
const projectConfig = readProjectConfiguration(tree, 'test');
|
||||
updateProjectConfiguration(tree, 'test', {
|
||||
...projectConfig,
|
||||
prefix: 'bar',
|
||||
} as AngularProjectConfiguration);
|
||||
|
||||
await directiveGenerator(tree, {
|
||||
name: 'test/src/app/example/example',
|
||||
nameAndDirectoryFormat: 'as-provided',
|
||||
});
|
||||
|
||||
const content = tree.read(
|
||||
'test/src/app/example/example.directive.ts',
|
||||
'utf-8'
|
||||
);
|
||||
expect(content).toMatch(/selector: '\[barExample\]'/);
|
||||
});
|
||||
|
||||
it('should not use the default project prefix when supplied prefix is ""', async () => {
|
||||
const projectConfig = readProjectConfiguration(tree, 'test');
|
||||
updateProjectConfiguration(tree, 'test', {
|
||||
...projectConfig,
|
||||
prefix: '',
|
||||
} as AngularProjectConfiguration);
|
||||
|
||||
await directiveGenerator(tree, {
|
||||
name: 'test/src/app/example/example',
|
||||
nameAndDirectoryFormat: 'as-provided',
|
||||
});
|
||||
|
||||
const content = tree.read(
|
||||
'test/src/app/example/example.directive.ts',
|
||||
'utf-8'
|
||||
);
|
||||
expect(content).toMatch(/selector: '\[example\]'/);
|
||||
});
|
||||
|
||||
it('should use provided selector as is', async () => {
|
||||
await directiveGenerator(tree, {
|
||||
name: 'test/src/app/example/example',
|
||||
selector: 'mySelector',
|
||||
nameAndDirectoryFormat: 'as-provided',
|
||||
});
|
||||
|
||||
const content = tree.read(
|
||||
'test/src/app/example/example.directive.ts',
|
||||
'utf-8'
|
||||
);
|
||||
expect(content).toMatch(/selector: '\[mySelector\]'/);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function addModule(tree: Tree) {
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import type { Tree } from '@nx/devkit';
|
||||
import { names, readProjectConfiguration } from '@nx/devkit';
|
||||
import type { AngularProjectConfiguration } from '../../../utils/types';
|
||||
import { buildSelector } from '../../utils/selector';
|
||||
import { buildSelector, validateHtmlSelector } from '../../utils/selector';
|
||||
import type { NormalizedSchema, Schema } from '../schema';
|
||||
import { determineArtifactNameAndDirectoryOptions } from '@nx/devkit/src/generators/artifact-name-and-directory-utils';
|
||||
|
||||
@ -37,7 +37,8 @@ export async function normalizeOptions(
|
||||
|
||||
const selector =
|
||||
options.selector ??
|
||||
buildSelector(tree, name, options.prefix, prefix, 'propertyName');
|
||||
buildSelector(name, options.prefix, prefix, 'propertyName');
|
||||
validateHtmlSelector(selector);
|
||||
|
||||
return {
|
||||
...options,
|
||||
|
||||
@ -954,7 +954,7 @@ import { NxWelcomeComponent } from './nx-welcome.component';
|
||||
@Component({
|
||||
standalone: true,
|
||||
imports: [NxWelcomeComponent, RouterModule],
|
||||
selector: 'proj-root',
|
||||
selector: 'app-root',
|
||||
templateUrl: './app.component.html',
|
||||
styleUrl: './app.component.css',
|
||||
})
|
||||
|
||||
@ -7,7 +7,7 @@ exports[`lib --standalone should generate a library with a standalone component
|
||||
import { CommonModule } from '@angular/common';
|
||||
|
||||
@Component({
|
||||
selector: 'proj-my-lib',
|
||||
selector: 'lib-my-lib',
|
||||
standalone: true,
|
||||
imports: [CommonModule],
|
||||
templateUrl: './my-lib.component.html',
|
||||
@ -53,7 +53,7 @@ exports[`lib --standalone should generate a library with a standalone component
|
||||
import { CommonModule } from '@angular/common';
|
||||
|
||||
@Component({
|
||||
selector: 'proj-my-lib',
|
||||
selector: 'lib-my-lib',
|
||||
standalone: true,
|
||||
imports: [CommonModule],
|
||||
templateUrl: './my-lib.component.html',
|
||||
@ -105,7 +105,7 @@ exports[`lib --standalone should generate a library with a standalone component
|
||||
import { CommonModule } from '@angular/common';
|
||||
|
||||
@Component({
|
||||
selector: 'proj-my-lib',
|
||||
selector: 'lib-my-lib',
|
||||
standalone: true,
|
||||
imports: [CommonModule],
|
||||
templateUrl: './my-lib.component.html',
|
||||
@ -147,7 +147,7 @@ exports[`lib --standalone should generate a library with a standalone component
|
||||
import { CommonModule } from '@angular/common';
|
||||
|
||||
@Component({
|
||||
selector: 'proj-my-lib',
|
||||
selector: 'lib-my-lib',
|
||||
standalone: true,
|
||||
imports: [CommonModule],
|
||||
template: \`<p>my-lib works!</p>\`,
|
||||
@ -166,7 +166,7 @@ exports[`lib --standalone should generate a library with a standalone component
|
||||
import { CommonModule } from '@angular/common';
|
||||
|
||||
@Component({
|
||||
selector: 'proj-my-lib',
|
||||
selector: 'lib-my-lib',
|
||||
standalone: true,
|
||||
imports: [CommonModule],
|
||||
template: \`<p>my-lib works!</p>\`,
|
||||
@ -183,7 +183,7 @@ exports[`lib --standalone should generate a library with a standalone component
|
||||
import { CommonModule } from '@angular/common';
|
||||
|
||||
@Component({
|
||||
selector: 'proj-my-lib',
|
||||
selector: 'lib-my-lib',
|
||||
standalone: true,
|
||||
imports: [CommonModule],
|
||||
template: \`<p>my-lib works!</p>\`,
|
||||
@ -239,7 +239,7 @@ exports[`lib --standalone should generate a library with a standalone component
|
||||
import { CommonModule } from '@angular/common';
|
||||
|
||||
@Component({
|
||||
selector: 'proj-my-lib',
|
||||
selector: 'lib-my-lib',
|
||||
standalone: true,
|
||||
imports: [CommonModule],
|
||||
templateUrl: './my-lib.component.html',
|
||||
@ -353,7 +353,7 @@ exports[`lib --standalone should generate a library with a standalone component
|
||||
import { CommonModule } from '@angular/common';
|
||||
|
||||
@Component({
|
||||
selector: 'proj-my-lib',
|
||||
selector: 'lib-my-lib',
|
||||
standalone: true,
|
||||
imports: [CommonModule],
|
||||
templateUrl: './my-lib.component.html',
|
||||
@ -395,7 +395,7 @@ exports[`lib --standalone should generate a library with a standalone component
|
||||
import { CommonModule } from '@angular/common';
|
||||
|
||||
@Component({
|
||||
selector: 'proj-my-lib',
|
||||
selector: 'lib-my-lib',
|
||||
standalone: true,
|
||||
imports: [CommonModule],
|
||||
templateUrl: './my-lib.component.html',
|
||||
|
||||
@ -1,9 +1,7 @@
|
||||
import { names, Tree } from '@nx/devkit';
|
||||
import { determineProjectNameAndRootOptions } from '@nx/devkit/src/generators/project-name-and-root-utils';
|
||||
import { getNpmScope } from '@nx/js/src/utils/package-json/get-npm-scope';
|
||||
import { Linter } from '@nx/eslint';
|
||||
import { UnitTestRunner } from '../../../utils/test-runners';
|
||||
import { normalizeNewProjectPrefix } from '../../utils/project';
|
||||
import { Schema } from '../schema';
|
||||
import { NormalizedSchema } from './normalized-schema';
|
||||
|
||||
@ -52,15 +50,12 @@ export async function normalizeOptions(
|
||||
: [];
|
||||
const modulePath = `${projectRoot}/src/lib/${fileName}.module.ts`;
|
||||
|
||||
const npmScope = getNpmScope(host);
|
||||
const prefix = normalizeNewProjectPrefix(options.prefix, npmScope, 'lib');
|
||||
|
||||
const ngCliSchematicLibRoot = projectName;
|
||||
const allNormalizedOptions = {
|
||||
...options,
|
||||
linter: options.linter ?? Linter.EsLint,
|
||||
unitTestRunner: options.unitTestRunner ?? UnitTestRunner.Jest,
|
||||
prefix,
|
||||
prefix: options.prefix ?? 'lib',
|
||||
name: projectName,
|
||||
projectRoot,
|
||||
entryFile: 'index',
|
||||
|
||||
@ -652,7 +652,7 @@ describe('lib', () => {
|
||||
"error",
|
||||
{
|
||||
"type": "attribute",
|
||||
"prefix": "proj",
|
||||
"prefix": "lib",
|
||||
"style": "camelCase"
|
||||
}
|
||||
],
|
||||
@ -660,7 +660,7 @@ describe('lib', () => {
|
||||
"error",
|
||||
{
|
||||
"type": "element",
|
||||
"prefix": "proj",
|
||||
"prefix": "lib",
|
||||
"style": "kebab-case"
|
||||
}
|
||||
]
|
||||
@ -1201,7 +1201,7 @@ describe('lib', () => {
|
||||
"@angular-eslint/component-selector": [
|
||||
"error",
|
||||
{
|
||||
"prefix": "proj",
|
||||
"prefix": "lib",
|
||||
"style": "kebab-case",
|
||||
"type": "element",
|
||||
},
|
||||
@ -1209,7 +1209,7 @@ describe('lib', () => {
|
||||
"@angular-eslint/directive-selector": [
|
||||
"error",
|
||||
{
|
||||
"prefix": "proj",
|
||||
"prefix": "lib",
|
||||
"style": "camelCase",
|
||||
"type": "attribute",
|
||||
},
|
||||
@ -1261,7 +1261,7 @@ describe('lib', () => {
|
||||
"@angular-eslint/component-selector": [
|
||||
"error",
|
||||
{
|
||||
"prefix": "proj",
|
||||
"prefix": "lib",
|
||||
"style": "kebab-case",
|
||||
"type": "element",
|
||||
},
|
||||
@ -1269,7 +1269,7 @@ describe('lib', () => {
|
||||
"@angular-eslint/directive-selector": [
|
||||
"error",
|
||||
{
|
||||
"prefix": "proj",
|
||||
"prefix": "lib",
|
||||
"style": "camelCase",
|
||||
"type": "attribute",
|
||||
},
|
||||
|
||||
@ -224,8 +224,8 @@ exports[`MF Remote App Generator --ssr should generate the correct files 8`] = `
|
||||
"import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'proj-test-entry',
|
||||
template: \`<proj-nx-welcome></proj-nx-welcome>\`,
|
||||
selector: 'app-test-entry',
|
||||
template: \`<app-nx-welcome></app-nx-welcome>\`,
|
||||
})
|
||||
export class RemoteEntryComponent {}
|
||||
"
|
||||
@ -448,8 +448,8 @@ exports[`MF Remote App Generator --ssr should generate the correct files when --
|
||||
"import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'proj-test-entry',
|
||||
template: \`<proj-nx-welcome></proj-nx-welcome>\`
|
||||
selector: 'app-test-entry',
|
||||
template: \`<app-nx-welcome></app-nx-welcome>\`
|
||||
})
|
||||
export class RemoteEntryComponent {}
|
||||
"
|
||||
@ -594,8 +594,8 @@ import { NxWelcomeComponent } from './nx-welcome.component';
|
||||
@Component({
|
||||
standalone: true,
|
||||
imports: [CommonModule, NxWelcomeComponent],
|
||||
selector: 'proj-test-entry',
|
||||
template: \`<proj-nx-welcome></proj-nx-welcome>\`,
|
||||
selector: 'app-test-entry',
|
||||
template: \`<app-nx-welcome></app-nx-welcome>\`,
|
||||
})
|
||||
export class RemoteEntryComponent {}
|
||||
"
|
||||
@ -657,8 +657,8 @@ import { NxWelcomeComponent } from './nx-welcome.component';
|
||||
@Component({
|
||||
standalone: true,
|
||||
imports: [CommonModule, NxWelcomeComponent],
|
||||
selector: 'proj-test-entry',
|
||||
template: \`<proj-nx-welcome></proj-nx-welcome>\`
|
||||
selector: 'app-test-entry',
|
||||
template: \`<app-nx-welcome></app-nx-welcome>\`
|
||||
})
|
||||
export class RemoteEntryComponent {}
|
||||
"
|
||||
|
||||
@ -275,7 +275,7 @@ describe('MF Remote App Generator', () => {
|
||||
"import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'proj-root',
|
||||
selector: 'app-root',
|
||||
template: '<router-outlet></router-outlet>'
|
||||
|
||||
})
|
||||
@ -294,11 +294,9 @@ describe('MF Remote App Generator', () => {
|
||||
});
|
||||
|
||||
// ASSERT
|
||||
expect(tree.read('test/src/index.html', 'utf-8')).not.toContain(
|
||||
'proj-root'
|
||||
);
|
||||
expect(tree.read('test/src/index.html', 'utf-8')).not.toContain('app-root');
|
||||
expect(tree.read('test/src/index.html', 'utf-8')).toContain(
|
||||
'proj-test-entry'
|
||||
'app-test-entry'
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@ -47,7 +47,7 @@ describe('convertDirectiveToScam', () => {
|
||||
import { CommonModule } from '@angular/common';
|
||||
|
||||
@Directive({
|
||||
selector: '[projExample]'
|
||||
selector: '[example]'
|
||||
})
|
||||
export class ExampleDirective {
|
||||
constructor() {}
|
||||
@ -159,7 +159,7 @@ describe('convertDirectiveToScam', () => {
|
||||
import { CommonModule } from '@angular/common';
|
||||
|
||||
@Directive({
|
||||
selector: '[projExample]'
|
||||
selector: '[example]'
|
||||
})
|
||||
export class ExampleDirective {
|
||||
constructor() {}
|
||||
@ -272,7 +272,7 @@ describe('convertDirectiveToScam', () => {
|
||||
import { CommonModule } from '@angular/common';
|
||||
|
||||
@Directive({
|
||||
selector: '[projExample]'
|
||||
selector: '[example]'
|
||||
})
|
||||
export class ExampleDirective {
|
||||
constructor() {}
|
||||
@ -332,7 +332,7 @@ describe('convertDirectiveToScam', () => {
|
||||
import { CommonModule } from '@angular/common';
|
||||
|
||||
@Directive({
|
||||
selector: '[projExample]'
|
||||
selector: '[example]'
|
||||
})
|
||||
export class ExampleDirective {
|
||||
constructor() {}
|
||||
|
||||
@ -31,7 +31,7 @@ describe('SCAM Directive Generator', () => {
|
||||
import { CommonModule } from '@angular/common';
|
||||
|
||||
@Directive({
|
||||
selector: '[projExample]'
|
||||
selector: '[example]'
|
||||
})
|
||||
export class ExampleDirective {
|
||||
constructor() {}
|
||||
@ -166,7 +166,7 @@ describe('SCAM Directive Generator', () => {
|
||||
import { CommonModule } from '@angular/common';
|
||||
|
||||
@Directive({
|
||||
selector: '[projExample]'
|
||||
selector: '[example]'
|
||||
})
|
||||
export class ExampleDirective {
|
||||
constructor() {}
|
||||
@ -211,7 +211,7 @@ describe('SCAM Directive Generator', () => {
|
||||
import { CommonModule } from '@angular/common';
|
||||
|
||||
@Directive({
|
||||
selector: '[projExample]'
|
||||
selector: '[example]'
|
||||
})
|
||||
export class ExampleDirective {
|
||||
constructor() {}
|
||||
|
||||
@ -36,7 +36,7 @@ describe('scam-to-standalone', () => {
|
||||
@Component({
|
||||
standalone: true,
|
||||
imports: [CommonModule],
|
||||
selector: 'proj-bar',
|
||||
selector: 'app-bar',
|
||||
templateUrl: './bar.component.html',
|
||||
styleUrl: './bar.component.css',
|
||||
})
|
||||
|
||||
@ -45,7 +45,7 @@ describe('convertComponentToScam', () => {
|
||||
import { CommonModule } from '@angular/common';
|
||||
|
||||
@Component({
|
||||
selector: 'proj-example',
|
||||
selector: 'example',
|
||||
templateUrl: './example.component.html',
|
||||
styleUrl: './example.component.css'
|
||||
})
|
||||
@ -155,7 +155,7 @@ describe('convertComponentToScam', () => {
|
||||
import { CommonModule } from '@angular/common';
|
||||
|
||||
@Component({
|
||||
selector: 'proj-example',
|
||||
selector: 'example',
|
||||
templateUrl: './example.component.html',
|
||||
styleUrl: './example.component.css'
|
||||
})
|
||||
@ -269,7 +269,7 @@ describe('convertComponentToScam', () => {
|
||||
import { CommonModule } from '@angular/common';
|
||||
|
||||
@Component({
|
||||
selector: 'proj-example',
|
||||
selector: 'example',
|
||||
templateUrl: './example.random.html',
|
||||
styleUrl: './example.random.css'
|
||||
})
|
||||
@ -384,7 +384,7 @@ describe('convertComponentToScam', () => {
|
||||
import { CommonModule } from '@angular/common';
|
||||
|
||||
@Component({
|
||||
selector: 'proj-example',
|
||||
selector: 'example',
|
||||
templateUrl: './example.component.html',
|
||||
styleUrl: './example.component.css'
|
||||
})
|
||||
@ -444,7 +444,7 @@ describe('convertComponentToScam', () => {
|
||||
import { CommonModule } from '@angular/common';
|
||||
|
||||
@Component({
|
||||
selector: 'proj-example',
|
||||
selector: 'example',
|
||||
templateUrl: './example.component.html',
|
||||
styleUrl: './example.component.css'
|
||||
})
|
||||
|
||||
@ -30,7 +30,7 @@ describe('SCAM Generator', () => {
|
||||
import { CommonModule } from '@angular/common';
|
||||
|
||||
@Component({
|
||||
selector: 'proj-example',
|
||||
selector: 'example',
|
||||
templateUrl: './example.component.html',
|
||||
styleUrl: './example.component.css'
|
||||
})
|
||||
@ -163,7 +163,7 @@ describe('SCAM Generator', () => {
|
||||
import { CommonModule } from '@angular/common';
|
||||
|
||||
@Component({
|
||||
selector: 'proj-example',
|
||||
selector: 'example',
|
||||
templateUrl: './example.component.html',
|
||||
styleUrl: './example.component.css'
|
||||
})
|
||||
@ -207,7 +207,7 @@ describe('SCAM Generator', () => {
|
||||
import { CommonModule } from '@angular/common';
|
||||
|
||||
@Component({
|
||||
selector: 'proj-example',
|
||||
selector: 'example',
|
||||
templateUrl: './example.component.html',
|
||||
styleUrl: './example.component.css'
|
||||
})
|
||||
|
||||
@ -262,8 +262,8 @@ exports[`Init MF should generate the remote entry component correctly when prefi
|
||||
"import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'proj-remote1-entry',
|
||||
template: \`<proj-nx-welcome></proj-nx-welcome>\`
|
||||
selector: 'app-remote1-entry',
|
||||
template: \`<app-nx-welcome></app-nx-welcome>\`
|
||||
})
|
||||
export class RemoteEntryComponent {}
|
||||
"
|
||||
|
||||
@ -10,7 +10,7 @@ export function normalizeOptions(
|
||||
...options,
|
||||
typescriptConfiguration: options.typescriptConfiguration ?? true,
|
||||
federationType: options.federationType ?? 'static',
|
||||
prefix: options.prefix ?? getProjectPrefix(tree, options.appName),
|
||||
prefix: options.prefix ?? getProjectPrefix(tree, options.appName) ?? 'app',
|
||||
standalone: options.standalone ?? true,
|
||||
};
|
||||
}
|
||||
|
||||
@ -1,50 +1,12 @@
|
||||
import type { Tree } from '@nx/devkit';
|
||||
import { readProjectConfiguration } from '@nx/devkit';
|
||||
import type { AngularProjectConfiguration } from '../../utils/types';
|
||||
import { getNpmScope } from '@nx/js/src/utils/package-json/get-npm-scope';
|
||||
|
||||
export function normalizeNewProjectPrefix(
|
||||
prefix: string | undefined,
|
||||
npmScope: string | undefined,
|
||||
fallbackPrefix: string
|
||||
): string {
|
||||
// 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/aa9f0528f174e856a4923cb24861fdf6e6f96b48/packages/schematics/angular/component/index.ts#L64
|
||||
const htmlSelectorRegex =
|
||||
/^[a-zA-Z][.0-9a-zA-Z]*((:?-[0-9]+)*|(:?-[a-zA-Z][.0-9a-zA-Z]*(:?-[0-9]+)*)*)$/;
|
||||
|
||||
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.'
|
||||
);
|
||||
}
|
||||
|
||||
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.
|
||||
|
||||
There are two options that can be followed to resolve this issue:
|
||||
- Pass a valid "--prefix" option.
|
||||
- Update the "npmScope" in "nx.json" (Note: this can be an involved process, as other libraries and applications may need to be updated to match the new scope).
|
||||
|
||||
If you encountered this error when creating a new Nx Workspace, the workspace name or "npmScope" is invalid to use as the selector prefix for the application being generated.
|
||||
|
||||
Valid selector prefixes must start with a letter, and must contain only alphanumeric characters or dashes.`);
|
||||
}
|
||||
|
||||
return npmScope || fallbackPrefix;
|
||||
}
|
||||
|
||||
export function getProjectPrefix(
|
||||
tree: Tree,
|
||||
project: string
|
||||
): string | undefined {
|
||||
return (
|
||||
(readProjectConfiguration(tree, project) as AngularProjectConfiguration)
|
||||
.prefix ?? getNpmScope(tree)
|
||||
);
|
||||
readProjectConfiguration(tree, project) as AngularProjectConfiguration
|
||||
).prefix;
|
||||
}
|
||||
|
||||
@ -1,20 +1,26 @@
|
||||
import type { Tree } from '@nx/devkit';
|
||||
import { names } from '@nx/devkit';
|
||||
|
||||
import { getNpmScope } from '@nx/js/src/utils/package-json/get-npm-scope';
|
||||
|
||||
export function buildSelector(
|
||||
tree: Tree,
|
||||
name: string,
|
||||
prefix: string | undefined,
|
||||
projectPrefix: string | undefined,
|
||||
casing: keyof Pick<ReturnType<typeof names>, 'fileName' | 'propertyName'>
|
||||
): string {
|
||||
let selector = name;
|
||||
prefix ??= projectPrefix ?? getNpmScope(tree);
|
||||
prefix ??= projectPrefix;
|
||||
if (prefix) {
|
||||
selector = `${prefix}-${selector}`;
|
||||
}
|
||||
|
||||
return names(selector)[casing];
|
||||
}
|
||||
|
||||
// https://github.com/angular/angular-cli/blob/main/packages/schematics/angular/utility/validation.ts#L11-L14
|
||||
const htmlSelectorRegex =
|
||||
/^[a-zA-Z][.0-9a-zA-Z]*((:?-[0-9]+)*|(:?-[a-zA-Z][.0-9a-zA-Z]*(:?-[0-9]+)*)*)$/;
|
||||
|
||||
export function validateHtmlSelector(selector: string): void {
|
||||
if (selector && !htmlSelectorRegex.test(selector)) {
|
||||
throw new Error(`The selector "${selector}" is invalid.`);
|
||||
}
|
||||
}
|
||||
|
||||
@ -61,6 +61,7 @@ interface AngularArguments extends BaseArguments {
|
||||
e2eTestRunner: 'none' | 'cypress' | 'playwright';
|
||||
bundler: 'webpack' | 'esbuild';
|
||||
ssr: boolean;
|
||||
prefix: string;
|
||||
}
|
||||
|
||||
interface VueArguments extends BaseArguments {
|
||||
@ -175,6 +176,10 @@ export const commandsObject: yargs.Argv<Arguments> = yargs
|
||||
.option('ssr', {
|
||||
describe: chalk.dim`Enable Server-Side Rendering (SSR) and Static Site Generation (SSG/Prerendering) for the Angular application`,
|
||||
type: 'boolean',
|
||||
})
|
||||
.option('prefix', {
|
||||
describe: chalk.dim`Prefix to use for Angular component and directive selectors.`,
|
||||
type: 'string',
|
||||
}),
|
||||
withNxCloud,
|
||||
withAllPrompts,
|
||||
@ -717,6 +722,25 @@ async function determineAngularOptions(
|
||||
|
||||
const standaloneApi = parsedArgs.standaloneApi;
|
||||
const routing = parsedArgs.routing;
|
||||
const prefix = parsedArgs.prefix;
|
||||
|
||||
if (prefix) {
|
||||
// https://github.com/angular/angular-cli/blob/main/packages/schematics/angular/utility/validation.ts#L11-L14
|
||||
const htmlSelectorRegex =
|
||||
/^[a-zA-Z][.0-9a-zA-Z]*((:?-[0-9]+)*|(:?-[a-zA-Z][.0-9a-zA-Z]*(:?-[0-9]+)*)*)$/;
|
||||
|
||||
// validate whether component/directive selectors will be valid with the provided prefix
|
||||
if (!htmlSelectorRegex.test(`${prefix}-placeholder`)) {
|
||||
output.error({
|
||||
title: `Failed to create a workspace.`,
|
||||
bodyLines: [
|
||||
`The provided "${prefix}" prefix is invalid. It must be a valid HTML selector.`,
|
||||
],
|
||||
});
|
||||
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
if (parsedArgs.preset && parsedArgs.preset !== Preset.Angular) {
|
||||
preset = parsedArgs.preset;
|
||||
@ -817,6 +841,7 @@ async function determineAngularOptions(
|
||||
e2eTestRunner,
|
||||
bundler,
|
||||
ssr,
|
||||
prefix,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -81,6 +81,7 @@ export function generatePreset(host: Tree, opts: NormalizedSchema) {
|
||||
? `--e2eTestRunner=${opts.e2eTestRunner}`
|
||||
: null,
|
||||
opts.ssr ? `--ssr` : null,
|
||||
opts.prefix !== undefined ? `--prefix=${opts.prefix}` : null,
|
||||
].filter((e) => !!e);
|
||||
}
|
||||
}
|
||||
|
||||
@ -34,6 +34,7 @@ interface Schema {
|
||||
packageManager?: PackageManager;
|
||||
e2eTestRunner?: 'cypress' | 'playwright' | 'detox' | 'jest' | 'none';
|
||||
ssr?: boolean;
|
||||
prefix?: string;
|
||||
}
|
||||
|
||||
export interface NormalizedSchema extends Schema {
|
||||
|
||||
@ -82,6 +82,10 @@
|
||||
"description": "Enable Server-Side Rendering (SSR) and Static Site Generation (SSG/Prerendering) for the Angular application.",
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
},
|
||||
"prefix": {
|
||||
"description": "The prefix to use for Angular component and directive selectors.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": true
|
||||
|
||||
@ -34,6 +34,7 @@ async function createPreset(tree: Tree, options: Schema) {
|
||||
e2eTestRunner: options.e2eTestRunner ?? 'cypress',
|
||||
bundler: options.bundler,
|
||||
ssr: options.ssr,
|
||||
prefix: options.prefix,
|
||||
});
|
||||
} else if (options.preset === Preset.AngularStandalone) {
|
||||
const {
|
||||
@ -52,6 +53,7 @@ async function createPreset(tree: Tree, options: Schema) {
|
||||
e2eTestRunner: options.e2eTestRunner ?? 'cypress',
|
||||
bundler: options.bundler,
|
||||
ssr: options.ssr,
|
||||
prefix: options.prefix,
|
||||
});
|
||||
} else if (options.preset === Preset.ReactMonorepo) {
|
||||
const { applicationGenerator: reactApplicationGenerator } = require('@nx' +
|
||||
|
||||
@ -18,4 +18,5 @@ export interface Schema {
|
||||
e2eTestRunner?: 'cypress' | 'playwright' | 'jest' | 'detox' | 'none';
|
||||
js?: boolean;
|
||||
ssr?: boolean;
|
||||
prefix?: string;
|
||||
}
|
||||
|
||||
@ -99,6 +99,10 @@
|
||||
"description": "Enable Server-Side Rendering (SSR) and Static Site Generation (SSG/Prerendering) for the Angular application.",
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
},
|
||||
"prefix": {
|
||||
"description": "The prefix to use for Angular component and directive selectors.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": ["preset", "name"]
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user