diff --git a/.changeset/yellow-trees-clean.md b/.changeset/yellow-trees-clean.md new file mode 100644 index 000000000..efd2c364b --- /dev/null +++ b/.changeset/yellow-trees-clean.md @@ -0,0 +1,5 @@ +--- +'astro': patch +--- + +Update server start message to use localhost for local hostnames diff --git a/packages/astro/src/core/dev/index.ts b/packages/astro/src/core/dev/index.ts index 2084c643c..0a790c06e 100644 --- a/packages/astro/src/core/dev/index.ts +++ b/packages/astro/src/core/dev/index.ts @@ -6,6 +6,7 @@ import { createVite } from '../create-vite.js'; import { defaultLogOptions, info, LogOptions } from '../logger.js'; import * as vite from 'vite'; import * as msg from '../messages.js'; +import { getLocalAddress } from './util.js'; export interface DevOptions { logging: LogOptions; @@ -37,10 +38,10 @@ 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 site = config.buildOptions.site ? new URL(config.buildOptions.site) : undefined; - info(options.logging, 'astro', msg.devStart({ startupTime: performance.now() - devStart })); - info(options.logging, 'astro', msg.devHost({ address, site, https: !!viteUserConfig.server?.https })); + info(options.logging, null, msg.devStart({ startupTime: performance.now() - devStart, port: address.port, localAddress, networkAddress: address.address, site, https: !!viteUserConfig.server?.https })); return { address, diff --git a/packages/astro/src/core/dev/util.ts b/packages/astro/src/core/dev/util.ts index 1b942c3b8..78d21ad02 100644 --- a/packages/astro/src/core/dev/util.ts +++ b/packages/astro/src/core/dev/util.ts @@ -6,3 +6,15 @@ export function pad(input: string, minLength: number, dir?: 'left' | 'right'): s } return output; } + +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') { + return 'localhost' + } else { + return serverAddress + } +} \ No newline at end of file diff --git a/packages/astro/src/core/logger.ts b/packages/astro/src/core/logger.ts index 195094ba3..069630982 100644 --- a/packages/astro/src/core/logger.ts +++ b/packages/astro/src/core/logger.ts @@ -35,10 +35,11 @@ export const defaultLogDestination = new Writable({ dest = process.stdout; } - dest.write(dim(dt.format(new Date()) + ' ')); let type = event.type; if (type) { + // hide timestamp when type is undefined + dest.write(dim(dt.format(new Date()) + ' ')); if (event.level === 'info') { type = bold(blue(type)); } else if (event.level === 'warn') { diff --git a/packages/astro/src/core/messages.ts b/packages/astro/src/core/messages.ts index e2272bb9b..b2d4d07f8 100644 --- a/packages/astro/src/core/messages.ts +++ b/packages/astro/src/core/messages.ts @@ -3,8 +3,8 @@ */ import type { AddressInfo } from 'net'; -import { bold, dim, green, magenta, yellow } from 'kleur/colors'; -import { pad } from './dev/util.js'; +import { bold, dim, green, magenta, yellow, cyan } from 'kleur/colors'; +import { pad, emoji } from './dev/util.js'; /** Display */ export function req({ url, statusCode, reqTime }: { url: string; statusCode: number; reqTime?: number }): string { @@ -23,8 +23,21 @@ export function reload({ url, reqTime }: { url: string; reqTime: number }): stri } /** Display dev server host and startup time */ -export function devStart({ startupTime }: { startupTime: number }): string { - return `${pad(`Server started`, 44)} ${dim(`${Math.round(startupTime)}ms`)}`; +export function devStart({ startupTime, port, localAddress, networkAddress, https, site }: { startupTime: number; port: number; localAddress: string; networkAddress: string; https: boolean; site: URL | undefined }): string { + // PACAKGE_VERSION is injected at build-time + const pkgVersion = process.env.PACKAGE_VERSION; + + const rootPath = site ? site.pathname : '/'; + const toDisplayUrl = (hostname: string) => `${https ? 'https' : 'http'}://${hostname}:${port}${rootPath}` + const messages = [ + ``, + `${emoji('🚀 ', '')}${magenta(`astro ${pkgVersion}`)} ${dim(`started in ${Math.round(startupTime)}ms`)}`, + ``, + `Local: ${bold(cyan(toDisplayUrl(localAddress)))}`, + `Network: ${bold(cyan(toDisplayUrl(networkAddress)))}`, + ``, + ] + return messages.join('\n') } /** Display dev server host */ diff --git a/packages/astro/src/core/preview/index.ts b/packages/astro/src/core/preview/index.ts index 5dff165e2..de6e4e173 100644 --- a/packages/astro/src/core/preview/index.ts +++ b/packages/astro/src/core/preview/index.ts @@ -1,6 +1,7 @@ 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 { performance } from 'perf_hooks'; @@ -11,6 +12,7 @@ import * as msg from '../messages.js'; import { error, info } from '../logger.js'; import { subpathNotUsedTemplate, notFoundTemplate, default as template } from '../../template/4xx.js'; import { appendForwardSlash, trimSlashes } from '../path.js'; +import { getLocalAddress } from '../dev/util.js'; interface PreviewOptions { logging: LogOptions; @@ -123,10 +125,12 @@ export default async function preview(config: AstroConfig, { logging }: PreviewO let showedListenMsg = false; return new Promise((resolve, reject) => { const listen = () => { - httpServer = server.listen(port, hostname, () => { + httpServer = server.listen(port, hostname, async () => { if (!showedListenMsg) { - info(logging, 'astro', msg.devStart({ startupTime: performance.now() - timerStart })); - info(logging, 'astro', msg.devHost({ address: { family: 'ipv4', address: hostname, port }, https: false, site: baseURL })); + 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 })); } showedListenMsg = true; resolve(); diff --git a/packages/astro/test/cli.test.js b/packages/astro/test/cli.test.js index c04cc6c2f..863cfcae2 100644 --- a/packages/astro/test/cli.test.js +++ b/packages/astro/test/cli.test.js @@ -19,22 +19,26 @@ describe('astro cli', () => { expect(proc.stdout).to.equal(pkgVersion); }); - it('astro dev', async () => { - const projectRootURL = new URL('./fixtures/astro-basic/', import.meta.url); + [undefined, '0.0.0.0', '127.0.0.1'].forEach(hostname => { + it(`astro dev --hostname=${hostname}`, async () => { + const projectRootURL = new URL('./fixtures/astro-basic/', import.meta.url); - const proc = cli('dev', '--project-root', fileURLToPath(projectRootURL)); + const hostnameArgs = hostname ? ['--hostname', hostname] : [] + const proc = cli('dev', '--project-root', fileURLToPath(projectRootURL), ...hostnameArgs); - let stdout = ''; + let stdout = ''; - for await (const chunk of proc.stdout) { - stdout += chunk; + for await (const chunk of proc.stdout) { + stdout += chunk; - if (chunk.includes('Local:')) break; - } + if (chunk.includes('Local:')) break; + } - proc.kill(); + proc.kill(); - expect(stdout).to.include('Server started'); + expect(stdout).to.include('Local: http://localhost:3000'); + expect(stdout).to.include(`Network: http://${hostname ?? '127.0.0.1'}:3000`); + }); }); it('astro build', async () => { diff --git a/scripts/cmd/build.js b/scripts/cmd/build.js index b5eb91195..02c0f5798 100644 --- a/scripts/cmd/build.js +++ b/scripts/cmd/build.js @@ -28,7 +28,9 @@ export default async function build(...args) { .map((f) => f.replace(/^'/, '').replace(/'$/, '')); // Needed for Windows: glob strings contain surrounding string chars??? remove these let entryPoints = [].concat(...(await Promise.all(patterns.map((pattern) => glob(pattern, { filesOnly: true, absolute: true }))))); - const { type = 'module', dependencies = {} } = await fs.readFile('./package.json').then((res) => JSON.parse(res.toString())); + const { type = 'module', version, dependencies = {} } = await fs.readFile('./package.json').then((res) => JSON.parse(res.toString())); + // expose PACKAGE_VERSION on process.env for CLI utils + config.define = { 'process.env.PACKAGE_VERSION': JSON.stringify(version) }; const format = type === 'module' ? 'esm' : 'cjs'; const outdir = 'dist'; await clean(outdir);