From 9849b94e9f98775af395513e52cd7911851cc314 Mon Sep 17 00:00:00 2001 From: Charles Lyding <19598772+clydin@users.noreply.github.com> Date: Thu, 23 Jan 2025 19:30:23 -0500 Subject: [PATCH 1/4] fix(@angular/build): update vite to version 5.4.14 Version update from 5.4.6 to address advisory https://github.com/vitejs/vite/security/advisories/GHSA-vg6x-rcgg-rjx6 Vite version 5.4.12+, which is now used by the Angular CLI with the `application`/`browser-esbuild` builders, contains a potentially breaking change for some development setups. Examples of such setups include those that use reverse proxies or custom host names during development. The change within a patch release was made by Vite to address a security vulnerability. For projects that directly access the development server via `localhost`, no changes should be needed. However, some development setups may now need to adjust the `allowedHosts` development server option. This option can include an array of host names that are allowed to communicate with the development server. The option sets the corresponding Vite option within the Angular CLI. For more information on the option and its specific behavior, please see the Vite documentation located here: https://vite.dev/config/server-options.html#server-allowedhosts The following is an example of the configuration option allowing `example.com`: ``` "serve": { "builder": "@angular-devkit/build-angular:dev-server", "options": { "allowedHosts": ["example.com"] }, ``` --- goldens/public-api/angular/build/index.api.md | 1 + package.json | 2 +- packages/angular/build/package.json | 2 +- .../build/src/builders/dev-server/options.ts | 2 + .../build/src/builders/dev-server/schema.json | 17 ++++ .../dev-server/tests/execute-fetch.ts | 48 ++++++++++- .../tests/options/allowed-hosts_spec.ts | 80 +++++++++++++++++++ .../src/builders/dev-server/vite-server.ts | 1 + .../src/builders/dev-server/builder.ts | 11 ++- .../src/builders/dev-server/schema.json | 4 +- yarn.lock | 47 ++++++++++- 11 files changed, 207 insertions(+), 8 deletions(-) create mode 100644 packages/angular/build/src/builders/dev-server/tests/options/allowed-hosts_spec.ts diff --git a/goldens/public-api/angular/build/index.api.md b/goldens/public-api/angular/build/index.api.md index c3efe5dda318..443a55279ab2 100644 --- a/goldens/public-api/angular/build/index.api.md +++ b/goldens/public-api/angular/build/index.api.md @@ -110,6 +110,7 @@ export enum BuildOutputFileType { // @public export interface DevServerBuilderOptions { + allowedHosts?: AllowedHosts; buildTarget: string; headers?: { [key: string]: string; diff --git a/package.json b/package.json index 29acd802c7d0..a296832c7752 100644 --- a/package.json +++ b/package.json @@ -201,7 +201,7 @@ "undici": "6.19.7", "verdaccio": "5.32.1", "verdaccio-auth-memory": "^10.0.0", - "vite": "5.4.6", + "vite": "5.4.14", "watchpack": "2.4.1", "webpack": "5.94.0", "webpack-dev-middleware": "7.4.2", diff --git a/packages/angular/build/package.json b/packages/angular/build/package.json index f8b94876401e..0f420f8f4ca4 100644 --- a/packages/angular/build/package.json +++ b/packages/angular/build/package.json @@ -41,7 +41,7 @@ "rollup": "4.22.4", "sass": "1.77.6", "semver": "7.6.3", - "vite": "5.4.6", + "vite": "5.4.14", "watchpack": "2.4.1" }, "peerDependencies": { diff --git a/packages/angular/build/src/builders/dev-server/options.ts b/packages/angular/build/src/builders/dev-server/options.ts index 080e168699bc..cb6dd438ad6f 100644 --- a/packages/angular/build/src/builders/dev-server/options.ts +++ b/packages/angular/build/src/builders/dev-server/options.ts @@ -103,6 +103,7 @@ export async function normalizeOptions( sslCert, sslKey, prebundle, + allowedHosts, } = options; // Return all the normalized options @@ -128,5 +129,6 @@ export async function normalizeOptions( // Prebundling defaults to true but requires caching to function prebundle: cacheOptions.enabled && !optimization.scripts && prebundle, inspect, + allowedHosts: allowedHosts ? allowedHosts : [], }; } diff --git a/packages/angular/build/src/builders/dev-server/schema.json b/packages/angular/build/src/builders/dev-server/schema.json index 3adce45eb71a..775ce72ea4d4 100644 --- a/packages/angular/build/src/builders/dev-server/schema.json +++ b/packages/angular/build/src/builders/dev-server/schema.json @@ -36,6 +36,23 @@ "type": "string", "description": "SSL certificate to use for serving HTTPS." }, + "allowedHosts": { + "description": "The hosts that can access the development server. This option sets the Vite option of the same name. For further details: https://vite.dev/config/server-options.html#server-allowedhosts", + "default": [], + "oneOf": [ + { + "type": "array", + "description": "List of hosts that are allowed to access the development server.", + "items": { + "type": "string" + } + }, + { + "type": "boolean", + "description": "Indicates that all hosts are allowed. This is not recommended and a security risk." + } + ] + }, "headers": { "type": "object", "description": "Custom HTTP headers to be added to all responses.", diff --git a/packages/angular/build/src/builders/dev-server/tests/execute-fetch.ts b/packages/angular/build/src/builders/dev-server/tests/execute-fetch.ts index 3bb731a6c6b3..a36196da14be 100644 --- a/packages/angular/build/src/builders/dev-server/tests/execute-fetch.ts +++ b/packages/angular/build/src/builders/dev-server/tests/execute-fetch.ts @@ -7,7 +7,8 @@ */ import { lastValueFrom, mergeMap, take, timeout } from 'rxjs'; -import { URL } from 'url'; +import { get, IncomingMessage, RequestOptions } from 'node:http'; +import { text } from 'node:stream/consumers'; import { BuilderHarness, BuilderHarnessExecutionOptions, @@ -41,3 +42,48 @@ export async function executeOnceAndFetch( ), ); } + +/** + * Executes the builder and then immediately performs a GET request + * via the Node.js `http` builtin module. This is useful for cases + * where the `fetch` API is limited such as testing different `Host` + * header values with the development server. + * The `fetch` based alternative is preferred otherwise. + * + * @param harness A builder harness instance. + * @param url The URL string to get. + * @param options An options object. + */ +export async function executeOnceAndGet( + harness: BuilderHarness, + url: string, + options?: Partial & { request?: RequestOptions }, +): Promise { + return lastValueFrom( + harness.execute().pipe( + timeout(30_000), + mergeMap(async (executionResult) => { + let response = undefined; + let content = undefined; + if (executionResult.result?.success) { + let baseUrl = `${executionResult.result.baseUrl}`; + baseUrl = baseUrl[baseUrl.length - 1] === '/' ? baseUrl : `${baseUrl}/`; + const resolvedUrl = new URL(url, baseUrl); + + response = await new Promise((resolve) => + get(resolvedUrl, options?.request ?? {}, resolve), + ); + + if (response.statusCode === 200) { + content = await text(response); + } + + response.resume(); + } + + return { ...executionResult, response, content }; + }), + take(1), + ), + ); +} diff --git a/packages/angular/build/src/builders/dev-server/tests/options/allowed-hosts_spec.ts b/packages/angular/build/src/builders/dev-server/tests/options/allowed-hosts_spec.ts new file mode 100644 index 000000000000..8e96c7b4b4b0 --- /dev/null +++ b/packages/angular/build/src/builders/dev-server/tests/options/allowed-hosts_spec.ts @@ -0,0 +1,80 @@ +/** + * @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 { executeDevServer } from '../../index'; +import { executeOnceAndGet } from '../execute-fetch'; +import { describeServeBuilder } from '../jasmine-helpers'; +import { BASE_OPTIONS, DEV_SERVER_BUILDER_INFO } from '../setup'; + +const FETCH_HEADERS = Object.freeze({ Host: 'example.com' }); + +describeServeBuilder(executeDevServer, DEV_SERVER_BUILDER_INFO, (harness, setupTarget) => { + describe('option: "allowedHosts"', () => { + beforeEach(async () => { + setupTarget(harness); + + // Application code is not needed for these tests + await harness.writeFile('src/main.ts', ''); + }); + + it('does not allow an invalid host when option is not present', async () => { + harness.useTarget('serve', { + ...BASE_OPTIONS, + }); + + const { result, response } = await executeOnceAndGet(harness, '/', { + request: { headers: FETCH_HEADERS }, + }); + + expect(result?.success).toBeTrue(); + expect(response?.statusCode).toBe(403); + }); + + it('does not allow an invalid host when option is an empty array', async () => { + harness.useTarget('serve', { + ...BASE_OPTIONS, + allowedHosts: [], + }); + + const { result, response } = await executeOnceAndGet(harness, '/', { + request: { headers: FETCH_HEADERS }, + }); + + expect(result?.success).toBeTrue(); + expect(response?.statusCode).toBe(403); + }); + + it('allows a host when specified in the option', async () => { + harness.useTarget('serve', { + ...BASE_OPTIONS, + allowedHosts: ['example.com'], + }); + + const { result, content } = await executeOnceAndGet(harness, '/', { + request: { headers: FETCH_HEADERS }, + }); + + expect(result?.success).toBeTrue(); + expect(content).toContain(''); + }); + + it('allows a host when option is true', async () => { + harness.useTarget('serve', { + ...BASE_OPTIONS, + allowedHosts: true, + }); + + const { result, content } = await executeOnceAndGet(harness, '/', { + request: { headers: FETCH_HEADERS }, + }); + + expect(result?.success).toBeTrue(); + expect(content).toContain('<title>'); + }); + }); +}); diff --git a/packages/angular/build/src/builders/dev-server/vite-server.ts b/packages/angular/build/src/builders/dev-server/vite-server.ts index f6a4f54accd2..b005deee8dce 100644 --- a/packages/angular/build/src/builders/dev-server/vite-server.ts +++ b/packages/angular/build/src/builders/dev-server/vite-server.ts @@ -551,6 +551,7 @@ export async function setupServer( strictPort: true, host: serverOptions.host, open: serverOptions.open, + allowedHosts: serverOptions.allowedHosts, headers: serverOptions.headers, proxy, cors: { diff --git a/packages/angular_devkit/build_angular/src/builders/dev-server/builder.ts b/packages/angular_devkit/build_angular/src/builders/dev-server/builder.ts index 3b244a008c2c..f99bb5c3d6c8 100644 --- a/packages/angular_devkit/build_angular/src/builders/dev-server/builder.ts +++ b/packages/angular_devkit/build_angular/src/builders/dev-server/builder.ts @@ -85,12 +85,21 @@ export function execute( ); } + // New build system uses Vite's allowedHost option convention of true for disabling host checks + if (normalizedOptions.disableHostCheck) { + (normalizedOptions as unknown as { allowedHosts: true }).allowedHosts = true; + } else { + normalizedOptions.allowedHosts ??= []; + } + return defer(() => Promise.all([import('@angular/build/private'), import('../browser-esbuild')]), ).pipe( switchMap(([{ serveWithVite, buildApplicationInternal }, { convertBrowserOptions }]) => serveWithVite( - normalizedOptions, + normalizedOptions as typeof normalizedOptions & { + allowedHosts: true | string[]; + }, builderName, (options, context, codePlugins) => { return builderName === '@angular-devkit/build-angular:browser-esbuild' diff --git a/packages/angular_devkit/build_angular/src/builders/dev-server/schema.json b/packages/angular_devkit/build_angular/src/builders/dev-server/schema.json index 5796dd04e895..ce8242b234dc 100644 --- a/packages/angular_devkit/build_angular/src/builders/dev-server/schema.json +++ b/packages/angular_devkit/build_angular/src/builders/dev-server/schema.json @@ -73,7 +73,7 @@ }, "allowedHosts": { "type": "array", - "description": "List of hosts that are allowed to access the dev server. This option has no effect when using the 'application' or other esbuild-based builders.", + "description": "List of hosts that are allowed to access the dev server.", "default": [], "items": { "type": "string" @@ -85,7 +85,7 @@ }, "disableHostCheck": { "type": "boolean", - "description": "Don't verify connected clients are part of allowed hosts. This option has no effect when using the 'application' or other esbuild-based builders.", + "description": "Don't verify connected clients are part of allowed hosts.", "default": false }, "hmr": { diff --git a/yarn.lock b/yarn.lock index 9303c206eea5..d8a69a7091be 100644 --- a/yarn.lock +++ b/yarn.lock @@ -401,7 +401,7 @@ __metadata: rollup: "npm:4.22.4" sass: "npm:1.77.6" semver: "npm:7.6.3" - vite: "npm:5.4.6" + vite: "npm:5.4.14" watchpack: "npm:2.4.1" peerDependencies: "@angular/compiler-cli": ^18.0.0 @@ -805,7 +805,7 @@ __metadata: undici: "npm:6.19.7" verdaccio: "npm:5.32.1" verdaccio-auth-memory: "npm:^10.0.0" - vite: "npm:5.4.6" + vite: "npm:5.4.14" watchpack: "npm:2.4.1" webpack: "npm:5.94.0" webpack-dev-middleware: "npm:7.4.2" @@ -18053,6 +18053,49 @@ __metadata: languageName: node linkType: hard +"vite@npm:5.4.14": + version: 5.4.14 + resolution: "vite@npm:5.4.14" + dependencies: + esbuild: "npm:^0.21.3" + fsevents: "npm:~2.3.3" + postcss: "npm:^8.4.43" + rollup: "npm:^4.20.0" + peerDependencies: + "@types/node": ^18.0.0 || >=20.0.0 + less: "*" + lightningcss: ^1.21.0 + sass: "*" + sass-embedded: "*" + stylus: "*" + sugarss: "*" + terser: ^5.4.0 + dependenciesMeta: + fsevents: + optional: true + peerDependenciesMeta: + "@types/node": + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + bin: + vite: bin/vite.js + checksum: 10c0/8842933bd70ca6a98489a0bb9c8464bec373de00f9a97c8c7a4e64b24d15c88bfaa8c1acb38a68c3e5eb49072ffbccb146842c2d4edcdd036a9802964cffe3d1 + languageName: node + linkType: hard + "vite@npm:5.4.6": version: 5.4.6 resolution: "vite@npm:5.4.6" From ccc48a826a2f3119281e44a5510f73163a4283d2 Mon Sep 17 00:00:00 2001 From: Alan Agius <alanagius@google.com> Date: Thu, 19 Dec 2024 09:07:07 +0000 Subject: [PATCH 2/4] test: add `--disable-dev-shm-usage` to address `WebDriverError: unknown error: Chrome failed to start: crashed` This fixes an issue where protractor integration tests are failing with ``` [07:38:37] I/direct - Using ChromeDriver directly... [07:38:39] E/launcher - unknown error: Chrome failed to start: crashed. (unknown error: DevToolsActivePort file doesn't exist) ``` (cherry picked from commit 0718e1b70c101e624cd43dc08f7c58d2bae07f1f) --- .../testing/builder/projects/hello-world-app/protractor.conf.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/testing/builder/projects/hello-world-app/protractor.conf.js b/modules/testing/builder/projects/hello-world-app/protractor.conf.js index 89b7edda6324..313f7ac7c53b 100644 --- a/modules/testing/builder/projects/hello-world-app/protractor.conf.js +++ b/modules/testing/builder/projects/hello-world-app/protractor.conf.js @@ -18,7 +18,7 @@ exports.config = { capabilities: { browserName: 'chrome', chromeOptions: { - args: ['--headless', '--disable-gpu', '--window-size=800,600'], + args: ['--headless', '--no-sandbox', '--disable-gpu', '--disable-dev-shm-usage'], binary: require('puppeteer').executablePath(), }, }, From 8730ffb721cd932c8c1cfb4778147d688fc44bb7 Mon Sep 17 00:00:00 2001 From: Alan Agius <alanagius@google.com> Date: Wed, 27 Nov 2024 13:45:37 +0000 Subject: [PATCH 3/4] fix(@angular/cli): correctly select package versions in descending order during `ng add` When using the `ng add` command, the package version selection logic was not correctly selected based on the available versions in desc order. This could lead to selecting an unintended version of the package. Closes: #28985 (cherry picked from commit 4ef45ecf99a9b8b4c4fefb5b2cfd75f11a36331d) --- packages/angular/cli/src/commands/add/cli.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/angular/cli/src/commands/add/cli.ts b/packages/angular/cli/src/commands/add/cli.ts index ccc830eaa1f0..c280ba1af067 100644 --- a/packages/angular/cli/src/commands/add/cli.ts +++ b/packages/angular/cli/src/commands/add/cli.ts @@ -242,6 +242,7 @@ export default class AddCommandModule versionManifest.version, ); found = true; + break; } if (!found) { From 86c31717880b04142b5ae46b7d0049389495eb93 Mon Sep 17 00:00:00 2001 From: Alan Agius <alanagius@google.com> Date: Thu, 19 Dec 2024 06:29:42 +0000 Subject: [PATCH 4/4] test: disable WTR e2e test Temporary disable this test due to ``` Failed to launch local browser installed at /home/runner/.cache/bazel/_bazel_runner/f47b8283cc0f5922f9455b06771398a1/sandbox/processwrapper-sandbox/410/execroot/angular_cli/bazel-out/k8-fastbuild/bin/tests/legacy-cli/e2e.npm_node22.sh.runfiles/org_chromium_chromium_linux_x64/chrome-linux/chrome. This could be because of a mismatch between the version of puppeteer and Chrome or Chromium. Try updating either of them, or adjust the executablePath option to point to another browser installation. Use the --puppeteer flag to run tests with bundled compatible version of Chromium. dist/test-out/c48222bb-ca34-455e-bc1b-122521e1e71e/app.component.spec.js: ``` (cherry picked from commit 307eda17e669c3edc9e7cd5603eca1b59828a267) --- tests/legacy-cli/e2e/tests/web-test-runner/basic.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/legacy-cli/e2e/tests/web-test-runner/basic.ts b/tests/legacy-cli/e2e/tests/web-test-runner/basic.ts index 2067e382b6d9..4985f872fb18 100644 --- a/tests/legacy-cli/e2e/tests/web-test-runner/basic.ts +++ b/tests/legacy-cli/e2e/tests/web-test-runner/basic.ts @@ -2,6 +2,9 @@ import { noSilentNg } from '../../utils/process'; import { applyWtrBuilder } from '../../utils/web-test-runner'; export default async function () { + // Temporary disabled due to failure. + return; + await applyWtrBuilder(); const { stderr } = await noSilentNg('test');