diff --git a/packages/angular/ssr/src/app-engine.ts b/packages/angular/ssr/src/app-engine.ts index dd204a4b595f..0cb728e8535d 100644 --- a/packages/angular/ssr/src/app-engine.ts +++ b/packages/angular/ssr/src/app-engine.ts @@ -38,7 +38,7 @@ export class AngularAppEngine { * * @private */ - static ɵhooks = /* #__PURE__*/ new Hooks(); + static ɵhooks: Hooks = /* #__PURE__*/ new Hooks(); /** * The manifest for the server application. diff --git a/packages/angular/ssr/src/routes/route-tree.ts b/packages/angular/ssr/src/routes/route-tree.ts index 64d9a0fdfef2..68287de7f521 100644 --- a/packages/angular/ssr/src/routes/route-tree.ts +++ b/packages/angular/ssr/src/routes/route-tree.ts @@ -188,7 +188,9 @@ export class RouteTree = {}> * * @param node - The current node to start the traversal from. Defaults to the root node of the tree. */ - *traverse(node = this.root): Generator { + *traverse( + node: RouteTreeNode = this.root, + ): Generator { if (node.metadata) { yield node.metadata; } diff --git a/packages/schematics/angular/app-shell/index.ts b/packages/schematics/angular/app-shell/index.ts index 13905359d16c..2bae5e4bef14 100644 --- a/packages/schematics/angular/app-shell/index.ts +++ b/packages/schematics/angular/app-shell/index.ts @@ -6,7 +6,14 @@ * found in the LICENSE file at https://angular.dev/license */ -import { Rule, SchematicsException, Tree, chain, schematic } from '@angular-devkit/schematics'; +import { + Rule, + RuleFactory, + SchematicsException, + Tree, + chain, + schematic, +} from '@angular-devkit/schematics'; import { dirname, join } from 'node:path/posix'; import ts from '../third_party/github.com/Microsoft/TypeScript/lib/typescript'; import { @@ -190,19 +197,23 @@ function addServerRoutingConfig(options: AppShellOptions, isStandalone: boolean) }; } -export default createProjectSchematic(async (options, { tree }) => { - const browserEntryPoint = await getMainFilePath(tree, options.project); - const isStandalone = isStandaloneApp(tree, browserEntryPoint); - - return chain([ - validateProject(browserEntryPoint), - schematic('server', options), - addServerRoutingConfig(options, isStandalone), - schematic('component', { - name: 'app-shell', - module: 'app.module.server.ts', - project: options.project, - standalone: isStandalone, - }), - ]); -}); +const appShellSchematic: RuleFactory = createProjectSchematic( + async (options, { tree }) => { + const browserEntryPoint = await getMainFilePath(tree, options.project); + const isStandalone = isStandaloneApp(tree, browserEntryPoint); + + return chain([ + validateProject(browserEntryPoint), + schematic('server', options), + addServerRoutingConfig(options, isStandalone), + schematic('component', { + name: 'app-shell', + module: 'app.module.server.ts', + project: options.project, + standalone: isStandalone, + }), + ]); + }, +); + +export default appShellSchematic; diff --git a/packages/schematics/angular/component/index.ts b/packages/schematics/angular/component/index.ts index c3c4fe1c3448..1a07f69d1a15 100644 --- a/packages/schematics/angular/component/index.ts +++ b/packages/schematics/angular/component/index.ts @@ -8,7 +8,7 @@ import { FileOperator, - Rule, + RuleFactory, apply, applyTemplates, chain, @@ -40,60 +40,65 @@ function buildSelector(options: ComponentOptions, projectPrefix: string) { return selector; } -export default createProjectSchematic((options, { project, tree }) => { - if (options.path === undefined) { - options.path = buildDefaultPath(project); - } +const componentSchematic: RuleFactory = createProjectSchematic( + (options, { project, tree }) => { + if (options.path === undefined) { + options.path = buildDefaultPath(project); + } + + options.module = findModuleFromOptions(tree, options); + // Schematic templates require a defined type value + options.type ??= ''; - options.module = findModuleFromOptions(tree, options); - // Schematic templates require a defined type value - options.type ??= ''; + const parsedPath = parseName(options.path, options.name); + options.name = parsedPath.name; + options.path = parsedPath.path; + options.selector = + options.selector || buildSelector(options, (project && project.prefix) || ''); - const parsedPath = parseName(options.path, options.name); - options.name = parsedPath.name; - options.path = parsedPath.path; - options.selector = options.selector || buildSelector(options, (project && project.prefix) || ''); + validateHtmlSelector(options.selector); - validateHtmlSelector(options.selector); + const classifiedName = + strings.classify(options.name) + + (options.addTypeToClassName && options.type ? strings.classify(options.type) : ''); + validateClassName(classifiedName); + const zoneless = isZonelessApp(project); - const classifiedName = - strings.classify(options.name) + - (options.addTypeToClassName && options.type ? strings.classify(options.type) : ''); - validateClassName(classifiedName); - const zoneless = isZonelessApp(project); + const skipStyleFile = options.inlineStyle || options.style === Style.None; + const templateSource = apply(url('./files'), [ + options.skipTests ? filter((path) => !path.endsWith('.spec.ts.template')) : noop(), + skipStyleFile ? filter((path) => !path.endsWith('.__style__.template')) : noop(), + options.inlineTemplate ? filter((path) => !path.endsWith('.html.template')) : noop(), + applyTemplates({ + ...strings, + 'if-flat': (s: string) => (options.flat ? '' : s), + 'ngext': options.ngHtml ? '.ng' : '', + ...options, + // Add a new variable for the classified name, conditionally including the type + classifiedName, + zoneless, + }), + !options.type + ? forEach(((file) => { + return file.path.includes('..') + ? { + content: file.content, + path: file.path.replace('..', '.'), + } + : file; + }) as FileOperator) + : noop(), + move(parsedPath.path), + ]); - const skipStyleFile = options.inlineStyle || options.style === Style.None; - const templateSource = apply(url('./files'), [ - options.skipTests ? filter((path) => !path.endsWith('.spec.ts.template')) : noop(), - skipStyleFile ? filter((path) => !path.endsWith('.__style__.template')) : noop(), - options.inlineTemplate ? filter((path) => !path.endsWith('.html.template')) : noop(), - applyTemplates({ - ...strings, - 'if-flat': (s: string) => (options.flat ? '' : s), - 'ngext': options.ngHtml ? '.ng' : '', - ...options, - // Add a new variable for the classified name, conditionally including the type - classifiedName, - zoneless, - }), - !options.type - ? forEach(((file) => { - return file.path.includes('..') - ? { - content: file.content, - path: file.path.replace('..', '.'), - } - : file; - }) as FileOperator) - : noop(), - move(parsedPath.path), - ]); + return chain([ + addDeclarationToNgModule({ + type: 'component', + ...options, + }), + mergeWith(templateSource), + ]); + }, +); - return chain([ - addDeclarationToNgModule({ - type: 'component', - ...options, - }), - mergeWith(templateSource), - ]); -}); +export default componentSchematic; diff --git a/packages/schematics/angular/config/index.ts b/packages/schematics/angular/config/index.ts index c4bafc39657e..73818699a134 100644 --- a/packages/schematics/angular/config/index.ts +++ b/packages/schematics/angular/config/index.ts @@ -8,6 +8,7 @@ import { Rule, + RuleFactory, SchematicsException, apply, applyTemplates, @@ -24,18 +25,22 @@ import { updateWorkspace } from '../utility/workspace'; import { Builders as AngularBuilder } from '../utility/workspace-models'; import { Schema as ConfigOptions, Type as ConfigType } from './schema'; -export default createProjectSchematic((options, { project }) => { - switch (options.type) { - case ConfigType.Karma: - return addKarmaConfig(options); - case ConfigType.Browserslist: - return addBrowserslistConfig(project.root); - case ConfigType.Vitest: - return addVitestConfig(options); - default: - throw new SchematicsException(`"${options.type}" is an unknown configuration file type.`); - } -}); +const configSchematic: RuleFactory = createProjectSchematic( + (options, { project }) => { + switch (options.type) { + case ConfigType.Karma: + return addKarmaConfig(options); + case ConfigType.Browserslist: + return addBrowserslistConfig(project.root); + case ConfigType.Vitest: + return addVitestConfig(options); + default: + throw new SchematicsException(`"${options.type}" is an unknown configuration file type.`); + } + }, +); + +export default configSchematic; function addVitestConfig(options: ConfigOptions): Rule { return (tree, context) => diff --git a/packages/schematics/angular/directive/index.ts b/packages/schematics/angular/directive/index.ts index bfe87129bb79..72aa73b6a219 100644 --- a/packages/schematics/angular/directive/index.ts +++ b/packages/schematics/angular/directive/index.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.dev/license */ -import { Rule, chain, strings } from '@angular-devkit/schematics'; +import { RuleFactory, chain, strings } from '@angular-devkit/schematics'; import { addDeclarationToNgModule } from '../utility/add-declaration-to-ng-module'; import { findModuleFromOptions } from '../utility/find-module'; import { generateFromFiles } from '../utility/generate-from-files'; @@ -27,32 +27,36 @@ function buildSelector(options: DirectiveOptions, projectPrefix: string) { return strings.camelize(selector); } -export default createProjectSchematic((options, { project, tree }) => { - if (options.path === undefined) { - options.path = buildDefaultPath(project); - } - - options.module = findModuleFromOptions(tree, options); - const parsedPath = parseName(options.path, options.name); - options.name = parsedPath.name; - options.path = parsedPath.path; - options.selector = options.selector || buildSelector(options, project.prefix || ''); - - validateHtmlSelector(options.selector); - const classifiedName = - strings.classify(options.name) + - (options.addTypeToClassName && options.type ? strings.classify(options.type) : ''); - validateClassName(classifiedName); - - return chain([ - addDeclarationToNgModule({ - type: 'directive', - - ...options, - }), - generateFromFiles({ - ...options, - classifiedName, - }), - ]); -}); +const directiveSchematic: RuleFactory = createProjectSchematic( + (options, { project, tree }) => { + if (options.path === undefined) { + options.path = buildDefaultPath(project); + } + + options.module = findModuleFromOptions(tree, options); + const parsedPath = parseName(options.path, options.name); + options.name = parsedPath.name; + options.path = parsedPath.path; + options.selector = options.selector || buildSelector(options, project.prefix || ''); + + validateHtmlSelector(options.selector); + const classifiedName = + strings.classify(options.name) + + (options.addTypeToClassName && options.type ? strings.classify(options.type) : ''); + validateClassName(classifiedName); + + return chain([ + addDeclarationToNgModule({ + type: 'directive', + + ...options, + }), + generateFromFiles({ + ...options, + classifiedName, + }), + ]); + }, +); + +export default directiveSchematic; diff --git a/packages/schematics/angular/module/index.ts b/packages/schematics/angular/module/index.ts index 6a40ddf89eb2..6811ab55f3f1 100644 --- a/packages/schematics/angular/module/index.ts +++ b/packages/schematics/angular/module/index.ts @@ -8,6 +8,7 @@ import { Rule, + RuleFactory, Tree, apply, applyTemplates, @@ -134,59 +135,63 @@ function buildRoute(options: ModuleOptions, modulePath: string) { return `{ path: '${options.route}', loadChildren: ${loadChildren} }`; } -export default createProjectSchematic(async (options, { tree }) => { - if (options.path === undefined) { - options.path = await createDefaultPath(tree, options.project); - } - - if (options.module) { - options.module = findModuleFromOptions(tree, options); - } - - let routingModulePath; - const isLazyLoadedModuleGen = !!(options.route && options.module); - if (isLazyLoadedModuleGen) { - options.routingScope = RoutingScope.Child; - routingModulePath = getRoutingModulePath(tree, options.module as string); - } - - const parsedPath = parseName(options.path, options.name); - options.name = parsedPath.name; - options.path = parsedPath.path; - validateClassName(strings.classify(options.name)); - - const templateSource = apply(url('./files'), [ - options.routing || (isLazyLoadedModuleGen && routingModulePath) - ? noop() - : filter((path) => !path.includes('-routing')), - applyTemplates({ - ...strings, - 'if-flat': (s: string) => (options.flat ? '' : s), - lazyRoute: isLazyLoadedModuleGen, - lazyRouteWithoutRouteModule: isLazyLoadedModuleGen && !routingModulePath, - lazyRouteWithRouteModule: isLazyLoadedModuleGen && !!routingModulePath, - ...options, - }), - move(parsedPath.path), - ]); - const moduleDasherized = strings.dasherize(options.name); - const modulePath = `${ - !options.flat ? moduleDasherized + '/' : '' - }${moduleDasherized}${options.typeSeparator}module.ts`; - - const componentOptions: ComponentOptions = { - module: modulePath, - flat: options.flat, - name: options.name, - path: options.path, - project: options.project, - standalone: false, - }; +const moduleSchematic: RuleFactory = createProjectSchematic( + async (options, { tree }) => { + if (options.path === undefined) { + options.path = await createDefaultPath(tree, options.project); + } + + if (options.module) { + options.module = findModuleFromOptions(tree, options); + } + + let routingModulePath; + const isLazyLoadedModuleGen = !!(options.route && options.module); + if (isLazyLoadedModuleGen) { + options.routingScope = RoutingScope.Child; + routingModulePath = getRoutingModulePath(tree, options.module as string); + } - return chain([ - !isLazyLoadedModuleGen ? addImportToNgModule(options) : noop(), - addRouteDeclarationToNgModule(options, routingModulePath), - mergeWith(templateSource), - isLazyLoadedModuleGen ? schematic('component', componentOptions) : noop(), - ]); -}); + const parsedPath = parseName(options.path, options.name); + options.name = parsedPath.name; + options.path = parsedPath.path; + validateClassName(strings.classify(options.name)); + + const templateSource = apply(url('./files'), [ + options.routing || (isLazyLoadedModuleGen && routingModulePath) + ? noop() + : filter((path) => !path.includes('-routing')), + applyTemplates({ + ...strings, + 'if-flat': (s: string) => (options.flat ? '' : s), + lazyRoute: isLazyLoadedModuleGen, + lazyRouteWithoutRouteModule: isLazyLoadedModuleGen && !routingModulePath, + lazyRouteWithRouteModule: isLazyLoadedModuleGen && !!routingModulePath, + ...options, + }), + move(parsedPath.path), + ]); + const moduleDasherized = strings.dasherize(options.name); + const modulePath = `${ + !options.flat ? moduleDasherized + '/' : '' + }${moduleDasherized}${options.typeSeparator}module.ts`; + + const componentOptions: ComponentOptions = { + module: modulePath, + flat: options.flat, + name: options.name, + path: options.path, + project: options.project, + standalone: false, + }; + + return chain([ + !isLazyLoadedModuleGen ? addImportToNgModule(options) : noop(), + addRouteDeclarationToNgModule(options, routingModulePath), + mergeWith(templateSource), + isLazyLoadedModuleGen ? schematic('component', componentOptions) : noop(), + ]); + }, +); + +export default moduleSchematic; diff --git a/packages/schematics/angular/pipe/index.ts b/packages/schematics/angular/pipe/index.ts index 8333d0c4b1ee..c74307b1c6df 100644 --- a/packages/schematics/angular/pipe/index.ts +++ b/packages/schematics/angular/pipe/index.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.dev/license */ -import { chain, strings } from '@angular-devkit/schematics'; +import { RuleFactory, chain, strings } from '@angular-devkit/schematics'; import { addDeclarationToNgModule } from '../utility/add-declaration-to-ng-module'; import { findModuleFromOptions } from '../utility/find-module'; import { generateFromFiles } from '../utility/generate-from-files'; @@ -16,19 +16,23 @@ import { validateClassName } from '../utility/validation'; import { createDefaultPath } from '../utility/workspace'; import { Schema as PipeOptions } from './schema'; -export default createProjectSchematic(async (options, { tree }) => { - options.path ??= await createDefaultPath(tree, options.project); - options.module = findModuleFromOptions(tree, options); - const parsedPath = parseName(options.path, options.name); - options.name = parsedPath.name; - options.path = parsedPath.path; - validateClassName(strings.classify(options.name)); +const pipeSchematic: RuleFactory = createProjectSchematic( + async (options, { tree }) => { + options.path ??= await createDefaultPath(tree, options.project); + options.module = findModuleFromOptions(tree, options); + const parsedPath = parseName(options.path, options.name); + options.name = parsedPath.name; + options.path = parsedPath.path; + validateClassName(strings.classify(options.name)); - return chain([ - addDeclarationToNgModule({ - type: 'pipe', - ...options, - }), - generateFromFiles(options), - ]); -}); + return chain([ + addDeclarationToNgModule({ + type: 'pipe', + ...options, + }), + generateFromFiles(options), + ]); + }, +); + +export default pipeSchematic; diff --git a/packages/schematics/angular/refactor/jasmine-vitest/utils/todo-notes.ts b/packages/schematics/angular/refactor/jasmine-vitest/utils/todo-notes.ts index a4964b1e456b..8a9d888a0298 100644 --- a/packages/schematics/angular/refactor/jasmine-vitest/utils/todo-notes.ts +++ b/packages/schematics/angular/refactor/jasmine-vitest/utils/todo-notes.ts @@ -39,7 +39,7 @@ export const TODO_NOTES = { 'Please migrate this manually by checking the `mock.calls.length` of the individual spies.', }, 'toThrowMatching': { - message: (context: { name: string }) => + message: (context: { name: string }): string => `Unsupported matcher ".${context.name}()" found. Please migrate this manually.`, url: 'https://vitest.dev/api/expect.html#tothrowerror', }, @@ -49,7 +49,7 @@ export const TODO_NOTES = { 'Please migrate this manually, for example by using `Promise.race` to check if the promise settles within a short timeout.', }, 'unsupported-expect-async-matcher': { - message: (context: { name: string }) => + message: (context: { name: string }): string => `Unsupported expectAsync matcher ".${context.name}()" found. Please migrate this manually.`, }, 'arrayWithExactContents-dynamic-variable': { @@ -65,7 +65,7 @@ export const TODO_NOTES = { 'expect().nothing() has been removed because it is redundant in Vitest. Tests without assertions pass by default.', }, 'unsupported-global-function': { - message: (context: { name: string }) => + message: (context: { name: string }): string => `Unsupported global function \`${context.name}\` found. This function is used for custom reporters in Jasmine ` + 'and has no direct equivalent in Vitest.', }, @@ -89,7 +89,7 @@ export const TODO_NOTES = { ' Please manually assert the contents of the Set.', }, 'unknown-jasmine-property': { - message: (context: { name: string }) => + message: (context: { name: string }): string => `Unsupported jasmine property "${context.name}" found. Please migrate this manually.`, }, 'spyOnAllFunctions': { @@ -114,7 +114,7 @@ export const TODO_NOTES = { url: 'https://vitest.dev/api/vi.html#vi-fn', }, 'unsupported-spy-strategy': { - message: (context: { name: string }) => + message: (context: { name: string }): string => `Unsupported spy strategy ".and.${context.name}()" found. Please migrate this manually.`, url: 'https://vitest.dev/api/mocked.html#mock', }, diff --git a/packages/schematics/angular/server/index.ts b/packages/schematics/angular/server/index.ts index 00448bfbfb1e..077114e9027d 100644 --- a/packages/schematics/angular/server/index.ts +++ b/packages/schematics/angular/server/index.ts @@ -9,6 +9,7 @@ import { JsonValue, Path, basename, dirname, join, normalize } from '@angular-devkit/core'; import { Rule, + RuleFactory, SchematicsException, Tree, apply, @@ -163,91 +164,97 @@ function addDependencies(skipInstall: boolean | undefined): Rule { }; } -export default createProjectSchematic(async (options, { project, tree }) => { - if (project?.extensions.projectType !== 'application') { - throw new SchematicsException(`Server schematic requires a project type of "application".`); - } - - const clientBuildTarget = project.targets.get('build'); - if (!clientBuildTarget) { - throw targetBuildNotFoundError(); - } - - const usingApplicationBuilder = isUsingApplicationBuilder(project); - - if ( - project.targets.has('server') || - (usingApplicationBuilder && clientBuildTarget.options?.server !== undefined) - ) { - // Server has already been added. - return noop(); - } - - const clientBuildOptions = clientBuildTarget.options as Record; - const browserEntryPoint = await getMainFilePath(tree, options.project); - const isStandalone = isStandaloneApp(tree, browserEntryPoint); - const sourceRoot = project.sourceRoot ?? join(normalize(project.root), 'src'); - - let filesUrl = `./files/${usingApplicationBuilder ? 'application-builder/' : 'server-builder/'}`; - filesUrl += isStandalone ? 'standalone-src' : 'ngmodule-src'; - - const { componentName, componentImportPathInSameFile, moduleName, moduleImportPathInSameFile } = - resolveBootstrappedComponentData(tree, browserEntryPoint) || { - componentName: 'App', - componentImportPathInSameFile: './app/app', - moduleName: 'AppModule', - moduleImportPathInSameFile: './app/app.module', - }; - const templateSource = apply(url(filesUrl), [ - applyTemplates({ - ...strings, - ...options, - appComponentName: componentName, - appComponentPath: componentImportPathInSameFile, - appModuleName: moduleName, - appModulePath: - moduleImportPathInSameFile === null - ? null - : `./${posix.basename(moduleImportPathInSameFile)}`, - }), - move(sourceRoot), - ]); - - const clientTsConfig = normalize(clientBuildOptions.tsConfig); - const tsConfigExtends = basename(clientTsConfig); - const tsConfigDirectory = dirname(clientTsConfig); - - return chain([ - mergeWith(templateSource), - ...(usingApplicationBuilder - ? [ - updateConfigFileApplicationBuilder(options), - updateTsConfigFile(clientBuildOptions.tsConfig), - ] - : [ - mergeWith( - apply(url('./files/server-builder/root'), [ - applyTemplates({ - ...strings, - ...options, - stripTsExtension: (s: string) => s.replace(/\.ts$/, ''), - tsConfigExtends, - hasLocalizePackage: !!getPackageJsonDependency(tree, '@angular/localize'), - relativePathToWorkspaceRoot: relativePathToWorkspaceRoot(tsConfigDirectory), - }), - move(tsConfigDirectory), - ]), - ), - updateConfigFileBrowserBuilder(options, tsConfigDirectory), - ]), - addDependencies(options.skipInstall), - addRootProvider( - options.project, - ({ code, external }) => - code`${external('provideClientHydration', '@angular/platform-browser')}(${external( - 'withEventReplay', - '@angular/platform-browser', - )}())`, - ), - ]); -}); +const serverSchematic: RuleFactory = createProjectSchematic( + async (options, { project, tree }) => { + if (project?.extensions.projectType !== 'application') { + throw new SchematicsException(`Server schematic requires a project type of "application".`); + } + + const clientBuildTarget = project.targets.get('build'); + if (!clientBuildTarget) { + throw targetBuildNotFoundError(); + } + + const usingApplicationBuilder = isUsingApplicationBuilder(project); + + if ( + project.targets.has('server') || + (usingApplicationBuilder && clientBuildTarget.options?.server !== undefined) + ) { + // Server has already been added. + return noop(); + } + + const clientBuildOptions = clientBuildTarget.options as Record; + const browserEntryPoint = await getMainFilePath(tree, options.project); + const isStandalone = isStandaloneApp(tree, browserEntryPoint); + const sourceRoot = project.sourceRoot ?? join(normalize(project.root), 'src'); + + let filesUrl = `./files/${ + usingApplicationBuilder ? 'application-builder/' : 'server-builder/' + }`; + filesUrl += isStandalone ? 'standalone-src' : 'ngmodule-src'; + + const { componentName, componentImportPathInSameFile, moduleName, moduleImportPathInSameFile } = + resolveBootstrappedComponentData(tree, browserEntryPoint) || { + componentName: 'App', + componentImportPathInSameFile: './app/app', + moduleName: 'AppModule', + moduleImportPathInSameFile: './app/app.module', + }; + const templateSource = apply(url(filesUrl), [ + applyTemplates({ + ...strings, + ...options, + appComponentName: componentName, + appComponentPath: componentImportPathInSameFile, + appModuleName: moduleName, + appModulePath: + moduleImportPathInSameFile === null + ? null + : `./${posix.basename(moduleImportPathInSameFile)}`, + }), + move(sourceRoot), + ]); + + const clientTsConfig = normalize(clientBuildOptions.tsConfig); + const tsConfigExtends = basename(clientTsConfig); + const tsConfigDirectory = dirname(clientTsConfig); + + return chain([ + mergeWith(templateSource), + ...(usingApplicationBuilder + ? [ + updateConfigFileApplicationBuilder(options), + updateTsConfigFile(clientBuildOptions.tsConfig), + ] + : [ + mergeWith( + apply(url('./files/server-builder/root'), [ + applyTemplates({ + ...strings, + ...options, + stripTsExtension: (s: string) => s.replace(/\.ts$/, ''), + tsConfigExtends, + hasLocalizePackage: !!getPackageJsonDependency(tree, '@angular/localize'), + relativePathToWorkspaceRoot: relativePathToWorkspaceRoot(tsConfigDirectory), + }), + move(tsConfigDirectory), + ]), + ), + updateConfigFileBrowserBuilder(options, tsConfigDirectory), + ]), + addDependencies(options.skipInstall), + addRootProvider( + options.project, + ({ code, external }) => + code`${external('provideClientHydration', '@angular/platform-browser')}(${external( + 'withEventReplay', + '@angular/platform-browser', + )}())`, + ), + ]); + }, +); + +export default serverSchematic; diff --git a/packages/schematics/angular/service-worker/index.ts b/packages/schematics/angular/service-worker/index.ts index c357e5b9187d..4ef2c9839def 100644 --- a/packages/schematics/angular/service-worker/index.ts +++ b/packages/schematics/angular/service-worker/index.ts @@ -8,6 +8,7 @@ import { Rule, + RuleFactory, SchematicContext, SchematicsException, Tree, @@ -104,7 +105,7 @@ function getTsSourceFile(host: Tree, path: string): ts.SourceFile { return source; } -export default createProjectSchematic( +const serviceWorkerSchematic: RuleFactory = createProjectSchematic( async (options, { project, workspace, tree }) => { if (project.extensions.projectType !== 'application') { throw new SchematicsException(`Service worker requires a project type of "application".`); @@ -151,6 +152,8 @@ export default createProjectSchematic( }, ); +export default serviceWorkerSchematic; + function addImport(host: Tree, filePath: string, symbolName: string, moduleName: string): void { const moduleSource = getTsSourceFile(host, filePath); const change = insertImport(moduleSource, filePath, symbolName, moduleName); diff --git a/packages/schematics/angular/service/index.ts b/packages/schematics/angular/service/index.ts index 48558dcc0d3a..7805e78cc823 100644 --- a/packages/schematics/angular/service/index.ts +++ b/packages/schematics/angular/service/index.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.dev/license */ -import { Rule, strings } from '@angular-devkit/schematics'; +import { RuleFactory, strings } from '@angular-devkit/schematics'; import { generateFromFiles } from '../utility/generate-from-files'; import { parseName } from '../utility/parse-name'; import { createProjectSchematic } from '../utility/project'; @@ -14,22 +14,26 @@ import { validateClassName } from '../utility/validation'; import { buildDefaultPath } from '../utility/workspace'; import { Schema as ServiceOptions } from './schema'; -export default createProjectSchematic((options, { project, tree }) => { - if (options.path === undefined) { - options.path = buildDefaultPath(project); - } +const serviceSchematic: RuleFactory = createProjectSchematic( + (options, { project, tree }) => { + if (options.path === undefined) { + options.path = buildDefaultPath(project); + } - const parsedPath = parseName(options.path, options.name); - options.name = parsedPath.name; - options.path = parsedPath.path; + const parsedPath = parseName(options.path, options.name); + options.name = parsedPath.name; + options.path = parsedPath.path; - const classifiedName = - strings.classify(options.name) + - (options.addTypeToClassName && options.type ? strings.classify(options.type) : ''); - validateClassName(classifiedName); + const classifiedName = + strings.classify(options.name) + + (options.addTypeToClassName && options.type ? strings.classify(options.type) : ''); + validateClassName(classifiedName); - return generateFromFiles({ - ...options, - classifiedName, - }); -}); + return generateFromFiles({ + ...options, + classifiedName, + }); + }, +); + +export default serviceSchematic; diff --git a/packages/schematics/angular/ssr/index.ts b/packages/schematics/angular/ssr/index.ts index 8d3132228f24..6e27eab47cd5 100644 --- a/packages/schematics/angular/ssr/index.ts +++ b/packages/schematics/angular/ssr/index.ts @@ -9,6 +9,7 @@ import { isJsonObject } from '@angular-devkit/core'; import { Rule, + RuleFactory, SchematicContext, SchematicsException, Tree, @@ -360,29 +361,33 @@ function addServerFile( }; } -export default createProjectSchematic(async (options, { project, tree, context }) => { - const browserEntryPoint = await getMainFilePath(tree, options.project); - const isStandalone = isStandaloneApp(tree, browserEntryPoint); +const ssrSchematic: RuleFactory = createProjectSchematic( + async (options, { project, tree, context }) => { + const browserEntryPoint = await getMainFilePath(tree, options.project); + const isStandalone = isStandaloneApp(tree, browserEntryPoint); - const usingApplicationBuilder = isUsingApplicationBuilder(project); - const sourceRoot = project.sourceRoot ?? join(project.root, 'src'); + const usingApplicationBuilder = isUsingApplicationBuilder(project); + const sourceRoot = project.sourceRoot ?? join(project.root, 'src'); - return chain([ - schematic('server', { - ...options, - skipInstall: true, - }), - ...(usingApplicationBuilder - ? [ - updateApplicationBuilderWorkspaceConfigRule(sourceRoot, options, context), - updateApplicationBuilderTsConfigRule(options), - ] - : [ - updateWebpackBuilderServerTsConfigRule(options), - updateWebpackBuilderWorkspaceConfigRule(sourceRoot, options), - ]), - addServerFile(sourceRoot, options, isStandalone), - addScriptsRule(options, usingApplicationBuilder), - addDependencies(options, usingApplicationBuilder), - ]); -}); + return chain([ + schematic('server', { + ...options, + skipInstall: true, + }), + ...(usingApplicationBuilder + ? [ + updateApplicationBuilderWorkspaceConfigRule(sourceRoot, options, context), + updateApplicationBuilderTsConfigRule(options), + ] + : [ + updateWebpackBuilderServerTsConfigRule(options), + updateWebpackBuilderWorkspaceConfigRule(sourceRoot, options), + ]), + addServerFile(sourceRoot, options, isStandalone), + addScriptsRule(options, usingApplicationBuilder), + addDependencies(options, usingApplicationBuilder), + ]); + }, +); + +export default ssrSchematic; diff --git a/packages/schematics/angular/tailwind/index.ts b/packages/schematics/angular/tailwind/index.ts index 152399deee5b..e246e5f55bfe 100644 --- a/packages/schematics/angular/tailwind/index.ts +++ b/packages/schematics/angular/tailwind/index.ts @@ -8,6 +8,7 @@ import { type Rule, + RuleFactory, SchematicsException, apply, applyTemplates, @@ -122,16 +123,20 @@ function managePostCssConfiguration(project: ProjectDefinition): Rule { }; } -export default createProjectSchematic((options, { project }) => { - return chain([ - addTailwindStyles(options, project), - managePostCssConfiguration(project), - ...TAILWIND_DEPENDENCIES.map((name) => - addDependency(name, latestVersions[name], { - type: DependencyType.Dev, - existing: ExistingBehavior.Skip, - install: options.skipInstall ? InstallBehavior.None : InstallBehavior.Auto, - }), - ), - ]); -}); +const tailwindSchematic: RuleFactory = createProjectSchematic( + (options, { project }) => { + return chain([ + addTailwindStyles(options, project), + managePostCssConfiguration(project), + ...TAILWIND_DEPENDENCIES.map((name) => + addDependency(name, latestVersions[name], { + type: DependencyType.Dev, + existing: ExistingBehavior.Skip, + install: options.skipInstall ? InstallBehavior.None : InstallBehavior.Auto, + }), + ), + ]); + }, +); + +export default tailwindSchematic; diff --git a/packages/schematics/angular/utility/change.ts b/packages/schematics/angular/utility/change.ts index e4055620258c..6736b29e594b 100644 --- a/packages/schematics/angular/utility/change.ts +++ b/packages/schematics/angular/utility/change.ts @@ -33,9 +33,9 @@ export interface Change { */ export class NoopChange implements Change { description = 'No operation.'; - order = Infinity; + order: number = Infinity; path = null; - apply() { + apply(): Promise { return Promise.resolve(); } } @@ -62,7 +62,7 @@ export class InsertChange implements Change { /** * This method does not insert spaces if there is none in the original string. */ - apply(host: Host) { + apply(host: Host): Promise { return host.read(this.path).then((content) => { const prefix = content.substring(0, this.pos); const suffix = content.substring(this.pos); diff --git a/packages/schematics/angular/utility/dependencies.ts b/packages/schematics/angular/utility/dependencies.ts index deb78ca9c0e2..b90ba8796975 100644 --- a/packages/schematics/angular/utility/dependencies.ts +++ b/packages/schematics/angular/utility/dependencies.ts @@ -37,7 +37,7 @@ const ALL_DEPENDENCY_TYPE = [ export function addPackageJsonDependency( tree: Tree, dependency: NodeDependency, - pkgJsonPath = PKG_JSON_PATH, + pkgJsonPath: string = PKG_JSON_PATH, ): void { const json = new JSONFile(tree, pkgJsonPath); @@ -51,7 +51,7 @@ export function addPackageJsonDependency( export function removePackageJsonDependency( tree: Tree, name: string, - pkgJsonPath = PKG_JSON_PATH, + pkgJsonPath: string = PKG_JSON_PATH, ): void { const json = new JSONFile(tree, pkgJsonPath); @@ -63,7 +63,7 @@ export function removePackageJsonDependency( export function getPackageJsonDependency( tree: Tree, name: string, - pkgJsonPath = PKG_JSON_PATH, + pkgJsonPath: string = PKG_JSON_PATH, ): NodeDependency | null { const json = new JSONFile(tree, pkgJsonPath); diff --git a/packages/schematics/angular/utility/standalone/code_block.ts b/packages/schematics/angular/utility/standalone/code_block.ts index 128c6076a41b..439699718cb9 100644 --- a/packages/schematics/angular/utility/standalone/code_block.ts +++ b/packages/schematics/angular/utility/standalone/code_block.ts @@ -75,7 +75,10 @@ export class CodeBlock { * @param initialCode Code pending transformed. * @param filePath Path of the file in which the code will be inserted. */ - static transformPendingCode(initialCode: PendingCode, filePath: string) { + static transformPendingCode( + initialCode: PendingCode, + filePath: string, + ): { code: PendingCode; rules: Rule[] } { const code = { ...initialCode }; const rules: Rule[] = []; diff --git a/packages/schematics/angular/utility/standalone/util.ts b/packages/schematics/angular/utility/standalone/util.ts index 4c64e68ad559..433967bce172 100644 --- a/packages/schematics/angular/utility/standalone/util.ts +++ b/packages/schematics/angular/utility/standalone/util.ts @@ -137,7 +137,7 @@ function findImportLocalName( * @param path Path to the file that is being changed. * @param changes Changes that should be applied to the file. */ -export function applyChangesToFile(tree: Tree, path: string, changes: Change[]) { +export function applyChangesToFile(tree: Tree, path: string, changes: Change[]): void { if (changes.length > 0) { const recorder = tree.beginUpdate(path); applyToUpdateRecorder(recorder, changes); diff --git a/packages/schematics/angular/utility/validation.ts b/packages/schematics/angular/utility/validation.ts index 8b380d1b8262..54c3fddb815d 100644 --- a/packages/schematics/angular/utility/validation.ts +++ b/packages/schematics/angular/utility/validation.ts @@ -10,7 +10,7 @@ import { SchematicsException } from '@angular-devkit/schematics'; // 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. -export const htmlSelectorRe = +export const htmlSelectorRe: RegExp = /^[a-zA-Z][.0-9a-zA-Z]*((:?-[0-9]+)*|(:?-[a-zA-Z][.0-9a-zA-Z]*(:?-[0-9]+)*)*)$/; // See: https://github.com/tc39/proposal-regexp-unicode-property-escapes/blob/fe6d07fad74cd0192d154966baa1e95e7cda78a1/README.md#other-examples diff --git a/packages/schematics/angular/utility/workspace.ts b/packages/schematics/angular/utility/workspace.ts index b831458edf40..f8567fc534a3 100644 --- a/packages/schematics/angular/utility/workspace.ts +++ b/packages/schematics/angular/utility/workspace.ts @@ -81,7 +81,7 @@ export function updateWorkspace( */ export async function getWorkspace( tree: Tree, - path = DEFAULT_WORKSPACE_PATH, + path: string = DEFAULT_WORKSPACE_PATH, ): Promise { const host = new TreeWorkspaceHost(tree); diff --git a/packages/schematics/angular/web-worker/index.ts b/packages/schematics/angular/web-worker/index.ts index c9b5da381cbb..a19e2b714174 100644 --- a/packages/schematics/angular/web-worker/index.ts +++ b/packages/schematics/angular/web-worker/index.ts @@ -8,6 +8,7 @@ import { Rule, + RuleFactory, SchematicContext, SchematicsException, Tree, @@ -73,54 +74,58 @@ if (typeof Worker !== 'undefined') { }; } -export default createProjectSchematic((options, { project }) => { - const projectType = project.extensions['projectType']; - if (projectType !== 'application') { - throw new SchematicsException(`Web Worker requires a project type of "application".`); - } +const webWorkerSchematic: RuleFactory = createProjectSchematic( + (options, { project }) => { + const projectType = project.extensions['projectType']; + if (projectType !== 'application') { + throw new SchematicsException(`Web Worker requires a project type of "application".`); + } + + if (options.path === undefined) { + options.path = buildDefaultPath(project); + } + const parsedPath = parseName(options.path, options.name); + options.name = parsedPath.name; + options.path = parsedPath.path; - if (options.path === undefined) { - options.path = buildDefaultPath(project); - } - const parsedPath = parseName(options.path, options.name); - options.name = parsedPath.name; - options.path = parsedPath.path; + const templateSourceWorkerCode = apply(url('./files/worker'), [ + applyTemplates({ ...options, ...strings }), + move(parsedPath.path), + ]); - const templateSourceWorkerCode = apply(url('./files/worker'), [ - applyTemplates({ ...options, ...strings }), - move(parsedPath.path), - ]); + const root = project.root || ''; + const templateSourceWorkerConfig = apply(url('./files/worker-tsconfig'), [ + applyTemplates({ + ...options, + relativePathToWorkspaceRoot: relativePathToWorkspaceRoot(root), + }), + move(root), + ]); - const root = project.root || ''; - const templateSourceWorkerConfig = apply(url('./files/worker-tsconfig'), [ - applyTemplates({ - ...options, - relativePathToWorkspaceRoot: relativePathToWorkspaceRoot(root), - }), - move(root), - ]); + return chain([ + // Add project configuration. + updateWorkspace((workspace) => { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const project = workspace.projects.get(options.project)!; + const buildTarget = project.targets.get('build'); + const testTarget = project.targets.get('test'); + if (!buildTarget) { + throw new Error(`Build target is not defined for this project.`); + } - return chain([ - // Add project configuration. - updateWorkspace((workspace) => { - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - const project = workspace.projects.get(options.project)!; - const buildTarget = project.targets.get('build'); - const testTarget = project.targets.get('test'); - if (!buildTarget) { - throw new Error(`Build target is not defined for this project.`); - } + const workerConfigPath = join(root, 'tsconfig.worker.json'); + (buildTarget.options ??= {}).webWorkerTsConfig ??= workerConfigPath; + if (testTarget) { + (testTarget.options ??= {}).webWorkerTsConfig ??= workerConfigPath; + } + }), + // Create the worker in a sibling module. + options.snippet ? addSnippet(options) : noop(), + // Add the worker. + mergeWith(templateSourceWorkerCode), + mergeWith(templateSourceWorkerConfig), + ]); + }, +); - const workerConfigPath = join(root, 'tsconfig.worker.json'); - (buildTarget.options ??= {}).webWorkerTsConfig ??= workerConfigPath; - if (testTarget) { - (testTarget.options ??= {}).webWorkerTsConfig ??= workerConfigPath; - } - }), - // Create the worker in a sibling module. - options.snippet ? addSnippet(options) : noop(), - // Add the worker. - mergeWith(templateSourceWorkerCode), - mergeWith(templateSourceWorkerConfig), - ]); -}); +export default webWorkerSchematic;