diff --git a/packages/angular_devkit/build_angular/src/builders/dev-server/tests/options/proxy-config_spec.ts b/packages/angular_devkit/build_angular/src/builders/dev-server/tests/options/proxy-config_spec.ts index 5ad6388497c0..31c11cdd11a9 100644 --- a/packages/angular_devkit/build_angular/src/builders/dev-server/tests/options/proxy-config_spec.ts +++ b/packages/angular_devkit/build_angular/src/builders/dev-server/tests/options/proxy-config_spec.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import * as http from 'http'; +import { createServer } from 'node:http'; import { executeDevServer } from '../../index'; import { executeOnceAndFetch } from '../execute-fetch'; import { describeServeBuilder } from '../jasmine-helpers'; @@ -27,13 +27,10 @@ describeServeBuilder(executeDevServer, DEV_SERVER_BUILDER_INFO, (harness, setupT proxyConfig: 'proxy.config.json', }); - const proxyServer = createProxyServer(); + const proxyServer = await createProxyServer(); try { - await new Promise((resolve) => proxyServer.listen(0, '127.0.0.1', resolve)); - const proxyAddress = proxyServer.address() as import('net').AddressInfo; - await harness.writeFiles({ - 'proxy.config.json': `{ "/api/*": { "target": "http://127.0.0.1:${proxyAddress.port}" } }`, + 'proxy.config.json': `{ "/api/*": { "target": "http://127.0.0.1:${proxyServer.address.port}" } }`, }); const { result, response } = await executeOnceAndFetch(harness, '/api/test'); @@ -41,7 +38,7 @@ describeServeBuilder(executeDevServer, DEV_SERVER_BUILDER_INFO, (harness, setupT expect(result?.success).toBeTrue(); expect(await response?.text()).toContain('TEST_API_RETURN'); } finally { - await new Promise((resolve) => proxyServer.close(() => resolve())); + await proxyServer.close(); } }); @@ -51,15 +48,12 @@ describeServeBuilder(executeDevServer, DEV_SERVER_BUILDER_INFO, (harness, setupT proxyConfig: 'proxy.config.json', }); - const proxyServer = createProxyServer(); + const proxyServer = await createProxyServer(); try { - await new Promise((resolve) => proxyServer.listen(0, '127.0.0.1', resolve)); - const proxyAddress = proxyServer.address() as import('net').AddressInfo; - await harness.writeFiles({ 'proxy.config.json': ` // JSON file with comments - { "/api/*": { "target": "http://127.0.0.1:${proxyAddress.port}" } } + { "/api/*": { "target": "http://127.0.0.1:${proxyServer.address.port}" } } `, }); @@ -68,7 +62,7 @@ describeServeBuilder(executeDevServer, DEV_SERVER_BUILDER_INFO, (harness, setupT expect(result?.success).toBeTrue(); expect(await response?.text()).toContain('TEST_API_RETURN'); } finally { - await new Promise((resolve) => proxyServer.close(() => resolve())); + await proxyServer.close(); } }); @@ -77,14 +71,10 @@ describeServeBuilder(executeDevServer, DEV_SERVER_BUILDER_INFO, (harness, setupT ...BASE_OPTIONS, proxyConfig: 'proxy.config.js', }); - - const proxyServer = createProxyServer(); + const proxyServer = await createProxyServer(); try { - await new Promise((resolve) => proxyServer.listen(0, '127.0.0.1', resolve)); - const proxyAddress = proxyServer.address() as import('net').AddressInfo; - await harness.writeFiles({ - 'proxy.config.js': `module.exports = { "/api/*": { "target": "http://127.0.0.1:${proxyAddress.port}" } }`, + 'proxy.config.js': `module.exports = { "/api/*": { "target": "http://127.0.0.1:${proxyServer.address.port}" } }`, }); const { result, response } = await executeOnceAndFetch(harness, '/api/test'); @@ -92,7 +82,7 @@ describeServeBuilder(executeDevServer, DEV_SERVER_BUILDER_INFO, (harness, setupT expect(result?.success).toBeTrue(); expect(await response?.text()).toContain('TEST_API_RETURN'); } finally { - await new Promise((resolve) => proxyServer.close(() => resolve())); + await proxyServer.close(); } }); @@ -102,13 +92,10 @@ describeServeBuilder(executeDevServer, DEV_SERVER_BUILDER_INFO, (harness, setupT proxyConfig: 'proxy.config.js', }); - const proxyServer = createProxyServer(); + const proxyServer = await createProxyServer(); try { - await new Promise((resolve) => proxyServer.listen(0, '127.0.0.1', resolve)); - const proxyAddress = proxyServer.address() as import('net').AddressInfo; - await harness.writeFiles({ - 'proxy.config.js': `export default { "/api/*": { "target": "http://127.0.0.1:${proxyAddress.port}" } }`, + 'proxy.config.js': `export default { "/api/*": { "target": "http://127.0.0.1:${proxyServer.address.port}" } }`, 'package.json': '{ "type": "module" }', }); @@ -117,7 +104,7 @@ describeServeBuilder(executeDevServer, DEV_SERVER_BUILDER_INFO, (harness, setupT expect(result?.success).toBeTrue(); expect(await response?.text()).toContain('TEST_API_RETURN'); } finally { - await new Promise((resolve) => proxyServer.close(() => resolve())); + await proxyServer.close(); } }); @@ -127,10 +114,9 @@ describeServeBuilder(executeDevServer, DEV_SERVER_BUILDER_INFO, (harness, setupT proxyConfig: 'proxy.config.cjs', }); - const proxyServer = createProxyServer(); + const proxyServer = await createProxyServer(); try { - await new Promise((resolve) => proxyServer.listen(0, '127.0.0.1', resolve)); - const proxyAddress = proxyServer.address() as import('net').AddressInfo; + const proxyAddress = proxyServer.address; await harness.writeFiles({ 'proxy.config.cjs': `module.exports = { "/api/*": { "target": "http://127.0.0.1:${proxyAddress.port}" } }`, @@ -141,7 +127,7 @@ describeServeBuilder(executeDevServer, DEV_SERVER_BUILDER_INFO, (harness, setupT expect(result?.success).toBeTrue(); expect(await response?.text()).toContain('TEST_API_RETURN'); } finally { - await new Promise((resolve) => proxyServer.close(() => resolve())); + await proxyServer.close(); } }); @@ -151,13 +137,10 @@ describeServeBuilder(executeDevServer, DEV_SERVER_BUILDER_INFO, (harness, setupT proxyConfig: 'proxy.config.mjs', }); - const proxyServer = createProxyServer(); + const proxyServer = await createProxyServer(); try { - await new Promise((resolve) => proxyServer.listen(0, '127.0.0.1', resolve)); - const proxyAddress = proxyServer.address() as import('net').AddressInfo; - await harness.writeFiles({ - 'proxy.config.mjs': `export default { "/api/*": { "target": "http://127.0.0.1:${proxyAddress.port}" } }`, + 'proxy.config.mjs': `export default { "/api/*": { "target": "http://127.0.0.1:${proxyServer.address.port}" } }`, }); const { result, response } = await executeOnceAndFetch(harness, '/api/test'); @@ -165,7 +148,7 @@ describeServeBuilder(executeDevServer, DEV_SERVER_BUILDER_INFO, (harness, setupT expect(result?.success).toBeTrue(); expect(await response?.text()).toContain('TEST_API_RETURN'); } finally { - await new Promise((resolve) => proxyServer.close(() => resolve())); + await proxyServer.close(); } }); @@ -175,13 +158,10 @@ describeServeBuilder(executeDevServer, DEV_SERVER_BUILDER_INFO, (harness, setupT proxyConfig: 'proxy.config.json', }); - const proxyServer = createProxyServer(); + const proxyServer = await createProxyServer(); try { - await new Promise((resolve) => proxyServer.listen(0, '127.0.0.1', resolve)); - const proxyAddress = proxyServer.address() as import('net').AddressInfo; - await harness.writeFiles({ - 'proxy.config.json': `[ { "context": ["/api", "/abc"], "target": "http://127.0.0.1:${proxyAddress.port}" } ]`, + 'proxy.config.json': `[ { "context": ["/api", "/abc"], "target": "http://127.0.0.1:${proxyServer.address.port}" } ]`, }); const { result, response } = await executeOnceAndFetch(harness, '/api/test'); @@ -189,7 +169,7 @@ describeServeBuilder(executeDevServer, DEV_SERVER_BUILDER_INFO, (harness, setupT expect(result?.success).toBeTrue(); expect(await response?.text()).toContain('TEST_API_RETURN'); } finally { - await new Promise((resolve) => proxyServer.close(() => resolve())); + await proxyServer.close(); } }); @@ -232,6 +212,29 @@ describeServeBuilder(executeDevServer, DEV_SERVER_BUILDER_INFO, (harness, setupT }), ); }); + + it('supports negation of globs', async () => { + harness.useTarget('serve', { + ...BASE_OPTIONS, + proxyConfig: 'proxy.config.json', + }); + + const proxyServer = await createProxyServer(); + try { + await harness.writeFiles({ + 'proxy.config.json': ` + { "!something/**/*": { "target": "http://127.0.0.1:${proxyServer.address.port}" } } + `, + }); + + const { result, response } = await executeOnceAndFetch(harness, '/api/test'); + + expect(result?.success).toBeTrue(); + expect(await response?.text()).toContain('TEST_API_RETURN'); + } finally { + await proxyServer.close(); + } + }); }); }); @@ -239,11 +242,9 @@ describeServeBuilder(executeDevServer, DEV_SERVER_BUILDER_INFO, (harness, setupT * Creates an HTTP Server used for proxy testing that provides a `/test` endpoint * that returns a 200 response with a body of `TEST_API_RETURN`. All other requests * will return a 404 response. - * - * @returns An HTTP Server instance. */ -function createProxyServer() { - return http.createServer((request, response) => { +async function createProxyServer() { + const proxyServer = createServer((request, response) => { if (request.url?.endsWith('/test')) { response.writeHead(200); response.end('TEST_API_RETURN'); @@ -252,4 +253,11 @@ function createProxyServer() { response.end(); } }); + + await new Promise((resolve) => proxyServer.listen(0, '127.0.0.1', resolve)); + + return { + address: proxyServer.address() as import('net').AddressInfo, + close: () => new Promise((resolve) => proxyServer.close(() => resolve())), + }; } diff --git a/packages/angular_devkit/build_angular/src/utils/load-proxy-config.ts b/packages/angular_devkit/build_angular/src/utils/load-proxy-config.ts index 0bb8b36a95ea..76da35db34d9 100644 --- a/packages/angular_devkit/build_angular/src/utils/load-proxy-config.ts +++ b/packages/angular_devkit/build_angular/src/utils/load-proxy-config.ts @@ -11,7 +11,7 @@ import { existsSync } from 'node:fs'; import { readFile } from 'node:fs/promises'; import { extname, resolve } from 'node:path'; import { pathToFileURL } from 'node:url'; -import { parse as parseGlob } from 'picomatch'; +import { makeRe as makeRegExpFromGlob } from 'picomatch'; import { assertIsError } from './error'; import { loadEsmModule } from './load-esm'; @@ -129,8 +129,8 @@ function normalizeProxyConfiguration( // TODO: Consider upstreaming glob support for (const key of Object.keys(normalizedProxy)) { if (isDynamicPattern(key)) { - const { output } = parseGlob(key); - normalizedProxy[`^${output}$`] = normalizedProxy[key]; + const pattern = makeRegExpFromGlob(key).source; + normalizedProxy[pattern] = normalizedProxy[key]; delete normalizedProxy[key]; } }