Skip to content

Commit d832370

Browse files
committed
fix(@angular-devkit/build-angular): update vite to version 5.4.14
Version update from 5.1.8 to address advisory 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"] }, ```
1 parent 29c6b0d commit d832370

File tree

9 files changed

+406
-75
lines changed

9 files changed

+406
-75
lines changed

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,7 @@
210210
"undici": "6.11.1",
211211
"verdaccio": "5.29.2",
212212
"verdaccio-auth-memory": "^10.0.0",
213-
"vite": "5.1.8",
213+
"vite": "5.4.14",
214214
"watchpack": "2.4.0",
215215
"webpack": "5.94.0",
216216
"webpack-dev-middleware": "6.1.2",

packages/angular_devkit/build_angular/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@
6262
"tree-kill": "1.2.2",
6363
"tslib": "2.6.2",
6464
"undici": "6.11.1",
65-
"vite": "5.1.8",
65+
"vite": "5.4.14",
6666
"watchpack": "2.4.0",
6767
"webpack": "5.94.0",
6868
"webpack-dev-middleware": "6.1.2",

packages/angular_devkit/build_angular/src/builders/dev-server/builder.ts

-6
Original file line numberDiff line numberDiff line change
@@ -76,12 +76,6 @@ export function execute(
7676
);
7777
}
7878

79-
if (options.allowedHosts?.length) {
80-
context.logger.warn(
81-
`The "allowedHosts" option will not be used because it is not supported by the "${builderName}" builder.`,
82-
);
83-
}
84-
8579
if (options.publicHost) {
8680
context.logger.warn(
8781
`The "publicHost" option will not be used because it is not supported by the "${builderName}" builder.`,

packages/angular_devkit/build_angular/src/builders/dev-server/schema.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@
7373
},
7474
"allowedHosts": {
7575
"type": "array",
76-
"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.",
76+
"description": "List of hosts that are allowed to access the dev server.",
7777
"default": [],
7878
"items": {
7979
"type": "string"

packages/angular_devkit/build_angular/src/builders/dev-server/tests/execute-fetch.ts

+47-1
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,9 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9+
import { IncomingMessage, RequestOptions, get } from 'node:http';
10+
import { text } from 'node:stream/consumers';
911
import { lastValueFrom, mergeMap, take, timeout } from 'rxjs';
10-
import { URL } from 'url';
1112
import {
1213
BuilderHarness,
1314
BuilderHarnessExecutionOptions,
@@ -41,3 +42,48 @@ export async function executeOnceAndFetch<T>(
4142
),
4243
);
4344
}
45+
46+
/**
47+
* Executes the builder and then immediately performs a GET request
48+
* via the Node.js `http` builtin module. This is useful for cases
49+
* where the `fetch` API is limited such as testing different `Host`
50+
* header values with the development server.
51+
* The `fetch` based alternative is preferred otherwise.
52+
*
53+
* @param harness A builder harness instance.
54+
* @param url The URL string to get.
55+
* @param options An options object.
56+
*/
57+
export async function executeOnceAndGet<T>(
58+
harness: BuilderHarness<T>,
59+
url: string,
60+
options?: Partial<BuilderHarnessExecutionOptions> & { request?: RequestOptions },
61+
): Promise<BuilderHarnessExecutionResult & { response?: IncomingMessage; content?: string }> {
62+
return lastValueFrom(
63+
harness.execute().pipe(
64+
timeout(30_000),
65+
mergeMap(async (executionResult) => {
66+
let response = undefined;
67+
let content = undefined;
68+
if (executionResult.result?.success) {
69+
let baseUrl = `${executionResult.result.baseUrl}`;
70+
baseUrl = baseUrl[baseUrl.length - 1] === '/' ? baseUrl : `${baseUrl}/`;
71+
const resolvedUrl = new URL(url, baseUrl);
72+
73+
response = await new Promise<IncomingMessage>((resolve) =>
74+
get(resolvedUrl, options?.request ?? {}, resolve),
75+
);
76+
77+
if (response.statusCode === 200) {
78+
content = await text(response);
79+
}
80+
81+
response.resume();
82+
}
83+
84+
return { ...executionResult, response, content };
85+
}),
86+
take(1),
87+
),
88+
);
89+
}

packages/angular_devkit/build_angular/src/builders/dev-server/tests/options/allowed-hosts_spec.ts

+39-44
Original file line numberDiff line numberDiff line change
@@ -7,65 +7,60 @@
77
*/
88

99
import { executeDevServer } from '../../index';
10-
import { executeOnceAndFetch } from '../execute-fetch';
10+
import { executeOnceAndGet } from '../execute-fetch';
1111
import { describeServeBuilder } from '../jasmine-helpers';
1212
import { BASE_OPTIONS, DEV_SERVER_BUILDER_INFO } from '../setup';
1313

1414
const FETCH_HEADERS = Object.freeze({ host: 'example.com' });
1515

16-
describeServeBuilder(
17-
executeDevServer,
18-
DEV_SERVER_BUILDER_INFO,
19-
(harness, setupTarget, isViteRun) => {
20-
// TODO(fix-vite): currently this is broken in vite.
21-
(isViteRun ? xdescribe : describe)('option: "allowedHosts"', () => {
22-
beforeEach(async () => {
23-
setupTarget(harness);
16+
describeServeBuilder(executeDevServer, DEV_SERVER_BUILDER_INFO, (harness, setupTarget, isVite) => {
17+
describe('option: "allowedHosts"', () => {
18+
beforeEach(async () => {
19+
setupTarget(harness);
2420

25-
// Application code is not needed for these tests
26-
await harness.writeFile('src/main.ts', '');
27-
});
28-
29-
it('does not allow an invalid host when option is not present', async () => {
30-
harness.useTarget('serve', {
31-
...BASE_OPTIONS,
32-
});
21+
// Application code is not needed for these tests
22+
await harness.writeFile('src/main.ts', '');
23+
});
3324

34-
const { result, response } = await executeOnceAndFetch(harness, '/', {
35-
request: { headers: FETCH_HEADERS },
36-
});
25+
it('does not allow an invalid host when option is not present', async () => {
26+
harness.useTarget('serve', { ...BASE_OPTIONS });
3727

38-
expect(result?.success).toBeTrue();
39-
expect(await response?.text()).toBe('Invalid Host header');
28+
const { result, response, content } = await executeOnceAndGet(harness, '/', {
29+
request: { headers: FETCH_HEADERS },
4030
});
4131

42-
it('does not allow an invalid host when option is an empty array', async () => {
43-
harness.useTarget('serve', {
44-
...BASE_OPTIONS,
45-
allowedHosts: [],
46-
});
32+
expect(result?.success).toBeTrue();
33+
if (isVite) {
34+
expect(response?.statusCode).toBe(403);
35+
} else {
36+
expect(content).toBe('Invalid Host header');
37+
}
38+
});
4739

48-
const { result, response } = await executeOnceAndFetch(harness, '/', {
49-
request: { headers: FETCH_HEADERS },
50-
});
40+
it('does not allow an invalid host when option is an empty array', async () => {
41+
harness.useTarget('serve', { ...BASE_OPTIONS, allowedHosts: [] });
5142

52-
expect(result?.success).toBeTrue();
53-
expect(await response?.text()).toBe('Invalid Host header');
43+
const { result, response, content } = await executeOnceAndGet(harness, '/', {
44+
request: { headers: FETCH_HEADERS },
5445
});
5546

56-
it('allows a host when specified in the option', async () => {
57-
harness.useTarget('serve', {
58-
...BASE_OPTIONS,
59-
allowedHosts: ['example.com'],
60-
});
47+
expect(result?.success).toBeTrue();
48+
if (isVite) {
49+
expect(response?.statusCode).toBe(403);
50+
} else {
51+
expect(content).toBe('Invalid Host header');
52+
}
53+
});
6154

62-
const { result, response } = await executeOnceAndFetch(harness, '/', {
63-
request: { headers: FETCH_HEADERS },
64-
});
55+
it('allows a host when specified in the option', async () => {
56+
harness.useTarget('serve', { ...BASE_OPTIONS, allowedHosts: ['example.com'] });
6557

66-
expect(result?.success).toBeTrue();
67-
expect(await response?.text()).toContain('<title>');
58+
const { result, content } = await executeOnceAndGet(harness, '/', {
59+
request: { headers: FETCH_HEADERS },
6860
});
61+
62+
expect(result?.success).toBeTrue();
63+
expect(content).toContain('<title>');
6964
});
70-
},
71-
);
65+
});
66+
});

packages/angular_devkit/build_angular/src/builders/dev-server/vite-server.ts

+1
Original file line numberDiff line numberDiff line change
@@ -485,6 +485,7 @@ export async function setupServer(
485485
strictPort: true,
486486
host: serverOptions.host,
487487
open: serverOptions.open,
488+
allowedHosts: serverOptions.allowedHosts,
488489
headers: serverOptions.headers,
489490
proxy,
490491
cors: {

packages/angular_devkit/build_angular/src/tools/vite/angular-memory-plugin.ts

+12-13
Original file line numberDiff line numberDiff line change
@@ -74,10 +74,7 @@ export function createAngularMemoryPlugin(options: AngularMemoryPluginOptions):
7474
const codeContents = outputFiles.get(relativeFile)?.contents;
7575
if (codeContents === undefined) {
7676
if (relativeFile.endsWith('/node_modules/vite/dist/client/client.mjs')) {
77-
return {
78-
code: await loadViteClientCode(file),
79-
map: await readFile(file + '.map', 'utf-8'),
80-
};
77+
return await loadViteClientCode(file);
8178
}
8279

8380
return;
@@ -309,19 +306,21 @@ export function createAngularMemoryPlugin(options: AngularMemoryPluginOptions):
309306
* @param file The absolute path to the Vite client code.
310307
* @returns
311308
*/
312-
async function loadViteClientCode(file: string) {
309+
async function loadViteClientCode(file: string): Promise<string> {
313310
const originalContents = await readFile(file, 'utf-8');
314-
const firstUpdate = originalContents.replace('You can also disable this overlay by setting', '');
315-
assert(originalContents !== firstUpdate, 'Failed to update Vite client error overlay text. (1)');
316-
317-
const secondUpdate = firstUpdate.replace(
318-
// eslint-disable-next-line max-len
319-
'<code part="config-option-name">server.hmr.overlay</code> to <code part="config-option-value">false</code> in <code part="config-file-name">${hmrConfigName}.</code>',
311+
const updatedContents = originalContents.replace(
312+
`"You can also disable this overlay by setting ",
313+
h("code", { part: "config-option-name" }, "server.hmr.overlay"),
314+
" to ",
315+
h("code", { part: "config-option-value" }, "false"),
316+
" in ",
317+
h("code", { part: "config-file-name" }, hmrConfigName),
318+
"."`,
320319
'',
321320
);
322-
assert(firstUpdate !== secondUpdate, 'Failed to update Vite client error overlay text. (2)');
321+
assert(originalContents !== updatedContents, 'Failed to update Vite client error overlay text.');
323322

324-
return secondUpdate;
323+
return updatedContents;
325324
}
326325

327326
function pathnameWithoutBasePath(url: string, basePath: string): string {

0 commit comments

Comments
 (0)