1
0
mirror of https://github.com/angular/angular-cli.git synced 2025-05-15 18:13:38 +08:00

Compare commits

...

8 Commits

Author SHA1 Message Date
Charles
ffdd926625
Merge 882dc4884fbc9667851fade86004cd0735e90b7f into b66d36b4b03f3d2051ca8c9d3b56a59718ea1b30 2025-03-18 18:25:00 +00:00
Charles Lyding
b66d36b4b0 build: move @angular/build specific dependencies out of root
With the migration to `rules_js`, package specific dependencies now only
need to be referenced with the source `package.json` for each specific
package. Most of the `@angular/build` specific dependencies have now been
moved. This is not exhaustive and further changes will continue to move
additional dependencies.
2025-03-18 13:01:26 -04:00
Alan Agius
26fd4ea73a feat(@schematics/angular): add migrations for server rendering updates
- Migrate imports of `provideServerRendering` from `@angular/platform-server` to `@angular/ssr`.
- Update `provideServerRendering` to use `withRoutes` and remove `provideServerRouting` from `@angular/ssr`.
2025-03-18 17:58:59 +01:00
Alan Agius
33b9de3eb1 feat(@angular/ssr): expose provideServerRendering and remove provideServerRouting
This commit introduces `provideServerRendering` as the primary function for configuring server-side rendering, replacing `provideServerRouting`. `provideServerRendering` now includes the functionality of `provideServerRouting` through the use of the `withRoutes` feature.

This change consolidates server-side rendering configuration into a single, more flexible function, aligning with the evolution of Angular SSR.

**Before:**
```ts
import { provideServerRouting } from '@angular/ssr';
import { serverRoutes } from './app.routes';

provideServerRouting(serverRoutes);
```

**After:**
```ts
import { provideServerRendering, withRoutes } from '@angular/ssr';
import { serverRoutes } from './app.routes';

provideServerRendering(withRoutes(serverRoutes));
```
2025-03-18 17:58:59 +01:00
Paul Gschwendtner
fe69a9b23a build: improve debugging mode of e2e test runner
Currently the `while` loop may either be causing the process to be
really stuck/hanging, or it somehow causes Node to exit.

This change makes the logic more robust and less CPU consuming.
2025-03-18 14:52:40 +01:00
Charles Lyding
bc0f07b484 fix(@schematics/angular): generate services without a .service extension/type
To align with the updated style guide, Angular v20 will generate services
without a `.service` file extension type for all service related
files by default. Projects will automatically use this naming convention.
Projects can however opt-out by setting the `type` option to `Service`
for the service schematic. This can be done as a default in the `angular.json`
or directly on the commandline via `--type=Service` when executing `ng generate`.
As an example, `example.service.ts` will now be named `example.ts`. Additionally,
the TypeScript class name will be `Example` instead of the previous `ExampleService`.
2025-03-18 07:25:46 -04:00
Charles Lyding
e7ae25a76b refactor(@schematics/angular): remove unneeded initial library service generation
The `library` schematic will now longer generate an empty Angular service
with the name of the library. Using only the name would generate a file
name conflict with the component now that the type suffix is no longer
used by default. Additionally, services tend to be specific to a particular
behavior and/or functionality and are named as such. A generically named
empty service will typically be deleted or renamed.
2025-03-18 07:25:46 -04:00
Charles Lyding
882dc4884f build: update puppeteer to v24.4.0 2025-03-11 17:50:47 -04:00
40 changed files with 711 additions and 314 deletions
goldens/public-api/angular/ssr
package.json
packages
pnpm-lock.yaml
tests/legacy-cli
e2e/tests
build/library
generate/service
misc
e2e_runner.ts

@ -27,7 +27,7 @@ export enum PrerenderFallback {
}
// @public
export function provideServerRouting(routes: ServerRoute[], ...features: ServerRoutesFeature<ServerRoutesFeatureKind>[]): EnvironmentProviders;
export function provideServerRendering(...features: ServerRenderingFeature<ServerRenderingFeatureKind>[]): EnvironmentProviders;
// @public
export enum RenderMode {
@ -72,7 +72,10 @@ export interface ServerRouteServer extends ServerRouteCommon {
}
// @public
export function withAppShell(component: Type<unknown> | (() => Promise<Type<unknown> | DefaultExport<Type<unknown>>>)): ServerRoutesFeature<ServerRoutesFeatureKind.AppShell>;
export function withAppShell(component: Type<unknown> | (() => Promise<Type<unknown> | DefaultExport<Type<unknown>>>)): ServerRenderingFeature<ServerRenderingFeatureKind.AppShell>;
// @public
export function withRoutes(routes: ServerRoute[]): ServerRenderingFeature<ServerRenderingFeatureKind.ServerRoutes>;
// (No @packageDocumentation comment for this package)

@ -46,7 +46,6 @@
},
"homepage": "https://github.com/angular/angular-cli",
"devDependencies": {
"@ampproject/remapping": "2.3.0",
"@angular/animations": "20.0.0-next.2",
"@angular/cdk": "20.0.0-next.1",
"@angular/common": "20.0.0-next.2",
@ -134,34 +133,24 @@
"karma-jasmine": "~5.1.0",
"karma-jasmine-html-reporter": "~2.1.0",
"karma-source-map-support": "1.4.0",
"less": "4.2.2",
"listr2": "8.2.5",
"lmdb": "3.2.6",
"lodash": "^4.17.21",
"magic-string": "0.30.17",
"mrmime": "2.0.1",
"ng-packagr": "20.0.0-next.1",
"npm": "^11.0.0",
"open": "10.1.0",
"ora": "5.4.1",
"parse5-html-rewriting-stream": "7.0.0",
"piscina": "4.9.0",
"postcss": "8.5.3",
"prettier": "^3.0.0",
"protractor": "~7.0.0",
"puppeteer": "18.2.1",
"puppeteer": "24.4.0",
"quicktype-core": "23.0.171",
"rollup": "4.36.0",
"rollup-license-plugin": "~3.0.1",
"rollup-plugin-sourcemaps": "^0.6.0",
"rxjs": "7.8.2",
"sass": "1.85.1",
"semver": "7.7.1",
"shelljs": "^0.9.0",
"source-map-support": "0.5.21",
"symbol-observable": "4.0.0",
"tar": "^7.0.0",
"tinyglobby": "0.2.12",
"tree-kill": "1.2.2",
"ts-node": "^10.9.1",
"tslib": "2.8.1",
@ -170,7 +159,6 @@
"unenv": "^1.10.0",
"verdaccio": "6.0.5",
"verdaccio-auth-memory": "^10.0.0",
"watchpack": "2.4.2",
"yargs-parser": "21.1.1",
"zone.js": "^0.15.0"
},

@ -74,15 +74,28 @@ ts_project(
data = RUNTIME_ASSETS,
module_name = "@angular/build",
deps = [
":node_modules/@ampproject/remapping",
":node_modules/@angular-devkit/architect",
":node_modules/@angular-devkit/core",
":node_modules/@angular/ssr",
":node_modules/@inquirer/confirm",
":node_modules/@vitejs/plugin-basic-ssl",
":node_modules/jsonc-parser",
":node_modules/less",
":node_modules/listr2",
":node_modules/lmdb",
":node_modules/magic-string",
":node_modules/mrmime",
":node_modules/ng-packagr",
":node_modules/parse5-html-rewriting-stream",
":node_modules/picomatch",
":node_modules/piscina",
":node_modules/postcss",
":node_modules/sass",
":node_modules/source-map-support",
":node_modules/tinyglobby",
":node_modules/vite",
"//:node_modules/@ampproject/remapping",
":node_modules/watchpack",
"//:node_modules/@angular/common",
"//:node_modules/@angular/compiler",
"//:node_modules/@angular/compiler-cli",
@ -108,23 +121,10 @@ ts_project(
"//:node_modules/https-proxy-agent",
"//:node_modules/istanbul-lib-instrument",
"//:node_modules/karma",
"//:node_modules/less",
"//:node_modules/listr2",
"//:node_modules/lmdb",
"//:node_modules/magic-string",
"//:node_modules/mrmime",
"//:node_modules/ng-packagr",
"//:node_modules/parse5-html-rewriting-stream",
"//:node_modules/piscina",
"//:node_modules/postcss",
"//:node_modules/rollup",
"//:node_modules/sass",
"//:node_modules/semver",
"//:node_modules/source-map-support",
"//:node_modules/tinyglobby",
"//:node_modules/tslib",
"//:node_modules/typescript",
"//:node_modules/watchpack",
],
)
@ -204,7 +204,7 @@ ts_project(
"//:node_modules/@angular/platform-browser",
"//:node_modules/@angular/platform-browser-dynamic",
"//:node_modules/@angular/router",
"//:node_modules/ng-packagr",
":node_modules/ng-packagr",
"//:node_modules/rxjs",
"//:node_modules/tslib",
"//:node_modules/typescript",

@ -51,7 +51,10 @@
},
"devDependencies": {
"@angular/ssr": "workspace:*",
"@angular-devkit/core": "workspace:*"
"@angular-devkit/core": "workspace:*",
"less": "4.2.2",
"ng-packagr": "20.0.0-next.1",
"postcss": "8.5.3"
},
"peerDependencies": {
"@angular/compiler": "0.0.0-ANGULAR-FW-PEER-DEP",

@ -162,7 +162,7 @@ describeServeBuilder(
// See: https://github.com/angular/angular-cli/pull/17624
// eslint-disable-next-line max-len
// executablePath: '/Users/<USERNAME>/git/angular-cli/node_modules/puppeteer/.local-chromium/mac-818858/chrome-mac/Chromium.app/Contents/MacOS/Chromium',
ignoreHTTPSErrors: true,
acceptInsecureCerts: true,
args: ['--no-sandbox', '--disable-gpu'],
});
});

@ -36,8 +36,8 @@ ts_project(
deps = [
":node_modules/@angular-devkit/schematics",
":node_modules/@schematics/angular",
":node_modules/parse5-html-rewriting-stream",
"//:node_modules/@types/node",
"//:node_modules/parse5-html-rewriting-stream",
],
)

@ -14,8 +14,9 @@ export { createRequestHandler, type RequestHandlerFunction } from './src/handler
export {
PrerenderFallback,
type ServerRoute,
provideServerRouting,
provideServerRendering,
withAppShell,
withRoutes,
RenderMode,
type ServerRouteClient,
type ServerRoutePrerender,

@ -11,8 +11,11 @@ import {
InjectionToken,
Provider,
Type,
inject,
makeEnvironmentProviders,
provideEnvironmentInitializer,
} from '@angular/core';
import { provideServerRendering as provideServerRenderingPlatformServer } from '@angular/platform-server';
import { type DefaultExport, ROUTES, type Route } from '@angular/router';
/**
@ -22,25 +25,26 @@ import { type DefaultExport, ROUTES, type Route } from '@angular/router';
const APP_SHELL_ROUTE = 'ng-app-shell';
/**
* Identifies a particular kind of `ServerRoutesFeatureKind`.
* @see {@link ServerRoutesFeature}
* Identifies a particular kind of `ServerRenderingFeatureKind`.
* @see {@link ServerRenderingFeature}
*/
enum ServerRoutesFeatureKind {
enum ServerRenderingFeatureKind {
AppShell,
ServerRoutes,
}
/**
* Helper type to represent a server routes feature.
* @see {@link ServerRoutesFeatureKind}
* @see {@link ServerRenderingFeatureKind}
*/
interface ServerRoutesFeature<FeatureKind extends ServerRoutesFeatureKind> {
interface ServerRenderingFeature<FeatureKind extends ServerRenderingFeatureKind> {
ɵkind: FeatureKind;
ɵproviders: Provider[];
ɵproviders: (Provider | EnvironmentProviders)[];
}
/**
* Different rendering modes for server routes.
* @see {@link provideServerRouting}
* @see {@link withRoutes}
* @see {@link ServerRoute}
*/
export enum RenderMode {
@ -171,7 +175,7 @@ export interface ServerRouteServer extends ServerRouteCommon {
/**
* Server route configuration.
* @see {@link provideServerRouting}
* @see {@link withRoutes}
*/
export type ServerRoute =
| ServerRouteClient
@ -200,62 +204,103 @@ export interface ServerRoutesConfig {
export const SERVER_ROUTES_CONFIG = new InjectionToken<ServerRoutesConfig>('SERVER_ROUTES_CONFIG');
/**
* Sets up the necessary providers for configuring server routes.
* This function accepts an array of server routes and optional configuration
* options, returning an `EnvironmentProviders` object that encapsulates
* the server routes and configuration settings.
* Configures server-side routing for the application.
*
* @param routes - An array of server routes to be provided.
* @param features - (Optional) server routes features.
* @returns An `EnvironmentProviders` instance with the server routes configuration.
* This function registers an array of `ServerRoute` definitions, enabling server-side rendering
* for specific URL paths. These routes are used to pre-render content on the server, improving
* initial load performance and SEO.
*
* @param routes - An array of `ServerRoute` objects, each defining a server-rendered route.
* @returns A `ServerRenderingFeature` object configuring server-side routes.
*
* @example
* ```ts
* import { provideServerRendering, withRoutes, ServerRoute, RenderMode } from '@angular/ssr';
*
* const serverRoutes: ServerRoute[] = [
* {
* route: '', // This renders the "/" route on the client (CSR)
* renderMode: RenderMode.Client,
* },
* {
* route: 'about', // This page is static, so we prerender it (SSG)
* renderMode: RenderMode.Prerender,
* },
* {
* route: 'profile', // This page requires user-specific data, so we use SSR
* renderMode: RenderMode.Server,
* },
* {
* route: '**', // All other routes will be rendered on the server (SSR)
* renderMode: RenderMode.Server,
* },
* ];
*
* provideServerRendering(withRoutes(serverRoutes));
* ```
*
* @see {@link provideServerRendering}
* @see {@link ServerRoute}
* @see {@link withAppShell}
*/
export function provideServerRouting(
export function withRoutes(
routes: ServerRoute[],
...features: ServerRoutesFeature<ServerRoutesFeatureKind>[]
): EnvironmentProviders {
): ServerRenderingFeature<ServerRenderingFeatureKind.ServerRoutes> {
const config: ServerRoutesConfig = { routes };
const hasAppShell = features.some((f) => f.ɵkind === ServerRoutesFeatureKind.AppShell);
if (hasAppShell) {
config.appShellRoute = APP_SHELL_ROUTE;
}
const providers: Provider[] = [
{
provide: SERVER_ROUTES_CONFIG,
useValue: config,
},
];
for (const feature of features) {
providers.push(...feature.ɵproviders);
}
return makeEnvironmentProviders(providers);
return {
ɵkind: ServerRenderingFeatureKind.ServerRoutes,
ɵproviders: [
{
provide: SERVER_ROUTES_CONFIG,
useValue: config,
},
],
};
}
/**
* Configures the app shell route with the provided component.
* Configures the shell of the application.
*
* The app shell serves as the main entry point for the application and is commonly used
* to enable server-side rendering (SSR) of the application shell. It handles requests
* that do not match any specific server route, providing a fallback mechanism and improving
* perceived performance during navigation.
* The app shell is a minimal, static HTML page that is served immediately, while the
* full Angular application loads in the background. This improves perceived performance
* by providing instant feedback to the user.
*
* This configuration is particularly useful in applications leveraging Progressive Web App (PWA)
* patterns, such as service workers, to deliver a seamless user experience.
* This function configures the app shell route, which serves the provided component for
* requests that do not match any defined server routes.
*
* @param component The Angular component to render for the app shell route.
* @returns A server routes feature configuration for the app shell.
* @param component - The Angular component to render for the app shell. Can be a direct
* component type or a dynamic import function.
* @returns A `ServerRenderingFeature` object configuring the app shell.
*
* @see {@link provideServerRouting}
* @example
* ```ts
* import { provideServerRendering, withAppShell, withRoutes } from '@angular/ssr';
* import { AppShellComponent } from './app-shell.component';
*
* provideServerRendering(
* withRoutes(serverRoutes),
* withAppShell(AppShellComponent)
* );
* ```
*
* @example
* ```ts
* import { provideServerRendering, withAppShell, withRoutes } from '@angular/ssr';
*
* provideServerRendering(
* withRoutes(serverRoutes),
* withAppShell(() =>
* import('./app-shell.component').then((m) => m.AppShellComponent)
* )
* );
* ```
*
* @see {@link provideServerRendering}
* @see {@link https://angular.dev/ecosystem/service-workers/app-shell | App shell pattern on Angular.dev}
*/
export function withAppShell(
component: Type<unknown> | (() => Promise<Type<unknown> | DefaultExport<Type<unknown>>>),
): ServerRoutesFeature<ServerRoutesFeatureKind.AppShell> {
): ServerRenderingFeature<ServerRenderingFeatureKind.AppShell> {
const routeConfig: Route = {
path: APP_SHELL_ROUTE,
};
@ -267,13 +312,73 @@ export function withAppShell(
}
return {
ɵkind: ServerRoutesFeatureKind.AppShell,
ɵkind: ServerRenderingFeatureKind.AppShell,
ɵproviders: [
{
provide: ROUTES,
useValue: routeConfig,
multi: true,
},
provideEnvironmentInitializer(() => {
const config = inject(SERVER_ROUTES_CONFIG);
config.appShellRoute = APP_SHELL_ROUTE;
}),
],
};
}
/**
* Configures server-side rendering for an Angular application.
*
* This function sets up the necessary providers for server-side rendering, including
* support for server routes and app shell. It combines features configured using
* `withRoutes` and `withAppShell` to provide a comprehensive server-side rendering setup.
*
* @param features - Optional features to configure additional server rendering behaviors.
* @returns An `EnvironmentProviders` instance with the server-side rendering configuration.
*
* @example
* Basic example of how you can enable server-side rendering in your application
* when using the `bootstrapApplication` function:
*
* ```ts
* import { bootstrapApplication } from '@angular/platform-browser';
* import { provideServerRendering, withRoutes, withAppShell } from '@angular/ssr';
* import { AppComponent } from './app/app.component';
* import { SERVER_ROUTES } from './app/app.server.routes';
* import { AppShellComponent } from './app/app-shell.component';
*
* bootstrapApplication(AppComponent, {
* providers: [
* provideServerRendering(
* withRoutes(SERVER_ROUTES),
* withAppShell(AppShellComponent)
* )
* ]
* });
* ```
* @see {@link withRoutes} configures server-side routing
* @see {@link withAppShell} configures the application shell
*/
export function provideServerRendering(
...features: ServerRenderingFeature<ServerRenderingFeatureKind>[]
): EnvironmentProviders {
let hasAppShell = false;
let hasServerRoutes = false;
const providers: (Provider | EnvironmentProviders)[] = [provideServerRenderingPlatformServer()];
for (const { ɵkind, ɵproviders } of features) {
hasAppShell ||= ɵkind === ServerRenderingFeatureKind.AppShell;
hasServerRoutes ||= ɵkind === ServerRenderingFeatureKind.ServerRoutes;
providers.push(...ɵproviders);
}
if (!hasServerRoutes && hasAppShell) {
throw new Error(
`Configuration error: found 'withAppShell()' without 'withRoutes()' in the same call to 'provideServerRendering()'.` +
`The 'withAppShell()' function requires 'withRoutes()' to be used.`,
);
}
return makeEnvironmentProviders(providers);
}

@ -13,7 +13,6 @@ ts_project(
"//:node_modules/@angular/compiler",
"//:node_modules/@angular/core",
"//:node_modules/@angular/platform-browser",
"//:node_modules/@angular/platform-server",
"//:node_modules/@angular/router",
"//:node_modules/@types/node",
"//packages/angular/ssr",

@ -24,12 +24,6 @@ const CRITTERS_ACTUAL_LICENSE_FILE_PATH = join(
'third_party/beasties/THIRD_PARTY_LICENSES.txt',
);
/**
* Path to the golden reference license file for the Beasties library.
* This file is used as a reference for comparison and is located in the same directory as this script.
*/
const CRITTERS_GOLDEN_LICENSE_FILE_PATH = join(__dirname, 'THIRD_PARTY_LICENSES.txt.golden');
describe('NPM Package Tests', () => {
it('should not include the contents of third_party/beasties/index.js in the FESM bundle', async () => {
const fesmFilePath = join(ANGULAR_SSR_PACKAGE_PATH, 'fesm2022/ssr.mjs');

@ -14,11 +14,10 @@ import {
provideExperimentalZonelessChangeDetection,
} from '@angular/core';
import { bootstrapApplication } from '@angular/platform-browser';
import { provideServerRendering } from '@angular/platform-server';
import { RouterOutlet, Routes, provideRouter } from '@angular/router';
import { destroyAngularServerApp } from '../src/app';
import { ServerAsset, setAngularAppManifest } from '../src/manifest';
import { ServerRoute, provideServerRouting } from '../src/routes/route-config';
import { ServerRoute, provideServerRendering, withRoutes } from '../src/routes/route-config';
@Component({
standalone: true,
@ -94,10 +93,9 @@ export function setAngularAppTestingManifest(
bootstrap: async () => () => {
return bootstrapApplication(rootComponent, {
providers: [
provideServerRendering(),
provideExperimentalZonelessChangeDetection(),
provideRouter(routes),
provideServerRouting(serverRoutes),
provideServerRendering(withRoutes(serverRoutes)),
...extraProviders,
],
});

@ -129,6 +129,7 @@ ts_project(
data = RUNTIME_ASSETS,
module_name = "@angular-devkit/build-angular",
deps = [
":node_modules/@ampproject/remapping",
":node_modules/@angular-devkit/architect",
":node_modules/@angular-devkit/build-webpack",
":node_modules/@angular-devkit/core",
@ -151,17 +152,21 @@ ts_project(
":node_modules/license-webpack-plugin",
":node_modules/loader-utils",
":node_modules/mini-css-extract-plugin",
":node_modules/ng-packagr",
":node_modules/piscina",
":node_modules/postcss",
":node_modules/postcss-loader",
":node_modules/resolve-url-loader",
":node_modules/sass",
":node_modules/sass-loader",
":node_modules/source-map-loader",
":node_modules/source-map-support",
":node_modules/terser",
":node_modules/webpack",
":node_modules/webpack-dev-middleware",
":node_modules/webpack-dev-server",
":node_modules/webpack-merge",
":node_modules/webpack-subresource-integrity",
"//:node_modules/@ampproject/remapping",
"//:node_modules/@angular/common",
"//:node_modules/@angular/compiler-cli",
"//:node_modules/@angular/core",
@ -193,15 +198,10 @@ ts_project(
"//:node_modules/istanbul-lib-instrument",
"//:node_modules/karma",
"//:node_modules/karma-source-map-support",
"//:node_modules/ng-packagr",
"//:node_modules/open",
"//:node_modules/ora",
"//:node_modules/piscina",
"//:node_modules/postcss",
"//:node_modules/rxjs",
"//:node_modules/sass",
"//:node_modules/semver",
"//:node_modules/source-map-support",
"//:node_modules/tree-kill",
"//:node_modules/tslib",
"//:node_modules/typescript",

@ -66,8 +66,9 @@
"esbuild": "0.25.1"
},
"devDependencies": {
"undici": "7.5.0",
"@angular/ssr": "workspace:*"
"@angular/ssr": "workspace:*",
"ng-packagr": "20.0.0-next.1",
"undici": "7.5.0"
},
"peerDependencies": {
"@angular/compiler-cli": "0.0.0-ANGULAR-FW-PEER-DEP",

@ -162,7 +162,7 @@ describeServeBuilder(
// See: https://github.com/angular/angular-cli/pull/17624
// eslint-disable-next-line max-len
// executablePath: '/Users/<USERNAME>/git/angular-cli/node_modules/puppeteer/.local-chromium/mac-818858/chrome-mac/Chromium.app/Contents/MacOS/Chromium',
ignoreHTTPSErrors: true,
acceptInsecureCerts: true,
args: ['--no-sandbox', '--disable-gpu'],
});
});

@ -29,8 +29,8 @@ ts_project(
deps = [
":node_modules/@angular-devkit/core",
":node_modules/jsonc-parser",
":node_modules/magic-string",
"//:node_modules/@types/node",
"//:node_modules/magic-string",
"//:node_modules/rxjs",
],
)

@ -300,12 +300,12 @@ function addServerRoutingConfig(options: AppShellOptions, isStandalone: boolean)
/** max */ undefined,
/** recursive */ true,
).find(
(n) => ts.isIdentifier(n.expression) && n.expression.getText() === 'provideServerRouting',
(n) => ts.isIdentifier(n.expression) && n.expression.getText() === 'provideServerRendering',
);
if (!functionCall) {
throw new SchematicsException(
`Cannot find the "provideServerRouting" function call in "${configFilePath}".`,
`Cannot find the "provideServerRendering" function call in "${configFilePath}".`,
);
}

@ -123,11 +123,11 @@ describe('App Shell Schematic', () => {
expect(content).toMatch(/app-shell/);
});
it(`should update the 'provideServerRouting' call to include 'withAppShell'`, async () => {
it(`should update the 'provideServerRendering' call to include 'withAppShell'`, async () => {
const tree = await schematicRunner.runSchematic('app-shell', defaultOptions, appTree);
const content = tree.readContent('/projects/bar/src/app/app.config.server.ts');
expect(tags.oneLine`${content}`).toContain(
tags.oneLine`provideServerRouting(serverRoutes, withAppShell(AppShell))`,
tags.oneLine`provideServerRendering(withRoutes(serverRoutes), withAppShell(AppShell))`,
);
});

@ -2,6 +2,5 @@
* Public API Surface of <%= dasherize(name) %>
*/
export * from './lib/<%= dasherize(name) %>.service';
export * from './lib/<%= dasherize(name) %>';<% if (!standalone) { %>
export * from './lib/<%= dasherize(name) %>.module';<% } %>

@ -185,12 +185,6 @@ export default function (options: LibraryOptions): Rule {
standalone: options.standalone,
project: packageName,
}),
schematic('service', {
name: options.name,
flat: true,
path: sourceDir,
project: packageName,
}),
(_tree: Tree, context: SchematicContext) => {
if (!options.skipPackageJson && !options.skipInstall) {
context.addTask(new NodePackageInstallTask());

@ -57,8 +57,6 @@ describe('Library Schematic', () => {
'/projects/foo/src/my-index.ts',
'/projects/foo/src/lib/foo.spec.ts',
'/projects/foo/src/lib/foo.ts',
'/projects/foo/src/lib/foo.service.spec.ts',
'/projects/foo/src/lib/foo.service.ts',
]),
);
});
@ -102,8 +100,6 @@ describe('Library Schematic', () => {
'/some/other/directory/bar/src/my-index.ts',
'/some/other/directory/bar/src/lib/foo.spec.ts',
'/some/other/directory/bar/src/lib/foo.ts',
'/some/other/directory/bar/src/lib/foo.service.spec.ts',
'/some/other/directory/bar/src/lib/foo.service.ts',
]),
);
});
@ -207,10 +203,8 @@ describe('Library Schematic', () => {
const project = config.projects.pascalCasedName;
expect(project).toBeDefined();
expect(project.root).toEqual('projects/pascal-cased-name');
const svcContent = tree.readContent(
'/projects/pascal-cased-name/src/lib/pascal-cased-name.service.ts',
);
expect(svcContent).toMatch(/providedIn: 'root'/);
const svcContent = tree.readContent('/projects/pascal-cased-name/src/lib/pascal-cased-name.ts');
expect(svcContent).toContain('@Component');
});
describe(`update package.json`, () => {
@ -320,7 +314,6 @@ describe('Library Schematic', () => {
const pkgJsonPath = '/projects/myscope/mylib/package.json';
expect(tree.files).toContain(pkgJsonPath);
expect(tree.files).toContain('/projects/myscope/mylib/src/lib/mylib.service.ts');
expect(tree.files).toContain('/projects/myscope/mylib/src/lib/mylib.ts');
const pkgJson = JSON.parse(tree.readContent(pkgJsonPath));
@ -431,8 +424,6 @@ describe('Library Schematic', () => {
'/projects/foo/src/lib/foo.module.ts',
'/projects/foo/src/lib/foo.spec.ts',
'/projects/foo/src/lib/foo.ts',
'/projects/foo/src/lib/foo.service.spec.ts',
'/projects/foo/src/lib/foo.service.ts',
]),
);
});

@ -1,5 +1,15 @@
{
"schematics": {
"replace-provide-server-rendering-import": {
"version": "20.0.0",
"factory": "./replace-provide-server-rendering-import/migration",
"description": "Migrate imports of 'provideServerRendering' from '@angular/platform-server' to '@angular/ssr'."
},
"replace-provide-server-routing": {
"version": "20.0.0",
"factory": "./replace-provide-server-routing/migration",
"description": "Migrate 'provideServerRendering' to use 'withRoutes' and remove 'provideServerRouting' from '@angular/ssr'."
},
"use-application-builder": {
"version": "20.0.0",
"factory": "./use-application-builder/migration",

@ -0,0 +1,110 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
import { DirEntry, Rule } from '@angular-devkit/schematics';
import * as ts from '../../third_party/github.com/Microsoft/TypeScript/lib/typescript';
import { NodeDependencyType, addPackageJsonDependency } from '../../utility/dependencies';
import { latestVersions } from '../../utility/latest-versions';
function* visit(directory: DirEntry): IterableIterator<[fileName: string, contents: string]> {
for (const path of directory.subfiles) {
if (path.endsWith('.ts') && !path.endsWith('.d.ts')) {
const entry = directory.file(path);
if (entry) {
const content = entry.content;
if (
content.includes('provideServerRendering') &&
content.includes('@angular/platform-server')
) {
// Only need to rename the import so we can just string replacements.
yield [entry.path, content.toString()];
}
}
}
}
for (const path of directory.subdirs) {
if (path === 'node_modules' || path.startsWith('.')) {
continue;
}
yield* visit(directory.dir(path));
}
}
export default function (): Rule {
return async (tree) => {
addPackageJsonDependency(tree, {
name: '@angular/ssr',
version: latestVersions.AngularSSR,
type: NodeDependencyType.Default,
overwrite: false,
});
for (const [filePath, content] of visit(tree.root)) {
let updatedContent = content;
const ssrImports = new Set<string>();
const platformServerImports = new Set<string>();
const sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true);
sourceFile.forEachChild((node) => {
if (ts.isImportDeclaration(node)) {
const moduleSpecifier = node.moduleSpecifier.getText(sourceFile);
if (moduleSpecifier.includes('@angular/platform-server')) {
const importClause = node.importClause;
if (
importClause &&
importClause.namedBindings &&
ts.isNamedImports(importClause.namedBindings)
) {
const namedImports = importClause.namedBindings.elements.map((e) =>
e.getText(sourceFile),
);
namedImports.forEach((importName) => {
if (importName === 'provideServerRendering') {
ssrImports.add(importName);
} else {
platformServerImports.add(importName);
}
});
}
updatedContent = updatedContent.replace(node.getFullText(sourceFile), '');
} else if (moduleSpecifier.includes('@angular/ssr')) {
const importClause = node.importClause;
if (
importClause &&
importClause.namedBindings &&
ts.isNamedImports(importClause.namedBindings)
) {
importClause.namedBindings.elements.forEach((e) => {
ssrImports.add(e.getText(sourceFile));
});
}
updatedContent = updatedContent.replace(node.getFullText(sourceFile), '');
}
}
});
if (platformServerImports.size > 0) {
updatedContent =
`import { ${Array.from(platformServerImports).sort().join(', ')} } from '@angular/platform-server';\n` +
updatedContent;
}
if (ssrImports.size > 0) {
updatedContent =
`import { ${Array.from(ssrImports).sort().join(', ')} } from '@angular/ssr';\n` +
updatedContent;
}
if (content !== updatedContent) {
tree.overwrite(filePath, updatedContent);
}
}
};
}

@ -0,0 +1,75 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
import { EmptyTree } from '@angular-devkit/schematics';
import { SchematicTestRunner, UnitTestTree } from '@angular-devkit/schematics/testing';
describe(`Migration to use the 'provideServerRendering' from '@angular/ssr'`, () => {
const schematicRunner = new SchematicTestRunner(
'migrations',
require.resolve('../migration-collection.json'),
);
let tree: UnitTestTree;
const schematicName = 'replace-provide-server-rendering-import';
beforeEach(() => {
tree = new UnitTestTree(new EmptyTree());
tree.create(
'/package.json',
JSON.stringify({
dependencies: {
'@angular/ssr': '0.0.0',
},
}),
);
});
it('should replace provideServerRendering with @angular/ssr and keep other imports', async () => {
tree.create(
'test.ts',
`import { provideServerRendering, otherFunction } from '@angular/platform-server';`,
);
const newTree = await schematicRunner.runSchematic(schematicName, {}, tree);
const content = newTree.readContent('test.ts');
expect(content).toContain("import { provideServerRendering } from '@angular/ssr';");
expect(content).toContain("import { otherFunction } from '@angular/platform-server';");
});
it('should not replace provideServerRendering that is imported from @angular/ssr', async () => {
tree.create(
'test.ts',
`
import { otherFunction } from '@angular/platform-server';
import { provideServerRendering, provideServerRouting } from '@angular/ssr';
`,
);
const newTree = await schematicRunner.runSchematic(schematicName, {}, tree);
const content = newTree.readContent('test.ts');
expect(content).toContain(
"import { provideServerRendering, provideServerRouting } from '@angular/ssr';",
);
expect(content).toContain("import { otherFunction } from '@angular/platform-server';");
});
it('should merge with existing @angular/ssr imports', async () => {
tree.create(
'test.ts',
`
import { provideServerRouting } from '@angular/ssr';
import { provideServerRendering } from '@angular/platform-server';
`,
);
const newTree = await schematicRunner.runSchematic(schematicName, {}, tree);
const content = newTree.readContent('test.ts');
expect(content).toContain(
"import { provideServerRendering, provideServerRouting } from '@angular/ssr';",
);
expect(content.match(/@angular\/ssr/g) || []).toHaveSize(1);
});
});

@ -0,0 +1,114 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
import { DirEntry, Rule } from '@angular-devkit/schematics';
import * as ts from '../../third_party/github.com/Microsoft/TypeScript/lib/typescript';
import { getPackageJsonDependency } from '../../utility/dependencies';
function* visit(directory: DirEntry): IterableIterator<[fileName: string, contents: string]> {
for (const path of directory.subfiles) {
if (path.endsWith('.ts') && !path.endsWith('.d.ts')) {
const entry = directory.file(path);
if (entry) {
const content = entry.content;
if (content.includes('provideServerRouting') && content.includes('@angular/ssr')) {
// Only need to rename the import so we can just string replacements.
yield [entry.path, content.toString()];
}
}
}
}
for (const path of directory.subdirs) {
if (path === 'node_modules' || path.startsWith('.')) {
continue;
}
yield* visit(directory.dir(path));
}
}
export default function (): Rule {
return async (tree) => {
if (!getPackageJsonDependency(tree, '@angular/ssr')) {
return;
}
for (const [filePath, content] of visit(tree.root)) {
const recorder = tree.beginUpdate(filePath);
const sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true);
function visit(node: ts.Node) {
if (
ts.isPropertyAssignment(node) &&
ts.isIdentifier(node.name) &&
node.name.text === 'providers' &&
ts.isArrayLiteralExpression(node.initializer)
) {
const providersArray = node.initializer;
const newProviders = providersArray.elements
.filter((el) => {
return !(
ts.isCallExpression(el) &&
ts.isIdentifier(el.expression) &&
el.expression.text === 'provideServerRendering'
);
})
.map((el) => {
if (
ts.isCallExpression(el) &&
ts.isIdentifier(el.expression) &&
el.expression.text === 'provideServerRouting'
) {
const [withRouteVal, ...others] = el.arguments.map((arg) => arg.getText());
return `provideServerRendering(withRoutes(${withRouteVal})${others.length ? ', ' + others.join(', ') : ''})`;
}
return el.getText();
});
// Update the 'providers' array in the source file
recorder.remove(providersArray.getStart(), providersArray.getWidth());
recorder.insertRight(providersArray.getStart(), `[${newProviders.join(', ')}]`);
}
ts.forEachChild(node, visit);
}
// Visit all nodes to update 'providers'
visit(sourceFile);
// Update imports by removing 'provideServerRouting'
const importDecl = sourceFile.statements.find(
(stmt) =>
ts.isImportDeclaration(stmt) &&
ts.isStringLiteral(stmt.moduleSpecifier) &&
stmt.moduleSpecifier.text === '@angular/ssr',
) as ts.ImportDeclaration | undefined;
if (importDecl?.importClause?.namedBindings) {
const namedBindings = importDecl?.importClause.namedBindings;
if (ts.isNamedImports(namedBindings)) {
const elements = namedBindings.elements;
const updatedElements = elements
.map((el) => el.getText())
.filter((x) => x !== 'provideServerRouting');
updatedElements.push('withRoutes');
recorder.remove(namedBindings.getStart(), namedBindings.getWidth());
recorder.insertLeft(namedBindings.getStart(), `{ ${updatedElements.sort().join(', ')} }`);
}
}
tree.commitUpdate(recorder);
}
};
}

@ -0,0 +1,89 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
import { EmptyTree } from '@angular-devkit/schematics';
import { SchematicTestRunner, UnitTestTree } from '@angular-devkit/schematics/testing';
describe(`Migration to replace 'provideServerRouting' with 'provideServerRendering' from '@angular/ssr'`, () => {
const schematicRunner = new SchematicTestRunner(
'migrations',
require.resolve('../migration-collection.json'),
);
const schematicName = 'replace-provide-server-routing';
let tree: UnitTestTree;
beforeEach(async () => {
tree = new UnitTestTree(new EmptyTree());
tree.create(
'/package.json',
JSON.stringify({
dependencies: {
'@angular/ssr': '0.0.0',
},
}),
);
tree.create(
'src/app/app.config.ts',
`
import { ApplicationConfig } from '@angular/core';
import { provideServerRendering, provideServerRouting } from '@angular/ssr';
import { serverRoutes } from './app.routes';
const serverConfig: ApplicationConfig = {
providers: [
provideServerRendering(),
provideServerRouting(serverRoutes)
]
};
`,
);
});
it('should add "withRoutes" to the import statement', async () => {
const newTree = await schematicRunner.runSchematic(schematicName, {}, tree);
const content = newTree.readContent('src/app/app.config.ts');
expect(content).toContain(`import { provideServerRendering, withRoutes } from '@angular/ssr';`);
});
it('should remove "provideServerRouting" and update "provideServerRendering"', async () => {
const newTree = await schematicRunner.runSchematic(schematicName, {}, tree);
const content = newTree.readContent('src/app/app.config.ts');
expect(content).toContain(`providers: [provideServerRendering(withRoutes(serverRoutes))]`);
expect(content).not.toContain(`provideServerRouting(serverRoutes)`);
});
it('should correctly handle provideServerRouting with extra arguments', async () => {
tree.overwrite(
'src/app/app.config.ts',
`
import { ApplicationConfig } from '@angular/core';
import { provideServerRendering, provideServerRouting } from '@angular/ssr';
import { serverRoutes } from './app.routes';
const serverConfig: ApplicationConfig = {
providers: [
provideServerRendering(),
provideServerRouting(serverRoutes, withAppShell(AppShellComponent))
]
};
`,
);
const newTree = await schematicRunner.runSchematic(schematicName, {}, tree);
const content = newTree.readContent('src/app/app.config.ts');
expect(content).toContain(
`providers: [provideServerRendering(withRoutes(serverRoutes), withAppShell(AppShellComponent))]`,
);
expect(content).not.toContain(`provideServerRouting(serverRoutes)`);
});
});

@ -1,13 +1,12 @@
import { NgModule } from '@angular/core';
import { ServerModule } from '@angular/platform-server';
import { provideServerRouting } from '@angular/ssr';
import { provideServerRendering, withRoutes } from '@angular/ssr';
import { App } from './app';
import { AppModule } from './app.module';
import { serverRoutes } from './app.routes.server';
@NgModule({
imports: [AppModule, ServerModule],
providers: [provideServerRouting(serverRoutes)],
imports: [AppModule],
providers: [provideServerRendering(withRoutes(serverRoutes))],
bootstrap: [App],
})
export class AppServerModule {}

@ -1,13 +1,11 @@
import { mergeApplicationConfig, ApplicationConfig } from '@angular/core';
import { provideServerRendering } from '@angular/platform-server';
import { provideServerRouting } from '@angular/ssr';
import { provideServerRendering, withRoutes } from '@angular/ssr';
import { appConfig } from './app.config';
import { serverRoutes } from './app.routes.server';
const serverConfig: ApplicationConfig = {
providers: [
provideServerRendering(),
provideServerRouting(serverRoutes)
provideServerRendering(withRoutes(serverRoutes))
]
};

@ -1,5 +1,5 @@
import { mergeApplicationConfig, ApplicationConfig } from '@angular/core';
import { provideServerRendering } from '@angular/platform-server';
import { provideServerRendering } from '@angular/ssr';
import { appConfig } from './app.config';
const serverConfig: ApplicationConfig = {

@ -0,0 +1,16 @@
import { TestBed } from '@angular/core/testing';
import { <%= classify(name) %><%= classify(type) %> } from './<%= dasherize(name) %><%= type ? '.' + dasherize(type) : '' %>';
describe('<%= classify(name) %><%= classify(type) %>', () => {
let service: <%= classify(name) %><%= classify(type) %>;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(<%= classify(name) %><%= classify(type) %>);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});

@ -3,7 +3,7 @@ import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class <%= classify(name) %>Service {
export class <%= classify(name) %><%= classify(type) %> {
constructor() { }
}

@ -1,16 +0,0 @@
import { TestBed } from '@angular/core/testing';
import { <%= classify(name) %>Service } from './<%= dasherize(name) %>.service';
describe('<%= classify(name) %>Service', () => {
let service: <%= classify(name) %>Service;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(<%= classify(name) %>Service);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});

@ -15,6 +15,9 @@ export default function (options: ServiceOptions): Rule {
const flat = options.flat;
options.flat = true;
// Schematic templates require a defined type value
options.type ??= '';
return generateFromFiles(options, {
'if-flat': (s: string) => (flat ? '' : s),
});

@ -46,15 +46,15 @@ describe('Service Schematic', () => {
const tree = await schematicRunner.runSchematic('service', options, appTree);
const files = tree.files;
expect(files).toContain('/projects/bar/src/app/foo/foo.service.spec.ts');
expect(files).toContain('/projects/bar/src/app/foo/foo.service.ts');
expect(files).toContain('/projects/bar/src/app/foo/foo.spec.ts');
expect(files).toContain('/projects/bar/src/app/foo/foo.ts');
});
it('service should be tree-shakeable', async () => {
const options = { ...defaultOptions };
const tree = await schematicRunner.runSchematic('service', options, appTree);
const content = tree.readContent('/projects/bar/src/app/foo/foo.service.ts');
const content = tree.readContent('/projects/bar/src/app/foo/foo.ts');
expect(content).toMatch(/providedIn: 'root'/);
});
@ -63,8 +63,8 @@ describe('Service Schematic', () => {
const tree = await schematicRunner.runSchematic('service', options, appTree);
const files = tree.files;
expect(files).toContain('/projects/bar/src/app/foo/foo.service.ts');
expect(files).not.toContain('/projects/bar/src/app/foo/foo.service.spec.ts');
expect(files).toContain('/projects/bar/src/app/foo/foo.ts');
expect(files).not.toContain('/projects/bar/src/app/foo/foo.spec.ts');
});
it('should respect the sourceRoot value', async () => {
@ -72,6 +72,24 @@ describe('Service Schematic', () => {
config.projects.bar.sourceRoot = 'projects/bar/custom';
appTree.overwrite('/angular.json', JSON.stringify(config, null, 2));
appTree = await schematicRunner.runSchematic('service', defaultOptions, appTree);
expect(appTree.files).toContain('/projects/bar/custom/app/foo/foo.service.ts');
expect(appTree.files).toContain('/projects/bar/custom/app/foo/foo.ts');
});
it('should respect the type option', async () => {
const options = { ...defaultOptions, type: 'Service' };
const tree = await schematicRunner.runSchematic('service', options, appTree);
const content = tree.readContent('/projects/bar/src/app/foo/foo.service.ts');
const testContent = tree.readContent('/projects/bar/src/app/foo/foo.service.spec.ts');
expect(content).toContain('export class FooService');
expect(testContent).toContain("describe('FooService'");
});
it('should allow empty string in the type option', async () => {
const options = { ...defaultOptions, type: '' };
const tree = await schematicRunner.runSchematic('service', options, appTree);
const content = tree.readContent('/projects/bar/src/app/foo/foo.ts');
const testContent = tree.readContent('/projects/bar/src/app/foo/foo.spec.ts');
expect(content).toContain('export class Foo');
expect(testContent).toContain("describe('Foo'");
});
});

@ -39,6 +39,10 @@
"type": "boolean",
"description": "Skip the generation of a unit test file `spec.ts` for the service.",
"default": false
},
"type": {
"type": "string",
"description": "Append a custom type to the service's filename. For example, if you set the type to `service`, the file will be named `my-service.service.ts`."
}
},
"required": ["name", "project"]

@ -7,12 +7,14 @@
*/
import {
FileOperator,
Rule,
Tree,
apply,
applyTemplates,
chain,
filter,
forEach,
mergeWith,
move,
noop,
@ -31,6 +33,7 @@ export interface GenerateFromFilesOptions {
project: string;
skipTests?: boolean;
templateFilesDirectory?: string;
type?: string;
}
export function generateFromFiles(
@ -56,6 +59,16 @@ export function generateFromFiles(
...options,
...extraTemplateValues,
}),
!options.type
? forEach(((file) => {
return file.path.includes('..')
? {
content: file.content,
path: file.path.replace('..', '.'),
}
: file;
}) as FileOperator)
: noop(),
move(parsedPath.path + (options.flat ? '' : '/' + strings.dasherize(options.name))),
]);

185
pnpm-lock.yaml generated

@ -14,9 +14,6 @@ importers:
.:
devDependencies:
'@ampproject/remapping':
specifier: 2.3.0
version: 2.3.0
'@angular/animations':
specifier: 20.0.0-next.2
version: 20.0.0-next.2(@angular/core@20.0.0-next.2)
@ -278,27 +275,12 @@ importers:
karma-source-map-support:
specifier: 1.4.0
version: 1.4.0
less:
specifier: 4.2.2
version: 4.2.2
listr2:
specifier: 8.2.5
version: 8.2.5
lmdb:
specifier: 3.2.6
version: 3.2.6
lodash:
specifier: ^4.17.21
version: 4.17.21
magic-string:
specifier: 0.30.17
version: 0.30.17
mrmime:
specifier: 2.0.1
version: 2.0.1
ng-packagr:
specifier: 20.0.0-next.1
version: 20.0.0-next.1(@angular/compiler-cli@20.0.0-next.2(@angular/compiler@20.0.0-next.2)(typescript@5.8.2))(tslib@2.8.1)(typescript@5.8.2)
npm:
specifier: ^11.0.0
version: 11.2.0
@ -308,15 +290,6 @@ importers:
ora:
specifier: 5.4.1
version: 5.4.1
parse5-html-rewriting-stream:
specifier: 7.0.0
version: 7.0.0
piscina:
specifier: 4.9.0
version: 4.9.0
postcss:
specifier: 8.5.3
version: 8.5.3
prettier:
specifier: ^3.0.0
version: 3.5.3
@ -324,8 +297,8 @@ importers:
specifier: ~7.0.0
version: 7.0.0
puppeteer:
specifier: 18.2.1
version: 18.2.1(encoding@0.1.13)
specifier: 24.4.0
version: 24.4.0(typescript@5.8.2)
quicktype-core:
specifier: 23.0.171
version: 23.0.171(encoding@0.1.13)
@ -341,9 +314,6 @@ importers:
rxjs:
specifier: 7.8.2
version: 7.8.2
sass:
specifier: 1.85.1
version: 1.85.1
semver:
specifier: 7.7.1
version: 7.7.1
@ -359,9 +329,6 @@ importers:
tar:
specifier: ^7.0.0
version: 7.4.3
tinyglobby:
specifier: 0.2.12
version: 0.2.12
tree-kill:
specifier: 1.2.2
version: 1.2.2
@ -386,9 +353,6 @@ importers:
verdaccio-auth-memory:
specifier: ^10.0.0
version: 10.2.2
watchpack:
specifier: 2.4.2
version: 2.4.2
yargs-parser:
specifier: 21.1.1
version: 21.1.1
@ -510,6 +474,15 @@ importers:
'@angular/ssr':
specifier: workspace:*
version: link:../ssr
less:
specifier: 4.2.2
version: 4.2.2
ng-packagr:
specifier: 20.0.0-next.1
version: 20.0.0-next.1(@angular/compiler-cli@20.0.0-next.2(@angular/compiler@20.0.0-next.2)(typescript@5.8.2))(tslib@2.8.1)(typescript@5.8.2)
postcss:
specifier: 8.5.3
version: 8.5.3
packages/angular/cli:
dependencies:
@ -817,6 +790,9 @@ importers:
'@angular/ssr':
specifier: workspace:*
version: link:../../angular/ssr
ng-packagr:
specifier: 20.0.0-next.1
version: 20.0.0-next.1(@angular/compiler-cli@20.0.0-next.2(@angular/compiler@20.0.0-next.2)(typescript@5.8.2))(tslib@2.8.1)(typescript@5.8.2)
undici:
specifier: 7.5.0
version: 7.5.0
@ -3689,9 +3665,6 @@ packages:
resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==}
engines: {node: '>= 14.16.0'}
chownr@1.1.4:
resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==}
chownr@2.0.0:
resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==}
engines: {node: '>=10'}
@ -3907,9 +3880,6 @@ packages:
create-require@1.1.1:
resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==}
cross-fetch@3.1.5:
resolution: {integrity: sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==}
cross-fetch@4.1.0:
resolution: {integrity: sha512-uKm5PU+MHTootlWEY+mZ4vvXoCn4fLQxT9dSc1sXVMSFkINTJVN8cAQROpwcKm8bJ/c7rgZVIBWzH5T78sNZZw==}
@ -4129,9 +4099,6 @@ packages:
engines: {node: '>= 0.8.0'}
hasBin: true
devtools-protocol@0.0.1045489:
resolution: {integrity: sha512-D+PTmWulkuQW4D1NTiCRCFxF7pQPn0hgp4YyX4wAQ6xYXKOadSWPR3ENGDQ47MW/Ewc9v2rpC/UEEGahgBYpSQ==}
devtools-protocol@0.0.1413902:
resolution: {integrity: sha512-yRtvFD8Oyk7C9Os3GmnFZLu53yAfsnyw1s+mLmHHUK0GQEc9zthHWvS1r67Zqzm5t7v56PILHIVZ7kmFMaL2yQ==}
@ -4660,9 +4627,6 @@ packages:
resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==}
engines: {node: '>= 0.6'}
fs-constants@1.0.0:
resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==}
fs-extra@3.0.1:
resolution: {integrity: sha512-V3Z3WZWVUYd8hoCL5xfXJCaHWYzmtwW5XWYSlLgERi8PWd8bx1kUHUk8L1BT57e49oKnDDD180mjfrHc1yA9rg==}
@ -5880,9 +5844,6 @@ packages:
mitt@3.0.1:
resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==}
mkdirp-classic@0.5.3:
resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==}
mkdirp@0.5.6:
resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==}
hasBin: true
@ -6596,18 +6557,14 @@ packages:
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
engines: {node: '>=6'}
puppeteer-core@18.2.1:
resolution: {integrity: sha512-MRtTAZfQTluz3U2oU/X2VqVWPcR1+94nbA2V6ZrSZRVEwLqZ8eclZ551qGFQD/vD2PYqHJwWOW/fpC721uznVw==}
engines: {node: '>=14.1.0'}
puppeteer-core@24.4.0:
resolution: {integrity: sha512-eFw66gCnWo0X8Hyf9KxxJtms7a61NJVMiSaWfItsFPzFBsjsWdmcNlBdsA1WVwln6neoHhsG+uTVesKmTREn/g==}
engines: {node: '>=18'}
puppeteer@18.2.1:
resolution: {integrity: sha512-7+UhmYa7wxPh2oMRwA++k8UGVDxh3YdWFB52r9C3tM81T6BU7cuusUSxImz0GEYSOYUKk/YzIhkQ6+vc0gHbxQ==}
engines: {node: '>=14.1.0'}
deprecated: < 22.8.2 is no longer supported
puppeteer@24.4.0:
resolution: {integrity: sha512-E4JhJzjS8AAI+6N/b+Utwarhz6zWl3+MR725fal+s3UlOlX2eWdsvYYU+Q5bXMjs9eZEGkNQroLkn7j11s2k1Q==}
engines: {node: '>=18'}
hasBin: true
q@1.4.1:
resolution: {integrity: sha512-/CdEdaw49VZVmyIDGUQKDDT53c7qBkO6g5CefWz91Ae+l4+cRtcDYwMTXh6me4O8TMldeGHG3N2Bl84V78Ywbg==}
@ -7287,16 +7244,9 @@ packages:
resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==}
engines: {node: '>=6'}
tar-fs@2.1.1:
resolution: {integrity: sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==}
tar-fs@3.0.8:
resolution: {integrity: sha512-ZoROL70jptorGAlgAYiLoBLItEKw/fUxg9BSYK/dF/GAGYFJOJJJMvjPAKDJraCXFwadD456FCuvLWgfhMsPwg==}
tar-stream@2.2.0:
resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==}
engines: {node: '>=6'}
tar-stream@3.1.7:
resolution: {integrity: sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==}
@ -7526,9 +7476,6 @@ packages:
resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==}
engines: {node: '>= 0.4'}
unbzip2-stream@1.4.3:
resolution: {integrity: sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==}
undici-types@6.19.8:
resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==}
@ -7902,18 +7849,6 @@ packages:
utf-8-validate:
optional: true
ws@8.9.0:
resolution: {integrity: sha512-Ja7nszREasGaYUYCI2k4lCKIRTt+y7XuqVoHR44YpI49TtryyqbqvDMn5eqfW7e6HzTukDRIsXqzVHScqRcafg==}
engines: {node: '>=10.0.0'}
peerDependencies:
bufferutil: ^4.0.1
utf-8-validate: ^5.0.2
peerDependenciesMeta:
bufferutil:
optional: true
utf-8-validate:
optional: true
xhr2@0.2.1:
resolution: {integrity: sha512-sID0rrVCqkVNUn8t6xuv9+6FViXjUVXq8H5rWOH2rz9fDNQEd4g0EA2XlcEdJXRz5BMEn4O1pJFdT+z4YHhoWw==}
engines: {node: '>= 6'}
@ -11221,8 +11156,6 @@ snapshots:
dependencies:
readdirp: 4.1.2
chownr@1.1.4: {}
chownr@2.0.0: {}
chownr@3.0.0: {}
@ -11453,12 +11386,6 @@ snapshots:
create-require@1.1.1: {}
cross-fetch@3.1.5(encoding@0.1.13):
dependencies:
node-fetch: 2.6.7(encoding@0.1.13)
transitivePeerDependencies:
- encoding
cross-fetch@4.1.0(encoding@0.1.13):
dependencies:
node-fetch: 2.7.0(encoding@0.1.13)
@ -11636,14 +11563,13 @@ snapshots:
detect-libc@1.0.3:
optional: true
detect-libc@2.0.3: {}
detect-libc@2.0.3:
optional: true
detect-node@2.1.0: {}
dev-ip@1.0.1: {}
devtools-protocol@0.0.1045489: {}
devtools-protocol@0.0.1413902: {}
di@0.0.1: {}
@ -12346,8 +12272,6 @@ snapshots:
fresh@0.5.2: {}
fs-constants@1.0.0: {}
fs-extra@3.0.1:
dependencies:
graceful-fs: 4.2.11
@ -13457,6 +13381,7 @@ snapshots:
'@lmdb/lmdb-linux-arm64': 3.2.6
'@lmdb/lmdb-linux-x64': 3.2.6
'@lmdb/lmdb-win32-x64': 3.2.6
optional: true
loader-runner@4.3.0: {}
@ -13704,8 +13629,6 @@ snapshots:
mitt@3.0.1: {}
mkdirp-classic@0.5.3: {}
mkdirp@0.5.6:
dependencies:
minimist: 1.2.8
@ -13737,6 +13660,7 @@ snapshots:
msgpackr@1.11.2:
optionalDependencies:
msgpackr-extract: 3.0.3
optional: true
multicast-dns@7.2.5:
dependencies:
@ -13799,7 +13723,8 @@ snapshots:
nice-try@1.0.5: {}
node-addon-api@6.1.0: {}
node-addon-api@6.1.0:
optional: true
node-addon-api@7.1.1:
optional: true
@ -13831,6 +13756,7 @@ snapshots:
node-gyp-build-optional-packages@5.2.2:
dependencies:
detect-libc: 2.0.3
optional: true
node-gyp@11.1.0:
dependencies:
@ -14017,7 +13943,8 @@ snapshots:
strip-ansi: 6.0.1
wcwidth: 1.0.1
ordered-binary@1.5.3: {}
ordered-binary@1.5.3:
optional: true
os-tmpdir@1.0.2: {}
@ -14402,24 +14329,6 @@ snapshots:
punycode@2.3.1: {}
puppeteer-core@18.2.1(encoding@0.1.13):
dependencies:
cross-fetch: 3.1.5(encoding@0.1.13)
debug: 4.3.4
devtools-protocol: 0.0.1045489
extract-zip: 2.0.1
https-proxy-agent: 5.0.1(supports-color@10.0.0)
proxy-from-env: 1.1.0
rimraf: 3.0.2
tar-fs: 2.1.1
unbzip2-stream: 1.4.3
ws: 8.9.0
transitivePeerDependencies:
- bufferutil
- encoding
- supports-color
- utf-8-validate
puppeteer-core@24.4.0:
dependencies:
'@puppeteer/browsers': 2.8.0
@ -14434,16 +14343,19 @@ snapshots:
- supports-color
- utf-8-validate
puppeteer@18.2.1(encoding@0.1.13):
puppeteer@24.4.0(typescript@5.8.2):
dependencies:
https-proxy-agent: 5.0.1(supports-color@10.0.0)
progress: 2.0.3
proxy-from-env: 1.1.0
puppeteer-core: 18.2.1(encoding@0.1.13)
'@puppeteer/browsers': 2.8.0
chromium-bidi: 2.1.2(devtools-protocol@0.0.1413902)
cosmiconfig: 9.0.0(typescript@5.8.2)
devtools-protocol: 0.0.1413902
puppeteer-core: 24.4.0
typed-query-selector: 2.12.0
transitivePeerDependencies:
- bare-buffer
- bufferutil
- encoding
- supports-color
- typescript
- utf-8-validate
q@1.4.1: {}
@ -15300,13 +15212,6 @@ snapshots:
tapable@2.2.1: {}
tar-fs@2.1.1:
dependencies:
chownr: 1.1.4
mkdirp-classic: 0.5.3
pump: 3.0.2
tar-stream: 2.2.0
tar-fs@3.0.8:
dependencies:
pump: 3.0.2
@ -15317,14 +15222,6 @@ snapshots:
transitivePeerDependencies:
- bare-buffer
tar-stream@2.2.0:
dependencies:
bl: 4.1.0
end-of-stream: 1.4.4
fs-constants: 1.0.0
inherits: 2.0.4
readable-stream: 3.6.2
tar-stream@3.1.7:
dependencies:
b4a: 1.6.7
@ -15572,11 +15469,6 @@ snapshots:
has-symbols: 1.1.0
which-boxed-primitive: 1.1.1
unbzip2-stream@1.4.3:
dependencies:
buffer: 5.7.1
through: 2.3.8
undici-types@6.19.8: {}
undici@7.5.0: {}
@ -15773,7 +15665,8 @@ snapshots:
dependencies:
defaults: 1.0.4
weak-lru-cache@1.2.2: {}
weak-lru-cache@1.2.2:
optional: true
web-streams-polyfill@3.3.3: {}
@ -16004,8 +15897,6 @@ snapshots:
ws@8.18.1: {}
ws@8.9.0: {}
xhr2@0.2.1: {}
xml2js@0.4.23:

@ -17,7 +17,7 @@ export async function libraryConsumptionSetup(): Promise<void> {
export class MyLibComponent {}`,
'./src/app/app.ts': `
import { Component } from '@angular/core';
import { MyLibService, MyLibComponent } from 'my-lib';
import { MyLibComponent } from 'my-lib';
@Component({
standalone: true,
@ -28,8 +28,7 @@ export async function libraryConsumptionSetup(): Promise<void> {
export class App {
title = 'test-project';
constructor(myLibService: MyLibService) {
console.log(myLibService);
constructor() {
}
}
`,

@ -9,8 +9,8 @@ export default function () {
return (
ng('generate', 'service', 'test-service')
.then(() => expectFileToExist(serviceDir))
.then(() => expectFileToExist(join(serviceDir, 'test-service.service.ts')))
.then(() => expectFileToExist(join(serviceDir, 'test-service.service.spec.ts')))
.then(() => expectFileToExist(join(serviceDir, 'test-service.ts')))
.then(() => expectFileToExist(join(serviceDir, 'test-service.spec.ts')))
// Try to run the unit tests.
.then(() => ng('test', '--watch=false'))

@ -2,10 +2,10 @@ import { prependToFile, replaceInFile } from '../../utils/fs';
import { ng } from '../../utils/process';
export default async function () {
await ng('generate', 'service', 'user');
await ng('generate', 'service', 'user-service');
// Update the application to use the new service
await prependToFile('src/app/app.ts', "import { UserService } from './user.service';");
await prependToFile('src/app/app.ts', "import { UserService } from './user-service';");
await replaceInFile(
'src/app/app.ts',

@ -257,10 +257,8 @@ Promise.all([findFreePort(), findFreePort(), findPackageTars()])
console.log(`Current Directory: ${process.cwd()}`);
console.log('Will loop forever while you debug... CTRL-C to quit.');
/* eslint-disable no-constant-condition */
while (1) {
// That's right!
}
// Wait forever until user explicitly cancels.
await new Promise(() => {});
}
process.exitCode = 1;