From e6056d38a998bb74d20c25e1e7ab3aac08e05a54 Mon Sep 17 00:00:00 2001 From: MeAkib Date: Mon, 19 Oct 2026 15:02:33 +0600 Subject: [PATCH 1/4] fix(@angular/build): resolve dev-server `sslKey` and `sslCert` relative to the workspace root The Vite-based dev server read `sslKey` and `sslCert` relative to the current working directory, so serving failed with `ENOENT` when `ng serve` ran from a subdirectory of the workspace and the paths in `angular.json` were relative. Resolve both paths against the workspace root, as the webpack dev server already does. The new dev-server `ssl` spec covers relative paths. It fails with `ENOENT` without this change. --- .../dev-server/tests/options/ssl_spec.ts | 59 +++++++++++++++++++ .../src/builders/dev-server/vite/server.ts | 6 +- 2 files changed, 62 insertions(+), 3 deletions(-) create mode 100644 packages/angular/build/src/builders/dev-server/tests/options/ssl_spec.ts diff --git a/packages/angular/build/src/builders/dev-server/tests/options/ssl_spec.ts b/packages/angular/build/src/builders/dev-server/tests/options/ssl_spec.ts new file mode 100644 index 000000000000..490ba7f37d3c --- /dev/null +++ b/packages/angular/build/src/builders/dev-server/tests/options/ssl_spec.ts @@ -0,0 +1,59 @@ +/** + * @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 { describeServeBuilder } from '../jasmine-helpers'; +import { BASE_OPTIONS, DEV_SERVER_BUILDER_INFO } from '../setup'; + +const SSL_KEY = `-----BEGIN PRIVATE KEY----- +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgEpythQjrJo9bm8GM +LFacGutCqc/XxDZ3dDkSMYZYZ2ehRANCAARvwtZ3OwP1uLhrcLp7nemvaWqkA+FN +NDjOA3OZKhyVHYog1jNTawJJjwk66upbXGFL8622yU1INWbZFxvrSANl +-----END PRIVATE KEY----- +`; + +const SSL_CERT = `-----BEGIN CERTIFICATE----- +MIIBfzCCASWgAwIBAgIUUoRhwzxAE3y5nrOW8de+cJEIVvQwCgYIKoZIzj0EAwIw +FDESMBAGA1UEAwwJbG9jYWxob3N0MCAXDTI2MTAxOTA4NTUyNVoYDzIxMjYwOTI1 +MDg1NTI1WjAUMRIwEAYDVQQDDAlsb2NhbGhvc3QwWTATBgcqhkjOPQIBBggqhkjO +PQMBBwNCAARvwtZ3OwP1uLhrcLp7nemvaWqkA+FNNDjOA3OZKhyVHYog1jNTawJJ +jwk66upbXGFL8622yU1INWbZFxvrSANlo1MwUTAdBgNVHQ4EFgQUWyqB1QeFcs6L +LL7Utsybm6+XFvIwHwYDVR0jBBgwFoAUWyqB1QeFcs6LLL7Utsybm6+XFvIwDwYD +VR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAgNIADBFAiBazry25W+Qewb/WzadhmdY +YDGdshNZCk0g0DI3ctNjlgIhAKm2X8pmqdkM38Yh8QDN0cHBsAcDfRYffDnjJ3tP +dpnU +-----END CERTIFICATE----- +`; + +describeServeBuilder(executeDevServer, DEV_SERVER_BUILDER_INFO, (harness, setupTarget) => { + describe('option: "ssl"', () => { + beforeEach(async () => { + setupTarget(harness); + + // Application code is not needed for these tests + await harness.writeFile('src/main.ts', ''); + }); + + it('resolves "sslKey" and "sslCert" relative to the workspace root', async () => { + await harness.writeFile('ssl/server.key', SSL_KEY); + await harness.writeFile('ssl/server.crt', SSL_CERT); + + harness.useTarget('serve', { + ...BASE_OPTIONS, + ssl: true, + sslKey: 'ssl/server.key', + sslCert: 'ssl/server.crt', + }); + + const { result } = await harness.executeOnce(); + + expect(result?.success).toBeTrue(); + expect(result?.baseUrl).toMatch(/^https:\/\/[^/]+:\d+\//); + }); + }); +}); 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 52b83a2b5f1a..61a188c4c8c5 100644 --- a/packages/angular/build/src/builders/dev-server/vite/server.ts +++ b/packages/angular/build/src/builders/dev-server/vite/server.ts @@ -7,7 +7,7 @@ */ import { readFile } from 'node:fs/promises'; -import { join } from 'node:path'; +import { join, resolve } from 'node:path'; import type * as Vite from 'vite' with { 'resolution-mode': 'import', }; @@ -85,8 +85,8 @@ async function createServerConfig( if (serverOptions.ssl && serverOptions.sslCert && serverOptions.sslKey) { const [cert, key] = await Promise.all([ - readFile(serverOptions.sslCert), - readFile(serverOptions.sslKey), + readFile(resolve(serverOptions.workspaceRoot, serverOptions.sslCert)), + readFile(resolve(serverOptions.workspaceRoot, serverOptions.sslKey)), ]); server.https = { From a41acae698df50f22edd1e9fad1863c9ad510bd4 Mon Sep 17 00:00:00 2001 From: MeAkib Date: Mon, 19 Oct 2026 15:26:21 +0600 Subject: [PATCH 2/4] fixup! fix(@angular/build): resolve dev-server `sslKey` and `sslCert` relative to the workspace root --- packages/angular/build/src/builders/dev-server/options.ts | 4 ++-- .../angular/build/src/builders/dev-server/vite/server.ts | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/angular/build/src/builders/dev-server/options.ts b/packages/angular/build/src/builders/dev-server/options.ts index bc63c69540f7..71210f9d7d98 100644 --- a/packages/angular/build/src/builders/dev-server/options.ts +++ b/packages/angular/build/src/builders/dev-server/options.ts @@ -147,8 +147,8 @@ export async function normalizeOptions( proxyConfig, servePath, ssl, - sslCert, - sslKey, + sslCert: sslCert && path.resolve(workspaceRoot, sslCert), + sslKey: sslKey && path.resolve(workspaceRoot, sslKey), // Prebundling defaults to true but requires caching to function prebundle: cacheOptions.enabled && !optimization.scripts && prebundle, inspect, 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 61a188c4c8c5..52b83a2b5f1a 100644 --- a/packages/angular/build/src/builders/dev-server/vite/server.ts +++ b/packages/angular/build/src/builders/dev-server/vite/server.ts @@ -7,7 +7,7 @@ */ import { readFile } from 'node:fs/promises'; -import { join, resolve } from 'node:path'; +import { join } from 'node:path'; import type * as Vite from 'vite' with { 'resolution-mode': 'import', }; @@ -85,8 +85,8 @@ async function createServerConfig( if (serverOptions.ssl && serverOptions.sslCert && serverOptions.sslKey) { const [cert, key] = await Promise.all([ - readFile(resolve(serverOptions.workspaceRoot, serverOptions.sslCert)), - readFile(resolve(serverOptions.workspaceRoot, serverOptions.sslKey)), + readFile(serverOptions.sslCert), + readFile(serverOptions.sslKey), ]); server.https = { From 08e9727e60fb8b9ac210cc30cfd64d6e6ae0b515 Mon Sep 17 00:00:00 2001 From: Shuaib Hasan Akib <48770016+MeAkib@users.noreply.github.com> Date: Mon, 19 Oct 2026 15:36:20 +0600 Subject: [PATCH 3/4] fixup! fix(@angular/build): resolve dev-server `sslKey` and `sslCert` relative to the workspace root --- packages/angular/build/src/builders/dev-server/options.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/angular/build/src/builders/dev-server/options.ts b/packages/angular/build/src/builders/dev-server/options.ts index 71210f9d7d98..a64b77b00d53 100644 --- a/packages/angular/build/src/builders/dev-server/options.ts +++ b/packages/angular/build/src/builders/dev-server/options.ts @@ -147,8 +147,8 @@ export async function normalizeOptions( proxyConfig, servePath, ssl, - sslCert: sslCert && path.resolve(workspaceRoot, sslCert), - sslKey: sslKey && path.resolve(workspaceRoot, sslKey), + sslCert: sslCert && path.join(workspaceRoot, sslCert), + sslKey: sslKey && path.join(workspaceRoot, sslKey), // Prebundling defaults to true but requires caching to function prebundle: cacheOptions.enabled && !optimization.scripts && prebundle, inspect, From 83c6edded2887e1c0d2fde80f26a6d8b2e73839f Mon Sep 17 00:00:00 2001 From: MeAkib Date: Tue, 20 Oct 2026 00:28:26 +0600 Subject: [PATCH 4/4] fixup! fix(@angular/build): resolve dev-server `sslKey` and `sslCert` relative to the workspace root --- .../build/src/builders/dev-server/options.ts | 4 +-- .../dev-server/tests/options/ssl_spec.ts | 25 +++++++++++++++++++ 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/packages/angular/build/src/builders/dev-server/options.ts b/packages/angular/build/src/builders/dev-server/options.ts index a64b77b00d53..71210f9d7d98 100644 --- a/packages/angular/build/src/builders/dev-server/options.ts +++ b/packages/angular/build/src/builders/dev-server/options.ts @@ -147,8 +147,8 @@ export async function normalizeOptions( proxyConfig, servePath, ssl, - sslCert: sslCert && path.join(workspaceRoot, sslCert), - sslKey: sslKey && path.join(workspaceRoot, sslKey), + sslCert: sslCert && path.resolve(workspaceRoot, sslCert), + sslKey: sslKey && path.resolve(workspaceRoot, sslKey), // Prebundling defaults to true but requires caching to function prebundle: cacheOptions.enabled && !optimization.scripts && prebundle, inspect, diff --git a/packages/angular/build/src/builders/dev-server/tests/options/ssl_spec.ts b/packages/angular/build/src/builders/dev-server/tests/options/ssl_spec.ts index 490ba7f37d3c..0cf5614dc89b 100644 --- a/packages/angular/build/src/builders/dev-server/tests/options/ssl_spec.ts +++ b/packages/angular/build/src/builders/dev-server/tests/options/ssl_spec.ts @@ -6,6 +6,9 @@ * found in the LICENSE file at https://angular.dev/license */ +import assert from 'node:assert'; +import { mkdtemp, writeFile } from 'node:fs/promises'; +import { join } from 'node:path'; import { executeDevServer } from '../../index'; import { describeServeBuilder } from '../jasmine-helpers'; import { BASE_OPTIONS, DEV_SERVER_BUILDER_INFO } from '../setup'; @@ -55,5 +58,27 @@ describeServeBuilder(executeDevServer, DEV_SERVER_BUILDER_INFO, (harness, setupT expect(result?.success).toBeTrue(); expect(result?.baseUrl).toMatch(/^https:\/\/[^/]+:\d+\//); }); + + it('supports absolute "sslKey" and "sslCert" paths outside the workspace', async () => { + const baseTmpDir = process.env['TEST_TMPDIR']; + assert(baseTmpDir, 'TEST_TMPDIR is not set'); + const certsDir = await mkdtemp(join(baseTmpDir, 'angular-cli-ssl-')); + const sslKey = join(certsDir, 'server.key'); + const sslCert = join(certsDir, 'server.crt'); + await writeFile(sslKey, SSL_KEY); + await writeFile(sslCert, SSL_CERT); + + harness.useTarget('serve', { + ...BASE_OPTIONS, + ssl: true, + sslKey, + sslCert, + }); + + const { result } = await harness.executeOnce(); + + expect(result?.success).toBeTrue(); + expect(result?.baseUrl).toMatch(/^https:\/\/[^/]+:\d+\//); + }); }); });