Default preview host to localhost (#5753)

* Initial refactor

* Extract as vite plugin

* Cleanup vite plugin

* Reduce option passing

* Use localhost as preview default host

* Simplify base handling

* Fix host handling

* Add changeset

* Remove unused imports

* Remove unused sirv dep

* Try pin playwright to 1.28.1

* Update playwright

* Try this

* Speed up CI

* Try fix page off

* Refactor networkidle

* Ensure open connections are destroyed when the preview server is closed

* Revert debug code

Co-authored-by: Matthew Phillips <matthew@matthewphillips.info>
This commit is contained in:
Bjorn Lu 2023-01-10 01:14:07 +08:00 committed by GitHub
parent f35411487b
commit 302e0ef8f5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 183 additions and 270 deletions

View file

@ -0,0 +1,5 @@
---
'astro': major
---
Default preview host to `localhost` instead of `127.0.0.1`. This allows the static server and integration preview servers to serve under ipv6.

View file

@ -155,8 +155,8 @@
"rehype": "^12.0.1",
"resolve": "^1.22.0",
"semver": "^7.3.7",
"server-destroy": "^1.0.1",
"shiki": "^0.11.1",
"sirv": "^2.0.2",
"slash": "^4.0.0",
"string-width": "^5.1.2",
"strip-ansi": "^7.0.1",
@ -171,7 +171,7 @@
"zod": "^3.17.3"
},
"devDependencies": {
"@playwright/test": "^1.22.2",
"@playwright/test": "^1.29.2",
"@types/babel__generator": "^7.6.4",
"@types/babel__traverse": "^7.17.1",
"@types/chai": "^4.3.1",
@ -191,6 +191,7 @@
"@types/resolve": "^1.20.2",
"@types/rimraf": "^3.0.2",
"@types/send": "^0.17.1",
"@types/server-destroy": "^1.0.1",
"@types/unist": "^2.0.6",
"astro-scripts": "workspace:*",
"chai": "^4.3.6",

View file

@ -49,9 +49,6 @@ export default async function dev(
// Start listening to the port
const devServerAddressInfo = await startContainer(restart.container);
const site = settings.config.site
? new URL(settings.config.base, settings.config.site)
: undefined;
info(
options.logging,
null,
@ -59,7 +56,7 @@ export default async function dev(
startupTime: performance.now() - devStart,
resolvedUrls: restart.container.viteServer.resolvedUrls || { local: [], network: [] },
host: settings.config.server.host,
site,
base: settings.config.base,
isRestart: options.isRestart,
})
);

View file

@ -14,14 +14,11 @@ import {
underline,
yellow,
} from 'kleur/colors';
import type { AddressInfo } from 'net';
import os from 'os';
import { ResolvedServerUrls } from 'vite';
import { ZodError } from 'zod';
import { renderErrorMarkdown } from './errors/dev/utils.js';
import { AstroError, CompilerError, ErrorWithMetadata } from './errors/index.js';
import { removeTrailingForwardSlash } from './path.js';
import { emoji, getLocalAddress, padMultilineString } from './util.js';
import { emoji, padMultilineString } from './util.js';
const PREFIX_PADDING = 6;
@ -58,31 +55,26 @@ export function serverStart({
startupTime,
resolvedUrls,
host,
site,
base,
isRestart = false,
}: {
startupTime: number;
resolvedUrls: ResolvedServerUrls;
host: string | boolean;
site: URL | undefined;
base: string;
isRestart?: boolean;
}): string {
// PACKAGE_VERSION is injected at build-time
const version = process.env.PACKAGE_VERSION ?? '0.0.0';
const rootPath = site ? site.pathname : '/';
const localPrefix = `${dim('┃')} Local `;
const networkPrefix = `${dim('┃')} Network `;
const emptyPrefix = ' '.repeat(11);
const localUrlMessages = resolvedUrls.local.map((url, i) => {
return `${i === 0 ? localPrefix : emptyPrefix}${bold(
cyan(removeTrailingForwardSlash(url) + rootPath)
)}`;
return `${i === 0 ? localPrefix : emptyPrefix}${bold(cyan(new URL(url).origin + base))}`;
});
const networkUrlMessages = resolvedUrls.network.map((url, i) => {
return `${i === 0 ? networkPrefix : emptyPrefix}${bold(
cyan(removeTrailingForwardSlash(url) + rootPath)
)}`;
return `${i === 0 ? networkPrefix : emptyPrefix}${bold(cyan(new URL(url).origin + base))}`;
});
if (networkUrlMessages.length === 0) {
@ -109,50 +101,6 @@ export function serverStart({
.join('\n');
}
export function resolveServerUrls({
address,
host,
https,
}: {
address: AddressInfo;
host: string | boolean;
https: boolean;
}): ResolvedServerUrls {
const { address: networkAddress, port } = address;
const localAddress = getLocalAddress(networkAddress, host);
const networkLogging = getNetworkLogging(host);
const toDisplayUrl = (hostname: string) => `${https ? 'https' : 'http'}://${hostname}:${port}`;
let local = toDisplayUrl(localAddress);
let network: string | null = null;
if (networkLogging === 'visible') {
const ipv4Networks = Object.values(os.networkInterfaces())
.flatMap((networkInterface) => networkInterface ?? [])
.filter(
(networkInterface) =>
networkInterface?.address &&
// Node < v18
((typeof networkInterface.family === 'string' && networkInterface.family === 'IPv4') ||
// Node >= v18
(typeof networkInterface.family === 'number' && (networkInterface as any).family === 4))
);
for (let { address: ipv4Address } of ipv4Networks) {
if (ipv4Address.includes('127.0.0.1')) {
const displayAddress = ipv4Address.replace('127.0.0.1', localAddress);
local = toDisplayUrl(displayAddress);
} else {
network = toDisplayUrl(ipv4Address);
}
}
}
return {
local: [local],
network: network ? [network] : [],
};
}
export function telemetryNotice() {
const headline = yellow(`Astro now collects ${bold('anonymous')} usage data.`);
const why = `This ${bold('optional program')} will help shape our roadmap.`;
@ -228,11 +176,6 @@ export function cancelled(message: string, tip?: string) {
.join('\n');
}
/** Display port in use */
export function portInUse({ port }: { port: number }): string {
return `Port ${port} in use. Trying a new one…`;
}
const LOCAL_IP_HOSTS = new Set(['localhost', '127.0.0.1']);
export function getNetworkLogging(host: string | boolean): 'none' | 'host-to-expose' | 'visible' {

View file

@ -23,11 +23,9 @@ export default async function preview(
logging: logging,
});
await runHookConfigDone({ settings: settings, logging: logging });
const host = getResolvedHostForHttpServer(settings.config.server.host);
const { port, headers } = settings.config.server;
if (settings.config.output === 'static') {
const server = await createStaticPreviewServer(settings, { logging, host, port, headers });
const server = await createStaticPreviewServer(settings, logging);
return server;
}
if (!settings.adapter) {
@ -55,8 +53,8 @@ export default async function preview(
outDir: settings.config.outDir,
client: settings.config.build.client,
serverEntrypoint: new URL(settings.config.build.serverEntry, settings.config.build.server),
host,
port,
host: getResolvedHostForHttpServer(settings.config.server.host),
port: settings.config.server.port,
base: settings.config.base,
});

View file

@ -1,15 +1,14 @@
import type { AddressInfo } from 'net';
import http from 'http';
import { performance } from 'perf_hooks';
import { fileURLToPath } from 'url';
import { preview, type PreviewServer as VitePreviewServer } from 'vite';
import type { AstroSettings } from '../../@types/astro';
import type { LogOptions } from '../logger/core';
import fs from 'fs';
import http, { OutgoingHttpHeaders } from 'http';
import { performance } from 'perf_hooks';
import sirv from 'sirv';
import { fileURLToPath } from 'url';
import { notFoundTemplate, subpathNotUsedTemplate } from '../../template/4xx.js';
import { error, info } from '../logger/core.js';
import * as msg from '../messages.js';
import { getResolvedHostForHttpServer } from './util.js';
import { vitePluginAstroPreview } from './vite-plugin-astro-preview.js';
import enableDestroy from 'server-destroy';
export interface PreviewServer {
host?: string;
@ -19,160 +18,65 @@ export interface PreviewServer {
stop(): Promise<void>;
}
const HAS_FILE_EXTENSION_REGEXP = /^.*\.[^\\]+$/;
/** The primary dev action */
export default async function createStaticPreviewServer(
settings: AstroSettings,
{
logging,
host,
port,
headers,
}: {
logging: LogOptions;
host: string | undefined;
port: number;
headers: OutgoingHttpHeaders | undefined;
}
logging: LogOptions
): Promise<PreviewServer> {
const startServerTime = performance.now();
const defaultOrigin = 'http://localhost';
const trailingSlash = settings.config.trailingSlash;
/** Base request URL. */
let baseURL = new URL(settings.config.base, new URL(settings.config.site || '/', defaultOrigin));
const staticFileServer = sirv(fileURLToPath(settings.config.outDir), {
dev: true,
etag: true,
maxAge: 0,
setHeaders: (res, pathname, stats) => {
for (const [name, value] of Object.entries(headers ?? {})) {
if (value) res.setHeader(name, value);
}
},
});
// Create the preview server, send static files out of the `dist/` directory.
const server = http.createServer((req, res) => {
const requestURL = new URL(req.url as string, defaultOrigin);
// respond 404 to requests outside the base request directory
if (!requestURL.pathname.startsWith(baseURL.pathname)) {
res.statusCode = 404;
res.end(subpathNotUsedTemplate(baseURL.pathname, requestURL.pathname));
return;
}
/** Relative request path. */
const pathname = requestURL.pathname.slice(baseURL.pathname.length - 1);
const isRoot = pathname === '/';
const hasTrailingSlash = isRoot || pathname.endsWith('/');
function sendError(message: string) {
res.statusCode = 404;
res.end(notFoundTemplate(pathname, message));
}
switch (true) {
case hasTrailingSlash && trailingSlash == 'never' && !isRoot:
sendError('Not Found (trailingSlash is set to "never")');
return;
case !hasTrailingSlash &&
trailingSlash == 'always' &&
!isRoot &&
!HAS_FILE_EXTENSION_REGEXP.test(pathname):
sendError('Not Found (trailingSlash is set to "always")');
return;
default: {
// HACK: rewrite req.url so that sirv finds the file
req.url = '/' + req.url?.replace(baseURL.pathname, '');
staticFileServer(req, res, () => {
const errorPagePath = fileURLToPath(settings.config.outDir + '/404.html');
if (fs.existsSync(errorPagePath)) {
res.statusCode = 404;
res.setHeader('Content-Type', 'text/html;charset=utf-8');
res.end(fs.readFileSync(errorPagePath));
} else {
staticFileServer(req, res, () => {
sendError('Not Found');
});
}
});
return;
}
}
});
let httpServer: http.Server;
/** Expose dev server to `port` */
function startServer(timerStart: number): Promise<void> {
let showedPortTakenMsg = false;
let showedListenMsg = false;
return new Promise<void>((resolve, reject) => {
const listen = () => {
httpServer = server.listen(port, host, async () => {
if (!showedListenMsg) {
const resolvedUrls = msg.resolveServerUrls({
address: server.address() as AddressInfo,
host: settings.config.server.host,
https: false,
});
info(
logging,
null,
msg.serverStart({
startupTime: performance.now() - timerStart,
resolvedUrls,
host: settings.config.server.host,
site: baseURL,
})
);
}
showedListenMsg = true;
resolve();
});
httpServer?.on('error', onError);
};
const onError = (err: NodeJS.ErrnoException) => {
if (err.code && err.code === 'EADDRINUSE') {
if (!showedPortTakenMsg) {
info(logging, 'astro', msg.portInUse({ port }));
showedPortTakenMsg = true; // only print this once
}
port++;
return listen(); // retry
} else {
error(logging, 'astro', err.stack || err.message);
httpServer?.removeListener('error', onError);
reject(err); // reject
}
};
listen();
let previewServer: VitePreviewServer;
try {
previewServer = await preview({
configFile: false,
base: settings.config.base,
appType: 'mpa',
build: {
outDir: fileURLToPath(settings.config.outDir),
},
preview: {
host: settings.config.server.host,
port: settings.config.server.port,
headers: settings.config.server.headers,
},
plugins: [vitePluginAstroPreview(settings)],
});
} catch (err) {
if (err instanceof Error) {
error(logging, 'astro', err.stack || err.message);
}
throw err;
}
// Start listening on `hostname:port`.
await startServer(startServerTime);
enableDestroy(previewServer.httpServer);
// Log server start URLs
info(
logging,
null,
msg.serverStart({
startupTime: performance.now() - startServerTime,
resolvedUrls: previewServer.resolvedUrls,
host: settings.config.server.host,
base: settings.config.base,
})
);
// Resolves once the server is closed
function closed() {
return new Promise<void>((resolve, reject) => {
httpServer!.addListener('close', resolve);
httpServer!.addListener('error', reject);
previewServer.httpServer.addListener('close', resolve);
previewServer.httpServer.addListener('error', reject);
});
}
return {
host,
port,
host: getResolvedHostForHttpServer(settings.config.server.host),
port: settings.config.server.port,
closed,
server: httpServer!,
server: previewServer.httpServer,
stop: async () => {
await new Promise((resolve, reject) => {
httpServer.close((err) => (err ? reject(err) : resolve(undefined)));
previewServer.httpServer.destroy((err) => (err ? reject(err) : resolve(undefined)));
});
},
};

View file

@ -1,7 +1,7 @@
export function getResolvedHostForHttpServer(host: string | boolean) {
if (host === false) {
// Use a secure default
return '127.0.0.1';
return 'localhost';
} else if (host === true) {
// If passed --host in the CLI without arguments
return undefined; // undefined typically means 0.0.0.0 or :: (listen on all IPs)
@ -9,3 +9,11 @@ export function getResolvedHostForHttpServer(host: string | boolean) {
return host;
}
}
export function stripBase(path: string, base: string): string {
if (path === base) {
return '/';
}
const baseWithSlash = base.endsWith('/') ? base : base + '/';
return path.replace(RegExp('^' + baseWithSlash), '/');
}

View file

@ -0,0 +1,68 @@
import fs from 'fs';
import { fileURLToPath } from 'url';
import { Plugin } from 'vite';
import { AstroSettings } from '../../@types/astro.js';
import { notFoundTemplate, subpathNotUsedTemplate } from '../../template/4xx.js';
import { stripBase } from './util.js';
const HAS_FILE_EXTENSION_REGEXP = /^.*\.[^\\]+$/;
export function vitePluginAstroPreview(settings: AstroSettings): Plugin {
const { base, outDir, trailingSlash } = settings.config;
return {
name: 'astro:preview',
apply: 'serve',
configurePreviewServer(server) {
server.middlewares.use((req, res, next) => {
// respond 404 to requests outside the base request directory
if (!req.url!.startsWith(base)) {
res.statusCode = 404;
res.end(subpathNotUsedTemplate(base, req.url!));
return;
}
const pathname = stripBase(req.url!, base);
const isRoot = pathname === '/';
// Validate trailingSlash
if (!isRoot) {
const hasTrailingSlash = pathname.endsWith('/');
if (hasTrailingSlash && trailingSlash == 'never') {
res.statusCode = 404;
res.end(notFoundTemplate(pathname, 'Not Found (trailingSlash is set to "never")'));
return;
}
if (
!hasTrailingSlash &&
trailingSlash == 'always' &&
!HAS_FILE_EXTENSION_REGEXP.test(pathname)
) {
res.statusCode = 404;
res.end(notFoundTemplate(pathname, 'Not Found (trailingSlash is set to "always")'));
return;
}
}
next();
});
return () => {
server.middlewares.use((req, res) => {
const errorPagePath = fileURLToPath(outDir + '/404.html');
if (fs.existsSync(errorPagePath)) {
res.statusCode = 404;
res.setHeader('Content-Type', 'text/html;charset=utf-8');
res.end(fs.readFileSync(errorPagePath));
} else {
const pathname = stripBase(req.url!, base);
res.statusCode = 404;
res.end(notFoundTemplate(pathname, 'Not Found'));
}
});
};
},
};
}

View file

@ -164,14 +164,6 @@ export function emoji(char: string, fallback: string) {
return process.platform !== 'win32' ? char : fallback;
}
export function getLocalAddress(serverAddress: string, host: string | boolean): string {
if (typeof host === 'boolean' || host === 'localhost') {
return 'localhost';
} else {
return serverAddress;
}
}
/**
* Simulate Vite's resolve and import analysis so we can import the id as an URL
* through a script tag or a dynamic import as-is.

View file

@ -77,17 +77,11 @@ describe('astro cli', () => {
const localURL = new URL(local);
const networkURL = new URL(network);
if (cmd === 'dev') {
expect(localURL.hostname).to.be.oneOf(
['localhost', '127.0.0.1'],
`Expected local URL to be on localhost`
);
} else {
expect(localURL.hostname).to.be.equal(
flagValue ?? 'localhost',
`Expected local URL to be on localhost`
);
}
expect(localURL.hostname).to.be.oneOf(
['localhost', '127.0.0.1'],
`Expected local URL to be on localhost`
);
// Note: our tests run in parallel so this could be 3000+!
expect(Number.parseInt(localURL.port)).to.be.greaterThanOrEqual(
3000,
@ -113,17 +107,11 @@ describe('astro cli', () => {
expect(network).to.not.be.undefined;
const localURL = new URL(local);
if (cmd === 'dev') {
expect(localURL.hostname).to.be.oneOf(
['localhost', '127.0.0.1'],
`Expected local URL to be on localhost`
);
} else {
expect(localURL.hostname).to.be.equal(
'localhost',
`Expected local URL to be on localhost`
);
}
expect(localURL.hostname).to.be.oneOf(
['localhost', '127.0.0.1'],
`Expected local URL to be on localhost`
);
expect(() => new URL(networkURL)).to.throw();
});
});
@ -140,14 +128,10 @@ describe('astro cli', () => {
expect(network).to.be.undefined;
const localURL = new URL(local);
if (cmd === 'dev') {
expect(localURL.hostname).to.be.oneOf(
['localhost', '127.0.0.1'],
`Expected local URL to be on localhost`
);
} else {
expect(localURL.hostname).to.be.equal(flagValue, `Expected local URL to be on localhost`);
}
expect(localURL.hostname).to.be.oneOf(
['localhost', '127.0.0.1'],
`Expected local URL to be on localhost`
);
});
});
});

View file

@ -31,13 +31,13 @@
"test:match": "playwright test -g"
},
"devDependencies": {
"@playwright/test": "^1.26.0",
"@playwright/test": "^1.29.2",
"@types/chai": "^4.3.1",
"@types/chai-as-promised": "^7.1.5",
"@types/mocha": "^9.1.1",
"astro": "workspace:*",
"astro-scripts": "workspace:*",
"playwright": "^1.22.2"
"playwright": "^1.29.2"
},
"dependencies": {
"throttles": "^1.0.1"

View file

@ -19,7 +19,7 @@ test.describe('Basic prefetch', () => {
test('skips /admin', async ({ page, astro }) => {
const requests = [];
page.on('request', async (request) => requests.push(request.url()));
page.on('request', (request) => requests.push(request.url()));
await page.goto(astro.resolveUrl('/'));
@ -56,7 +56,7 @@ test.describe('Basic prefetch', () => {
test('skips /admin', async ({ page, astro }) => {
const requests = [];
page.on('request', async (request) => requests.push(request.url()));
page.on('request', (request) => requests.push(request.url()));
await page.goto(astro.resolveUrl('/'));

View file

@ -27,7 +27,7 @@ test.describe('Custom prefetch selectors', () => {
test('only prefetches /contact', async ({ page, astro }) => {
const requests = [];
page.on('request', async (request) => requests.push(request.url()));
page.on('request', (request) => requests.push(request.url()));
await page.goto(astro.resolveUrl('/'));
@ -64,7 +64,7 @@ test.describe('Custom prefetch selectors', () => {
test('only prefetches /contact', async ({ page, astro }) => {
const requests = [];
page.on('request', async (request) => requests.push(request.url()));
page.on('request', (request) => requests.push(request.url()));
await page.goto(astro.resolveUrl('/'));

View file

@ -39,7 +39,7 @@ test.describe('Style prefetch', () => {
test('style fetching', async ({ page, astro }) => {
const requests = [];
page.on('request', async (request) => requests.push(request.url()));
page.on('request', (request) => requests.push(request.url()));
await page.goto(astro.resolveUrl('/'));

View file

@ -396,7 +396,7 @@ importers:
'@babel/plugin-transform-react-jsx': ^7.17.12
'@babel/traverse': ^7.18.2
'@babel/types': ^7.18.4
'@playwright/test': ^1.22.2
'@playwright/test': ^1.29.2
'@types/babel__core': ^7.1.19
'@types/babel__generator': ^7.6.4
'@types/babel__traverse': ^7.17.1
@ -418,6 +418,7 @@ importers:
'@types/resolve': ^1.20.2
'@types/rimraf': ^3.0.2
'@types/send': ^0.17.1
'@types/server-destroy': ^1.0.1
'@types/unist': ^2.0.6
'@types/yargs-parser': ^21.0.0
acorn: ^8.8.1
@ -466,8 +467,8 @@ importers:
rollup: ^3.9.0
sass: ^1.52.2
semver: ^7.3.7
server-destroy: ^1.0.1
shiki: ^0.11.1
sirv: ^2.0.2
slash: ^4.0.0
srcset-parse: ^1.1.0
string-width: ^5.1.2
@ -529,8 +530,8 @@ importers:
rehype: 12.0.1
resolve: 1.22.1
semver: 7.3.8
server-destroy: 1.0.1
shiki: 0.11.1
sirv: 2.0.2
slash: 4.0.0
string-width: 5.1.2
strip-ansi: 7.0.1
@ -564,6 +565,7 @@ importers:
'@types/resolve': 1.20.2
'@types/rimraf': 3.0.2
'@types/send': 0.17.1
'@types/server-destroy': 1.0.1
'@types/unist': 2.0.6
astro-scripts: link:../../scripts
chai: 4.3.7
@ -3139,13 +3141,13 @@ importers:
packages/integrations/prefetch:
specifiers:
'@playwright/test': ^1.26.0
'@playwright/test': ^1.29.2
'@types/chai': ^4.3.1
'@types/chai-as-promised': ^7.1.5
'@types/mocha': ^9.1.1
astro: workspace:*
astro-scripts: workspace:*
playwright: ^1.22.2
playwright: ^1.29.2
throttles: ^1.0.1
dependencies:
throttles: 1.0.1
@ -7055,6 +7057,7 @@ packages:
/@types/node/14.18.36:
resolution: {integrity: sha512-FXKWbsJ6a1hIrRxv+FoukuHnGTgEzKYGi7kilfMae96AL9UNkPFNWJEEYWzdRI9ooIkbr4AKldyuSTLql06vLQ==}
dev: true
/@types/node/16.18.11:
resolution: {integrity: sha512-3oJbGBUWuS6ahSnEq1eN2XrCyf4YsWI8OyCvo7c64zQJNplk3mO84t53o8lfTk+2ji59g5ycfc6qQ3fdHliHuA==}
@ -7126,7 +7129,7 @@ packages:
/@types/resolve/1.17.1:
resolution: {integrity: sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw==}
dependencies:
'@types/node': 14.18.36
'@types/node': 18.11.18
/@types/resolve/1.20.2:
resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==}
@ -7141,7 +7144,7 @@ packages:
/@types/sax/1.2.4:
resolution: {integrity: sha512-pSAff4IAxJjfAXUG6tFkO7dsSbTmf8CtUpfhhZ5VhkRpC4628tJhh3+V6H1E+/Gs9piSzYKT5yzHO5M4GG9jkw==}
dependencies:
'@types/node': 17.0.45
'@types/node': 18.11.18
dev: false
/@types/scheduler/0.16.2:
@ -7162,6 +7165,12 @@ packages:
'@types/node': 18.11.18
dev: true
/@types/server-destroy/1.0.1:
resolution: {integrity: sha512-77QGr7waZbE0Y0uF+G+uH3H3SmhyA78Jf2r5r7QSrpg0U3kSXduWpGjzP9PvPLR/KCy+kHjjpnugRHsYTnHopg==}
dependencies:
'@types/node': 18.11.18
dev: true
/@types/set-cookie-parser/2.4.2:
resolution: {integrity: sha512-fBZgytwhYAUkj/jC/FAV4RQ5EerRup1YQsXQCh8rZfiHkc4UahC192oH0smGwsXol3cL3A5oETuAHeQHmhXM4w==}
dependencies:
@ -11129,7 +11138,7 @@ packages:
resolution: {integrity: sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==}
engines: {node: '>= 10.13.0'}
dependencies:
'@types/node': 14.18.36
'@types/node': 18.11.18
merge-stream: 2.0.0
supports-color: 7.2.0
@ -13994,6 +14003,10 @@ packages:
randombytes: 2.1.0
dev: true
/server-destroy/1.0.1:
resolution: {integrity: sha512-rb+9B5YBIEzYcD6x2VKidaa+cqYBJQKnU4oe4E3ANwRRN56yk/ua1YCJT1n21NTS8w6CcOclAKNP3PhdCXKYtQ==}
dev: false
/set-blocking/2.0.0:
resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==}