diff --git a/.changeset/breezy-walls-guess.md b/.changeset/breezy-walls-guess.md new file mode 100644 index 000000000..160423173 --- /dev/null +++ b/.changeset/breezy-walls-guess.md @@ -0,0 +1,5 @@ +--- +'astro': minor +--- + +Introduce a new --host flag + host devOption to expose your server on a network IP diff --git a/packages/astro/src/@types/astro.ts b/packages/astro/src/@types/astro.ts index 452974d63..400f5070b 100644 --- a/packages/astro/src/@types/astro.ts +++ b/packages/astro/src/@types/astro.ts @@ -24,6 +24,7 @@ export interface CLIFlags { projectRoot?: string; site?: string; sitemap?: boolean; + host?: string | boolean; hostname?: string; port?: number; config?: string; @@ -314,12 +315,29 @@ export interface AstroUserConfig { * @name Dev Options */ devOptions?: { + /** + * @docs + * @name devOptions.host + * @type {string | boolean} + * @default `false` + * @version 0.24.0 + * @description + * Set which network IP addresses the dev server should listen on (i.e. non-localhost IPs). + * - `false` - do not expose on a network IP address + * - `true` - listen on all addresses, including LAN and public addresses + * - `[custom-address]` - expose on a network IP address at `[custom-address]` + */ + host?: string | boolean; + /** * @docs * @name devOptions.hostname * @type {string} * @default `'localhost'` + * @deprecated Use `host` instead * @description + * > **This option is deprecated.** Consider using `host` instead. + * * Set which IP addresses the dev server should listen on. Set this to 0.0.0.0 to listen on all addresses, including LAN and public addresses. */ hostname?: string; diff --git a/packages/astro/src/cli/index.ts b/packages/astro/src/cli/index.ts index afb564847..ffa1071cc 100644 --- a/packages/astro/src/cli/index.ts +++ b/packages/astro/src/cli/index.ts @@ -38,6 +38,7 @@ function printHelp() { title('Flags'); table( [ + ['--host [optional IP]', 'Expose server on network'], ['--config ', 'Specify the path to the Astro config file.'], ['--project-root ', 'Specify the path to the project root folder.'], ['--no-sitemap', 'Disable sitemap generation (build only).'], diff --git a/packages/astro/src/core/config.ts b/packages/astro/src/core/config.ts index e20bc4132..5400a9514 100644 --- a/packages/astro/src/core/config.ts +++ b/packages/astro/src/core/config.ts @@ -65,6 +65,7 @@ export const AstroConfigSchema = z.object({ .default({}), devOptions: z .object({ + host: z.union([z.string(), z.boolean()]).optional().default(false), hostname: z.string().optional().default('localhost'), port: z.number().optional().default(3000), trailingSlash: z @@ -125,6 +126,7 @@ function resolveFlags(flags: Partial): CLIFlags { port: typeof flags.port === 'number' ? flags.port : undefined, config: typeof flags.config === 'string' ? flags.config : undefined, hostname: typeof flags.hostname === 'string' ? flags.hostname : undefined, + host: typeof flags.host === 'string' || typeof flags.host === 'boolean' ? flags.host : undefined, legacyBuild: typeof flags.legacyBuild === 'boolean' ? flags.legacyBuild : false, experimentalSsr: typeof flags.experimentalSsr === 'boolean' ? flags.experimentalSsr : false, drafts: typeof flags.drafts === 'boolean' ? flags.drafts : false, @@ -138,6 +140,7 @@ function mergeCLIFlags(astroConfig: AstroUserConfig, flags: CLIFlags) { if (typeof flags.sitemap === 'boolean') astroConfig.buildOptions.sitemap = flags.sitemap; if (typeof flags.site === 'string') astroConfig.buildOptions.site = flags.site; if (typeof flags.port === 'number') astroConfig.devOptions.port = flags.port; + if (typeof flags.host === 'string' || typeof flags.host === 'boolean') astroConfig.devOptions.host = flags.host; if (typeof flags.hostname === 'string') astroConfig.devOptions.hostname = flags.hostname; if (typeof flags.legacyBuild === 'boolean') astroConfig.buildOptions.legacyBuild = flags.legacyBuild; if (typeof flags.experimentalSsr === 'boolean') { diff --git a/packages/astro/src/core/dev/index.ts b/packages/astro/src/core/dev/index.ts index 085e5d665..a1b48e1d9 100644 --- a/packages/astro/src/core/dev/index.ts +++ b/packages/astro/src/core/dev/index.ts @@ -6,7 +6,7 @@ import { createVite } from '../create-vite.js'; import { defaultLogOptions, info, warn, LogOptions } from '../logger.js'; import * as vite from 'vite'; import * as msg from '../messages.js'; -import { getLocalAddress } from './util.js'; +import { getResolvedHostForVite } from './util.js'; export interface DevOptions { logging: LogOptions; @@ -24,13 +24,13 @@ export default async function dev(config: AstroConfig, options: DevOptions = { l polyfill(globalThis, { exclude: 'window document', }); - // start the server + + // TODO: remove call once --hostname is baselined + const host = getResolvedHostForVite(config); const viteUserConfig = vite.mergeConfig( { mode: 'development', - server: { - host: config.devOptions.hostname, - }, + server: { host }, }, config.vite || {} ); @@ -38,15 +38,9 @@ export default async function dev(config: AstroConfig, options: DevOptions = { l const viteServer = await vite.createServer(viteConfig); await viteServer.listen(config.devOptions.port); - const address = viteServer.httpServer!.address() as AddressInfo; - const localAddress = getLocalAddress(address.address, config.devOptions.hostname); - // Log to console + const devServerAddressInfo = viteServer.httpServer!.address() as AddressInfo; const site = config.buildOptions.site ? new URL(config.buildOptions.site) : undefined; - info( - options.logging, - null, - msg.devStart({ startupTime: performance.now() - devStart, port: address.port, localAddress, networkAddress: address.address, site, https: !!viteUserConfig.server?.https }) - ); + info(options.logging, null, msg.devStart({ startupTime: performance.now() - devStart, config, devServerAddressInfo, site, https: !!viteUserConfig.server?.https })); const currentVersion = process.env.PACKAGE_VERSION ?? '0.0.0'; if (currentVersion.includes('-')) { @@ -54,7 +48,7 @@ export default async function dev(config: AstroConfig, options: DevOptions = { l } return { - address, + address: devServerAddressInfo, stop: () => viteServer.close(), }; } diff --git a/packages/astro/src/core/dev/util.ts b/packages/astro/src/core/dev/util.ts index 97b622438..9b0c974fd 100644 --- a/packages/astro/src/core/dev/util.ts +++ b/packages/astro/src/core/dev/util.ts @@ -1,3 +1,7 @@ +import type { AstroConfig } from '../../@types/astro'; + +export const localIps = new Set(['localhost', '127.0.0.1']); + /** Pad string () */ export function pad(input: string, minLength: number, dir?: 'left' | 'right'): string { let output = input; @@ -11,10 +15,36 @@ export function emoji(char: string, fallback: string) { return process.platform !== 'win32' ? char : fallback; } -export function getLocalAddress(serverAddress: string, configHostname: string): string { - if (configHostname === 'localhost' || serverAddress === '127.0.0.1' || serverAddress === '0.0.0.0') { +// TODO: remove once --hostname is baselined +export function getResolvedHostForVite(config: AstroConfig) { + if (config.devOptions.host === false && config.devOptions.hostname !== 'localhost') { + return config.devOptions.hostname; + } else { + return config.devOptions.host; + } +} + +export function getLocalAddress(serverAddress: string, config: AstroConfig): string { + // TODO: remove once --hostname is baselined + const host = getResolvedHostForVite(config); + if (typeof host === 'boolean' || host === 'localhost') { return 'localhost'; } else { return serverAddress; } } + +export type NetworkLogging = 'none' | 'host-to-expose' | 'visible'; + +export function getNetworkLogging(config: AstroConfig): NetworkLogging { + // TODO: remove once --hostname is baselined + const host = getResolvedHostForVite(config); + + if (host === false) { + return 'host-to-expose'; + } else if (typeof host === 'string' && localIps.has(host)) { + return 'none'; + } else { + return 'visible'; + } +} diff --git a/packages/astro/src/core/messages.ts b/packages/astro/src/core/messages.ts index a538e7bbb..c84a454d1 100644 --- a/packages/astro/src/core/messages.ts +++ b/packages/astro/src/core/messages.ts @@ -2,10 +2,12 @@ * Dev server messages (organized here to prevent clutter) */ -import type { AddressInfo } from 'net'; import stripAnsi from 'strip-ansi'; import { bold, dim, red, green, underline, yellow, bgYellow, cyan, bgGreen, black } from 'kleur/colors'; -import { pad, emoji } from './dev/util.js'; +import { pad, emoji, getLocalAddress, getNetworkLogging } from './dev/util.js'; +import os from 'os'; +import type { AddressInfo } from 'net'; +import type { AstroConfig } from '../@types/astro'; const PREFIX_PADDING = 6; @@ -30,31 +32,50 @@ export function hmr({ file }: { file: string }): string { /** Display dev server host and startup time */ export function devStart({ startupTime, - port, - localAddress, - networkAddress, + devServerAddressInfo, + config, https, site, }: { startupTime: number; - port: number; - localAddress: string; - networkAddress: string; + devServerAddressInfo: AddressInfo; + config: AstroConfig; https: boolean; site: URL | undefined; }): string { - // PACAKGE_VERSION is injected at build-time + // PACKAGE_VERSION is injected at build-time const version = process.env.PACKAGE_VERSION ?? '0.0.0'; const rootPath = site ? site.pathname : '/'; - const toDisplayUrl = (hostname: string) => `${https ? 'https' : 'http'}://${hostname}:${port}${rootPath}`; + const localPrefix = `${dim('┃')} Local `; + const networkPrefix = `${dim('┃')} Network `; - const messages = [ - `${emoji('🚀 ', '')}${bgGreen(black(` astro `))} ${green(`v${version}`)} ${dim(`started in ${Math.round(startupTime)}ms`)}`, - '', - `${dim('┃')} Local ${bold(cyan(toDisplayUrl(localAddress)))}`, - `${dim('┃')} Network ${bold(cyan(toDisplayUrl(networkAddress)))}`, - '', - ]; + const { address: networkAddress, port } = devServerAddressInfo; + const localAddress = getLocalAddress(networkAddress, config); + const networkLogging = getNetworkLogging(config); + const toDisplayUrl = (hostname: string) => `${https ? 'https' : 'http'}://${hostname}:${port}${rootPath}`; + let addresses = []; + + if (networkLogging === 'none') { + addresses = [`${localPrefix}${bold(cyan(toDisplayUrl(localAddress)))}`]; + } else if (networkLogging === 'host-to-expose') { + addresses = [`${localPrefix}${bold(cyan(toDisplayUrl(localAddress)))}`, `${networkPrefix}${dim('use --host to expose')}`]; + } else { + addresses = Object.values(os.networkInterfaces()) + .flatMap((networkInterface) => networkInterface ?? []) + .filter((networkInterface) => networkInterface?.address && networkInterface?.family === 'IPv4') + .map(({ address }) => { + if (address.includes('127.0.0.1')) { + const displayAddress = address.replace('127.0.0.1', localAddress); + return `${localPrefix}${bold(cyan(toDisplayUrl(displayAddress)))}`; + } else { + return `${networkPrefix}${bold(cyan(toDisplayUrl(address)))}`; + } + }) + // ensure Local logs come before Network + .sort((msg) => (msg.startsWith(localPrefix) ? -1 : 1)); + } + + const messages = [`${emoji('🚀 ', '')}${bgGreen(black(` astro `))} ${green(`v${version}`)} ${dim(`started in ${Math.round(startupTime)}ms`)}`, '', ...addresses, '']; return messages.map((msg) => ` ${msg}`).join('\n'); } diff --git a/packages/astro/src/core/preview/index.ts b/packages/astro/src/core/preview/index.ts index b686cf3f9..71bdba82e 100644 --- a/packages/astro/src/core/preview/index.ts +++ b/packages/astro/src/core/preview/index.ts @@ -1,6 +1,5 @@ import type { AstroConfig } from '../../@types/astro'; import type { LogOptions } from '../logger'; -import type { Stats } from 'fs'; import type { AddressInfo } from 'net'; import http from 'http'; import sirv from 'sirv'; @@ -8,16 +7,15 @@ import { performance } from 'perf_hooks'; import { fileURLToPath } from 'url'; import * as msg from '../messages.js'; import { error, info } from '../logger.js'; -import { appendForwardSlash, trimSlashes } from '../path.js'; -import { getLocalAddress } from '../dev/util.js'; import { subpathNotUsedTemplate, notFoundTemplate } from '../../template/4xx.js'; +import { getResolvedHostForHttpServer } from './util.js'; interface PreviewOptions { logging: LogOptions; } export interface PreviewServer { - hostname: string; + host?: string; port: number; server: http.Server; stop(): Promise; @@ -75,7 +73,8 @@ export default async function preview(config: AstroConfig, { logging }: PreviewO } }); - let { hostname, port } = config.devOptions; + let { port } = config.devOptions; + const host = getResolvedHostForHttpServer(config); let httpServer: http.Server; @@ -85,12 +84,10 @@ export default async function preview(config: AstroConfig, { logging }: PreviewO let showedListenMsg = false; return new Promise((resolve, reject) => { const listen = () => { - httpServer = server.listen(port, hostname, async () => { + httpServer = server.listen(port, host, async () => { if (!showedListenMsg) { - const { address: networkAddress } = server.address() as AddressInfo; - const localAddress = getLocalAddress(networkAddress, hostname); - - info(logging, null, msg.devStart({ startupTime: performance.now() - timerStart, port, localAddress, networkAddress, https: false, site: baseURL })); + const devServerAddressInfo = server.address() as AddressInfo; + info(logging, null, msg.devStart({ startupTime: performance.now() - timerStart, config, devServerAddressInfo, https: false, site: baseURL })); } showedListenMsg = true; resolve(); @@ -121,7 +118,7 @@ export default async function preview(config: AstroConfig, { logging }: PreviewO await startServer(startServerTime); return { - hostname, + host, port, server: httpServer!, stop: async () => { diff --git a/packages/astro/src/core/preview/util.ts b/packages/astro/src/core/preview/util.ts new file mode 100644 index 000000000..90c42ec32 --- /dev/null +++ b/packages/astro/src/core/preview/util.ts @@ -0,0 +1,17 @@ +import type { AstroConfig } from '../../@types/astro'; + +export function getResolvedHostForHttpServer(config: AstroConfig) { + const { host, hostname } = config.devOptions; + + if (host === false && hostname === 'localhost') { + // Use a secure default + return '127.0.0.1'; + } 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) + } else if (typeof host === 'string') { + return host; + } else { + return hostname; + } +} diff --git a/packages/astro/test/cli.test.js b/packages/astro/test/cli.test.js index 0072727a2..a90f26cef 100644 --- a/packages/astro/test/cli.test.js +++ b/packages/astro/test/cli.test.js @@ -1,9 +1,15 @@ import { expect } from 'chai'; -import { cli, parseCliDevStart } from './test-utils.js'; +import { cli, parseCliDevStart, cliServerLogSetup } from './test-utils.js'; import { promises as fs } from 'fs'; import { fileURLToPath } from 'url'; +import { isIPv4 } from 'net'; describe('astro cli', () => { + const cliServerLogSetupWithFixture = (flags, cmd) => { + const projectRootURL = new URL('./fixtures/astro-basic/', import.meta.url); + return cliServerLogSetup(['--project-root', fileURLToPath(projectRootURL), ...flags], cmd); + }; + it('astro', async () => { const proc = await cli(); @@ -32,28 +38,52 @@ describe('astro cli', () => { expect(messages[0]).to.contain('started in'); }); - const hostnames = [undefined, '0.0.0.0', '127.0.0.1']; + ['dev', 'preview'].forEach((cmd) => { + const networkLogFlags = [['--host'], ['--host', '0.0.0.0'], ['--hostname', '0.0.0.0']]; + networkLogFlags.forEach(([flag, flagValue]) => { + it(`astro ${cmd} ${flag} ${flagValue ?? ''} - network log`, async () => { + const { local, network } = await cliServerLogSetupWithFixture(flagValue ? [flag, flagValue] : [flag], cmd); - hostnames.forEach((hostname) => { - const hostnameArgs = hostname ? ['--hostname', hostname] : []; - it(`astro dev ${hostnameArgs.join(' ') || '(no --hostname)'}`, async () => { - const projectRootURL = new URL('./fixtures/astro-basic/', import.meta.url); - const proc = cli('dev', '--project-root', fileURLToPath(projectRootURL), ...hostnameArgs); + expect(local).to.not.be.undefined; + expect(network).to.not.be.undefined; - const { messages } = await parseCliDevStart(proc); + const localURL = new URL(local); + const networkURL = new URL(network); - const local = messages[1].replace(/Local\s*/g, ''); - const network = messages[2].replace(/Network\s*/g, ''); + expect(localURL.hostname).to.be.equal(flagValue ?? 'localhost', `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, `Expected Port to be >= 3000`); + expect(networkURL.port).to.be.equal(localURL.port, `Expected local and network ports to be equal`); + expect(isIPv4(networkURL.hostname)).to.be.equal(true, `Expected network URL to respect --host flag`); + }); + }); - expect(local).to.not.be.undefined; - expect(network).to.not.be.undefined; - const localURL = new URL(local); - const networkURL = new URL(network); - expect(localURL.hostname).to.be.equal('localhost', `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, `Expected Port to be >= 3000`); - expect(networkURL.hostname).to.be.equal(hostname ?? '127.0.0.1', `Expected Network URL to use passed hostname`); + const hostToExposeFlags = [['', ''], ['--hostname', 'localhost']]; + hostToExposeFlags.forEach(([flag, flagValue]) => { + it(`astro ${cmd} ${flag} ${flagValue} - host to expose`, async () => { + const { local, network } = await cliServerLogSetupWithFixture([flag, flagValue], cmd); + + expect(local).to.not.be.undefined; + expect(network).to.not.be.undefined; + const localURL = new URL(local); + + expect(localURL.hostname).to.be.equal('localhost', `Expected local URL to be on localhost`); + expect(() => new URL(networkURL)).to.throw(); + }); + }); + + const noNetworkLogFlags = [['--host', 'localhost'], ['--host', '127.0.0.1'], ['--hostname', '127.0.0.1']]; + noNetworkLogFlags.forEach(([flag, flagValue]) => { + it(`astro ${cmd} ${flag} ${flagValue} - no network log`, async () => { + const { local, network } = await cliServerLogSetupWithFixture([flag, flagValue], cmd); + + expect(local).to.not.be.undefined; + expect(network).to.be.undefined; + + const localURL = new URL(local); + expect(localURL.hostname).to.be.equal(flagValue, `Expected local URL to be on localhost`); + }); }); }); diff --git a/packages/astro/test/config.test.js b/packages/astro/test/config.test.js index c8a491e11..84193694a 100644 --- a/packages/astro/test/config.test.js +++ b/packages/astro/test/config.test.js @@ -1,15 +1,22 @@ import { expect } from 'chai'; -import { cli, loadFixture } from './test-utils.js'; +import { cli, loadFixture, cliServerLogSetup } from './test-utils.js'; import { fileURLToPath } from 'url'; +import { isIPv4 } from 'net'; describe('config', () => { let hostnameFixture; + let hostFixture; let portFixture; before(async () => { - [hostnameFixture, portFixture] = await Promise.all([loadFixture({ projectRoot: './fixtures/config-hostname/' }), loadFixture({ projectRoot: './fixtures/config-port/' })]); + [hostnameFixture, hostFixture, portFixture] = await Promise.all([ + loadFixture({ projectRoot: './fixtures/config-hostname/' }), + loadFixture({ projectRoot: './fixtures/config-host/' }), + loadFixture({ projectRoot: './fixtures/config-port/' }), + ]); }); + // TODO: remove test once --hostname is baselined describe('hostname', () => { it('can be specified in astro.config.mjs', async () => { expect(hostnameFixture.config.devOptions.hostname).to.equal('0.0.0.0'); @@ -17,19 +24,24 @@ describe('config', () => { it('can be specified via --hostname flag', async () => { const projectRootURL = new URL('./fixtures/astro-basic/', import.meta.url); - const proc = cli('dev', '--project-root', fileURLToPath(projectRootURL), '--hostname', '127.0.0.1'); + const { network } = await cliServerLogSetup(['--project-root', fileURLToPath(projectRootURL), '--hostname', '0.0.0.0']); - let stdout = ''; + const networkURL = new URL(network); + expect(isIPv4(networkURL.hostname)).to.be.equal(true, `Expected network URL to respect --hostname flag`); + }); + }); - for await (const chunk of proc.stdout) { - stdout += chunk; + describe('host', () => { + it('can be specified in astro.config.mjs', async () => { + expect(hostFixture.config.devOptions.host).to.equal(true); + }); - if (chunk.includes('Local')) break; - } + it('can be specified via --host flag', async () => { + const projectRootURL = new URL('./fixtures/astro-basic/', import.meta.url); + const { network } = await cliServerLogSetup(['--project-root', fileURLToPath(projectRootURL), '--host']); - proc.kill(); - - expect(stdout).to.include('127.0.0.1'); + const networkURL = new URL(network); + expect(isIPv4(networkURL.hostname)).to.be.equal(true, `Expected network URL to respect --hostname flag`); }); }); @@ -37,19 +49,10 @@ describe('config', () => { it('can be passed via --config', async () => { const projectRootURL = new URL('./fixtures/astro-basic/', import.meta.url); const configFileURL = new URL('./fixtures/config-path/config/my-config.mjs', import.meta.url); - const proc = cli('dev', '--project-root', fileURLToPath(projectRootURL), '--config', configFileURL.pathname); + const { network } = await cliServerLogSetup(['--project-root', fileURLToPath(projectRootURL), '--config', configFileURL.pathname]); - let stdout = ''; - - for await (const chunk of proc.stdout) { - stdout += chunk; - - if (chunk.includes('Local')) break; - } - - proc.kill(); - - expect(stdout).to.include('127.0.0.1'); + const networkURL = new URL(network); + expect(isIPv4(networkURL.hostname)).to.be.equal(true, `Expected network URL to respect --hostname flag`); }); }); diff --git a/packages/astro/test/fixtures/config-host/astro.config.mjs b/packages/astro/test/fixtures/config-host/astro.config.mjs new file mode 100644 index 000000000..5dceb4a1f --- /dev/null +++ b/packages/astro/test/fixtures/config-host/astro.config.mjs @@ -0,0 +1,6 @@ + +export default { + devOptions: { + host: true + } +} diff --git a/packages/astro/test/fixtures/config-host/package.json b/packages/astro/test/fixtures/config-host/package.json new file mode 100644 index 000000000..8ddfe530a --- /dev/null +++ b/packages/astro/test/fixtures/config-host/package.json @@ -0,0 +1,8 @@ +{ + "name": "@astrojs/test-config-host", + "version": "0.0.0", + "private": true, + "dependencies": { + "astro": "workspace:*" + } +} diff --git a/packages/astro/test/fixtures/config-path/config/my-config.mjs b/packages/astro/test/fixtures/config-path/config/my-config.mjs index e873034e1..c9d45e6c8 100644 --- a/packages/astro/test/fixtures/config-path/config/my-config.mjs +++ b/packages/astro/test/fixtures/config-path/config/my-config.mjs @@ -1,6 +1,6 @@ export default { devOptions: { - hostname: '127.0.0.1', + host: true, port: 8080, }, } diff --git a/packages/astro/test/test-utils.js b/packages/astro/test/test-utils.js index 69fabacda..340653325 100644 --- a/packages/astro/test/test-utils.js +++ b/packages/astro/test/test-utils.js @@ -143,4 +143,18 @@ export async function parseCliDevStart(proc) { return { messages }; } +export async function cliServerLogSetup(flags = [], cmd = 'dev') { + const proc = cli(cmd, ...flags); + + const { messages } = await parseCliDevStart(proc); + + const localRaw = (messages[1] ?? '').includes('Local') ? messages[1] : undefined; + const networkRaw = (messages[2] ?? '').includes('Network') ? messages[2] : undefined; + + const local = localRaw?.replace(/Local\s*/g, ''); + const network = networkRaw?.replace(/Network\s*/g, ''); + + return { local, network }; +} + export const isWindows = os.platform() === 'win32'; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 628231b58..c23db5ee6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -754,6 +754,12 @@ importers: dependencies: astro: link:../../.. + packages/astro/test/fixtures/config-host: + specifiers: + astro: workspace:* + dependencies: + astro: link:../../.. + packages/astro/test/fixtures/config-hostname: specifiers: astro: workspace:*