From 335b2a82f9ed350b342f547464688ac2f06c5782 Mon Sep 17 00:00:00 2001 From: Matthew Phillips Date: Tue, 5 May 2026 11:54:42 -0400 Subject: [PATCH 01/25] Add experimental background dev server management --- .changeset/experimental-background-dev.md | 7 + packages/astro/package.json | 1 + packages/astro/src/cli/dev/background.ts | 158 ++++++++ packages/astro/src/cli/dev/index.ts | 45 +++ packages/astro/src/cli/dev/logs.ts | 40 ++ packages/astro/src/cli/dev/status.ts | 46 +++ packages/astro/src/cli/dev/stop.ts | 52 +++ packages/astro/src/core/create-vite.ts | 2 + packages/astro/src/core/dev/dev.ts | 35 ++ packages/astro/src/core/dev/lockfile.ts | 139 +++++++ .../astro/src/vite-plugin-dev-status/index.ts | 14 + .../astro/test/units/dev/dev-output.test.ts | 101 +++++ .../astro/test/units/dev/lockfile.test.ts | 147 ++++++++ pnpm-lock.yaml | 349 ++++++++++-------- 14 files changed, 975 insertions(+), 161 deletions(-) create mode 100644 .changeset/experimental-background-dev.md create mode 100644 packages/astro/src/cli/dev/background.ts create mode 100644 packages/astro/src/cli/dev/logs.ts create mode 100644 packages/astro/src/cli/dev/status.ts create mode 100644 packages/astro/src/cli/dev/stop.ts create mode 100644 packages/astro/src/core/dev/lockfile.ts create mode 100644 packages/astro/src/vite-plugin-dev-status/index.ts create mode 100644 packages/astro/test/units/dev/dev-output.test.ts create mode 100644 packages/astro/test/units/dev/lockfile.test.ts diff --git a/.changeset/experimental-background-dev.md b/.changeset/experimental-background-dev.md new file mode 100644 index 000000000000..c9c8cb7bb1b7 --- /dev/null +++ b/.changeset/experimental-background-dev.md @@ -0,0 +1,7 @@ +--- +'astro': minor +--- + +Adds experimental background dev server management for AI coding agents + +Adds `--experimental-background`, `--experimental-stop`, `--experimental-status`, and `--experimental-logs` flags to `astro dev`. Also adds a `/_astro/status` health endpoint and a dev server lock file (`.astro/dev.json`) to prevent duplicate servers. When an AI coding agent is detected via `am-i-vibing`, background mode is enabled automatically. diff --git a/packages/astro/package.json b/packages/astro/package.json index a88ef66dd70e..dc37faa97346 100644 --- a/packages/astro/package.json +++ b/packages/astro/package.json @@ -143,6 +143,7 @@ "@clack/prompts": "^1.1.0", "@oslojs/encoding": "^1.1.0", "@rollup/pluginutils": "^5.3.0", + "am-i-vibing": "^0.1.1", "aria-query": "^5.3.2", "axobject-query": "^4.1.0", "ci-info": "^4.4.0", diff --git a/packages/astro/src/cli/dev/background.ts b/packages/astro/src/cli/dev/background.ts new file mode 100644 index 000000000000..6d4f9d8e4231 --- /dev/null +++ b/packages/astro/src/cli/dev/background.ts @@ -0,0 +1,158 @@ +import { spawn } from 'node:child_process'; +import { openSync } from 'node:fs'; +import { resolve } from 'node:path'; +import { fileURLToPath, pathToFileURL } from 'node:url'; +import type { Flags } from '../flags.js'; +import { + checkExistingServer, + getLogFileURL, + readLockFile, + removeLockFile, + isProcessAlive, +} from '../../core/dev/lockfile.js'; + +export interface BackgroundResult { + pid: number; + url: string; + existing?: boolean; +} + +export interface BackgroundErrorResult { + error: string; + message: string; +} + +export function formatBackgroundOutput(result: BackgroundResult | BackgroundErrorResult): string { + return JSON.stringify(result); +} + +function resolveRootURL(flags: Flags): URL { + const rootPath = typeof flags.root === 'string' ? resolve(flags.root) : process.cwd(); + return pathToFileURL(rootPath + '/'); +} + +export async function background({ flags }: { flags: Flags }): Promise { + const root = resolveRootURL(flags); + + // Check for existing server + const existing = checkExistingServer(root); + if (existing && !flags.force) { + console.log( + formatBackgroundOutput({ + pid: existing.pid, + url: existing.url, + existing: true, + }), + ); + return; + } + + // If --force, kill the existing server first + if (existing && flags.force) { + try { + process.kill(existing.pid, 'SIGTERM'); + } catch { + // Already dead + } + // Wait for it to die + const deadline = Date.now() + 5000; + while (Date.now() < deadline) { + if (!isProcessAlive(existing.pid)) break; + await new Promise((r) => setTimeout(r, 100)); + } + removeLockFile(root); + } + + // Build the args for the child process: `astro dev` without --background + const args: string[] = ['dev']; + if (flags.port) args.push('--port', String(flags.port)); + if (flags.host != null) { + if (typeof flags.host === 'string') { + args.push('--host', flags.host); + } else { + args.push('--host'); + } + } + if (flags.config) args.push('--config', String(flags.config)); + if (flags.root) args.push('--root', String(flags.root)); + if (flags.allowedHosts) args.push('--allowed-hosts', String(flags.allowedHosts)); + + // Open the log file for writing + const logFileURL = getLogFileURL(root); + const logFilePath = fileURLToPath(logFileURL); + const logFd = openSync(logFilePath, 'w'); + + // Find the astro binary + const rootPath = fileURLToPath(root); + const astroBin = resolve(rootPath, 'node_modules', '.bin', 'astro'); + + // Spawn the dev server as a detached child process + const child = spawn(astroBin, args, { + detached: true, + stdio: ['ignore', logFd, logFd], + cwd: rootPath, + env: { ...process.env }, + }); + + child.unref(); + + const childPid = child.pid; + if (!childPid) { + console.log( + formatBackgroundOutput({ + error: 'spawn_failed', + message: 'Failed to spawn background dev server process.', + }), + ); + process.exitCode = 1; + return; + } + + // Poll the lock file to detect when the server is ready + const timeout = 30000; + const deadline = Date.now() + timeout; + + while (Date.now() < deadline) { + // Check if child is still alive + if (!isProcessAlive(childPid)) { + console.log( + formatBackgroundOutput({ + error: 'process_exited', + message: 'Dev server process exited before becoming ready.', + }), + ); + process.exitCode = 1; + return; + } + + // Check for the lock file (written by the child's dev server) + const lockData = readLockFile(root); + if (lockData && lockData.pid === childPid) { + console.log( + formatBackgroundOutput({ + pid: lockData.pid, + url: lockData.url, + }), + ); + return; + } + + await new Promise((r) => setTimeout(r, 200)); + } + + // Timeout: kill the child and report failure + try { + process.kill(childPid, 'SIGTERM'); + } catch { + // Already dead + } + removeLockFile(root); + + console.log( + formatBackgroundOutput({ + error: 'timeout', + message: `Dev server failed to start within ${timeout / 1000}s.`, + }), + ); + process.exitCode = 1; +} diff --git a/packages/astro/src/cli/dev/index.ts b/packages/astro/src/cli/dev/index.ts index 60bc5c2b9264..874240b82dda 100644 --- a/packages/astro/src/cli/dev/index.ts +++ b/packages/astro/src/cli/dev/index.ts @@ -1,3 +1,4 @@ +import { isAgent } from 'am-i-vibing'; import colors from 'piccolore'; import devServer from '../../core/dev/index.js'; import { printHelp } from '../../core/messages/runtime.js'; @@ -7,6 +8,14 @@ interface DevOptions { flags: Flags; } +function isRunByAgent(): boolean { + try { + return isAgent(); + } catch { + return false; + } +} + export async function dev({ flags }: DevOptions) { if (flags.help || flags.h) { printHelp({ @@ -24,6 +33,13 @@ export async function dev({ flags }: DevOptions) { '--allowed-hosts', 'Specify a comma-separated list of allowed hosts or allow any hostname.', ], + [ + '--experimental-background', + 'Start the dev server as a background process.', + ], + ['--experimental-stop', 'Stop a running background dev server.'], + ['--experimental-status', 'Check if a dev server is running.'], + ['--experimental-logs', 'View logs from a background dev server.'], ['--help (-h)', 'See all available flags.'], ], }, @@ -34,6 +50,35 @@ export async function dev({ flags }: DevOptions) { return; } + // Handle --experimental-stop: stop a running dev server + if (flags.experimentalStop) { + const { stop } = await import('./stop.js'); + await stop({ flags }); + return; + } + + // Handle --experimental-status: check if a dev server is running + if (flags.experimentalStatus) { + const { status } = await import('./status.js'); + await status({ flags }); + return; + } + + // Handle --experimental-logs: view logs from a background dev server + if (flags.experimentalLogs) { + const { logs } = await import('./logs.js'); + await logs({ flags }); + return; + } + + // Handle --experimental-background: start as a background process. + // Also auto-enable when an AI coding agent is detected. + if (flags.experimentalBackground || isRunByAgent()) { + const { background } = await import('./background.js'); + await background({ flags }); + return; + } + const inlineConfig = flagsToAstroInlineConfig(flags); return await devServer(inlineConfig); diff --git a/packages/astro/src/cli/dev/logs.ts b/packages/astro/src/cli/dev/logs.ts new file mode 100644 index 000000000000..55fd74cf72ff --- /dev/null +++ b/packages/astro/src/cli/dev/logs.ts @@ -0,0 +1,40 @@ +import { readFileSync, existsSync } from 'node:fs'; +import { resolve } from 'node:path'; +import { fileURLToPath, pathToFileURL } from 'node:url'; +import type { Flags } from '../flags.js'; +import { checkExistingServer, getLogFileURL } from '../../core/dev/lockfile.js'; + +function resolveRootURL(flags: Flags): URL { + const rootPath = typeof flags.root === 'string' ? resolve(flags.root) : process.cwd(); + return pathToFileURL(rootPath + '/'); +} + +export async function logs({ flags }: { flags: Flags }): Promise { + const root = resolveRootURL(flags); + const existing = checkExistingServer(root); + + if (!existing) { + console.error('No dev server is running.'); + process.exitCode = 1; + return; + } + + if (!existing.background) { + console.error( + 'The running dev server was not started with --background. View logs in the terminal where it was started.', + ); + process.exitCode = 1; + return; + } + + const logFileURL = getLogFileURL(root); + const logFilePath = fileURLToPath(logFileURL); + if (!existsSync(logFilePath)) { + console.error('No log file found.'); + process.exitCode = 1; + return; + } + + const content = readFileSync(logFilePath, 'utf-8'); + process.stdout.write(content); +} diff --git a/packages/astro/src/cli/dev/status.ts b/packages/astro/src/cli/dev/status.ts new file mode 100644 index 000000000000..5f02f9fb5346 --- /dev/null +++ b/packages/astro/src/cli/dev/status.ts @@ -0,0 +1,46 @@ +import { resolve } from 'node:path'; +import { pathToFileURL } from 'node:url'; +import type { Flags } from '../flags.js'; +import { checkExistingServer } from '../../core/dev/lockfile.js'; + +export interface StatusResult { + running: boolean; + pid?: number; + url?: string; + port?: number; + background?: boolean; + uptime?: number; +} + +export function formatStatusOutput(result: StatusResult): string { + return JSON.stringify(result); +} + +function resolveRootURL(flags: Flags): URL { + const rootPath = typeof flags.root === 'string' ? resolve(flags.root) : process.cwd(); + return pathToFileURL(rootPath + '/'); +} + +export async function status({ flags }: { flags: Flags }): Promise { + const root = resolveRootURL(flags); + const existing = checkExistingServer(root); + + if (!existing) { + console.log(formatStatusOutput({ running: false })); + return; + } + + const startedAt = new Date(existing.startedAt).getTime(); + const uptime = Math.floor((Date.now() - startedAt) / 1000); + + console.log( + formatStatusOutput({ + running: true, + pid: existing.pid, + url: existing.url, + port: existing.port, + background: existing.background, + uptime, + }), + ); +} diff --git a/packages/astro/src/cli/dev/stop.ts b/packages/astro/src/cli/dev/stop.ts new file mode 100644 index 000000000000..8dc7eda72fbb --- /dev/null +++ b/packages/astro/src/cli/dev/stop.ts @@ -0,0 +1,52 @@ +import { resolve } from 'node:path'; +import { pathToFileURL } from 'node:url'; +import type { Flags } from '../flags.js'; +import { checkExistingServer, removeLockFile } from '../../core/dev/lockfile.js'; + +export interface StopResult { + stopped: boolean; + pid?: number; + reason?: string; +} + +export function formatStopOutput(result: StopResult): string { + return JSON.stringify(result); +} + +function resolveRootURL(flags: Flags): URL { + const rootPath = typeof flags.root === 'string' ? resolve(flags.root) : process.cwd(); + return pathToFileURL(rootPath + '/'); +} + +export async function stop({ flags }: { flags: Flags }): Promise { + const root = resolveRootURL(flags); + const existing = checkExistingServer(root); + + if (!existing) { + console.log(formatStopOutput({ stopped: false, reason: 'not_running' })); + return; + } + + try { + process.kill(existing.pid, 'SIGTERM'); + } catch { + // Process may have already exited between check and kill + } + + // Wait briefly for the process to exit and clean up + const deadline = Date.now() + 5000; + while (Date.now() < deadline) { + try { + process.kill(existing.pid, 0); + } catch { + // Process is gone + break; + } + await new Promise((r) => setTimeout(r, 100)); + } + + // Clean up the lock file in case the process didn't remove it + removeLockFile(root); + + console.log(formatStopOutput({ stopped: true, pid: existing.pid })); +} diff --git a/packages/astro/src/core/create-vite.ts b/packages/astro/src/core/create-vite.ts index c1c4d7f7b23e..0250fdb68e06 100644 --- a/packages/astro/src/core/create-vite.ts +++ b/packages/astro/src/core/create-vite.ts @@ -55,6 +55,7 @@ import { isObject } from './util-runtime.js'; import { vitePluginEnvironment } from '../vite-plugin-environment/index.js'; import { ASTRO_VITE_ENVIRONMENT_NAMES } from './constants.js'; import { vitePluginChromedevtools } from '../vite-plugin-chromedevtools/index.js'; +import { vitePluginDevStatus } from '../vite-plugin-dev-status/index.js'; import { vitePluginAstroServerClient } from '../vite-plugin-overlay/index.js'; type CreateViteOptions = { @@ -228,6 +229,7 @@ export async function createVite( astroContainer(), astroHmrReloadPlugin(), vitePluginChromedevtools({ settings }), + command === 'dev' && vitePluginDevStatus(), ], publicDir: fileURLToPath(settings.config.publicDir), root: fileURLToPath(settings.config.root), diff --git a/packages/astro/src/core/dev/dev.ts b/packages/astro/src/core/dev/dev.ts index 1d8dda04fd19..5fce02a297e1 100644 --- a/packages/astro/src/core/dev/dev.ts +++ b/packages/astro/src/core/dev/dev.ts @@ -1,7 +1,9 @@ import fs from 'node:fs'; import type http from 'node:http'; import type { AddressInfo } from 'node:net'; +import { resolve } from 'node:path'; import { performance } from 'node:perf_hooks'; +import { pathToFileURL } from 'node:url'; import colors from 'piccolore'; import { gt, major, minor, patch } from 'semver'; import type * as vite from 'vite'; @@ -16,6 +18,7 @@ import * as msg from '../messages/runtime.js'; import { newVersionAvailable } from '../messages/node.js'; import { ensureProcessNodeEnv } from '../util.js'; import { startContainer } from './container.js'; +import { checkExistingServer, removeLockFile, writeLockFile } from './lockfile.js'; import { createContainerWithAutomaticRestart } from './restart.js'; import { fetchLatestAstroVersion, @@ -43,6 +46,24 @@ export default async function dev(inlineConfig: AstroInlineConfig): Promise { + it('formats a successful stop', () => { + const result = JSON.parse(formatStopOutput({ stopped: true, pid: 12345 })); + assert.equal(result.stopped, true); + assert.equal(result.pid, 12345); + }); + + it('formats a not-running stop', () => { + const result = JSON.parse(formatStopOutput({ stopped: false, reason: 'not_running' })); + assert.equal(result.stopped, false); + assert.equal(result.reason, 'not_running'); + }); + + it('produces valid JSON', () => { + const output = formatStopOutput({ stopped: true, pid: 1 }); + assert.doesNotThrow(() => JSON.parse(output)); + }); +}); +// #endregion + +// #region formatStatusOutput +describe('formatStatusOutput', () => { + it('formats a running server', () => { + const result = JSON.parse( + formatStatusOutput({ + running: true, + pid: 12345, + url: 'http://localhost:4321', + port: 4321, + background: false, + uptime: 3600, + }), + ); + assert.equal(result.running, true); + assert.equal(result.pid, 12345); + assert.equal(result.url, 'http://localhost:4321'); + assert.equal(result.port, 4321); + assert.equal(result.uptime, 3600); + }); + + it('formats a not-running server', () => { + const result = JSON.parse(formatStatusOutput({ running: false })); + assert.equal(result.running, false); + assert.equal(result.pid, undefined); + }); + + it('produces valid JSON', () => { + const output = formatStatusOutput({ running: false }); + assert.doesNotThrow(() => JSON.parse(output)); + }); +}); +// #endregion + +// #region formatBackgroundOutput +describe('formatBackgroundOutput', () => { + it('formats a successful background start', () => { + const result = JSON.parse( + formatBackgroundOutput({ + pid: 12345, + url: 'http://localhost:4321', + }), + ); + assert.equal(result.pid, 12345); + assert.equal(result.url, 'http://localhost:4321'); + }); + + it('formats an existing server response', () => { + const result = JSON.parse( + formatBackgroundOutput({ + pid: 12345, + url: 'http://localhost:4321', + existing: true, + }), + ); + assert.equal(result.existing, true); + }); + + it('formats an error response', () => { + const result = JSON.parse( + formatBackgroundOutput({ + error: 'timeout', + message: 'Dev server failed to start within 30s.', + }), + ); + assert.equal(result.error, 'timeout'); + assert.equal(result.message, 'Dev server failed to start within 30s.'); + }); + + it('produces valid JSON', () => { + const output = formatBackgroundOutput({ pid: 1, url: 'http://localhost:4321' }); + assert.doesNotThrow(() => JSON.parse(output)); + }); +}); +// #endregion diff --git a/packages/astro/test/units/dev/lockfile.test.ts b/packages/astro/test/units/dev/lockfile.test.ts new file mode 100644 index 000000000000..7fae3f55b06d --- /dev/null +++ b/packages/astro/test/units/dev/lockfile.test.ts @@ -0,0 +1,147 @@ +import * as assert from 'node:assert/strict'; +import { describe, it } from 'node:test'; +import { + parseLockFile, + serializeLockFile, + evaluateExistingServer, + type LockFileData, +} from '../../../dist/core/dev/lockfile.js'; + +const validData: LockFileData = { + pid: 12345, + port: 4321, + url: 'http://localhost:4321', + background: false, + startedAt: '2026-05-05T10:00:00.000Z', +}; + +// #region parseLockFile +describe('parseLockFile', () => { + it('parses valid lock file JSON', () => { + const content = JSON.stringify(validData); + const result = parseLockFile(content); + assert.deepEqual(result, validData); + }); + + it('returns null for invalid JSON', () => { + assert.equal(parseLockFile('not json'), null); + }); + + it('returns null for empty string', () => { + assert.equal(parseLockFile(''), null); + }); + + it('returns null when pid is missing', () => { + const data = { ...validData, pid: undefined }; + assert.equal(parseLockFile(JSON.stringify(data)), null); + }); + + it('returns null when pid is not a number', () => { + const data = { ...validData, pid: 'abc' }; + assert.equal(parseLockFile(JSON.stringify(data)), null); + }); + + it('returns null when port is missing', () => { + const data = { ...validData, port: undefined }; + assert.equal(parseLockFile(JSON.stringify(data)), null); + }); + + it('returns null when port is not a number', () => { + const data = { ...validData, port: 'abc' }; + assert.equal(parseLockFile(JSON.stringify(data)), null); + }); + + it('returns null when url is missing', () => { + const data = { ...validData, url: undefined }; + assert.equal(parseLockFile(JSON.stringify(data)), null); + }); + + it('returns null when url is not a string', () => { + const data = { ...validData, url: 123 }; + assert.equal(parseLockFile(JSON.stringify(data)), null); + }); + + it('returns null when background is missing', () => { + const data = { ...validData, background: undefined }; + assert.equal(parseLockFile(JSON.stringify(data)), null); + }); + + it('returns null when background is not a boolean', () => { + const data = { ...validData, background: 'true' }; + assert.equal(parseLockFile(JSON.stringify(data)), null); + }); + + it('returns null when startedAt is missing', () => { + const data = { ...validData, startedAt: undefined }; + assert.equal(parseLockFile(JSON.stringify(data)), null); + }); + + it('returns null when startedAt is not a string', () => { + const data = { ...validData, startedAt: 123 }; + assert.equal(parseLockFile(JSON.stringify(data)), null); + }); + + it('returns null for an empty object', () => { + assert.equal(parseLockFile('{}'), null); + }); + + it('returns null for an array', () => { + assert.equal(parseLockFile('[]'), null); + }); + + it('preserves extra fields without error', () => { + const data = { ...validData, extra: 'field' }; + const result = parseLockFile(JSON.stringify(data)); + assert.notEqual(result, null); + assert.equal(result!.pid, validData.pid); + }); + + it('handles background: true', () => { + const data = { ...validData, background: true }; + const result = parseLockFile(JSON.stringify(data)); + assert.notEqual(result, null); + assert.equal(result!.background, true); + }); +}); +// #endregion + +// #region serializeLockFile +describe('serializeLockFile', () => { + it('produces valid JSON that round-trips through parseLockFile', () => { + const serialized = serializeLockFile(validData); + const parsed = parseLockFile(serialized); + assert.deepEqual(parsed, validData); + }); + + it('uses tabs for indentation', () => { + const serialized = serializeLockFile(validData); + assert.ok(serialized.includes('\t')); + }); +}); +// #endregion + +// #region evaluateExistingServer +describe('evaluateExistingServer', () => { + it('returns null when data is null', () => { + assert.equal(evaluateExistingServer(null, false), null); + }); + + it('returns null when data is null even if alive is true', () => { + assert.equal(evaluateExistingServer(null, true), null); + }); + + it('returns stale: false when process is alive', () => { + const result = evaluateExistingServer(validData, true); + assert.notEqual(result, null); + assert.equal(result!.stale, false); + assert.deepEqual(result!.data, validData); + }); + + it('returns stale: true when process is not alive', () => { + const result = evaluateExistingServer(validData, false); + assert.notEqual(result, null); + assert.equal(result!.stale, true); + assert.deepEqual(result!.data, validData); + }); +}); +// #endregion diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1632823e50ae..76252f5354b9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -126,7 +126,7 @@ importers: devDependencies: '@codspeed/vitest-plugin': specifier: 5.2.0 - version: 5.2.0(tinybench@2.9.0)(vite@8.0.10(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.0(@opentelemetry/api@1.9.0)(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3)) + version: 5.2.0(tinybench@2.9.0)(vite@8.0.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.0(@opentelemetry/api@1.9.0)(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3)) vitest: specifier: ^4.1.0 version: 4.1.0(@opentelemetry/api@1.9.0)(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3) @@ -501,7 +501,7 @@ importers: version: link:../../packages/integrations/mdx '@tailwindcss/vite': specifier: ^4.2.1 - version: 4.2.2(vite@8.0.10(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3)) + version: 4.2.2(vite@8.0.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3)) '@types/canvas-confetti': specifier: ^1.9.0 version: 1.9.0 @@ -550,6 +550,9 @@ importers: '@rollup/pluginutils': specifier: ^5.3.0 version: 5.3.0(rollup@4.59.1) + am-i-vibing: + specifier: ^0.1.1 + version: 0.1.1 aria-query: specifier: ^5.3.2 version: 5.3.2 @@ -678,10 +681,10 @@ importers: version: 6.0.3 vite: specifier: ^8.0.8 - version: 8.0.10(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3) + version: 8.0.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3) vitefu: specifier: ^1.1.2 - version: 1.1.2(vite@8.0.10(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3)) + version: 1.1.2(vite@8.0.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3)) xxhash-wasm: specifier: ^1.1.0 version: 1.1.0 @@ -1020,7 +1023,7 @@ importers: version: 18.3.7(@types/react@18.3.28) '@vitejs/plugin-vue': specifier: ^6.0.5 - version: 6.0.5(vite@8.0.10(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3))(vue@3.5.30(typescript@5.9.3)) + version: 6.0.5(vite@8.0.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3))(vue@3.5.30(typescript@5.9.3)) astro: specifier: workspace:* version: link:../../.. @@ -1719,7 +1722,7 @@ importers: dependencies: '@tailwindcss/vite': specifier: ^4.2.2 - version: 4.2.2(vite@8.0.10(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3)) + version: 4.2.2(vite@8.0.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3)) astro: specifier: workspace:* version: link:../../.. @@ -2449,7 +2452,7 @@ importers: dependencies: '@tailwindcss/vite': specifier: ^4.2.2 - version: 4.2.2(vite@8.0.10(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3)) + version: 4.2.2(vite@8.0.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3)) astro: specifier: workspace:* version: link:../../.. @@ -3560,7 +3563,7 @@ importers: dependencies: '@tailwindcss/vite': specifier: ^4.2.2 - version: 4.2.2(vite@8.0.10(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3)) + version: 4.2.2(vite@8.0.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3)) astro: specifier: workspace:* version: link:../../.. @@ -4270,7 +4273,7 @@ importers: version: link:../../../../integrations/mdx '@tailwindcss/vite': specifier: ^4.2.2 - version: 4.2.2(vite@8.0.10(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3)) + version: 4.2.2(vite@8.0.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3)) astro: specifier: workspace:* version: link:../../.. @@ -4421,7 +4424,7 @@ importers: version: 5.9.3 vite: specifier: ^8.0.8 - version: 8.0.10(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3) + version: 8.0.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3) packages/db/test/fixtures/basics: dependencies: @@ -4577,7 +4580,7 @@ importers: version: link:../../../scripts vite: specifier: ^8.0.8 - version: 8.0.10(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3) + version: 8.0.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3) packages/integrations/alpinejs/test/fixtures/basics: dependencies: @@ -4634,7 +4637,7 @@ importers: version: link:../../underscore-redirects '@cloudflare/vite-plugin': specifier: ^1.32.3 - version: 1.32.3(@cloudflare/workers-types@4.20260228.0)(vite@8.0.10(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3))(workerd@1.20260415.1) + version: 1.32.3(@cloudflare/workers-types@4.20260228.0)(vite@8.0.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3))(workerd@1.20260415.1) piccolore: specifier: ^0.1.3 version: 0.1.3 @@ -4643,7 +4646,7 @@ importers: version: 0.2.16 vite: specifier: ^8.0.8 - version: 8.0.10(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3) + version: 8.0.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3) devDependencies: '@cloudflare/workers-types': specifier: ^4.20260228.0 @@ -4831,7 +4834,7 @@ importers: version: link:../../../../mdx '@tailwindcss/vite': specifier: ^4.2.1 - version: 4.2.2(vite@8.0.10(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3)) + version: 4.2.2(vite@8.0.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3)) astro: specifier: workspace:* version: link:../../../../../astro @@ -4977,7 +4980,7 @@ importers: version: 18.3.7(@types/react@18.3.28) '@vitejs/plugin-vue': specifier: ^6.0.4 - version: 6.0.5(vite@8.0.10(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3))(vue@3.5.30(typescript@5.9.3)) + version: 6.0.5(vite@8.0.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3))(vue@3.5.30(typescript@5.9.3)) astro: specifier: workspace:* version: link:../../../../../astro @@ -5119,7 +5122,7 @@ importers: version: 0.18.12 vite: specifier: ^8.0.8 - version: 8.0.10(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3) + version: 8.0.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3) packages/integrations/markdoc/test/fixtures/content-collections: dependencies: @@ -5411,7 +5414,7 @@ importers: version: 11.0.5 vite: specifier: ^8.0.8 - version: 8.0.10(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3) + version: 8.0.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3) packages/integrations/mdx/test/fixtures/content-layer: dependencies: @@ -5603,7 +5606,7 @@ importers: version: 5.1.2 '@netlify/vite-plugin': specifier: ^2.10.3 - version: 2.10.3(@azure/identity@4.13.0)(@vercel/functions@3.4.3(@aws-sdk/credential-provider-web-identity@3.972.38))(rollup@4.59.1)(vite@8.0.10(@types/node@22.19.11)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3)) + version: 2.10.3(@azure/identity@4.13.0)(@vercel/functions@3.4.3(@aws-sdk/credential-provider-web-identity@3.972.38))(rollup@4.59.1)(vite@8.0.12(@types/node@22.19.11)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3)) '@vercel/nft': specifier: ^1.3.2 version: 1.3.2(rollup@4.59.1) @@ -5615,7 +5618,7 @@ importers: version: 0.2.16 vite: specifier: ^8.0.8 - version: 8.0.10(@types/node@22.19.11)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3) + version: 8.0.12(@types/node@22.19.11)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3) devDependencies: '@types/node': specifier: ^22.10.6 @@ -5976,7 +5979,7 @@ importers: version: link:../../internal-helpers '@preact/preset-vite': specifier: ^2.10.5 - version: 2.10.5(@babel/core@7.29.0)(preact@10.29.0)(rollup@4.59.1)(vite@8.0.10(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3)) + version: 2.10.5(@babel/core@7.29.0)(preact@10.29.0)(rollup@4.59.1)(vite@8.0.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3)) '@preact/signals': specifier: ^2.8.2 version: 2.8.2(preact@10.29.0) @@ -5988,7 +5991,7 @@ importers: version: 6.6.6(preact@10.29.0) vite: specifier: ^8.0.8 - version: 8.0.10(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3) + version: 8.0.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3) devDependencies: astro: specifier: workspace:* @@ -6007,7 +6010,7 @@ importers: version: link:../../internal-helpers '@vitejs/plugin-react': specifier: ^5.2.0 - version: 5.2.0(vite@8.0.10(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3)) + version: 5.2.0(vite@8.0.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3)) devalue: specifier: ^5.6.4 version: 5.6.4 @@ -6016,7 +6019,7 @@ importers: version: 1.6.0 vite: specifier: ^8.0.8 - version: 8.0.10(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3) + version: 8.0.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3) devDependencies: '@types/react': specifier: ^18.3.28 @@ -6162,10 +6165,10 @@ importers: dependencies: vite: specifier: ^8.0.8 - version: 8.0.10(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3) + version: 8.0.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3) vite-plugin-solid: specifier: ^2.11.11 - version: 2.11.11(solid-js@1.9.11)(vite@8.0.10(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3)) + version: 2.11.11(solid-js@1.9.11)(vite@8.0.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3)) devDependencies: astro: specifier: workspace:* @@ -6181,16 +6184,16 @@ importers: dependencies: '@sveltejs/vite-plugin-svelte': specifier: ^6.2.4 - version: 6.2.4(svelte@5.55.3)(vite@8.0.10(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3)) + version: 6.2.4(svelte@5.55.3)(vite@8.0.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3)) svelte2tsx: specifier: ^0.7.52 version: 0.7.52(svelte@5.55.3)(typescript@5.9.3) vite: specifier: ^8.0.8 - version: 8.0.10(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3) + version: 8.0.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3) vitefu: specifier: ^1.1.2 - version: 1.1.2(vite@8.0.10(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3)) + version: 1.1.2(vite@8.0.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3)) devDependencies: astro: specifier: workspace:* @@ -6288,7 +6291,7 @@ importers: version: 1.2.0 vite: specifier: ^8.0.8 - version: 8.0.10(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3) + version: 8.0.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3) packages/integrations/vercel/test/fixtures/basic: dependencies: @@ -6477,19 +6480,19 @@ importers: dependencies: '@vitejs/plugin-vue': specifier: ^6.0.5 - version: 6.0.5(vite@8.0.10(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3))(vue@3.5.30(typescript@5.9.3)) + version: 6.0.5(vite@8.0.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3))(vue@3.5.30(typescript@5.9.3)) '@vitejs/plugin-vue-jsx': specifier: ^5.1.5 - version: 5.1.5(vite@8.0.10(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3))(vue@3.5.30(typescript@5.9.3)) + version: 5.1.5(vite@8.0.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3))(vue@3.5.30(typescript@5.9.3)) '@vue/compiler-sfc': specifier: ^3.5.30 version: 3.5.30 vite: specifier: ^8.0.8 - version: 8.0.10(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3) + version: 8.0.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3) vite-plugin-vue-devtools: specifier: ^8.1.0 - version: 8.1.0(vite@8.0.10(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3))(vue@3.5.30(typescript@5.9.3)) + version: 8.1.0(vite@8.0.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3))(vue@3.5.30(typescript@5.9.3)) devDependencies: astro: specifier: workspace:* @@ -9539,8 +9542,8 @@ packages: '@oslojs/encoding@1.1.0': resolution: {integrity: sha512-70wQhgYmndg4GCPxPPxPGevRKqTIJ2Nh4OkiMWmDAVYsTQ+Ta7Sq+rPevXyXGdzr30/qZBnyOalCszoMxlyldQ==} - '@oxc-project/types@0.127.0': - resolution: {integrity: sha512-aIYXQBo4lCbO4z0R3FHeucQHpF46l2LbMdxRvqvuRuW2OxdnSkcng5B8+K12spgLDj93rtN3+J2Vac/TIO+ciQ==} + '@oxc-project/types@0.129.0': + resolution: {integrity: sha512-3oz8m3FGdr2nDXVqmFUw7jolKliC4MoyXYIG2c7gpjBnzUWQpUGIYcXYKxTdTi+N2jusvt610ckTMkxdwHkYEg==} '@oxc-resolver/binding-android-arm-eabi@11.17.1': resolution: {integrity: sha512-+VuZyMYYaap5uDAU1xDU3Kul0FekLqpBS8kI5JozlWfYQKnc/HsZg2gHPkQrj0SC9lt74WMNCfOzZZJlYXSdEQ==} @@ -9838,103 +9841,103 @@ packages: engines: {node: '>=18.0.0'} hasBin: true - '@rolldown/binding-android-arm64@1.0.0-rc.17': - resolution: {integrity: sha512-s70pVGhw4zqGeFnXWvAzJDlvxhlRollagdCCKRgOsgUOH3N1l0LIxf83AtGzmb5SiVM4Hjl5HyarMRfdfj3DaQ==} + '@rolldown/binding-android-arm64@1.0.0': + resolution: {integrity: sha512-TWMZnRLMe63C2Lhyicviu7ZHaU4kxa6PS3rofvc9GmcvptzNN11BcfQ4Sl7MwTOsisQoa2keB/EBdNCAnUo8vA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [android] - '@rolldown/binding-darwin-arm64@1.0.0-rc.17': - resolution: {integrity: sha512-4ksWc9n0mhlZpZ9PMZgTGjeOPRu8MB1Z3Tz0Mo02eWfWCHMW1zN82Qz/pL/rC+yQa+8ZnutMF0JjJe7PjwasYw==} + '@rolldown/binding-darwin-arm64@1.0.0': + resolution: {integrity: sha512-6XcD+8k0gPVItNagEw78/qqcBDwKcwDYS8V2hRmVsfUSIrd8cWe/CBvRDI5toqFyPfj+FJr6t8U6Xj2P2prEew==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] - '@rolldown/binding-darwin-x64@1.0.0-rc.17': - resolution: {integrity: sha512-SUSDOI6WwUVNcWxd02QEBjLdY1VPHvlEkw6T/8nYG322iYWCTxRb1vzk4E+mWWYehTp7ERibq54LSJGjmouOsw==} + '@rolldown/binding-darwin-x64@1.0.0': + resolution: {integrity: sha512-iN/tWVXRQDWvmZlKdceP1Dwug9GDpEymhb9p4xnEe6zvCg5lFmzVljl+1qR1NVx3yfGpr2Na+CuLmv5IU8uzfQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] - '@rolldown/binding-freebsd-x64@1.0.0-rc.17': - resolution: {integrity: sha512-hwnz3nw9dbJ05EDO/PvcjaaewqqDy7Y1rn1UO81l8iIK1GjenME75dl16ajbvSSMfv66WXSRCYKIqfgq2KCfxw==} + '@rolldown/binding-freebsd-x64@1.0.0': + resolution: {integrity: sha512-jjQMDvvwSOuhOwMszD/klSOjyWMM3zI64hWTj9KT5x4MxRbZAf+7vLQ6qouRhtsLVFHr3f0ILaJAfgENPiQdAQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] - '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.17': - resolution: {integrity: sha512-IS+W7epTcwANmFSQFrS1SivEXHtl1JtuQA9wlxrZTcNi6mx+FDOYrakGevvvTwgj2JvWiK8B29/qD9BELZPyXQ==} + '@rolldown/binding-linux-arm-gnueabihf@1.0.0': + resolution: {integrity: sha512-d//Dtg2x6/m3mbV64yUGNnDGNZaDGRpDLLNGerHQUVObuNaIQaaDp25yUiqGXtHEXX+NP2d0wAlmKgpYgIAJ2A==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.17': - resolution: {integrity: sha512-e6usGaHKW5BMNZOymS1UcEYGowQMWcgZ71Z17Sl/h2+ZziNJ1a9n3Zvcz6LdRyIW5572wBCTH/Z+bKuZouGk9Q==} + '@rolldown/binding-linux-arm64-gnu@1.0.0': + resolution: {integrity: sha512-n7Ofp0mx+aB2cC+Sdy5YtMnXtY9lchnHbY+3Yt0uq9JsWQExf4f5Whu0tK0R8Jdc9S6RchTHjIFY7uc92puOVQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [glibc] - '@rolldown/binding-linux-arm64-musl@1.0.0-rc.17': - resolution: {integrity: sha512-b/CgbwAJpmrRLp02RPfhbudf5tZnN9nsPWK82znefso832etkem8H7FSZwxrOI9djcdTP7U6YfNhbRnh7djErg==} + '@rolldown/binding-linux-arm64-musl@1.0.0': + resolution: {integrity: sha512-EIVjy2cgd7uuMMo94FVkBp7F6DhcZAUwNURkSG3RwUmvAXR6s0ISxM81U+IydcZByPG0pZIHsf1b6kTxoFDgJA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [musl] - '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.17': - resolution: {integrity: sha512-4EII1iNGRUN5WwGbF/kOh/EIkoDN9HsupgLQoXfY+D1oyJm7/F4t5PYU5n8SWZgG0FEwakyM8pGgwcBYruGTlA==} + '@rolldown/binding-linux-ppc64-gnu@1.0.0': + resolution: {integrity: sha512-JEwwOPcwTLAcpDQlqSmjEmfs63xJnSiUNIGvLcDLUHCWK4XowpS/7c7tUsUH6uT/ct6bMUTdXKfI8967FYj6mg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] libc: [glibc] - '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.17': - resolution: {integrity: sha512-AH8oq3XqQo4IibpVXvPeLDI5pzkpYn0WiZAfT05kFzoJ6tQNzwRdDYQ45M8I/gslbodRZwW8uxLhbSBbkv96rA==} + '@rolldown/binding-linux-s390x-gnu@1.0.0': + resolution: {integrity: sha512-0wjCFhLrihtAubnT9iA0N++0pSV0z5Hg7tNGdNJ4RFaINceHadoF+kiFGyY1qSSNVIAZtLotG8Ju1bgDPkjnFA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] libc: [glibc] - '@rolldown/binding-linux-x64-gnu@1.0.0-rc.17': - resolution: {integrity: sha512-cLnjV3xfo7KslbU41Z7z8BH/E1y5mzUYzAqih1d1MDaIGZRCMqTijqLv76/P7fyHuvUcfGsIpqCdddbxLLK9rA==} + '@rolldown/binding-linux-x64-gnu@1.0.0': + resolution: {integrity: sha512-Dfn7iak9BcMMePxcoJfpSbWqnEyrp/dRF63/8qW/eHBdOZov6x5aShLLEYGYdIeSJ6vMLK/XCVB+lGIxm41bQA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [glibc] - '@rolldown/binding-linux-x64-musl@1.0.0-rc.17': - resolution: {integrity: sha512-0phclDw1spsL7dUB37sIARuis2tAgomCJXAHZlpt8PXZ4Ba0dRP1e+66lsRqrfhISeN9bEGNjQs+T/Fbd7oYGw==} + '@rolldown/binding-linux-x64-musl@1.0.0': + resolution: {integrity: sha512-5/utzzDmD/pD/bmuaUcbTf/sZYy0aztwIVlfpoW1fTjCZ0BaPOMVWGZL1zvgxyi7ZIVYWlxKONHmSbHuiOh8Jw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [musl] - '@rolldown/binding-openharmony-arm64@1.0.0-rc.17': - resolution: {integrity: sha512-0ag/hEgXOwgw4t8QyQvUCxvEg+V0KBcA6YuOx9g0r02MprutRF5dyljgm3EmR02O292UX7UeS6HzWHAl6KgyhA==} + '@rolldown/binding-openharmony-arm64@1.0.0': + resolution: {integrity: sha512-ouJs8VcUomfLfpbUECqFMRqdV4x6aeAK3MA4m6vTrJJjKyWTV5KnxZx7Jd9G+GlDaQQxubcba00x16OyJ1meig==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] - '@rolldown/binding-wasm32-wasi@1.0.0-rc.17': - resolution: {integrity: sha512-LEXei6vo0E5wTGwpkJ4KoT3OZJRnglwldt5ziLzOlc6qqb55z4tWNq2A+PFqCJuvWWdP53CVhG1Z9NtToDPJrA==} + '@rolldown/binding-wasm32-wasi@1.0.0': + resolution: {integrity: sha512-E+oHKGiDA+lsKMmFtffDDw91EryDT7uJocrIuCHqhm6bCTM6xFK+3gaCkYOHfPwQr0cCNarSM2xaELoQDz9jJg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [wasm32] - '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.17': - resolution: {integrity: sha512-gUmyzBl3SPMa6hrqFUth9sVfcLBlYsbMzBx5PlexMroZStgzGqlZ26pYG89rBb45Mnia+oil6YAIFeEWGWhoZA==} + '@rolldown/binding-win32-arm64-msvc@1.0.0': + resolution: {integrity: sha512-yYK02n8Rngo+gbm1y6G0+7jk1sJ/2Wt7K0me0Y7k/ErBpyf+LJ2gFpqWVTcRV1rUepBlQRmpgWkTQCiiwrK0Ow==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] - '@rolldown/binding-win32-x64-msvc@1.0.0-rc.17': - resolution: {integrity: sha512-3hkiolcUAvPB9FLb3UZdfjVVNWherN1f/skkGWJP/fgSQhYUZpSIRr0/I8ZK9TkF3F7kxvJAk0+IcKvPHk9qQg==} + '@rolldown/binding-win32-x64-msvc@1.0.0': + resolution: {integrity: sha512-14bpChMahXRRXiTwahSl+zzHPW6qQTXtkMuJBFlbo+pqSAews2d4BdCSHfrJ/MBsCZtpmTafsY+1QhBzitcmdg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] - '@rolldown/pluginutils@1.0.0-rc.17': - resolution: {integrity: sha512-n8iosDOt6Ig1UhJ2AYqoIhHWh/isz0xpicHTzpKBeotdVsTEcxsSA/i3EVM7gQAj0rU27OLAxCjzlj15IWY7bg==} + '@rolldown/pluginutils@1.0.0': + resolution: {integrity: sha512-aKs/3GSWyV0mrhNmt/96/Z3yczC3yvrzYATCiCXQebBsGyYzjNdUphRVLeJQ67ySKVXRfMxt2lm12pmXvbPFQQ==} '@rolldown/pluginutils@1.0.0-rc.2': resolution: {integrity: sha512-izyXV/v+cHiRfozX62W9htOAvwMo4/bXKDrQ+vom1L1qRuexPock/7VZDAhnpHCLNejd3NJ6hiab+tO0D44Rgw==} @@ -11232,6 +11235,10 @@ packages: alpinejs@3.15.8: resolution: {integrity: sha512-zxIfCRTBGvF1CCLIOMQOxAyBuqibxSEwS6Jm1a3HGA9rgrJVcjEWlwLcQTVGAWGS8YhAsTRLVrtQ5a5QT9bSSQ==} + am-i-vibing@0.1.1: + resolution: {integrity: sha512-6a7UWRIoCjdoGGG2rjlSJjOoJKxns78pl+x15Bic0KflMjoVnRGzzwQsrPDHE/mur8O2OuU5KV/YgF2ZevuW1g==} + hasBin: true + ansi-colors@4.1.3: resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} engines: {node: '>=6'} @@ -14897,6 +14904,10 @@ packages: resolution: {integrity: sha512-W62t/Se6rA0Az3DfCL0AqJwXuKwBeYg6nOaIgzP+xZ7N5BFCI7DYi1qs6ygUYT6rvfi6t9k65UMLJC+PHZpDAA==} engines: {node: ^10 || ^12 || >=14} + postcss@8.5.14: + resolution: {integrity: sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==} + engines: {node: ^10 || ^12 || >=14} + preact-render-to-string@6.6.6: resolution: {integrity: sha512-EfqZJytnjJldV+YaaqhthU2oXsEf5e+6rDv957p+zxAvNfFLQOPfvBOTncscQ+akzu6Wrl7s3Pa0LjUQmWJsGQ==} peerDependencies: @@ -14950,6 +14961,10 @@ packages: resolution: {integrity: sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==} engines: {node: '>=6'} + process-ancestry@0.1.0: + resolution: {integrity: sha512-tGqJW/UnclpYASFcM6Xh8D8l/BMtaQ9+CSG0vlJSJTcdMM4lDRv4c6H0Pdcsfted+bVczdYSfk2fdukg2gQkZg==} + engines: {node: '>=18.0.0'} + process-nextick-args@2.0.1: resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} @@ -15303,8 +15318,8 @@ packages: rfdc@1.4.1: resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} - rolldown@1.0.0-rc.17: - resolution: {integrity: sha512-ZrT53oAKrtA4+YtBWPQbtPOxIbVDbxT0orcYERKd63VJTF13zPcgXTvD4843L8pcsI7M6MErt8QtON6lrB9tyA==} + rolldown@1.0.0: + resolution: {integrity: sha512-yD986aXDESFGS95spT1LAv0jssywP4npMEjmMHyN2/5+eE8qQJUype2AaKkRiLgBgyD0LFlubwAht7VmY8rGoA==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true @@ -16457,13 +16472,13 @@ packages: yaml: optional: true - vite@8.0.10: - resolution: {integrity: sha512-rZuUu9j6J5uotLDs+cAA4O5H4K1SfPliUlQwqa6YEwSrWDZzP4rhm00oJR5snMewjxF5V/K3D4kctsUTsIU9Mw==} + vite@8.0.12: + resolution: {integrity: sha512-w2dDofOWv2QB09ZITZBsvKTVAlYvPR4IAmrY/v0ir9KvLs0xybR7i48wxhM1/oyBWO34wPns+bPGw5ZrZqDpZg==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: '@types/node': ^20.19.0 || >=22.12.0 - '@vitejs/devtools': ^0.1.0 + '@vitejs/devtools': ^0.1.18 esbuild: ^0.27.0 || ^0.28.0 jiti: '>=1.21.0' less: ^4.0.0 @@ -18047,12 +18062,12 @@ snapshots: optionalDependencies: workerd: 1.20260415.1 - '@cloudflare/vite-plugin@1.32.3(@cloudflare/workers-types@4.20260228.0)(vite@8.0.10(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3))(workerd@1.20260415.1)': + '@cloudflare/vite-plugin@1.32.3(@cloudflare/workers-types@4.20260228.0)(vite@8.0.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3))(workerd@1.20260415.1)': dependencies: '@cloudflare/unenv-preset': 2.16.0(unenv@2.0.0-rc.24)(workerd@1.20260415.1) miniflare: 4.20260415.0 unenv: 2.0.0-rc.24 - vite: 8.0.10(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3) + vite: 8.0.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3) wrangler: 4.83.0(@cloudflare/workers-types@4.20260228.0) ws: 8.18.0 transitivePeerDependencies: @@ -18087,11 +18102,11 @@ snapshots: transitivePeerDependencies: - debug - '@codspeed/vitest-plugin@5.2.0(tinybench@2.9.0)(vite@8.0.10(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.0(@opentelemetry/api@1.9.0)(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3))': + '@codspeed/vitest-plugin@5.2.0(tinybench@2.9.0)(vite@8.0.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.0(@opentelemetry/api@1.9.0)(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3))': dependencies: '@codspeed/core': 5.2.0 tinybench: 2.9.0 - vite: 8.0.10(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3) + vite: 8.0.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3) vitest: 4.1.0(@opentelemetry/api@1.9.0)(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3) transitivePeerDependencies: - debug @@ -19636,12 +19651,12 @@ snapshots: '@netlify/types@2.3.0': {} - '@netlify/vite-plugin@2.10.3(@azure/identity@4.13.0)(@vercel/functions@3.4.3(@aws-sdk/credential-provider-web-identity@3.972.38))(rollup@4.59.1)(vite@8.0.10(@types/node@22.19.11)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3))': + '@netlify/vite-plugin@2.10.3(@azure/identity@4.13.0)(@vercel/functions@3.4.3(@aws-sdk/credential-provider-web-identity@3.972.38))(rollup@4.59.1)(vite@8.0.12(@types/node@22.19.11)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3))': dependencies: '@netlify/dev': 4.11.3(@azure/identity@4.13.0)(@vercel/functions@3.4.3(@aws-sdk/credential-provider-web-identity@3.972.38))(rollup@4.59.1) '@netlify/dev-utils': 4.3.3 dedent: 1.7.1 - vite: 8.0.10(@types/node@22.19.11)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3) + vite: 8.0.12(@types/node@22.19.11)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3) transitivePeerDependencies: - '@azure/app-configuration' - '@azure/cosmos' @@ -19846,7 +19861,7 @@ snapshots: '@oslojs/encoding@1.1.0': {} - '@oxc-project/types@0.127.0': {} + '@oxc-project/types@0.129.0': {} '@oxc-resolver/binding-android-arm-eabi@11.17.1': optional: true @@ -19998,19 +20013,19 @@ snapshots: '@poppinss/exception@1.2.3': {} - '@preact/preset-vite@2.10.5(@babel/core@7.29.0)(preact@10.29.0)(rollup@4.59.1)(vite@8.0.10(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3))': + '@preact/preset-vite@2.10.5(@babel/core@7.29.0)(preact@10.29.0)(rollup@4.59.1)(vite@8.0.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3))': dependencies: '@babel/core': 7.29.0 '@babel/plugin-transform-react-jsx': 7.28.6(@babel/core@7.29.0) '@babel/plugin-transform-react-jsx-development': 7.27.1(@babel/core@7.29.0) - '@prefresh/vite': 2.4.11(preact@10.29.0)(vite@8.0.10(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3)) + '@prefresh/vite': 2.4.11(preact@10.29.0)(vite@8.0.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3)) '@rollup/pluginutils': 5.3.0(rollup@4.59.1) babel-plugin-transform-hook-names: 1.0.2(@babel/core@7.29.0) debug: 4.4.3(supports-color@8.1.1) magic-string: 0.30.21 picocolors: 1.1.1 - vite: 8.0.10(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3) - vite-prerender-plugin: 0.5.12(vite@8.0.10(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3)) + vite: 8.0.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3) + vite-prerender-plugin: 0.5.12(vite@8.0.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3)) zimmerframe: 1.1.4 transitivePeerDependencies: - preact @@ -20032,7 +20047,7 @@ snapshots: '@prefresh/utils@1.2.1': {} - '@prefresh/vite@2.4.11(preact@10.29.0)(vite@8.0.10(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3))': + '@prefresh/vite@2.4.11(preact@10.29.0)(vite@8.0.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3))': dependencies: '@babel/core': 7.29.0 '@prefresh/babel-plugin': 0.5.2 @@ -20040,7 +20055,7 @@ snapshots: '@prefresh/utils': 1.2.1 '@rollup/pluginutils': 4.2.1 preact: 10.29.0 - vite: 8.0.10(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3) + vite: 8.0.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3) transitivePeerDependencies: - supports-color @@ -20073,56 +20088,56 @@ snapshots: dependencies: dotenv: 16.6.1 - '@rolldown/binding-android-arm64@1.0.0-rc.17': + '@rolldown/binding-android-arm64@1.0.0': optional: true - '@rolldown/binding-darwin-arm64@1.0.0-rc.17': + '@rolldown/binding-darwin-arm64@1.0.0': optional: true - '@rolldown/binding-darwin-x64@1.0.0-rc.17': + '@rolldown/binding-darwin-x64@1.0.0': optional: true - '@rolldown/binding-freebsd-x64@1.0.0-rc.17': + '@rolldown/binding-freebsd-x64@1.0.0': optional: true - '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.17': + '@rolldown/binding-linux-arm-gnueabihf@1.0.0': optional: true - '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.17': + '@rolldown/binding-linux-arm64-gnu@1.0.0': optional: true - '@rolldown/binding-linux-arm64-musl@1.0.0-rc.17': + '@rolldown/binding-linux-arm64-musl@1.0.0': optional: true - '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.17': + '@rolldown/binding-linux-ppc64-gnu@1.0.0': optional: true - '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.17': + '@rolldown/binding-linux-s390x-gnu@1.0.0': optional: true - '@rolldown/binding-linux-x64-gnu@1.0.0-rc.17': + '@rolldown/binding-linux-x64-gnu@1.0.0': optional: true - '@rolldown/binding-linux-x64-musl@1.0.0-rc.17': + '@rolldown/binding-linux-x64-musl@1.0.0': optional: true - '@rolldown/binding-openharmony-arm64@1.0.0-rc.17': + '@rolldown/binding-openharmony-arm64@1.0.0': optional: true - '@rolldown/binding-wasm32-wasi@1.0.0-rc.17': + '@rolldown/binding-wasm32-wasi@1.0.0': dependencies: '@emnapi/core': 1.10.0 '@emnapi/runtime': 1.10.0 '@napi-rs/wasm-runtime': 1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) optional: true - '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.17': + '@rolldown/binding-win32-arm64-msvc@1.0.0': optional: true - '@rolldown/binding-win32-x64-msvc@1.0.0-rc.17': + '@rolldown/binding-win32-x64-msvc@1.0.0': optional: true - '@rolldown/pluginutils@1.0.0-rc.17': {} + '@rolldown/pluginutils@1.0.0': {} '@rolldown/pluginutils@1.0.0-rc.2': {} @@ -20675,22 +20690,22 @@ snapshots: dependencies: acorn: 8.16.0 - '@sveltejs/vite-plugin-svelte-inspector@5.0.2(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.55.3)(vite@8.0.10(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.55.3)(vite@8.0.10(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3))': + '@sveltejs/vite-plugin-svelte-inspector@5.0.2(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.55.3)(vite@8.0.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.55.3)(vite@8.0.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3))': dependencies: - '@sveltejs/vite-plugin-svelte': 6.2.4(svelte@5.55.3)(vite@8.0.10(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3)) + '@sveltejs/vite-plugin-svelte': 6.2.4(svelte@5.55.3)(vite@8.0.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3)) obug: 2.1.1 svelte: 5.55.3 - vite: 8.0.10(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3) + vite: 8.0.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3) - '@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.55.3)(vite@8.0.10(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3))': + '@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.55.3)(vite@8.0.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3))': dependencies: - '@sveltejs/vite-plugin-svelte-inspector': 5.0.2(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.55.3)(vite@8.0.10(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.55.3)(vite@8.0.10(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3)) + '@sveltejs/vite-plugin-svelte-inspector': 5.0.2(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.55.3)(vite@8.0.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.55.3)(vite@8.0.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3)) deepmerge: 4.3.1 magic-string: 0.30.21 obug: 2.1.1 svelte: 5.55.3 - vite: 8.0.10(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3) - vitefu: 1.1.2(vite@8.0.10(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3)) + vite: 8.0.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3) + vitefu: 1.1.2(vite@8.0.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3)) '@tailwindcss/node@4.2.2': dependencies: @@ -20753,12 +20768,12 @@ snapshots: '@tailwindcss/oxide-win32-arm64-msvc': 4.2.2 '@tailwindcss/oxide-win32-x64-msvc': 4.2.2 - '@tailwindcss/vite@4.2.2(vite@8.0.10(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3))': + '@tailwindcss/vite@4.2.2(vite@8.0.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3))': dependencies: '@tailwindcss/node': 4.2.2 '@tailwindcss/oxide': 4.2.2 tailwindcss: 4.2.2 - vite: 8.0.10(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3) + vite: 8.0.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3) '@test/server-entry-fake-adapter@file:packages/astro/test/fixtures/server-entry/fake-adapter': dependencies: @@ -21201,7 +21216,7 @@ snapshots: optionalDependencies: ajv: 6.12.6 - '@vitejs/plugin-react@5.2.0(vite@8.0.10(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3))': + '@vitejs/plugin-react@5.2.0(vite@8.0.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3))': dependencies: '@babel/core': 7.29.0 '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.29.0) @@ -21209,7 +21224,7 @@ snapshots: '@rolldown/pluginutils': 1.0.0-rc.3 '@types/babel__core': 7.20.5 react-refresh: 0.18.0 - vite: 8.0.10(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3) + vite: 8.0.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3) transitivePeerDependencies: - supports-color @@ -21224,14 +21239,14 @@ snapshots: transitivePeerDependencies: - supports-color - '@vitejs/plugin-vue-jsx@5.1.5(vite@8.0.10(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3))(vue@3.5.30(typescript@5.9.3))': + '@vitejs/plugin-vue-jsx@5.1.5(vite@8.0.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3))(vue@3.5.30(typescript@5.9.3))': dependencies: '@babel/core': 7.29.0 '@babel/plugin-syntax-typescript': 7.28.6(@babel/core@7.29.0) '@babel/plugin-transform-typescript': 7.28.6(@babel/core@7.29.0) '@rolldown/pluginutils': 1.0.0-rc.4 '@vue/babel-plugin-jsx': 2.0.1(@babel/core@7.29.0) - vite: 8.0.10(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3) + vite: 8.0.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3) vue: 3.5.30(typescript@5.9.3) transitivePeerDependencies: - supports-color @@ -21241,10 +21256,10 @@ snapshots: vite: 6.4.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3) vue: 3.5.30(typescript@5.9.3) - '@vitejs/plugin-vue@6.0.5(vite@8.0.10(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3))(vue@3.5.30(typescript@5.9.3))': + '@vitejs/plugin-vue@6.0.5(vite@8.0.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3))(vue@3.5.30(typescript@5.9.3))': dependencies: '@rolldown/pluginutils': 1.0.0-rc.2 - vite: 8.0.10(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3) + vite: 8.0.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3) vue: 3.5.30(typescript@5.9.3) '@vitest/expect@4.1.0': @@ -21733,6 +21748,10 @@ snapshots: dependencies: '@vue/reactivity': 3.1.5 + am-i-vibing@0.1.1: + dependencies: + process-ancestry: 0.1.0 + ansi-colors@4.1.3: {} ansi-escapes@5.0.0: @@ -25948,6 +25967,12 @@ snapshots: picocolors: 1.1.1 source-map-js: 1.2.1 + postcss@8.5.14: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + preact-render-to-string@6.6.6(preact@10.29.0): dependencies: preact: 10.29.0 @@ -26012,6 +26037,8 @@ snapshots: prismjs@1.30.0: {} + process-ancestry@0.1.0: {} + process-nextick-args@2.0.1: {} process-warning@4.0.1: {} @@ -26495,26 +26522,26 @@ snapshots: rfdc@1.4.1: {} - rolldown@1.0.0-rc.17: + rolldown@1.0.0: dependencies: - '@oxc-project/types': 0.127.0 - '@rolldown/pluginutils': 1.0.0-rc.17 + '@oxc-project/types': 0.129.0 + '@rolldown/pluginutils': 1.0.0 optionalDependencies: - '@rolldown/binding-android-arm64': 1.0.0-rc.17 - '@rolldown/binding-darwin-arm64': 1.0.0-rc.17 - '@rolldown/binding-darwin-x64': 1.0.0-rc.17 - '@rolldown/binding-freebsd-x64': 1.0.0-rc.17 - '@rolldown/binding-linux-arm-gnueabihf': 1.0.0-rc.17 - '@rolldown/binding-linux-arm64-gnu': 1.0.0-rc.17 - '@rolldown/binding-linux-arm64-musl': 1.0.0-rc.17 - '@rolldown/binding-linux-ppc64-gnu': 1.0.0-rc.17 - '@rolldown/binding-linux-s390x-gnu': 1.0.0-rc.17 - '@rolldown/binding-linux-x64-gnu': 1.0.0-rc.17 - '@rolldown/binding-linux-x64-musl': 1.0.0-rc.17 - '@rolldown/binding-openharmony-arm64': 1.0.0-rc.17 - '@rolldown/binding-wasm32-wasi': 1.0.0-rc.17 - '@rolldown/binding-win32-arm64-msvc': 1.0.0-rc.17 - '@rolldown/binding-win32-x64-msvc': 1.0.0-rc.17 + '@rolldown/binding-android-arm64': 1.0.0 + '@rolldown/binding-darwin-arm64': 1.0.0 + '@rolldown/binding-darwin-x64': 1.0.0 + '@rolldown/binding-freebsd-x64': 1.0.0 + '@rolldown/binding-linux-arm-gnueabihf': 1.0.0 + '@rolldown/binding-linux-arm64-gnu': 1.0.0 + '@rolldown/binding-linux-arm64-musl': 1.0.0 + '@rolldown/binding-linux-ppc64-gnu': 1.0.0 + '@rolldown/binding-linux-s390x-gnu': 1.0.0 + '@rolldown/binding-linux-x64-gnu': 1.0.0 + '@rolldown/binding-linux-x64-musl': 1.0.0 + '@rolldown/binding-openharmony-arm64': 1.0.0 + '@rolldown/binding-wasm32-wasi': 1.0.0 + '@rolldown/binding-win32-arm64-msvc': 1.0.0 + '@rolldown/binding-win32-x64-msvc': 1.0.0 rollup@4.59.1: dependencies: @@ -27577,19 +27604,19 @@ snapshots: '@types/unist': 3.0.3 vfile-message: 4.0.3 - vite-dev-rpc@1.1.0(vite@8.0.10(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3)): + vite-dev-rpc@1.1.0(vite@8.0.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3)): dependencies: birpc: 2.9.0 - vite: 8.0.10(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3) - vite-hot-client: 2.1.0(vite@8.0.10(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3)) + vite: 8.0.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3) + vite-hot-client: 2.1.0(vite@8.0.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3)) vite-hot-client@2.1.0(vite@6.4.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3)): dependencies: vite: 6.4.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3) - vite-hot-client@2.1.0(vite@8.0.10(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3)): + vite-hot-client@2.1.0(vite@8.0.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3)): dependencies: - vite: 8.0.10(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3) + vite: 8.0.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3) vite-plugin-inspect@0.8.9(rollup@4.59.1)(vite@6.4.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3)): dependencies: @@ -27607,7 +27634,7 @@ snapshots: - rollup - supports-color - vite-plugin-inspect@11.3.3(vite@8.0.10(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3)): + vite-plugin-inspect@11.3.3(vite@8.0.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3)): dependencies: ansis: 4.2.0 debug: 4.4.3(supports-color@8.1.1) @@ -27617,8 +27644,8 @@ snapshots: perfect-debounce: 2.1.0 sirv: 3.0.2 unplugin-utils: 0.3.1 - vite: 8.0.10(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3) - vite-dev-rpc: 1.1.0(vite@8.0.10(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3)) + vite: 8.0.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3) + vite-dev-rpc: 1.1.0(vite@8.0.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3)) transitivePeerDependencies: - supports-color @@ -27635,7 +27662,7 @@ snapshots: transitivePeerDependencies: - supports-color - vite-plugin-solid@2.11.11(solid-js@1.9.11)(vite@8.0.10(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3)): + vite-plugin-solid@2.11.11(solid-js@1.9.11)(vite@8.0.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3)): dependencies: '@babel/core': 7.29.0 '@types/babel__core': 7.20.5 @@ -27643,8 +27670,8 @@ snapshots: merge-anything: 5.1.7 solid-js: 1.9.11 solid-refresh: 0.6.3(solid-js@1.9.11) - vite: 8.0.10(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3) - vitefu: 1.1.2(vite@8.0.10(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3)) + vite: 8.0.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3) + vitefu: 1.1.2(vite@8.0.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3)) transitivePeerDependencies: - supports-color @@ -27664,15 +27691,15 @@ snapshots: - supports-color - vue - vite-plugin-vue-devtools@8.1.0(vite@8.0.10(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3))(vue@3.5.30(typescript@5.9.3)): + vite-plugin-vue-devtools@8.1.0(vite@8.0.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3))(vue@3.5.30(typescript@5.9.3)): dependencies: '@vue/devtools-core': 8.1.0(vue@3.5.30(typescript@5.9.3)) '@vue/devtools-kit': 8.1.0 '@vue/devtools-shared': 8.1.0 sirv: 3.0.2 - vite: 8.0.10(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3) - vite-plugin-inspect: 11.3.3(vite@8.0.10(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3)) - vite-plugin-vue-inspector: 5.3.2(vite@8.0.10(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3)) + vite: 8.0.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3) + vite-plugin-inspect: 11.3.3(vite@8.0.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3)) + vite-plugin-vue-inspector: 5.3.2(vite@8.0.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3)) transitivePeerDependencies: - '@nuxt/kit' - supports-color @@ -27693,7 +27720,7 @@ snapshots: transitivePeerDependencies: - supports-color - vite-plugin-vue-inspector@5.3.2(vite@8.0.10(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3)): + vite-plugin-vue-inspector@5.3.2(vite@8.0.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3)): dependencies: '@babel/core': 7.29.0 '@babel/plugin-proposal-decorators': 7.29.0(@babel/core@7.29.0) @@ -27704,11 +27731,11 @@ snapshots: '@vue/compiler-dom': 3.5.30 kolorist: 1.8.0 magic-string: 0.30.21 - vite: 8.0.10(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3) + vite: 8.0.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3) transitivePeerDependencies: - supports-color - vite-prerender-plugin@0.5.12(vite@8.0.10(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3)): + vite-prerender-plugin@0.5.12(vite@8.0.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3)): dependencies: kolorist: 1.8.0 magic-string: 0.30.21 @@ -27716,7 +27743,7 @@ snapshots: simple-code-frame: 1.3.0 source-map: 0.7.6 stack-trace: 1.0.0-pre2 - vite: 8.0.10(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3) + vite: 8.0.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3) vite-svg-loader@5.1.1(vue@3.5.30(typescript@5.9.3)): dependencies: @@ -27760,12 +27787,12 @@ snapshots: tsx: 4.21.0 yaml: 2.8.3 - vite@8.0.10(@types/node@22.19.11)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3): + vite@8.0.12(@types/node@22.19.11)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3): dependencies: lightningcss: 1.32.0 picomatch: 4.0.4 - postcss: 8.5.12 - rolldown: 1.0.0-rc.17 + postcss: 8.5.14 + rolldown: 1.0.0 tinyglobby: 0.2.16 optionalDependencies: '@types/node': 22.19.11 @@ -27776,12 +27803,12 @@ snapshots: tsx: 4.21.0 yaml: 2.8.3 - vite@8.0.10(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3): + vite@8.0.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3): dependencies: lightningcss: 1.32.0 picomatch: 4.0.4 - postcss: 8.5.12 - rolldown: 1.0.0-rc.17 + postcss: 8.5.14 + rolldown: 1.0.0 tinyglobby: 0.2.16 optionalDependencies: '@types/node': 25.2.3 @@ -27796,9 +27823,9 @@ snapshots: optionalDependencies: vite: 6.4.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3) - vitefu@1.1.2(vite@8.0.10(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3)): + vitefu@1.1.2(vite@8.0.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3)): optionalDependencies: - vite: 8.0.10(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3) + vite: 8.0.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3) vitest@4.1.0(@opentelemetry/api@1.9.0)(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.98.0)(tsx@4.21.0)(yaml@2.8.3): dependencies: From 8e2ea3474c72735915908f18dbd43dc5fc05720e Mon Sep 17 00:00:00 2001 From: Matthew Phillips Date: Tue, 5 May 2026 12:08:45 -0400 Subject: [PATCH 02/25] Skip lock file in test/production environments --- packages/astro/src/core/dev/dev.ts | 53 +++++++++++++++++------------- 1 file changed, 30 insertions(+), 23 deletions(-) diff --git a/packages/astro/src/core/dev/dev.ts b/packages/astro/src/core/dev/dev.ts index 5fce02a297e1..198a53ccdc0f 100644 --- a/packages/astro/src/core/dev/dev.ts +++ b/packages/astro/src/core/dev/dev.ts @@ -47,21 +47,24 @@ export default async function dev(inlineConfig: AstroInlineConfig): Promise Date: Tue, 5 May 2026 12:25:46 -0400 Subject: [PATCH 03/25] Move lock file logic from JS API to CLI layer --- packages/astro/src/cli/dev/index.ts | 43 ++++++++++++++++++++++++++++- packages/astro/src/core/dev/dev.ts | 41 --------------------------- 2 files changed, 42 insertions(+), 42 deletions(-) diff --git a/packages/astro/src/cli/dev/index.ts b/packages/astro/src/cli/dev/index.ts index 874240b82dda..50638c0fdfc2 100644 --- a/packages/astro/src/cli/dev/index.ts +++ b/packages/astro/src/cli/dev/index.ts @@ -1,6 +1,9 @@ +import { resolve } from 'node:path'; +import { pathToFileURL } from 'node:url'; import { isAgent } from 'am-i-vibing'; import colors from 'piccolore'; import devServer from '../../core/dev/index.js'; +import { checkExistingServer, removeLockFile, writeLockFile } from '../../core/dev/lockfile.js'; import { printHelp } from '../../core/messages/runtime.js'; import { type Flags, flagsToAstroInlineConfig } from '../flags.js'; @@ -16,6 +19,11 @@ function isRunByAgent(): boolean { } } +function resolveRootURL(flags: Flags): URL { + const rootPath = typeof flags.root === 'string' ? resolve(flags.root) : process.cwd(); + return pathToFileURL(rootPath + '/'); +} + export async function dev({ flags }: DevOptions) { if (flags.help || flags.h) { printHelp({ @@ -79,7 +87,40 @@ export async function dev({ flags }: DevOptions) { return; } + // Foreground dev server: check lock file, start server, write lock file + const root = resolveRootURL(flags); + const existingServer = checkExistingServer(root); + if (existingServer) { + const message = [ + 'Another astro dev server is already running.', + '', + ` URL: ${existingServer.url}`, + ` PID: ${existingServer.pid}`, + '', + `Run \`kill ${existingServer.pid}\` to stop it, or use \`astro dev --force\` to replace it.`, + ].join('\n'); + throw new Error(message); + } + const inlineConfig = flagsToAstroInlineConfig(flags); + const server = await devServer(inlineConfig); + + // Write the lock file now that we know the bound port + const serverUrl = `http://localhost:${server.address.port}`; + writeLockFile(root, { + pid: process.pid, + port: server.address.port, + url: serverUrl, + background: false, + startedAt: new Date().toISOString(), + }); + + // Wrap the original stop to also clean up the lock file + const originalStop = server.stop.bind(server); + server.stop = async () => { + removeLockFile(root); + await originalStop(); + }; - return await devServer(inlineConfig); + return server; } diff --git a/packages/astro/src/core/dev/dev.ts b/packages/astro/src/core/dev/dev.ts index 198a53ccdc0f..57d773039c15 100644 --- a/packages/astro/src/core/dev/dev.ts +++ b/packages/astro/src/core/dev/dev.ts @@ -1,9 +1,7 @@ import fs from 'node:fs'; import type http from 'node:http'; import type { AddressInfo } from 'node:net'; -import { resolve } from 'node:path'; import { performance } from 'node:perf_hooks'; -import { pathToFileURL } from 'node:url'; import colors from 'piccolore'; import { gt, major, minor, patch } from 'semver'; import type * as vite from 'vite'; @@ -18,7 +16,6 @@ import * as msg from '../messages/runtime.js'; import { newVersionAvailable } from '../messages/node.js'; import { ensureProcessNodeEnv } from '../util.js'; import { startContainer } from './container.js'; -import { checkExistingServer, removeLockFile, writeLockFile } from './lockfile.js'; import { createContainerWithAutomaticRestart } from './restart.js'; import { fetchLatestAstroVersion, @@ -46,27 +43,6 @@ export default async function dev(inlineConfig: AstroInlineConfig): Promise Date: Tue, 5 May 2026 13:45:18 -0400 Subject: [PATCH 04/25] Ensure .astro directory exists before opening log file --- packages/astro/src/cli/dev/background.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/astro/src/cli/dev/background.ts b/packages/astro/src/cli/dev/background.ts index 6d4f9d8e4231..79f1ca6e7d6d 100644 --- a/packages/astro/src/cli/dev/background.ts +++ b/packages/astro/src/cli/dev/background.ts @@ -1,5 +1,5 @@ import { spawn } from 'node:child_process'; -import { openSync } from 'node:fs'; +import { existsSync, mkdirSync, openSync } from 'node:fs'; import { resolve } from 'node:path'; import { fileURLToPath, pathToFileURL } from 'node:url'; import type { Flags } from '../flags.js'; @@ -77,9 +77,13 @@ export async function background({ flags }: { flags: Flags }): Promise { if (flags.root) args.push('--root', String(flags.root)); if (flags.allowedHosts) args.push('--allowed-hosts', String(flags.allowedHosts)); - // Open the log file for writing + // Open the log file for writing, ensuring the .astro directory exists const logFileURL = getLogFileURL(root); const logFilePath = fileURLToPath(logFileURL); + const dotAstroDir = fileURLToPath(new URL('.astro/', root)); + if (!existsSync(dotAstroDir)) { + mkdirSync(dotAstroDir, { recursive: true }); + } const logFd = openSync(logFilePath, 'w'); // Find the astro binary From e8ffef4c0223bcbc6511a1946adead0eaec9c8a9 Mon Sep 17 00:00:00 2001 From: Matthew Phillips Date: Tue, 5 May 2026 13:52:08 -0400 Subject: [PATCH 05/25] Fix infinite spawn recursion when agent env vars are inherited --- packages/astro/src/cli/dev/background.ts | 2 +- packages/astro/src/cli/dev/index.ts | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/astro/src/cli/dev/background.ts b/packages/astro/src/cli/dev/background.ts index 79f1ca6e7d6d..9b890033f4e5 100644 --- a/packages/astro/src/cli/dev/background.ts +++ b/packages/astro/src/cli/dev/background.ts @@ -95,7 +95,7 @@ export async function background({ flags }: { flags: Flags }): Promise { detached: true, stdio: ['ignore', logFd, logFd], cwd: rootPath, - env: { ...process.env }, + env: { ...process.env, ASTRO_DEV_BACKGROUND: '1' }, }); child.unref(); diff --git a/packages/astro/src/cli/dev/index.ts b/packages/astro/src/cli/dev/index.ts index 50638c0fdfc2..b8e8777c6f11 100644 --- a/packages/astro/src/cli/dev/index.ts +++ b/packages/astro/src/cli/dev/index.ts @@ -81,7 +81,9 @@ export async function dev({ flags }: DevOptions) { // Handle --experimental-background: start as a background process. // Also auto-enable when an AI coding agent is detected. - if (flags.experimentalBackground || isRunByAgent()) { + // Skip if ASTRO_DEV_BACKGROUND is set — this means we're the spawned child process + // and should run the foreground dev server, not recurse into background mode. + if (flags.experimentalBackground || (isRunByAgent() && !process.env.ASTRO_DEV_BACKGROUND)) { const { background } = await import('./background.js'); await background({ flags }); return; From e21d36848a021f3123ac8becc957a1032d9210d0 Mon Sep 17 00:00:00 2001 From: Matthew Phillips Date: Tue, 5 May 2026 15:39:05 -0400 Subject: [PATCH 06/25] Use logger instead of raw JSON output, add command hints --- BUG-REPORT.md | 77 ++++++++++++++++++++++++ packages/astro/src/cli/dev/background.ts | 48 +++++---------- packages/astro/src/cli/dev/index.ts | 12 ++-- packages/astro/src/cli/dev/logs.ts | 10 +-- packages/astro/src/cli/dev/status.ts | 20 +++--- packages/astro/src/cli/dev/stop.ts | 7 ++- 6 files changed, 119 insertions(+), 55 deletions(-) create mode 100644 BUG-REPORT.md diff --git a/BUG-REPORT.md b/BUG-REPORT.md new file mode 100644 index 000000000000..7aff5bd82ee1 --- /dev/null +++ b/BUG-REPORT.md @@ -0,0 +1,77 @@ +# Bug: Background dev server infinite recursion when spawned by AI agent + +## Summary + +When `astro dev` is run inside an AI coding agent (e.g. OpenCode, Claude Code), the background dev server feature enters an infinite spawn loop and times out after 30 seconds without ever starting a server. The user sees: + +```json +{"error":"timeout","message":"Dev server failed to start within 30s."} +``` + +## Root Cause + +The `dev` command in `packages/astro/src/cli/dev/index.ts` uses `am-i-vibing`'s `isAgent()` to detect AI agent environments and automatically enter background mode. When background mode is triggered, `background.ts` spawns a **child process** running `astro dev` (without `--experimental-background`) and polls for a lockfile indicating the server is ready. + +The problem is that the child process is spawned with `{ ...process.env }`, which **inherits the agent environment variables** (e.g. `OPENCODE=1`). When the child runs `astro dev`, it also calls `isAgent()`, gets `true`, and enters background mode again -- spawning yet another child. This creates an infinite recursion: + +``` +astro dev (PID 1) + → isAgent() = true (OPENCODE=1 in env) + → enters background(), spawns child with { ...process.env } + → astro dev (PID 2) + → isAgent() = true (OPENCODE=1 still in env) + → enters background(), spawns child with { ...process.env } + → astro dev (PID 3) + → isAgent() = true ... + → ... and so on until 30s timeout +``` + +No process ever reaches the foreground dev server code path, so no lockfile is written, and the parent times out. + +### Affected environment variables + +The `am-i-vibing` library checks for these env vars (among others) to detect agents: + +- **OpenCode**: `OPENCODE`, `OPENCODE_BIN_PATH`, `OPENCODE_SERVER`, `OPENCODE_APP_INFO`, `OPENCODE_MODES` +- **Claude Code**: `CLAUDECODE` +- **Cursor Agent**: `CURSOR_TRACE_ID` + `PAGER=head -n 10000 | cat` +- **Replit**: `REPL_ID` +- **Windsurf**: `CODEIUM_EDITOR_APP_ROOT` + +Any of these being present in the parent environment will trigger the recursion. + +## Reproduction + +1. Set `OPENCODE=1` in the environment (or run from within any detected AI agent) +2. Run `astro dev` in a project using the preview build from PR #16610 +3. Observe: the command hangs for 30 seconds, then outputs the timeout error + +## Fix + +Two changes, both in `packages/astro/src/cli/dev/`: + +### 1. `background.ts` -- Set a guard env var on the spawned child + +```diff + const child = spawn(astroBin, args, { + detached: true, + stdio: ['ignore', logFd, logFd], + cwd: rootPath, +- env: { ...process.env }, ++ env: { ...process.env, ASTRO_DEV_BACKGROUND: '1' }, + }); +``` + +### 2. `index.ts` -- Check for the guard before entering background mode + +```diff +- if (flags.experimentalBackground || isRunByAgent()) { ++ if (flags.experimentalBackground || (isRunByAgent() && !process.env.ASTRO_DEV_BACKGROUND)) { +``` + +The `ASTRO_DEV_BACKGROUND` env var acts as a recursion guard. The parent sets it when spawning the child, and the child checks for it before deciding whether to enter background mode. This way the child always takes the foreground code path and actually starts the dev server. + +This approach is preferable to stripping specific agent env vars because: +- It doesn't need to track which env vars `am-i-vibing` checks +- It's forward-compatible with new agent detections added to `am-i-vibing` +- It's explicit about intent: "I am the background-spawned child, run in foreground" diff --git a/packages/astro/src/cli/dev/background.ts b/packages/astro/src/cli/dev/background.ts index 9b890033f4e5..a779bf94cf45 100644 --- a/packages/astro/src/cli/dev/background.ts +++ b/packages/astro/src/cli/dev/background.ts @@ -2,6 +2,7 @@ import { spawn } from 'node:child_process'; import { existsSync, mkdirSync, openSync } from 'node:fs'; import { resolve } from 'node:path'; import { fileURLToPath, pathToFileURL } from 'node:url'; +import type { AstroLogger } from '../../core/logger/core.js'; import type { Flags } from '../flags.js'; import { checkExistingServer, @@ -31,19 +32,19 @@ function resolveRootURL(flags: Flags): URL { return pathToFileURL(rootPath + '/'); } -export async function background({ flags }: { flags: Flags }): Promise { +export async function background({ + flags, + logger, +}: { flags: Flags; logger: AstroLogger }): Promise { const root = resolveRootURL(flags); // Check for existing server const existing = checkExistingServer(root); if (existing && !flags.force) { - console.log( - formatBackgroundOutput({ - pid: existing.pid, - url: existing.url, - existing: true, - }), - ); + logger.info(null, `Dev server already running at ${existing.url} (pid ${existing.pid})\n` + + ' Stop: astro dev --experimental-stop\n' + + ' Status: astro dev --experimental-status\n' + + ' Logs: astro dev --experimental-logs'); return; } @@ -102,12 +103,7 @@ export async function background({ flags }: { flags: Flags }): Promise { const childPid = child.pid; if (!childPid) { - console.log( - formatBackgroundOutput({ - error: 'spawn_failed', - message: 'Failed to spawn background dev server process.', - }), - ); + logger.error(null, 'Failed to spawn background dev server process.'); process.exitCode = 1; return; } @@ -119,12 +115,7 @@ export async function background({ flags }: { flags: Flags }): Promise { while (Date.now() < deadline) { // Check if child is still alive if (!isProcessAlive(childPid)) { - console.log( - formatBackgroundOutput({ - error: 'process_exited', - message: 'Dev server process exited before becoming ready.', - }), - ); + logger.error(null, 'Dev server process exited before becoming ready.'); process.exitCode = 1; return; } @@ -132,12 +123,10 @@ export async function background({ flags }: { flags: Flags }): Promise { // Check for the lock file (written by the child's dev server) const lockData = readLockFile(root); if (lockData && lockData.pid === childPid) { - console.log( - formatBackgroundOutput({ - pid: lockData.pid, - url: lockData.url, - }), - ); + logger.info(null, `Dev server running at ${lockData.url} (pid ${lockData.pid})\n` + + ' Stop: astro dev --experimental-stop\n' + + ' Status: astro dev --experimental-status\n' + + ' Logs: astro dev --experimental-logs'); return; } @@ -152,11 +141,6 @@ export async function background({ flags }: { flags: Flags }): Promise { } removeLockFile(root); - console.log( - formatBackgroundOutput({ - error: 'timeout', - message: `Dev server failed to start within ${timeout / 1000}s.`, - }), - ); + logger.error(null, `Dev server failed to start within ${timeout / 1000}s.`); process.exitCode = 1; } diff --git a/packages/astro/src/cli/dev/index.ts b/packages/astro/src/cli/dev/index.ts index b8e8777c6f11..bed25e39b21a 100644 --- a/packages/astro/src/cli/dev/index.ts +++ b/packages/astro/src/cli/dev/index.ts @@ -5,7 +5,7 @@ import colors from 'piccolore'; import devServer from '../../core/dev/index.js'; import { checkExistingServer, removeLockFile, writeLockFile } from '../../core/dev/lockfile.js'; import { printHelp } from '../../core/messages/runtime.js'; -import { type Flags, flagsToAstroInlineConfig } from '../flags.js'; +import { type Flags, createLoggerFromFlags, flagsToAstroInlineConfig } from '../flags.js'; interface DevOptions { flags: Flags; @@ -58,24 +58,26 @@ export async function dev({ flags }: DevOptions) { return; } + const logger = createLoggerFromFlags(flags); + // Handle --experimental-stop: stop a running dev server if (flags.experimentalStop) { const { stop } = await import('./stop.js'); - await stop({ flags }); + await stop({ flags, logger }); return; } // Handle --experimental-status: check if a dev server is running if (flags.experimentalStatus) { const { status } = await import('./status.js'); - await status({ flags }); + await status({ flags, logger }); return; } // Handle --experimental-logs: view logs from a background dev server if (flags.experimentalLogs) { const { logs } = await import('./logs.js'); - await logs({ flags }); + await logs({ flags, logger }); return; } @@ -85,7 +87,7 @@ export async function dev({ flags }: DevOptions) { // and should run the foreground dev server, not recurse into background mode. if (flags.experimentalBackground || (isRunByAgent() && !process.env.ASTRO_DEV_BACKGROUND)) { const { background } = await import('./background.js'); - await background({ flags }); + await background({ flags, logger }); return; } diff --git a/packages/astro/src/cli/dev/logs.ts b/packages/astro/src/cli/dev/logs.ts index 55fd74cf72ff..916c003e9d19 100644 --- a/packages/astro/src/cli/dev/logs.ts +++ b/packages/astro/src/cli/dev/logs.ts @@ -1,6 +1,7 @@ import { readFileSync, existsSync } from 'node:fs'; import { resolve } from 'node:path'; import { fileURLToPath, pathToFileURL } from 'node:url'; +import type { AstroLogger } from '../../core/logger/core.js'; import type { Flags } from '../flags.js'; import { checkExistingServer, getLogFileURL } from '../../core/dev/lockfile.js'; @@ -9,18 +10,19 @@ function resolveRootURL(flags: Flags): URL { return pathToFileURL(rootPath + '/'); } -export async function logs({ flags }: { flags: Flags }): Promise { +export async function logs({ flags, logger }: { flags: Flags; logger: AstroLogger }): Promise { const root = resolveRootURL(flags); const existing = checkExistingServer(root); if (!existing) { - console.error('No dev server is running.'); + logger.error(null, 'No dev server is running.'); process.exitCode = 1; return; } if (!existing.background) { - console.error( + logger.error( + null, 'The running dev server was not started with --background. View logs in the terminal where it was started.', ); process.exitCode = 1; @@ -30,7 +32,7 @@ export async function logs({ flags }: { flags: Flags }): Promise { const logFileURL = getLogFileURL(root); const logFilePath = fileURLToPath(logFileURL); if (!existsSync(logFilePath)) { - console.error('No log file found.'); + logger.error(null, 'No log file found.'); process.exitCode = 1; return; } diff --git a/packages/astro/src/cli/dev/status.ts b/packages/astro/src/cli/dev/status.ts index 5f02f9fb5346..3d9d3f242503 100644 --- a/packages/astro/src/cli/dev/status.ts +++ b/packages/astro/src/cli/dev/status.ts @@ -1,5 +1,6 @@ import { resolve } from 'node:path'; import { pathToFileURL } from 'node:url'; +import type { AstroLogger } from '../../core/logger/core.js'; import type { Flags } from '../flags.js'; import { checkExistingServer } from '../../core/dev/lockfile.js'; @@ -21,26 +22,23 @@ function resolveRootURL(flags: Flags): URL { return pathToFileURL(rootPath + '/'); } -export async function status({ flags }: { flags: Flags }): Promise { +export async function status({ + flags, + logger, +}: { flags: Flags; logger: AstroLogger }): Promise { const root = resolveRootURL(flags); const existing = checkExistingServer(root); if (!existing) { - console.log(formatStatusOutput({ running: false })); + logger.info(null, 'No dev server is running.'); return; } const startedAt = new Date(existing.startedAt).getTime(); const uptime = Math.floor((Date.now() - startedAt) / 1000); - console.log( - formatStatusOutput({ - running: true, - pid: existing.pid, - url: existing.url, - port: existing.port, - background: existing.background, - uptime, - }), + logger.info( + null, + `Dev server running at ${existing.url} (pid ${existing.pid}, uptime ${uptime}s${existing.background ? ', background' : ''})`, ); } diff --git a/packages/astro/src/cli/dev/stop.ts b/packages/astro/src/cli/dev/stop.ts index 8dc7eda72fbb..fc45d045c673 100644 --- a/packages/astro/src/cli/dev/stop.ts +++ b/packages/astro/src/cli/dev/stop.ts @@ -1,5 +1,6 @@ import { resolve } from 'node:path'; import { pathToFileURL } from 'node:url'; +import type { AstroLogger } from '../../core/logger/core.js'; import type { Flags } from '../flags.js'; import { checkExistingServer, removeLockFile } from '../../core/dev/lockfile.js'; @@ -18,12 +19,12 @@ function resolveRootURL(flags: Flags): URL { return pathToFileURL(rootPath + '/'); } -export async function stop({ flags }: { flags: Flags }): Promise { +export async function stop({ flags, logger }: { flags: Flags; logger: AstroLogger }): Promise { const root = resolveRootURL(flags); const existing = checkExistingServer(root); if (!existing) { - console.log(formatStopOutput({ stopped: false, reason: 'not_running' })); + logger.info(null, 'No dev server is running.'); return; } @@ -48,5 +49,5 @@ export async function stop({ flags }: { flags: Flags }): Promise { // Clean up the lock file in case the process didn't remove it removeLockFile(root); - console.log(formatStopOutput({ stopped: true, pid: existing.pid })); + logger.info(null, `Stopped dev server (pid ${existing.pid}).`); } From 206ddb44c03e523c633ba285cecaa6c4008898b6 Mon Sep 17 00:00:00 2001 From: Matthew Phillips Date: Tue, 5 May 2026 15:40:32 -0400 Subject: [PATCH 07/25] Fix background flag in lock file, remove BUG-REPORT.md --- BUG-REPORT.md | 77 ----------------------------- packages/astro/src/cli/dev/index.ts | 2 +- 2 files changed, 1 insertion(+), 78 deletions(-) delete mode 100644 BUG-REPORT.md diff --git a/BUG-REPORT.md b/BUG-REPORT.md deleted file mode 100644 index 7aff5bd82ee1..000000000000 --- a/BUG-REPORT.md +++ /dev/null @@ -1,77 +0,0 @@ -# Bug: Background dev server infinite recursion when spawned by AI agent - -## Summary - -When `astro dev` is run inside an AI coding agent (e.g. OpenCode, Claude Code), the background dev server feature enters an infinite spawn loop and times out after 30 seconds without ever starting a server. The user sees: - -```json -{"error":"timeout","message":"Dev server failed to start within 30s."} -``` - -## Root Cause - -The `dev` command in `packages/astro/src/cli/dev/index.ts` uses `am-i-vibing`'s `isAgent()` to detect AI agent environments and automatically enter background mode. When background mode is triggered, `background.ts` spawns a **child process** running `astro dev` (without `--experimental-background`) and polls for a lockfile indicating the server is ready. - -The problem is that the child process is spawned with `{ ...process.env }`, which **inherits the agent environment variables** (e.g. `OPENCODE=1`). When the child runs `astro dev`, it also calls `isAgent()`, gets `true`, and enters background mode again -- spawning yet another child. This creates an infinite recursion: - -``` -astro dev (PID 1) - → isAgent() = true (OPENCODE=1 in env) - → enters background(), spawns child with { ...process.env } - → astro dev (PID 2) - → isAgent() = true (OPENCODE=1 still in env) - → enters background(), spawns child with { ...process.env } - → astro dev (PID 3) - → isAgent() = true ... - → ... and so on until 30s timeout -``` - -No process ever reaches the foreground dev server code path, so no lockfile is written, and the parent times out. - -### Affected environment variables - -The `am-i-vibing` library checks for these env vars (among others) to detect agents: - -- **OpenCode**: `OPENCODE`, `OPENCODE_BIN_PATH`, `OPENCODE_SERVER`, `OPENCODE_APP_INFO`, `OPENCODE_MODES` -- **Claude Code**: `CLAUDECODE` -- **Cursor Agent**: `CURSOR_TRACE_ID` + `PAGER=head -n 10000 | cat` -- **Replit**: `REPL_ID` -- **Windsurf**: `CODEIUM_EDITOR_APP_ROOT` - -Any of these being present in the parent environment will trigger the recursion. - -## Reproduction - -1. Set `OPENCODE=1` in the environment (or run from within any detected AI agent) -2. Run `astro dev` in a project using the preview build from PR #16610 -3. Observe: the command hangs for 30 seconds, then outputs the timeout error - -## Fix - -Two changes, both in `packages/astro/src/cli/dev/`: - -### 1. `background.ts` -- Set a guard env var on the spawned child - -```diff - const child = spawn(astroBin, args, { - detached: true, - stdio: ['ignore', logFd, logFd], - cwd: rootPath, -- env: { ...process.env }, -+ env: { ...process.env, ASTRO_DEV_BACKGROUND: '1' }, - }); -``` - -### 2. `index.ts` -- Check for the guard before entering background mode - -```diff -- if (flags.experimentalBackground || isRunByAgent()) { -+ if (flags.experimentalBackground || (isRunByAgent() && !process.env.ASTRO_DEV_BACKGROUND)) { -``` - -The `ASTRO_DEV_BACKGROUND` env var acts as a recursion guard. The parent sets it when spawning the child, and the child checks for it before deciding whether to enter background mode. This way the child always takes the foreground code path and actually starts the dev server. - -This approach is preferable to stripping specific agent env vars because: -- It doesn't need to track which env vars `am-i-vibing` checks -- It's forward-compatible with new agent detections added to `am-i-vibing` -- It's explicit about intent: "I am the background-spawned child, run in foreground" diff --git a/packages/astro/src/cli/dev/index.ts b/packages/astro/src/cli/dev/index.ts index bed25e39b21a..3d00d6f1e511 100644 --- a/packages/astro/src/cli/dev/index.ts +++ b/packages/astro/src/cli/dev/index.ts @@ -115,7 +115,7 @@ export async function dev({ flags }: DevOptions) { pid: process.pid, port: server.address.port, url: serverUrl, - background: false, + background: !!process.env.ASTRO_DEV_BACKGROUND, startedAt: new Date().toISOString(), }); From af8277f4bb7138941f32b28493f08ed472536bd5 Mon Sep 17 00:00:00 2001 From: Matthew Phillips Date: Mon, 11 May 2026 17:07:14 -0400 Subject: [PATCH 08/25] Replace --experimental-* flags with subcommands, upgrade am-i-vibing to 0.3.0 - Replace --experimental-background/stop/status/logs flags with positional subcommands: astro dev background, astro dev stop, astro dev status, astro dev logs - Update error/info messages to reference new subcommand form - Upgrade am-i-vibing from ^0.1.1 to ^0.3.0 for faster env-var-based agent detection and broader tool coverage --- packages/astro/package.json | 2 +- packages/astro/src/cli/dev/background.ts | 12 ++++---- packages/astro/src/cli/dev/index.ts | 35 ++++++++++++------------ packages/astro/src/cli/dev/logs.ts | 2 +- pnpm-lock.yaml | 10 +++---- 5 files changed, 30 insertions(+), 31 deletions(-) diff --git a/packages/astro/package.json b/packages/astro/package.json index dc37faa97346..7abad6da4453 100644 --- a/packages/astro/package.json +++ b/packages/astro/package.json @@ -143,7 +143,7 @@ "@clack/prompts": "^1.1.0", "@oslojs/encoding": "^1.1.0", "@rollup/pluginutils": "^5.3.0", - "am-i-vibing": "^0.1.1", + "am-i-vibing": "^0.3.0", "aria-query": "^5.3.2", "axobject-query": "^4.1.0", "ci-info": "^4.4.0", diff --git a/packages/astro/src/cli/dev/background.ts b/packages/astro/src/cli/dev/background.ts index a779bf94cf45..ab9ee4823357 100644 --- a/packages/astro/src/cli/dev/background.ts +++ b/packages/astro/src/cli/dev/background.ts @@ -42,9 +42,9 @@ export async function background({ const existing = checkExistingServer(root); if (existing && !flags.force) { logger.info(null, `Dev server already running at ${existing.url} (pid ${existing.pid})\n` + - ' Stop: astro dev --experimental-stop\n' + - ' Status: astro dev --experimental-status\n' + - ' Logs: astro dev --experimental-logs'); + ' Stop: astro dev stop\n' + + ' Status: astro dev status\n' + + ' Logs: astro dev logs'); return; } @@ -124,9 +124,9 @@ export async function background({ const lockData = readLockFile(root); if (lockData && lockData.pid === childPid) { logger.info(null, `Dev server running at ${lockData.url} (pid ${lockData.pid})\n` + - ' Stop: astro dev --experimental-stop\n' + - ' Status: astro dev --experimental-status\n' + - ' Logs: astro dev --experimental-logs'); + ' Stop: astro dev stop\n' + + ' Status: astro dev status\n' + + ' Logs: astro dev logs'); return; } diff --git a/packages/astro/src/cli/dev/index.ts b/packages/astro/src/cli/dev/index.ts index 3d00d6f1e511..f5e4ff151185 100644 --- a/packages/astro/src/cli/dev/index.ts +++ b/packages/astro/src/cli/dev/index.ts @@ -28,8 +28,14 @@ export async function dev({ flags }: DevOptions) { if (flags.help || flags.h) { printHelp({ commandName: 'astro dev', - usage: '[...flags]', + usage: '[command] [...flags]', tables: { + Commands: [ + ['background', 'Start the dev server as a background process.'], + ['stop', 'Stop a running background dev server.'], + ['status', 'Check if a dev server is running.'], + ['logs', 'View logs from a background dev server.'], + ], Flags: [ ['--mode', `Specify the mode of the project. Defaults to "development".`], ['--port', `Specify which port to run on. Defaults to 4321.`], @@ -41,13 +47,6 @@ export async function dev({ flags }: DevOptions) { '--allowed-hosts', 'Specify a comma-separated list of allowed hosts or allow any hostname.', ], - [ - '--experimental-background', - 'Start the dev server as a background process.', - ], - ['--experimental-stop', 'Stop a running background dev server.'], - ['--experimental-status', 'Check if a dev server is running.'], - ['--experimental-logs', 'View logs from a background dev server.'], ['--help (-h)', 'See all available flags.'], ], }, @@ -59,33 +58,33 @@ export async function dev({ flags }: DevOptions) { } const logger = createLoggerFromFlags(flags); + const subcommand = flags._[3]?.toString(); - // Handle --experimental-stop: stop a running dev server - if (flags.experimentalStop) { + // Handle `astro dev stop` + if (subcommand === 'stop') { const { stop } = await import('./stop.js'); await stop({ flags, logger }); return; } - // Handle --experimental-status: check if a dev server is running - if (flags.experimentalStatus) { + // Handle `astro dev status` + if (subcommand === 'status') { const { status } = await import('./status.js'); await status({ flags, logger }); return; } - // Handle --experimental-logs: view logs from a background dev server - if (flags.experimentalLogs) { + // Handle `astro dev logs` + if (subcommand === 'logs') { const { logs } = await import('./logs.js'); await logs({ flags, logger }); return; } - // Handle --experimental-background: start as a background process. - // Also auto-enable when an AI coding agent is detected. + // Handle `astro dev background` or auto-enable when an AI coding agent is detected. // Skip if ASTRO_DEV_BACKGROUND is set — this means we're the spawned child process // and should run the foreground dev server, not recurse into background mode. - if (flags.experimentalBackground || (isRunByAgent() && !process.env.ASTRO_DEV_BACKGROUND)) { + if (subcommand === 'background' || (isRunByAgent() && !process.env.ASTRO_DEV_BACKGROUND)) { const { background } = await import('./background.js'); await background({ flags, logger }); return; @@ -101,7 +100,7 @@ export async function dev({ flags }: DevOptions) { ` URL: ${existingServer.url}`, ` PID: ${existingServer.pid}`, '', - `Run \`kill ${existingServer.pid}\` to stop it, or use \`astro dev --force\` to replace it.`, + `Run \`astro dev stop\` to stop it, or use \`astro dev --force\` to replace it.`, ].join('\n'); throw new Error(message); } diff --git a/packages/astro/src/cli/dev/logs.ts b/packages/astro/src/cli/dev/logs.ts index 916c003e9d19..92c2bfe46ad0 100644 --- a/packages/astro/src/cli/dev/logs.ts +++ b/packages/astro/src/cli/dev/logs.ts @@ -23,7 +23,7 @@ export async function logs({ flags, logger }: { flags: Flags; logger: AstroLogge if (!existing.background) { logger.error( null, - 'The running dev server was not started with --background. View logs in the terminal where it was started.', + 'The running dev server was not started with `astro dev background`. View logs in the terminal where it was started.', ); process.exitCode = 1; return; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 76252f5354b9..2108ef2a76b8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -551,8 +551,8 @@ importers: specifier: ^5.3.0 version: 5.3.0(rollup@4.59.1) am-i-vibing: - specifier: ^0.1.1 - version: 0.1.1 + specifier: ^0.3.0 + version: 0.3.0 aria-query: specifier: ^5.3.2 version: 5.3.2 @@ -11235,8 +11235,8 @@ packages: alpinejs@3.15.8: resolution: {integrity: sha512-zxIfCRTBGvF1CCLIOMQOxAyBuqibxSEwS6Jm1a3HGA9rgrJVcjEWlwLcQTVGAWGS8YhAsTRLVrtQ5a5QT9bSSQ==} - am-i-vibing@0.1.1: - resolution: {integrity: sha512-6a7UWRIoCjdoGGG2rjlSJjOoJKxns78pl+x15Bic0KflMjoVnRGzzwQsrPDHE/mur8O2OuU5KV/YgF2ZevuW1g==} + am-i-vibing@0.3.0: + resolution: {integrity: sha512-JTg9e3qPmVP6x8PG9/J4+eCVP6sGx9oS1pSbgf7fpPVlkHzQdRPSNMb6dBlp9BrdBNxDkctMzehPVYSnt16k4w==} hasBin: true ansi-colors@4.1.3: @@ -21748,7 +21748,7 @@ snapshots: dependencies: '@vue/reactivity': 3.1.5 - am-i-vibing@0.1.1: + am-i-vibing@0.3.0: dependencies: process-ancestry: 0.1.0 From 6a7995cd8d90a55d844a860c2f123e3037ef4fe9 Mon Sep 17 00:00:00 2001 From: Matthew Phillips Date: Mon, 11 May 2026 17:10:57 -0400 Subject: [PATCH 09/25] Add --follow (-f) flag to astro dev logs Streams new log output as it's written, similar to tail -f. Automatically exits when the server process dies. --- packages/astro/src/cli/dev/index.ts | 2 +- packages/astro/src/cli/dev/logs.ts | 58 +++++++++++++++++++++++++++-- 2 files changed, 55 insertions(+), 5 deletions(-) diff --git a/packages/astro/src/cli/dev/index.ts b/packages/astro/src/cli/dev/index.ts index f5e4ff151185..0518e882e3ea 100644 --- a/packages/astro/src/cli/dev/index.ts +++ b/packages/astro/src/cli/dev/index.ts @@ -34,7 +34,7 @@ export async function dev({ flags }: DevOptions) { ['background', 'Start the dev server as a background process.'], ['stop', 'Stop a running background dev server.'], ['status', 'Check if a dev server is running.'], - ['logs', 'View logs from a background dev server.'], + ['logs [--follow]', 'View logs from a background dev server.'], ], Flags: [ ['--mode', `Specify the mode of the project. Defaults to "development".`], diff --git a/packages/astro/src/cli/dev/logs.ts b/packages/astro/src/cli/dev/logs.ts index 92c2bfe46ad0..36520c1833e3 100644 --- a/packages/astro/src/cli/dev/logs.ts +++ b/packages/astro/src/cli/dev/logs.ts @@ -1,9 +1,9 @@ -import { readFileSync, existsSync } from 'node:fs'; +import { readFileSync, existsSync, statSync, createReadStream, watch } from 'node:fs'; import { resolve } from 'node:path'; import { fileURLToPath, pathToFileURL } from 'node:url'; import type { AstroLogger } from '../../core/logger/core.js'; import type { Flags } from '../flags.js'; -import { checkExistingServer, getLogFileURL } from '../../core/dev/lockfile.js'; +import { checkExistingServer, getLogFileURL, isProcessAlive } from '../../core/dev/lockfile.js'; function resolveRootURL(flags: Flags): URL { const rootPath = typeof flags.root === 'string' ? resolve(flags.root) : process.cwd(); @@ -37,6 +37,56 @@ export async function logs({ flags, logger }: { flags: Flags; logger: AstroLogge return; } - const content = readFileSync(logFilePath, 'utf-8'); - process.stdout.write(content); + const follow = flags.follow || flags.f; + + if (!follow) { + const content = readFileSync(logFilePath, 'utf-8'); + process.stdout.write(content); + return; + } + + // --follow mode: print existing content, then watch for new data + let offset = statSync(logFilePath).size; + + // Print existing content + if (offset > 0) { + const content = readFileSync(logFilePath, 'utf-8'); + process.stdout.write(content); + } + + // Watch the file for changes and stream new bytes + const watcher = watch(logFilePath, () => { + const currentSize = statSync(logFilePath).size; + if (currentSize > offset) { + const stream = createReadStream(logFilePath, { start: offset, encoding: 'utf-8' }); + stream.on('data', (chunk) => process.stdout.write(chunk)); + stream.on('end', () => { + offset = currentSize; + }); + } + }); + + // Periodically check if the server process is still alive + const aliveCheck = setInterval(() => { + if (!isProcessAlive(existing.pid)) { + // Read any final bytes + const currentSize = statSync(logFilePath).size; + if (currentSize > offset) { + const remaining = readFileSync(logFilePath, { encoding: 'utf-8' }).slice(offset); + process.stdout.write(remaining); + } + watcher.close(); + clearInterval(aliveCheck); + } + }, 1000); + + // Clean up on Ctrl+C + process.on('SIGINT', () => { + watcher.close(); + clearInterval(aliveCheck); + process.exit(0); + }); + + // Keep the process alive + await new Promise(() => {}); } From aae0717ca1976620bac72dd1597c81d206483139 Mon Sep 17 00:00:00 2001 From: Matthew Phillips Date: Mon, 11 May 2026 17:13:00 -0400 Subject: [PATCH 10/25] SIGKILL fallback after SIGTERM timeout in force-kill and stop If a dev server process doesn't exit within 5s of SIGTERM, escalate to SIGKILL to guarantee it's dead before proceeding. --- packages/astro/src/cli/dev/background.ts | 13 +++++++++++-- packages/astro/src/cli/dev/stop.ts | 18 +++++++++++------- packages/astro/src/core/dev/lockfile.ts | 3 +++ 3 files changed, 25 insertions(+), 9 deletions(-) diff --git a/packages/astro/src/cli/dev/background.ts b/packages/astro/src/cli/dev/background.ts index ab9ee4823357..7e42c70aae24 100644 --- a/packages/astro/src/cli/dev/background.ts +++ b/packages/astro/src/cli/dev/background.ts @@ -10,6 +10,7 @@ import { readLockFile, removeLockFile, isProcessAlive, + GRACEFUL_SHUTDOWN_TIMEOUT, } from '../../core/dev/lockfile.js'; export interface BackgroundResult { @@ -55,12 +56,20 @@ export async function background({ } catch { // Already dead } - // Wait for it to die - const deadline = Date.now() + 5000; + // Wait for graceful shutdown before escalating to SIGKILL + const deadline = Date.now() + GRACEFUL_SHUTDOWN_TIMEOUT; while (Date.now() < deadline) { if (!isProcessAlive(existing.pid)) break; await new Promise((r) => setTimeout(r, 100)); } + // If still alive after timeout, force kill + if (isProcessAlive(existing.pid)) { + try { + process.kill(existing.pid, 'SIGKILL'); + } catch { + // Already dead + } + } removeLockFile(root); } diff --git a/packages/astro/src/cli/dev/stop.ts b/packages/astro/src/cli/dev/stop.ts index fc45d045c673..30a558d86e05 100644 --- a/packages/astro/src/cli/dev/stop.ts +++ b/packages/astro/src/cli/dev/stop.ts @@ -2,7 +2,7 @@ import { resolve } from 'node:path'; import { pathToFileURL } from 'node:url'; import type { AstroLogger } from '../../core/logger/core.js'; import type { Flags } from '../flags.js'; -import { checkExistingServer, removeLockFile } from '../../core/dev/lockfile.js'; +import { checkExistingServer, removeLockFile, isProcessAlive, GRACEFUL_SHUTDOWN_TIMEOUT } from '../../core/dev/lockfile.js'; export interface StopResult { stopped: boolean; @@ -34,16 +34,20 @@ export async function stop({ flags, logger }: { flags: Flags; logger: AstroLogge // Process may have already exited between check and kill } - // Wait briefly for the process to exit and clean up - const deadline = Date.now() + 5000; + // Wait for graceful shutdown before escalating to SIGKILL + const deadline = Date.now() + GRACEFUL_SHUTDOWN_TIMEOUT; while (Date.now() < deadline) { + if (!isProcessAlive(existing.pid)) break; + await new Promise((r) => setTimeout(r, 100)); + } + + // If still alive after timeout, force kill + if (isProcessAlive(existing.pid)) { try { - process.kill(existing.pid, 0); + process.kill(existing.pid, 'SIGKILL'); } catch { - // Process is gone - break; + // Already dead } - await new Promise((r) => setTimeout(r, 100)); } // Clean up the lock file in case the process didn't remove it diff --git a/packages/astro/src/core/dev/lockfile.ts b/packages/astro/src/core/dev/lockfile.ts index f78b0bac3ada..f3bb5e5d8d68 100644 --- a/packages/astro/src/core/dev/lockfile.ts +++ b/packages/astro/src/core/dev/lockfile.ts @@ -1,6 +1,9 @@ import { existsSync, readFileSync, unlinkSync, writeFileSync, mkdirSync } from 'node:fs'; import { fileURLToPath } from 'node:url'; +/** Maximum time (ms) to wait for a process to exit after SIGTERM before escalating to SIGKILL. */ +export const GRACEFUL_SHUTDOWN_TIMEOUT = 5000; + export interface LockFileData { pid: number; port: number; From 665cbaebd19330f095da26ea8a2527fd6318aad6 Mon Sep 17 00:00:00 2001 From: Matthew Phillips Date: Mon, 11 May 2026 17:28:31 -0400 Subject: [PATCH 11/25] Report AI agent info in CLI session telemetry Calls detectAgenticEnvironment() from am-i-vibing in eventCliSession() so all CLI commands (dev, build, preview, sync, add) report agent id, name, and type when run by an AI coding agent. --- packages/astro/src/events/session.ts | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/packages/astro/src/events/session.ts b/packages/astro/src/events/session.ts index 138c027536ad..b3a0badab522 100644 --- a/packages/astro/src/events/session.ts +++ b/packages/astro/src/events/session.ts @@ -1,3 +1,4 @@ +import { detectAgenticEnvironment } from 'am-i-vibing'; import { AstroConfigSchema } from '../core/config/schemas/index.js'; import type { AstroUserConfig } from '../types/public/config.js'; import type { AstroIntegration } from '../types/public/integrations.js'; @@ -10,6 +11,14 @@ interface EventPayload { configKeys?: string[]; flags?: string[]; optionalIntegrations?: number; + /** Whether the CLI session was run by an AI coding agent. */ + isAgentic?: boolean; + /** ID of the detected agent, e.g. "cursor-agent", "claude-code". */ + agentId?: string; + /** Display name of the detected agent, e.g. "Cursor Agent", "Claude Code". */ + agentName?: string; + /** Type of agentic environment: "agent", "interactive", or "hybrid". */ + agentType?: string; } type ConfigInfoValue = string | boolean | string[] | undefined; @@ -137,5 +146,19 @@ export function eventCliSession( config: createAnonymousConfigInfo(userConfig), flags: cliFlags, }; + + // Detect AI coding agent environment + try { + const agentInfo = detectAgenticEnvironment(); + if (agentInfo.isAgentic) { + payload.isAgentic = true; + if (agentInfo.id) payload.agentId = agentInfo.id; + if (agentInfo.name) payload.agentName = agentInfo.name; + if (agentInfo.type) payload.agentType = agentInfo.type; + } + } catch { + // Detection failure should never block telemetry + } + return [{ eventName: EVENT_SESSION, payload }]; } From b3fdbd6c20bd529595a8f45c4da388ce1b7dc82b Mon Sep 17 00:00:00 2001 From: Matthew Phillips Date: Mon, 11 May 2026 17:43:51 -0400 Subject: [PATCH 12/25] Update changeset to major with expanded description --- .changeset/experimental-background-dev.md | 25 ++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/.changeset/experimental-background-dev.md b/.changeset/experimental-background-dev.md index c9c8cb7bb1b7..197a676aab48 100644 --- a/.changeset/experimental-background-dev.md +++ b/.changeset/experimental-background-dev.md @@ -1,7 +1,26 @@ --- -'astro': minor +'astro': major --- -Adds experimental background dev server management for AI coding agents +Adds background dev server management for AI coding agents. -Adds `--experimental-background`, `--experimental-stop`, `--experimental-status`, and `--experimental-logs` flags to `astro dev`. Also adds a `/_astro/status` health endpoint and a dev server lock file (`.astro/dev.json`) to prevent duplicate servers. When an AI coding agent is detected via `am-i-vibing`, background mode is enabled automatically. +When an AI coding agent is detected (via the [`am-i-vibing`](https://github.com/ascorbic/am-i-vibing) library), `astro dev` now automatically starts the dev server as a detached background process. This prevents the dev server from blocking the agent's terminal and allows it to continue working while the server runs. + +A lock file (`.astro/dev.json`) is written when the dev server starts, recording the server's URL, port, and PID. This prevents duplicate servers from being started for the same project. + +#### New subcommands + +The following subcommands have been added to `astro dev`: + +- `astro dev background` — Start the dev server as a background process (this is what runs automatically when an agent is detected). +- `astro dev stop` — Stop a running background dev server. +- `astro dev status` — Check if a dev server is running and display its URL, PID, and uptime. +- `astro dev logs` — View logs from a background dev server. Use `--follow` (`-f`) to stream new output as it's written. + +These subcommands are also available for human use, but are primarily designed for AI coding agents that start and manage dev servers programmatically. + +#### What should I do? + +No action is required. If you are not using an AI coding agent, `astro dev` behaves exactly as before. If you are using an agent, background mode is enabled automatically — the agent will receive a JSON response with the server URL and PID, and can use `astro dev stop` to shut it down. + +To opt out of automatic background mode when an agent is detected, set the environment variable `ASTRO_DEV_BACKGROUND=0` before running `astro dev`. From 2d1cbe58bbb298c6fadff002257d0d254f76b73d Mon Sep 17 00:00:00 2001 From: Matthew Phillips Date: Mon, 11 May 2026 17:49:50 -0400 Subject: [PATCH 13/25] Bust turbo cache for CI build From a6af8eab4ae6dabe68f276fb4e9a7686a7276224 Mon Sep 17 00:00:00 2001 From: Matthew Phillips Date: Wed, 27 May 2026 10:20:57 -0400 Subject: [PATCH 14/25] Auto-enable JSON logger when AI agent is detected --- packages/astro/src/cli/dev/background.ts | 1 + packages/astro/src/cli/dev/index.ts | 8 +++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/astro/src/cli/dev/background.ts b/packages/astro/src/cli/dev/background.ts index 7e42c70aae24..b4d0b2f08a7d 100644 --- a/packages/astro/src/cli/dev/background.ts +++ b/packages/astro/src/cli/dev/background.ts @@ -86,6 +86,7 @@ export async function background({ if (flags.config) args.push('--config', String(flags.config)); if (flags.root) args.push('--root', String(flags.root)); if (flags.allowedHosts) args.push('--allowed-hosts', String(flags.allowedHosts)); + if (flags.experimentalJson) args.push('--experimental-json'); // Open the log file for writing, ensuring the .astro directory exists const logFileURL = getLogFileURL(root); diff --git a/packages/astro/src/cli/dev/index.ts b/packages/astro/src/cli/dev/index.ts index 0518e882e3ea..7b3309887435 100644 --- a/packages/astro/src/cli/dev/index.ts +++ b/packages/astro/src/cli/dev/index.ts @@ -57,6 +57,12 @@ export async function dev({ flags }: DevOptions) { return; } + // When an AI coding agent is detected, enable background mode and JSON logging automatically. + const agentDetected = !process.env.ASTRO_DEV_BACKGROUND && isRunByAgent(); + if (agentDetected) { + flags.experimentalJson = true; + } + const logger = createLoggerFromFlags(flags); const subcommand = flags._[3]?.toString(); @@ -84,7 +90,7 @@ export async function dev({ flags }: DevOptions) { // Handle `astro dev background` or auto-enable when an AI coding agent is detected. // Skip if ASTRO_DEV_BACKGROUND is set — this means we're the spawned child process // and should run the foreground dev server, not recurse into background mode. - if (subcommand === 'background' || (isRunByAgent() && !process.env.ASTRO_DEV_BACKGROUND)) { + if (subcommand === 'background' || agentDetected) { const { background } = await import('./background.js'); await background({ flags, logger }); return; From d677b439126e3e1ed490f367f89a5c29a1ebb9ff Mon Sep 17 00:00:00 2001 From: Matthew Phillips Date: Wed, 27 May 2026 10:39:51 -0400 Subject: [PATCH 15/25] Update .changeset/experimental-background-dev.md --- .changeset/experimental-background-dev.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/experimental-background-dev.md b/.changeset/experimental-background-dev.md index 197a676aab48..38bfce38f719 100644 --- a/.changeset/experimental-background-dev.md +++ b/.changeset/experimental-background-dev.md @@ -4,7 +4,7 @@ Adds background dev server management for AI coding agents. -When an AI coding agent is detected (via the [`am-i-vibing`](https://github.com/ascorbic/am-i-vibing) library), `astro dev` now automatically starts the dev server as a detached background process. This prevents the dev server from blocking the agent's terminal and allows it to continue working while the server runs. +When an AI coding agent is detected, `astro dev` now automatically starts the dev server as a detached background process. This prevents the dev server from blocking the agent's terminal and allows it to continue working while the server runs. A lock file (`.astro/dev.json`) is written when the dev server starts, recording the server's URL, port, and PID. This prevents duplicate servers from being started for the same project. From 103299f2753065157a2cae83728b813782b87923 Mon Sep 17 00:00:00 2001 From: Matthew Phillips Date: Wed, 27 May 2026 13:03:51 -0400 Subject: [PATCH 16/25] Improve lockfile error handling and clarify isProcessAlive --- packages/astro/src/core/dev/lockfile.ts | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/packages/astro/src/core/dev/lockfile.ts b/packages/astro/src/core/dev/lockfile.ts index f3bb5e5d8d68..718460ef287a 100644 --- a/packages/astro/src/core/dev/lockfile.ts +++ b/packages/astro/src/core/dev/lockfile.ts @@ -62,6 +62,7 @@ export function serializeLockFile(data: LockFileData): string { /** * Check if a process with the given PID is alive. + * Signal 0 does not kill the process — it only checks whether the process exists. */ export function isProcessAlive(pid: number): boolean { try { @@ -91,10 +92,15 @@ export function readLockFile(root: URL): LockFileData | null { export function writeLockFile(root: URL, data: LockFileData): void { const lockFileURL = getLockFileURL(root); const dirPath = fileURLToPath(new URL('.astro/', root)); - if (!existsSync(dirPath)) { - mkdirSync(dirPath, { recursive: true }); + try { + if (!existsSync(dirPath)) { + mkdirSync(dirPath, { recursive: true }); + } + writeFileSync(lockFileURL, serializeLockFile(data), 'utf-8'); + } catch (err) { + const message = err instanceof Error ? err.message : String(err); + throw new Error(`Failed to write lock file: ${message}`); } - writeFileSync(lockFileURL, serializeLockFile(data), 'utf-8'); } /** @@ -104,8 +110,12 @@ export function removeLockFile(root: URL): void { const lockFileURL = getLockFileURL(root); try { unlinkSync(lockFileURL); - } catch { - // File doesn't exist, that's fine + } catch (err: any) { + // ENOENT means the file doesn't exist, which is fine. + // Any other error (permissions, etc.) should be surfaced. + if (err?.code !== 'ENOENT') { + throw err; + } } } From f24f670a9e44611679c4279abab30c042097ddcc Mon Sep 17 00:00:00 2001 From: Matthew Phillips Date: Wed, 27 May 2026 13:26:50 -0400 Subject: [PATCH 17/25] Deduplicate resolveRootURL into lockfile module --- packages/astro/src/cli/dev/background.ts | 10 +++------- packages/astro/src/cli/dev/index.ts | 11 ++--------- packages/astro/src/cli/dev/logs.ts | 12 +++--------- packages/astro/src/cli/dev/stop.ts | 11 ++--------- packages/astro/src/core/dev/lockfile.ts | 11 ++++++++++- 5 files changed, 20 insertions(+), 35 deletions(-) diff --git a/packages/astro/src/cli/dev/background.ts b/packages/astro/src/cli/dev/background.ts index b4d0b2f08a7d..bc7b5ae941b6 100644 --- a/packages/astro/src/cli/dev/background.ts +++ b/packages/astro/src/cli/dev/background.ts @@ -1,7 +1,7 @@ import { spawn } from 'node:child_process'; import { existsSync, mkdirSync, openSync } from 'node:fs'; import { resolve } from 'node:path'; -import { fileURLToPath, pathToFileURL } from 'node:url'; +import { fileURLToPath } from 'node:url'; import type { AstroLogger } from '../../core/logger/core.js'; import type { Flags } from '../flags.js'; import { @@ -9,6 +9,7 @@ import { getLogFileURL, readLockFile, removeLockFile, + resolveRootURL, isProcessAlive, GRACEFUL_SHUTDOWN_TIMEOUT, } from '../../core/dev/lockfile.js'; @@ -28,16 +29,11 @@ export function formatBackgroundOutput(result: BackgroundResult | BackgroundErro return JSON.stringify(result); } -function resolveRootURL(flags: Flags): URL { - const rootPath = typeof flags.root === 'string' ? resolve(flags.root) : process.cwd(); - return pathToFileURL(rootPath + '/'); -} - export async function background({ flags, logger, }: { flags: Flags; logger: AstroLogger }): Promise { - const root = resolveRootURL(flags); + const root = resolveRootURL(flags.root); // Check for existing server const existing = checkExistingServer(root); diff --git a/packages/astro/src/cli/dev/index.ts b/packages/astro/src/cli/dev/index.ts index 7b3309887435..a0c2d0504f48 100644 --- a/packages/astro/src/cli/dev/index.ts +++ b/packages/astro/src/cli/dev/index.ts @@ -1,9 +1,7 @@ -import { resolve } from 'node:path'; -import { pathToFileURL } from 'node:url'; import { isAgent } from 'am-i-vibing'; import colors from 'piccolore'; import devServer from '../../core/dev/index.js'; -import { checkExistingServer, removeLockFile, writeLockFile } from '../../core/dev/lockfile.js'; +import { checkExistingServer, removeLockFile, resolveRootURL, writeLockFile } from '../../core/dev/lockfile.js'; import { printHelp } from '../../core/messages/runtime.js'; import { type Flags, createLoggerFromFlags, flagsToAstroInlineConfig } from '../flags.js'; @@ -19,11 +17,6 @@ function isRunByAgent(): boolean { } } -function resolveRootURL(flags: Flags): URL { - const rootPath = typeof flags.root === 'string' ? resolve(flags.root) : process.cwd(); - return pathToFileURL(rootPath + '/'); -} - export async function dev({ flags }: DevOptions) { if (flags.help || flags.h) { printHelp({ @@ -97,7 +90,7 @@ export async function dev({ flags }: DevOptions) { } // Foreground dev server: check lock file, start server, write lock file - const root = resolveRootURL(flags); + const root = resolveRootURL(flags.root); const existingServer = checkExistingServer(root); if (existingServer) { const message = [ diff --git a/packages/astro/src/cli/dev/logs.ts b/packages/astro/src/cli/dev/logs.ts index 36520c1833e3..76b7e6502167 100644 --- a/packages/astro/src/cli/dev/logs.ts +++ b/packages/astro/src/cli/dev/logs.ts @@ -1,17 +1,11 @@ import { readFileSync, existsSync, statSync, createReadStream, watch } from 'node:fs'; -import { resolve } from 'node:path'; -import { fileURLToPath, pathToFileURL } from 'node:url'; +import { fileURLToPath } from 'node:url'; import type { AstroLogger } from '../../core/logger/core.js'; import type { Flags } from '../flags.js'; -import { checkExistingServer, getLogFileURL, isProcessAlive } from '../../core/dev/lockfile.js'; - -function resolveRootURL(flags: Flags): URL { - const rootPath = typeof flags.root === 'string' ? resolve(flags.root) : process.cwd(); - return pathToFileURL(rootPath + '/'); -} +import { checkExistingServer, getLogFileURL, resolveRootURL, isProcessAlive } from '../../core/dev/lockfile.js'; export async function logs({ flags, logger }: { flags: Flags; logger: AstroLogger }): Promise { - const root = resolveRootURL(flags); + const root = resolveRootURL(flags.root); const existing = checkExistingServer(root); if (!existing) { diff --git a/packages/astro/src/cli/dev/stop.ts b/packages/astro/src/cli/dev/stop.ts index 30a558d86e05..77f22e6d5030 100644 --- a/packages/astro/src/cli/dev/stop.ts +++ b/packages/astro/src/cli/dev/stop.ts @@ -1,8 +1,6 @@ -import { resolve } from 'node:path'; -import { pathToFileURL } from 'node:url'; import type { AstroLogger } from '../../core/logger/core.js'; import type { Flags } from '../flags.js'; -import { checkExistingServer, removeLockFile, isProcessAlive, GRACEFUL_SHUTDOWN_TIMEOUT } from '../../core/dev/lockfile.js'; +import { checkExistingServer, removeLockFile, resolveRootURL, isProcessAlive, GRACEFUL_SHUTDOWN_TIMEOUT } from '../../core/dev/lockfile.js'; export interface StopResult { stopped: boolean; @@ -14,13 +12,8 @@ export function formatStopOutput(result: StopResult): string { return JSON.stringify(result); } -function resolveRootURL(flags: Flags): URL { - const rootPath = typeof flags.root === 'string' ? resolve(flags.root) : process.cwd(); - return pathToFileURL(rootPath + '/'); -} - export async function stop({ flags, logger }: { flags: Flags; logger: AstroLogger }): Promise { - const root = resolveRootURL(flags); + const root = resolveRootURL(flags.root); const existing = checkExistingServer(root); if (!existing) { diff --git a/packages/astro/src/core/dev/lockfile.ts b/packages/astro/src/core/dev/lockfile.ts index 718460ef287a..430c7f10cd30 100644 --- a/packages/astro/src/core/dev/lockfile.ts +++ b/packages/astro/src/core/dev/lockfile.ts @@ -1,5 +1,14 @@ import { existsSync, readFileSync, unlinkSync, writeFileSync, mkdirSync } from 'node:fs'; -import { fileURLToPath } from 'node:url'; +import { resolve } from 'node:path'; +import { fileURLToPath, pathToFileURL } from 'node:url'; + +/** + * Resolve the project root as a URL from an optional root flag. + */ +export function resolveRootURL(root?: string): URL { + const rootPath = typeof root === 'string' ? resolve(root) : process.cwd(); + return pathToFileURL(rootPath + '/'); +} /** Maximum time (ms) to wait for a process to exit after SIGTERM before escalating to SIGKILL. */ export const GRACEFUL_SHUTDOWN_TIMEOUT = 5000; From 9646f4e235d63be32ac615411d770b7b1d070692 Mon Sep 17 00:00:00 2001 From: Matthew Phillips Date: Wed, 27 May 2026 13:32:46 -0400 Subject: [PATCH 18/25] Use SKIP_FORMAT logger label instead of null --- packages/astro/src/cli/dev/background.ts | 10 +++++----- packages/astro/src/cli/dev/logs.ts | 6 +++--- packages/astro/src/cli/dev/status.ts | 15 ++++----------- packages/astro/src/cli/dev/stop.ts | 4 ++-- 4 files changed, 14 insertions(+), 21 deletions(-) diff --git a/packages/astro/src/cli/dev/background.ts b/packages/astro/src/cli/dev/background.ts index bc7b5ae941b6..a304036d56b8 100644 --- a/packages/astro/src/cli/dev/background.ts +++ b/packages/astro/src/cli/dev/background.ts @@ -38,7 +38,7 @@ export async function background({ // Check for existing server const existing = checkExistingServer(root); if (existing && !flags.force) { - logger.info(null, `Dev server already running at ${existing.url} (pid ${existing.pid})\n` + + logger.info('SKIP_FORMAT', `Dev server already running at ${existing.url} (pid ${existing.pid})\n` + ' Stop: astro dev stop\n' + ' Status: astro dev status\n' + ' Logs: astro dev logs'); @@ -109,7 +109,7 @@ export async function background({ const childPid = child.pid; if (!childPid) { - logger.error(null, 'Failed to spawn background dev server process.'); + logger.error('SKIP_FORMAT', 'Failed to spawn background dev server process.'); process.exitCode = 1; return; } @@ -121,7 +121,7 @@ export async function background({ while (Date.now() < deadline) { // Check if child is still alive if (!isProcessAlive(childPid)) { - logger.error(null, 'Dev server process exited before becoming ready.'); + logger.error('SKIP_FORMAT', 'Dev server process exited before becoming ready.'); process.exitCode = 1; return; } @@ -129,7 +129,7 @@ export async function background({ // Check for the lock file (written by the child's dev server) const lockData = readLockFile(root); if (lockData && lockData.pid === childPid) { - logger.info(null, `Dev server running at ${lockData.url} (pid ${lockData.pid})\n` + + logger.info('SKIP_FORMAT', `Dev server running at ${lockData.url} (pid ${lockData.pid})\n` + ' Stop: astro dev stop\n' + ' Status: astro dev status\n' + ' Logs: astro dev logs'); @@ -147,6 +147,6 @@ export async function background({ } removeLockFile(root); - logger.error(null, `Dev server failed to start within ${timeout / 1000}s.`); + logger.error('SKIP_FORMAT', `Dev server failed to start within ${timeout / 1000}s.`); process.exitCode = 1; } diff --git a/packages/astro/src/cli/dev/logs.ts b/packages/astro/src/cli/dev/logs.ts index 76b7e6502167..50958b1cc1a6 100644 --- a/packages/astro/src/cli/dev/logs.ts +++ b/packages/astro/src/cli/dev/logs.ts @@ -9,14 +9,14 @@ export async function logs({ flags, logger }: { flags: Flags; logger: AstroLogge const existing = checkExistingServer(root); if (!existing) { - logger.error(null, 'No dev server is running.'); + logger.error('SKIP_FORMAT', 'No dev server is running.'); process.exitCode = 1; return; } if (!existing.background) { logger.error( - null, + 'SKIP_FORMAT', 'The running dev server was not started with `astro dev background`. View logs in the terminal where it was started.', ); process.exitCode = 1; @@ -26,7 +26,7 @@ export async function logs({ flags, logger }: { flags: Flags; logger: AstroLogge const logFileURL = getLogFileURL(root); const logFilePath = fileURLToPath(logFileURL); if (!existsSync(logFilePath)) { - logger.error(null, 'No log file found.'); + logger.error('SKIP_FORMAT', 'No log file found.'); process.exitCode = 1; return; } diff --git a/packages/astro/src/cli/dev/status.ts b/packages/astro/src/cli/dev/status.ts index 3d9d3f242503..00b76265669b 100644 --- a/packages/astro/src/cli/dev/status.ts +++ b/packages/astro/src/cli/dev/status.ts @@ -1,8 +1,6 @@ -import { resolve } from 'node:path'; -import { pathToFileURL } from 'node:url'; import type { AstroLogger } from '../../core/logger/core.js'; import type { Flags } from '../flags.js'; -import { checkExistingServer } from '../../core/dev/lockfile.js'; +import { checkExistingServer, resolveRootURL } from '../../core/dev/lockfile.js'; export interface StatusResult { running: boolean; @@ -17,20 +15,15 @@ export function formatStatusOutput(result: StatusResult): string { return JSON.stringify(result); } -function resolveRootURL(flags: Flags): URL { - const rootPath = typeof flags.root === 'string' ? resolve(flags.root) : process.cwd(); - return pathToFileURL(rootPath + '/'); -} - export async function status({ flags, logger, }: { flags: Flags; logger: AstroLogger }): Promise { - const root = resolveRootURL(flags); + const root = resolveRootURL(flags.root); const existing = checkExistingServer(root); if (!existing) { - logger.info(null, 'No dev server is running.'); + logger.info('SKIP_FORMAT', 'No dev server is running.'); return; } @@ -38,7 +31,7 @@ export async function status({ const uptime = Math.floor((Date.now() - startedAt) / 1000); logger.info( - null, + 'SKIP_FORMAT', `Dev server running at ${existing.url} (pid ${existing.pid}, uptime ${uptime}s${existing.background ? ', background' : ''})`, ); } diff --git a/packages/astro/src/cli/dev/stop.ts b/packages/astro/src/cli/dev/stop.ts index 77f22e6d5030..ea097b97e48f 100644 --- a/packages/astro/src/cli/dev/stop.ts +++ b/packages/astro/src/cli/dev/stop.ts @@ -17,7 +17,7 @@ export async function stop({ flags, logger }: { flags: Flags; logger: AstroLogge const existing = checkExistingServer(root); if (!existing) { - logger.info(null, 'No dev server is running.'); + logger.info('SKIP_FORMAT', 'No dev server is running.'); return; } @@ -46,5 +46,5 @@ export async function stop({ flags, logger }: { flags: Flags; logger: AstroLogge // Clean up the lock file in case the process didn't remove it removeLockFile(root); - logger.info(null, `Stopped dev server (pid ${existing.pid}).`); + logger.info('SKIP_FORMAT', `Stopped dev server (pid ${existing.pid}).`); } From bfee51d1a4cabe2c52ce5f78b409c719ced4b891 Mon Sep 17 00:00:00 2001 From: Matthew Phillips Date: Wed, 27 May 2026 13:55:04 -0400 Subject: [PATCH 19/25] Handle SIGTERM in logs --follow cleanup --- packages/astro/src/cli/dev/logs.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/astro/src/cli/dev/logs.ts b/packages/astro/src/cli/dev/logs.ts index 50958b1cc1a6..bc74f759b0b7 100644 --- a/packages/astro/src/cli/dev/logs.ts +++ b/packages/astro/src/cli/dev/logs.ts @@ -74,12 +74,14 @@ export async function logs({ flags, logger }: { flags: Flags; logger: AstroLogge } }, 1000); - // Clean up on Ctrl+C - process.on('SIGINT', () => { + // Clean up on termination signals + const cleanup = () => { watcher.close(); clearInterval(aliveCheck); process.exit(0); - }); + }; + process.on('SIGINT', cleanup); + process.on('SIGTERM', cleanup); // Keep the process alive await new Promise(() => {}); From d6a37f7dfd643ce0d7115bdd43339010e89a90a2 Mon Sep 17 00:00:00 2001 From: Matthew Phillips Date: Wed, 27 May 2026 13:56:22 -0400 Subject: [PATCH 20/25] Use process.exit() consistently for error exits --- packages/astro/src/cli/dev/background.ts | 8 +++----- packages/astro/src/cli/dev/logs.ts | 9 +++------ 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/packages/astro/src/cli/dev/background.ts b/packages/astro/src/cli/dev/background.ts index a304036d56b8..c44ca9699043 100644 --- a/packages/astro/src/cli/dev/background.ts +++ b/packages/astro/src/cli/dev/background.ts @@ -110,8 +110,7 @@ export async function background({ const childPid = child.pid; if (!childPid) { logger.error('SKIP_FORMAT', 'Failed to spawn background dev server process.'); - process.exitCode = 1; - return; + process.exit(1); } // Poll the lock file to detect when the server is ready @@ -122,8 +121,7 @@ export async function background({ // Check if child is still alive if (!isProcessAlive(childPid)) { logger.error('SKIP_FORMAT', 'Dev server process exited before becoming ready.'); - process.exitCode = 1; - return; + process.exit(1); } // Check for the lock file (written by the child's dev server) @@ -148,5 +146,5 @@ export async function background({ removeLockFile(root); logger.error('SKIP_FORMAT', `Dev server failed to start within ${timeout / 1000}s.`); - process.exitCode = 1; + process.exit(1); } diff --git a/packages/astro/src/cli/dev/logs.ts b/packages/astro/src/cli/dev/logs.ts index bc74f759b0b7..c111775b20e6 100644 --- a/packages/astro/src/cli/dev/logs.ts +++ b/packages/astro/src/cli/dev/logs.ts @@ -10,8 +10,7 @@ export async function logs({ flags, logger }: { flags: Flags; logger: AstroLogge if (!existing) { logger.error('SKIP_FORMAT', 'No dev server is running.'); - process.exitCode = 1; - return; + process.exit(1); } if (!existing.background) { @@ -19,16 +18,14 @@ export async function logs({ flags, logger }: { flags: Flags; logger: AstroLogge 'SKIP_FORMAT', 'The running dev server was not started with `astro dev background`. View logs in the terminal where it was started.', ); - process.exitCode = 1; - return; + process.exit(1); } const logFileURL = getLogFileURL(root); const logFilePath = fileURLToPath(logFileURL); if (!existsSync(logFilePath)) { logger.error('SKIP_FORMAT', 'No log file found.'); - process.exitCode = 1; - return; + process.exit(1); } const follow = flags.follow || flags.f; From 1e413e8a05d69e48e222e49c9a29a3de70526a4f Mon Sep 17 00:00:00 2001 From: Matthew Phillips Date: Wed, 27 May 2026 14:02:57 -0400 Subject: [PATCH 21/25] Use Vite resolvedUrls for lock file URL instead of hardcoded localhost --- packages/astro/src/cli/dev/index.ts | 4 ++-- packages/astro/src/core/dev/dev.ts | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/astro/src/cli/dev/index.ts b/packages/astro/src/cli/dev/index.ts index a0c2d0504f48..89233958896c 100644 --- a/packages/astro/src/cli/dev/index.ts +++ b/packages/astro/src/cli/dev/index.ts @@ -107,8 +107,8 @@ export async function dev({ flags }: DevOptions) { const inlineConfig = flagsToAstroInlineConfig(flags); const server = await devServer(inlineConfig); - // Write the lock file now that we know the bound port - const serverUrl = `http://localhost:${server.address.port}`; + // Use Vite's resolved local URL which accounts for host and protocol (http/https). + const serverUrl = new URL(server.resolvedUrls.local[0]).origin; writeLockFile(root, { pid: process.pid, port: server.address.port, diff --git a/packages/astro/src/core/dev/dev.ts b/packages/astro/src/core/dev/dev.ts index 57d773039c15..8bc91004422b 100644 --- a/packages/astro/src/core/dev/dev.ts +++ b/packages/astro/src/core/dev/dev.ts @@ -27,6 +27,7 @@ import { piccoloreTextStyler } from '../../cli/infra/piccolore-text-styler.js'; export interface DevServer { address: AddressInfo; + resolvedUrls: vite.ResolvedServerUrls; handle: (req: http.IncomingMessage, res: http.ServerResponse) => void; watcher: vite.FSWatcher; stop(): Promise; @@ -145,6 +146,9 @@ export default async function dev(inlineConfig: AstroInlineConfig): Promise Date: Wed, 27 May 2026 14:23:39 -0400 Subject: [PATCH 22/25] Error on unknown dev subcommand instead of falling through --- packages/astro/src/cli/dev/index.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/astro/src/cli/dev/index.ts b/packages/astro/src/cli/dev/index.ts index 89233958896c..07e0b133aadd 100644 --- a/packages/astro/src/cli/dev/index.ts +++ b/packages/astro/src/cli/dev/index.ts @@ -89,6 +89,12 @@ export async function dev({ flags }: DevOptions) { return; } + // Unknown subcommand — exit with an error before starting the server. + if (subcommand) { + logger.error('SKIP_FORMAT', `Unknown command: astro dev ${subcommand}\n\nRun \`astro dev --help\` to see available commands.`); + process.exit(1); + } + // Foreground dev server: check lock file, start server, write lock file const root = resolveRootURL(flags.root); const existingServer = checkExistingServer(root); From 8cc9665f0041a095edc684cefe709e3ddde78fb4 Mon Sep 17 00:00:00 2001 From: Matthew Phillips Date: Wed, 27 May 2026 14:39:54 -0400 Subject: [PATCH 23/25] Change `astro dev background` subcommand to `astro dev --background` flag --- .changeset/experimental-background-dev.md | 10 +++--- packages/astro/src/cli/dev/background.ts | 2 +- packages/astro/src/cli/dev/index.ts | 40 +++++++++++------------ packages/astro/src/cli/dev/logs.ts | 2 +- 4 files changed, 26 insertions(+), 28 deletions(-) diff --git a/.changeset/experimental-background-dev.md b/.changeset/experimental-background-dev.md index 38bfce38f719..260dea71ec4c 100644 --- a/.changeset/experimental-background-dev.md +++ b/.changeset/experimental-background-dev.md @@ -8,19 +8,17 @@ When an AI coding agent is detected, `astro dev` now automatically starts the de A lock file (`.astro/dev.json`) is written when the dev server starts, recording the server's URL, port, and PID. This prevents duplicate servers from being started for the same project. -#### New subcommands +#### New flag and subcommands -The following subcommands have been added to `astro dev`: - -- `astro dev background` — Start the dev server as a background process (this is what runs automatically when an agent is detected). +- `astro dev --background` — Start the dev server as a background process (this is what runs automatically when an agent is detected). - `astro dev stop` — Stop a running background dev server. - `astro dev status` — Check if a dev server is running and display its URL, PID, and uptime. - `astro dev logs` — View logs from a background dev server. Use `--follow` (`-f`) to stream new output as it's written. -These subcommands are also available for human use, but are primarily designed for AI coding agents that start and manage dev servers programmatically. +These are also available for human use, but are primarily designed for AI coding agents that start and manage dev servers programmatically. #### What should I do? -No action is required. If you are not using an AI coding agent, `astro dev` behaves exactly as before. If you are using an agent, background mode is enabled automatically — the agent will receive a JSON response with the server URL and PID, and can use `astro dev stop` to shut it down. +No action is required. If you are not using an AI coding agent, `astro dev` behaves exactly as before. If you are using an agent, background mode is enabled automatically — the agent will receive the server URL and PID, and can use `astro dev stop` to shut it down. To opt out of automatic background mode when an agent is detected, set the environment variable `ASTRO_DEV_BACKGROUND=0` before running `astro dev`. diff --git a/packages/astro/src/cli/dev/background.ts b/packages/astro/src/cli/dev/background.ts index c44ca9699043..3e83e95bb46a 100644 --- a/packages/astro/src/cli/dev/background.ts +++ b/packages/astro/src/cli/dev/background.ts @@ -69,7 +69,7 @@ export async function background({ removeLockFile(root); } - // Build the args for the child process: `astro dev` without --background + // Build the args for the child process: plain `astro dev` (no --background) const args: string[] = ['dev']; if (flags.port) args.push('--port', String(flags.port)); if (flags.host != null) { diff --git a/packages/astro/src/cli/dev/index.ts b/packages/astro/src/cli/dev/index.ts index 07e0b133aadd..5c0013efba8f 100644 --- a/packages/astro/src/cli/dev/index.ts +++ b/packages/astro/src/cli/dev/index.ts @@ -23,25 +23,25 @@ export async function dev({ flags }: DevOptions) { commandName: 'astro dev', usage: '[command] [...flags]', tables: { - Commands: [ - ['background', 'Start the dev server as a background process.'], - ['stop', 'Stop a running background dev server.'], - ['status', 'Check if a dev server is running.'], - ['logs [--follow]', 'View logs from a background dev server.'], - ], - Flags: [ - ['--mode', `Specify the mode of the project. Defaults to "development".`], - ['--port', `Specify which port to run on. Defaults to 4321.`], - ['--host', `Listen on all addresses, including LAN and public addresses.`], - ['--host ', `Expose on a network IP address at `], - ['--open', 'Automatically open the app in the browser on server start'], - ['--force', 'Clear the content layer cache, forcing a full rebuild.'], - [ - '--allowed-hosts', - 'Specify a comma-separated list of allowed hosts or allow any hostname.', - ], - ['--help (-h)', 'See all available flags.'], + Commands: [ + ['stop', 'Stop a running background dev server.'], + ['status', 'Check if a dev server is running.'], + ['logs [--follow]', 'View logs from a background dev server.'], + ], + Flags: [ + ['--background', 'Start the dev server as a background process.'], + ['--mode', `Specify the mode of the project. Defaults to "development".`], + ['--port', `Specify which port to run on. Defaults to 4321.`], + ['--host', `Listen on all addresses, including LAN and public addresses.`], + ['--host ', `Expose on a network IP address at `], + ['--open', 'Automatically open the app in the browser on server start'], + ['--force', 'Clear the content layer cache, forcing a full rebuild.'], + [ + '--allowed-hosts', + 'Specify a comma-separated list of allowed hosts or allow any hostname.', ], + ['--help (-h)', 'See all available flags.'], + ], }, description: `Check ${colors.cyan( 'https://docs.astro.build/en/reference/cli-reference/#astro-dev', @@ -80,10 +80,10 @@ export async function dev({ flags }: DevOptions) { return; } - // Handle `astro dev background` or auto-enable when an AI coding agent is detected. + // Handle `astro dev --background` or auto-enable when an AI coding agent is detected. // Skip if ASTRO_DEV_BACKGROUND is set — this means we're the spawned child process // and should run the foreground dev server, not recurse into background mode. - if (subcommand === 'background' || agentDetected) { + if (flags.background || agentDetected) { const { background } = await import('./background.js'); await background({ flags, logger }); return; diff --git a/packages/astro/src/cli/dev/logs.ts b/packages/astro/src/cli/dev/logs.ts index c111775b20e6..ae8bb325aba5 100644 --- a/packages/astro/src/cli/dev/logs.ts +++ b/packages/astro/src/cli/dev/logs.ts @@ -16,7 +16,7 @@ export async function logs({ flags, logger }: { flags: Flags; logger: AstroLogge if (!existing.background) { logger.error( 'SKIP_FORMAT', - 'The running dev server was not started with `astro dev background`. View logs in the terminal where it was started.', + 'The running dev server was not started with `astro dev --background`. View logs in the terminal where it was started.', ); process.exit(1); } From da92a8e7734c559f03fa8a4432049546d7e5e73d Mon Sep 17 00:00:00 2001 From: Matthew Phillips Date: Thu, 28 May 2026 09:26:28 -0400 Subject: [PATCH 24/25] Update .changeset/experimental-background-dev.md Co-authored-by: Armand Philippot --- .changeset/experimental-background-dev.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/experimental-background-dev.md b/.changeset/experimental-background-dev.md index 260dea71ec4c..bdceb0d99cbe 100644 --- a/.changeset/experimental-background-dev.md +++ b/.changeset/experimental-background-dev.md @@ -15,7 +15,7 @@ A lock file (`.astro/dev.json`) is written when the dev server starts, recording - `astro dev status` — Check if a dev server is running and display its URL, PID, and uptime. - `astro dev logs` — View logs from a background dev server. Use `--follow` (`-f`) to stream new output as it's written. -These are also available for human use, but are primarily designed for AI coding agents that start and manage dev servers programmatically. +These allow you to start and manage dev servers programmatically and were designed with AI coding agents in mind. #### What should I do? From 105a113d7b8ab9509f63cd4c0a9cbfedfa108307 Mon Sep 17 00:00:00 2001 From: Matthew Phillips Date: Fri, 29 May 2026 17:39:53 -0400 Subject: [PATCH 25/25] Remove resolveRootURL, reuse resolveRoot from config --- packages/astro/src/cli/dev/background.ts | 6 +++--- packages/astro/src/cli/dev/index.ts | 6 ++++-- packages/astro/src/cli/dev/logs.ts | 7 ++++--- packages/astro/src/cli/dev/status.ts | 6 ++++-- packages/astro/src/cli/dev/stop.ts | 6 ++++-- packages/astro/src/core/dev/lockfile.ts | 11 +---------- 6 files changed, 20 insertions(+), 22 deletions(-) diff --git a/packages/astro/src/cli/dev/background.ts b/packages/astro/src/cli/dev/background.ts index 3e83e95bb46a..feaebf48cf1f 100644 --- a/packages/astro/src/cli/dev/background.ts +++ b/packages/astro/src/cli/dev/background.ts @@ -1,7 +1,7 @@ import { spawn } from 'node:child_process'; import { existsSync, mkdirSync, openSync } from 'node:fs'; import { resolve } from 'node:path'; -import { fileURLToPath } from 'node:url'; +import { fileURLToPath, pathToFileURL } from 'node:url'; import type { AstroLogger } from '../../core/logger/core.js'; import type { Flags } from '../flags.js'; import { @@ -9,10 +9,10 @@ import { getLogFileURL, readLockFile, removeLockFile, - resolveRootURL, isProcessAlive, GRACEFUL_SHUTDOWN_TIMEOUT, } from '../../core/dev/lockfile.js'; +import { resolveRoot } from '../../core/config/config.js'; export interface BackgroundResult { pid: number; @@ -33,7 +33,7 @@ export async function background({ flags, logger, }: { flags: Flags; logger: AstroLogger }): Promise { - const root = resolveRootURL(flags.root); + const root = pathToFileURL(resolveRoot(flags.root) + '/'); // Check for existing server const existing = checkExistingServer(root); diff --git a/packages/astro/src/cli/dev/index.ts b/packages/astro/src/cli/dev/index.ts index 5c0013efba8f..a725677abee6 100644 --- a/packages/astro/src/cli/dev/index.ts +++ b/packages/astro/src/cli/dev/index.ts @@ -1,7 +1,9 @@ import { isAgent } from 'am-i-vibing'; import colors from 'piccolore'; import devServer from '../../core/dev/index.js'; -import { checkExistingServer, removeLockFile, resolveRootURL, writeLockFile } from '../../core/dev/lockfile.js'; +import { pathToFileURL } from 'node:url'; +import { checkExistingServer, removeLockFile, writeLockFile } from '../../core/dev/lockfile.js'; +import { resolveRoot } from '../../core/config/config.js'; import { printHelp } from '../../core/messages/runtime.js'; import { type Flags, createLoggerFromFlags, flagsToAstroInlineConfig } from '../flags.js'; @@ -96,7 +98,7 @@ export async function dev({ flags }: DevOptions) { } // Foreground dev server: check lock file, start server, write lock file - const root = resolveRootURL(flags.root); + const root = pathToFileURL(resolveRoot(flags.root) + '/'); const existingServer = checkExistingServer(root); if (existingServer) { const message = [ diff --git a/packages/astro/src/cli/dev/logs.ts b/packages/astro/src/cli/dev/logs.ts index ae8bb325aba5..6c6caefa4660 100644 --- a/packages/astro/src/cli/dev/logs.ts +++ b/packages/astro/src/cli/dev/logs.ts @@ -1,11 +1,12 @@ import { readFileSync, existsSync, statSync, createReadStream, watch } from 'node:fs'; -import { fileURLToPath } from 'node:url'; +import { fileURLToPath, pathToFileURL } from 'node:url'; import type { AstroLogger } from '../../core/logger/core.js'; import type { Flags } from '../flags.js'; -import { checkExistingServer, getLogFileURL, resolveRootURL, isProcessAlive } from '../../core/dev/lockfile.js'; +import { checkExistingServer, getLogFileURL, isProcessAlive } from '../../core/dev/lockfile.js'; +import { resolveRoot } from '../../core/config/config.js'; export async function logs({ flags, logger }: { flags: Flags; logger: AstroLogger }): Promise { - const root = resolveRootURL(flags.root); + const root = pathToFileURL(resolveRoot(flags.root) + '/'); const existing = checkExistingServer(root); if (!existing) { diff --git a/packages/astro/src/cli/dev/status.ts b/packages/astro/src/cli/dev/status.ts index 00b76265669b..0b404fc4fa64 100644 --- a/packages/astro/src/cli/dev/status.ts +++ b/packages/astro/src/cli/dev/status.ts @@ -1,6 +1,8 @@ import type { AstroLogger } from '../../core/logger/core.js'; import type { Flags } from '../flags.js'; -import { checkExistingServer, resolveRootURL } from '../../core/dev/lockfile.js'; +import { pathToFileURL } from 'node:url'; +import { checkExistingServer } from '../../core/dev/lockfile.js'; +import { resolveRoot } from '../../core/config/config.js'; export interface StatusResult { running: boolean; @@ -19,7 +21,7 @@ export async function status({ flags, logger, }: { flags: Flags; logger: AstroLogger }): Promise { - const root = resolveRootURL(flags.root); + const root = pathToFileURL(resolveRoot(flags.root) + '/'); const existing = checkExistingServer(root); if (!existing) { diff --git a/packages/astro/src/cli/dev/stop.ts b/packages/astro/src/cli/dev/stop.ts index ea097b97e48f..68b6eec76c09 100644 --- a/packages/astro/src/cli/dev/stop.ts +++ b/packages/astro/src/cli/dev/stop.ts @@ -1,6 +1,8 @@ import type { AstroLogger } from '../../core/logger/core.js'; import type { Flags } from '../flags.js'; -import { checkExistingServer, removeLockFile, resolveRootURL, isProcessAlive, GRACEFUL_SHUTDOWN_TIMEOUT } from '../../core/dev/lockfile.js'; +import { pathToFileURL } from 'node:url'; +import { checkExistingServer, removeLockFile, isProcessAlive, GRACEFUL_SHUTDOWN_TIMEOUT } from '../../core/dev/lockfile.js'; +import { resolveRoot } from '../../core/config/config.js'; export interface StopResult { stopped: boolean; @@ -13,7 +15,7 @@ export function formatStopOutput(result: StopResult): string { } export async function stop({ flags, logger }: { flags: Flags; logger: AstroLogger }): Promise { - const root = resolveRootURL(flags.root); + const root = pathToFileURL(resolveRoot(flags.root) + '/'); const existing = checkExistingServer(root); if (!existing) { diff --git a/packages/astro/src/core/dev/lockfile.ts b/packages/astro/src/core/dev/lockfile.ts index 430c7f10cd30..718460ef287a 100644 --- a/packages/astro/src/core/dev/lockfile.ts +++ b/packages/astro/src/core/dev/lockfile.ts @@ -1,14 +1,5 @@ import { existsSync, readFileSync, unlinkSync, writeFileSync, mkdirSync } from 'node:fs'; -import { resolve } from 'node:path'; -import { fileURLToPath, pathToFileURL } from 'node:url'; - -/** - * Resolve the project root as a URL from an optional root flag. - */ -export function resolveRootURL(root?: string): URL { - const rootPath = typeof root === 'string' ? resolve(root) : process.cwd(); - return pathToFileURL(rootPath + '/'); -} +import { fileURLToPath } from 'node:url'; /** Maximum time (ms) to wait for a process to exit after SIGTERM before escalating to SIGKILL. */ export const GRACEFUL_SHUTDOWN_TIMEOUT = 5000;