diff --git a/.changeset/good-teachers-relax.md b/.changeset/good-teachers-relax.md new file mode 100644 index 0000000000..6048804bae --- /dev/null +++ b/.changeset/good-teachers-relax.md @@ -0,0 +1,5 @@ +--- +'astro': patch +--- + +Add ability to specify hostname in devOptions diff --git a/packages/astro/src/@types/astro.ts b/packages/astro/src/@types/astro.ts index 3ff0ceb7ee..e57d2db181 100644 --- a/packages/astro/src/@types/astro.ts +++ b/packages/astro/src/@types/astro.ts @@ -29,6 +29,7 @@ export interface AstroConfig { }; /** Options for the development server run with `astro dev`. */ devOptions: { + hostname?: string; /** The port to run the dev server on. */ port: number; projectRoot?: string; @@ -42,6 +43,7 @@ export type AstroUserConfig = Omit & sitemap: boolean; }; devOptions: { + hostname?: string; port?: number; projectRoot?: string; tailwindConfig?: string; diff --git a/packages/astro/src/cli.ts b/packages/astro/src/cli.ts index 6c33f3106e..a1628e2ace 100644 --- a/packages/astro/src/cli.ts +++ b/packages/astro/src/cli.ts @@ -27,6 +27,7 @@ interface CLIState { options: { projectRoot?: string; sitemap?: boolean; + hostname?: string; port?: number; config?: string; reload?: boolean; @@ -93,6 +94,7 @@ async function printVersion() { function mergeCLIFlags(astroConfig: AstroConfig, flags: CLIState['options']) { if (typeof flags.sitemap === 'boolean') astroConfig.buildOptions.sitemap = flags.sitemap; if (typeof flags.port === 'number') astroConfig.devOptions.port = flags.port; + if (typeof flags.hostname === 'string') astroConfig.devOptions.hostname = flags.hostname; } /** Handle `astro run` command */ diff --git a/packages/astro/src/compiler/index.ts b/packages/astro/src/compiler/index.ts index 329ffccc1f..215a80acaf 100644 --- a/packages/astro/src/compiler/index.ts +++ b/packages/astro/src/compiler/index.ts @@ -107,7 +107,8 @@ interface CompileComponentOptions { /** Compiles an Astro component */ export async function compileComponent(source: string, { compileOptions, filename, projectRoot }: CompileComponentOptions): Promise { const result = await transformFromSource(source, { compileOptions, filename, projectRoot }); - const site = compileOptions.astroConfig.buildOptions.site || `http://localhost:${compileOptions.astroConfig.devOptions.port}`; + const { hostname, port } = compileOptions.astroConfig.devOptions + const site = compileOptions.astroConfig.buildOptions.site || `http://${hostname}:${port}`; // return template let moduleJavaScript = ` diff --git a/packages/astro/src/config.ts b/packages/astro/src/config.ts index 7e2aeec4e4..b41d670031 100644 --- a/packages/astro/src/config.ts +++ b/packages/astro/src/config.ts @@ -43,6 +43,9 @@ function validateConfig(config: any): void { if (typeof config.devOptions?.port !== 'number') { throw new Error(`[config] devOptions.port: Expected number, received ${type(config.devOptions?.port)}`); } + if (typeof config.devOptions?.hostname !== 'string') { + throw new Error(`[config] devOptions.hostname: Expected string, received ${type(config.devOptions?.hostname)}`); + } if (config.devOptions?.tailwindConfig !== undefined && typeof config.devOptions?.tailwindConfig !== 'string') { throw new Error(`[config] devOptions.tailwindConfig: Expected string, received ${type(config.devOptions?.tailwindConfig)}`); } @@ -59,6 +62,7 @@ async function configDefaults(userConfig?: any): Promise { if (!config.public) config.public = './public'; if (!config.devOptions) config.devOptions = {}; if (!config.devOptions.port) config.devOptions.port = await getPort({ port: getPort.makeRange(3000, 3050) }); + if (!config.devOptions.hostname) config.devOptions.hostname = 'localhost'; if (!config.buildOptions) config.buildOptions = {}; if (!config.markdownOptions) config.markdownOptions = {}; if (typeof config.buildOptions.sitemap === 'undefined') config.buildOptions.sitemap = true; diff --git a/packages/astro/src/dev.ts b/packages/astro/src/dev.ts index f85d4dda1e..7a08281094 100644 --- a/packages/astro/src/dev.ts +++ b/packages/astro/src/dev.ts @@ -9,8 +9,6 @@ import { defaultLogDestination, defaultLogLevel, debug, error, info, parseError import { createRuntime } from './runtime.js'; import { stopTimer } from './build/util.js'; -const hostname = '127.0.0.1'; - const logging: LogOptions = { level: defaultLogLevel, dest: defaultLogDestination, @@ -50,7 +48,8 @@ export default async function dev(astroConfig: AstroConfig) { break; } case 404: { - const fullurl = new URL(req.url || '/', astroConfig.buildOptions.site || `http://localhost${astroConfig.devOptions.port}`); + const { hostname, port } = astroConfig.devOptions; + const fullurl = new URL(req.url || '/', astroConfig.buildOptions.site || `http://${hostname}:${port}`); const reqPath = decodeURI(fullurl.pathname); error(logging, 'static', 'Not found', reqPath); res.statusCode = 404; @@ -99,7 +98,7 @@ export default async function dev(astroConfig: AstroConfig) { } }); - const port = astroConfig.devOptions.port; + const { hostname, port } = astroConfig.devOptions; server .listen(port, hostname, () => { const endServerTime = performance.now(); diff --git a/packages/astro/src/runtime.ts b/packages/astro/src/runtime.ts index 2bcc9316b1..a1219c3a83 100644 --- a/packages/astro/src/runtime.ts +++ b/packages/astro/src/runtime.ts @@ -66,7 +66,7 @@ async function load(config: RuntimeConfig, rawPathname: string | undefined): Pro const { logging, snowpackRuntime, snowpack, configManager } = config; const { buildOptions, devOptions } = config.astroConfig; - let origin = buildOptions.site ? new URL(buildOptions.site).origin : `http://localhost:${devOptions.port}`; + let origin = buildOptions.site ? new URL(buildOptions.site).origin : `http://${devOptions.hostname}:${devOptions.port}`; const fullurl = new URL(rawPathname || '/', origin); const reqPath = decodeURI(fullurl.pathname); diff --git a/packages/astro/test/config-hostname.test.js b/packages/astro/test/config-hostname.test.js new file mode 100644 index 0000000000..0458c75360 --- /dev/null +++ b/packages/astro/test/config-hostname.test.js @@ -0,0 +1,43 @@ +import { fileURLToPath } from 'url'; +import { suite } from 'uvu'; +import * as assert from 'uvu/assert'; +import { runDevServer } from './helpers.js'; +import { loadConfig } from '#astro/config'; + +const ConfigPort = suite('Config hostname'); +const MAX_TEST_TIME = 10000; // max time this test suite may take + +const root = new URL('./fixtures/config-hostname/', import.meta.url); +const timers = {}; + +ConfigPort.before.each(({ __test__ }) => { + timers[__test__] = setTimeout(() => { + throw new Error(`Test "${__test__}" did not finish within allowed time`); + }, MAX_TEST_TIME); +}); + +ConfigPort('can be specified in the astro config', async (context) => { + const astroConfig = await loadConfig(fileURLToPath(root)); + assert.equal(astroConfig.devOptions.hostname, '0.0.0.0'); +}); + +ConfigPort('can be specified via --hostname flag', async (context) => { + const args = ['--hostname', '127.0.0.1']; + const proc = runDevServer(root, args); + + proc.stdout.setEncoding('utf8'); + for await (const chunk of proc.stdout) { + if (/Local:/.test(chunk)) { + assert.ok(/:127.0.0.1/.test(chunk), 'Using the right hostname'); + break; + } + } + + proc.kill(); +}); + +ConfigPort.after.each(({ __test__ }) => { + clearTimeout(timers[__test__]); +}); + +ConfigPort.run(); diff --git a/packages/astro/test/fixtures/config-hostname/astro.config.mjs b/packages/astro/test/fixtures/config-hostname/astro.config.mjs new file mode 100644 index 0000000000..e1e33ea0af --- /dev/null +++ b/packages/astro/test/fixtures/config-hostname/astro.config.mjs @@ -0,0 +1,6 @@ + +export default { + devOptions: { + hostname: '0.0.0.0' + } +} diff --git a/packages/create-astro/src/config.ts b/packages/create-astro/src/config.ts index 40e6653a05..31b7acc204 100644 --- a/packages/create-astro/src/config.ts +++ b/packages/create-astro/src/config.ts @@ -10,8 +10,9 @@ export const createConfig = ({ renderers }: { renderers: string[] }) => { sitemap: true, // Generate sitemap (set to "false" to disable) }, devOptions: { - // port: 3000, // The port to run the dev server on. - // tailwindConfig: '', // Path to tailwind.config.js if used, e.g. './tailwind.config.js' + // hostname: 'localhost', // The hostname to run the dev server on. + // port: 3000, // The port to run the dev server on. + // tailwindConfig: '', // Path to tailwind.config.js if used, e.g. './tailwind.config.js' },`, ` renderers: ${JSON.stringify(renderers, undefined, 2) .split('\n')