From 10a6df5833a72202bd4b867f052151ba4b1bdddc Mon Sep 17 00:00:00 2001 From: Kevin Brown Date: Sun, 21 Dec 2025 17:35:33 +0300 Subject: [PATCH 1/8] Add --db-app-token CLI flag to astro db execute, push, and verify commands --- .../db/src/core/cli/commands/execute/index.ts | 5 +-- .../db/src/core/cli/commands/push/index.ts | 11 ++++-- .../db/src/core/cli/commands/verify/index.ts | 5 +-- packages/db/src/core/cli/index.ts | 12 +++++++ packages/db/src/core/utils.ts | 11 ++++++ packages/db/test/basics.test.js | 28 +++++++++++++++ packages/db/test/error-handling.test.js | 34 +++++++++++++++++++ packages/db/test/test-utils.js | 7 ++-- 8 files changed, 104 insertions(+), 9 deletions(-) diff --git a/packages/db/src/core/cli/commands/execute/index.ts b/packages/db/src/core/cli/commands/execute/index.ts index 0ae836425dca..41838904267f 100644 --- a/packages/db/src/core/cli/commands/execute/index.ts +++ b/packages/db/src/core/cli/commands/execute/index.ts @@ -15,7 +15,7 @@ import { } from '../../../integration/vite-plugin-db.js'; import { bundleFile, importBundledFile } from '../../../load-file.js'; import type { DBConfig } from '../../../types.js'; -import { getRemoteDatabaseInfo } from '../../../utils.js'; +import { getRemoteDatabaseInfo, resolveDbAppToken } from '../../../utils.js'; export async function cmd({ astroConfig, @@ -41,9 +41,10 @@ export async function cmd({ let virtualModContents: string; if (flags.remote) { const dbInfo = getRemoteDatabaseInfo(); + const appToken = resolveDbAppToken(flags, dbInfo.token); virtualModContents = getRemoteVirtualModContents({ tables: dbConfig.tables ?? {}, - appToken: flags.token ?? dbInfo.token, + appToken, isBuild: false, output: 'server', localExecution: true, diff --git a/packages/db/src/core/cli/commands/push/index.ts b/packages/db/src/core/cli/commands/push/index.ts index 663b648119cf..a9781af37ae7 100644 --- a/packages/db/src/core/cli/commands/push/index.ts +++ b/packages/db/src/core/cli/commands/push/index.ts @@ -5,7 +5,11 @@ import type { Arguments } from 'yargs-parser'; import { MIGRATION_VERSION } from '../../../consts.js'; import { createClient } from '../../../db-client/libsql-node.js'; import type { DBConfig, DBSnapshot } from '../../../types.js'; -import { getRemoteDatabaseInfo, type RemoteDatabaseInfo } from '../../../utils.js'; +import { + getRemoteDatabaseInfo, + type RemoteDatabaseInfo, + resolveDbAppToken, +} from '../../../utils.js'; import { createCurrentSnapshot, createEmptySnapshot, @@ -25,7 +29,8 @@ export async function cmd({ const isDryRun = flags.dryRun; const isForceReset = flags.forceReset; const dbInfo = getRemoteDatabaseInfo(); - const productionSnapshot = await getProductionCurrentSnapshot(dbInfo); + const appToken = resolveDbAppToken(flags, dbInfo.token); + const productionSnapshot = await getProductionCurrentSnapshot({ ...dbInfo, token: appToken }); const currentSnapshot = createCurrentSnapshot(dbConfig); const isFromScratch = !productionSnapshot; const { queries: migrationQueries, confirmations } = await getMigrationQueries({ @@ -67,7 +72,7 @@ export async function cmd({ await pushSchema({ statements: migrationQueries, dbInfo, - appToken: flags.token ?? dbInfo.token, + appToken, isDryRun, currentSnapshot: currentSnapshot, }); diff --git a/packages/db/src/core/cli/commands/verify/index.ts b/packages/db/src/core/cli/commands/verify/index.ts index ae9c776090ca..3f876c323259 100644 --- a/packages/db/src/core/cli/commands/verify/index.ts +++ b/packages/db/src/core/cli/commands/verify/index.ts @@ -1,7 +1,7 @@ import type { AstroConfig } from 'astro'; import type { Arguments } from 'yargs-parser'; import type { DBConfig } from '../../../types.js'; -import { getRemoteDatabaseInfo } from '../../../utils.js'; +import { getRemoteDatabaseInfo, resolveDbAppToken } from '../../../utils.js'; import { createCurrentSnapshot, createEmptySnapshot, @@ -20,7 +20,8 @@ export async function cmd({ }) { const isJson = flags.json; const dbInfo = getRemoteDatabaseInfo(); - const productionSnapshot = await getProductionCurrentSnapshot(dbInfo); + const appToken = resolveDbAppToken(flags, dbInfo.token); + const productionSnapshot = await getProductionCurrentSnapshot({ ...dbInfo, token: appToken }); const currentSnapshot = createCurrentSnapshot(dbConfig); const { queries: migrationQueries, confirmations } = await getMigrationQueries({ oldSnapshot: productionSnapshot || createEmptySnapshot(), diff --git a/packages/db/src/core/cli/index.ts b/packages/db/src/core/cli/index.ts index 915d2a20f548..aa5e38af9c6c 100644 --- a/packages/db/src/core/cli/index.ts +++ b/packages/db/src/core/cli/index.ts @@ -14,6 +14,7 @@ export async function cli({ // Most commands are `astro db foo`, but for now login/logout // are also handled by this package, so first check if this is a db command. const command = args[2] === 'db' ? args[3] : args[2]; + validateDbAppTokenFlag(command, flags); const { dbConfig } = await resolveDbConfig(astroConfig); switch (command) { @@ -68,3 +69,14 @@ export async function cli({ } } } + +function validateDbAppTokenFlag(command: string | undefined, flags: Arguments) { + if (command !== 'execute' && command !== 'push' && command !== 'verify') return; + + const dbAppToken = (flags as Arguments & { dbAppToken?: unknown }).dbAppToken; + if (dbAppToken == null) return; + if (typeof dbAppToken !== 'string') { + console.error(`Invalid value for --db-app-token; expected a string.`); + process.exit(1); + } +} diff --git a/packages/db/src/core/utils.ts b/packages/db/src/core/utils.ts index 1a58ddcab646..6835f99507b9 100644 --- a/packages/db/src/core/utils.ts +++ b/packages/db/src/core/utils.ts @@ -1,5 +1,6 @@ import type { AstroConfig, AstroIntegration } from 'astro'; import { loadEnv } from 'vite'; +import type { Arguments } from 'yargs-parser'; import './types.js'; export type VitePlugin = Required['plugins'][number]; @@ -23,6 +24,16 @@ export function getRemoteDatabaseInfo(): RemoteDatabaseInfo { }; } +export function resolveDbAppToken( + flags: Arguments, + envToken: string | undefined, +): string | undefined { + const dbAppToken = (flags as Arguments & { dbAppToken?: unknown }).dbAppToken; + if (typeof dbAppToken === 'string') return dbAppToken; + + return envToken; +} + export function getDbDirectoryUrl(root: URL | string) { return new URL('db/', root); } diff --git a/packages/db/test/basics.test.js b/packages/db/test/basics.test.js index 701f31d1c9d7..c2d6aaea0df9 100644 --- a/packages/db/test/basics.test.js +++ b/packages/db/test/basics.test.js @@ -3,6 +3,7 @@ import { after, before, describe, it } from 'node:test'; import { load as cheerioLoad } from 'cheerio'; import testAdapter from '../../astro/test/test-adapter.js'; import { loadFixture } from '../../astro/test/test-utils.js'; +import { resolveDbAppToken } from '../dist/core/utils.js'; import { clearEnvironment, setupRemoteDb } from './test-utils.js'; describe('astro:db', () => { @@ -200,4 +201,31 @@ describe('astro:db', () => { assert.equal(ul.children().length, 5); }); }); + + describe('cli --db-app-token', () => { + it('Seeds remote database with --db-app-token flag set and without ASTRO_DB_APP_TOKEN env being set', async () => { + clearEnvironment(); + assert.equal(process.env.ASTRO_DB_APP_TOKEN, undefined); + + const remoteDbServer = await setupRemoteDb(fixture.config, { useDbAppTokenFlag: true }); + try { + assert.equal(process.env.ASTRO_DB_APP_TOKEN, undefined); + } finally { + await remoteDbServer.stop(); + } + assert.equal(process.env.ASTRO_DB_APP_TOKEN, undefined); + }); + }); + + describe('Precedence for --db-app-token and ASTRO_DB_APP_TOKEN handled correctly', () => { + it('prefers --db-app-token over `ASTRO_DB_APP_TOKEN`', () => { + const flags = /** @type {any} */ ({ _: [], dbAppToken: 'from-flag' }); + assert.equal(resolveDbAppToken(flags, 'from-env'), 'from-flag'); + }); + + it('falls back to ASTRO_DB_APP_TOKEN if no flags set', () => { + const flags = /** @type {any} */ ({ _: [] }); + assert.equal(resolveDbAppToken(flags, 'from-env'), 'from-env'); + }); + }); }); diff --git a/packages/db/test/error-handling.test.js b/packages/db/test/error-handling.test.js index 87673a174c77..356be77af97b 100644 --- a/packages/db/test/error-handling.test.js +++ b/packages/db/test/error-handling.test.js @@ -1,6 +1,7 @@ import assert from 'node:assert/strict'; import { after, before, describe, it } from 'node:test'; import { loadFixture } from '../../astro/test/test-utils.js'; +import { cli } from '../dist/core/cli/index.js'; import { setupRemoteDb } from './test-utils.js'; const foreignKeyConstraintError = @@ -14,6 +15,39 @@ describe('astro:db - error handling', () => { }); }); + it('Errors on invalid --db-app-token input', async () => { + const originalExit = process.exit; + const originalError = console.error; + /** @type {string[]} */ + const errorMessages = []; + console.error = (...args) => { + errorMessages.push(args.map(String).join(' ')); + }; + process.exit = (code) => { + throw new Error(`EXIT_${code}`); + }; + + try { + await cli({ + config: fixture.config, + flags: { + _: [undefined, 'astro', 'db', 'verify'], + dbAppToken: true, + }, + }); + assert.fail('Expected command to exit'); + } catch (err) { + assert.match(String(err), /EXIT_1/); + assert.ok( + errorMessages.some((m) => m.includes('Invalid value for --db-app-token')), + `Expected error output to mention invalid --db-app-token, got: ${errorMessages.join('\n')}`, + ); + } finally { + process.exit = originalExit; + console.error = originalError; + } + }); + describe('development', () => { let devServer; diff --git a/packages/db/test/test-utils.js b/packages/db/test/test-utils.js index fbc117a39f67..7e1a6623debd 100644 --- a/packages/db/test/test-utils.js +++ b/packages/db/test/test-utils.js @@ -9,13 +9,15 @@ const isWindows = process.platform === 'win32'; /** * @param {import('astro').AstroConfig} astroConfig */ -export async function setupRemoteDb(astroConfig) { +export async function setupRemoteDb(astroConfig, options = {}) { const url = isWindows ? new URL(`./.astro/${Date.now()}.db`, astroConfig.root) : new URL(`./${Date.now()}.db`, astroConfig.root); const token = 'foo'; process.env.ASTRO_DB_REMOTE_URL = url.toString(); - process.env.ASTRO_DB_APP_TOKEN = token; + if (!options.useDbAppTokenFlag) { + process.env.ASTRO_DB_APP_TOKEN = token; + } process.env.ASTRO_INTERNAL_TEST_REMOTE = true; if (isWindows) { @@ -47,6 +49,7 @@ export async function setupRemoteDb(astroConfig) { flags: { _: [undefined, 'astro', 'db', 'execute', 'db/seed.ts'], remote: true, + ...(options.useDbAppTokenFlag ? { dbAppToken: token } : {}), }, }); From 79dbad357ff7defb44f1f4fde678029952f5b16f Mon Sep 17 00:00:00 2001 From: Kevin Brown Date: Sun, 21 Dec 2025 17:45:54 +0300 Subject: [PATCH 2/8] Add changeset --- .changeset/happy-rooms-scream.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/happy-rooms-scream.md diff --git a/.changeset/happy-rooms-scream.md b/.changeset/happy-rooms-scream.md new file mode 100644 index 000000000000..d115d47ad1a4 --- /dev/null +++ b/.changeset/happy-rooms-scream.md @@ -0,0 +1,5 @@ +--- +'@astrojs/db': patch +--- + +Add --db-app-token CLI flag to astro db execute, push, and verify commands From 1c58a32f41cd1a0f3e645355b4859d7a9a9fc853 Mon Sep 17 00:00:00 2001 From: Kevin Brown Date: Sun, 21 Dec 2025 18:43:44 +0300 Subject: [PATCH 3/8] Fix type errors in utils.js revealed on build --- packages/db/src/core/utils.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packages/db/src/core/utils.ts b/packages/db/src/core/utils.ts index 6835f99507b9..5410b2522fbe 100644 --- a/packages/db/src/core/utils.ts +++ b/packages/db/src/core/utils.ts @@ -24,6 +24,14 @@ export function getRemoteDatabaseInfo(): RemoteDatabaseInfo { }; } +export function resolveDbAppToken( + flags: Arguments, + envToken: string, +): string; +export function resolveDbAppToken( + flags: Arguments, + envToken: string | undefined, +): string | undefined; export function resolveDbAppToken( flags: Arguments, envToken: string | undefined, From 3f0b1316a61b764c566ac93b504cf24d02b232f1 Mon Sep 17 00:00:00 2001 From: Kevin Brown Date: Sun, 21 Dec 2025 19:26:41 +0300 Subject: [PATCH 4/8] Bump timeout in unrelated test file to avoid flakiness in Windows-2025/Node 22 check, from 1000ms to 3000ms for two test cases --- packages/astro/test/custom-404-implicit-rerouting.test.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/astro/test/custom-404-implicit-rerouting.test.js b/packages/astro/test/custom-404-implicit-rerouting.test.js index 49f7e56689e7..b5139a78980d 100644 --- a/packages/astro/test/custom-404-implicit-rerouting.test.js +++ b/packages/astro/test/custom-404-implicit-rerouting.test.js @@ -27,13 +27,13 @@ for (const caseNumber of [1, 2, 3, 4, 5]) { }); // sanity check - it('dev server handles normal requests', { timeout: 1000 }, async () => { + it('dev server handles normal requests', { timeout: 3000 }, async () => { const response = await fixture.fetch('/'); assert.equal(response.status, 200); }); // IMPORTANT: never skip - it('dev server stays responsive', { timeout: 1000 }, async () => { + it('dev server stays responsive', { timeout: 3000 }, async () => { const response = await fixture.fetch('/alvsibdlvjks'); assert.equal(response.status, 404); }); @@ -52,7 +52,7 @@ for (const caseNumber of [1, 2, 3, 4, 5]) { }); // sanity check - it('prod server handles normal requests', { timeout: 1000 }, async () => { + it('prod server handles normal requests', { timeout: 3000 }, async () => { const response = await app.render(new Request('https://example.com/')); assert.equal(response.status, 200); }); @@ -60,7 +60,7 @@ for (const caseNumber of [1, 2, 3, 4, 5]) { // IMPORTANT: never skip it( 'prod server stays responsive for case number ' + caseNumber, - { timeout: 1000 }, + { timeout: 3000 }, async () => { const response = await app.render(new Request('https://example.com/alvsibdlvjks')); assert.equal(response.status, 404); From ca971efb2ab683cb7cf13355b5cbe0be766c02bf Mon Sep 17 00:00:00 2001 From: Kevin Brown Date: Tue, 23 Dec 2025 17:22:37 +0300 Subject: [PATCH 5/8] Add --db-app-token CLI parameter to query command --- packages/db/src/core/cli/commands/shell/index.ts | 5 +++-- packages/db/src/core/cli/index.ts | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/db/src/core/cli/commands/shell/index.ts b/packages/db/src/core/cli/commands/shell/index.ts index a4b61863c92e..d652fa017437 100644 --- a/packages/db/src/core/cli/commands/shell/index.ts +++ b/packages/db/src/core/cli/commands/shell/index.ts @@ -7,7 +7,7 @@ import { createClient as createLocalDatabaseClient } from '../../../db-client/li import { createClient as createRemoteDatabaseClient } from '../../../db-client/libsql-node.js'; import { SHELL_QUERY_MISSING_ERROR } from '../../../errors.js'; import type { DBConfigInput } from '../../../types.js'; -import { getAstroEnv, getRemoteDatabaseInfo } from '../../../utils.js'; +import { getAstroEnv, getRemoteDatabaseInfo, resolveDbAppToken } from '../../../utils.js'; export async function cmd({ flags, @@ -24,7 +24,8 @@ export async function cmd({ } const dbInfo = getRemoteDatabaseInfo(); if (flags.remote) { - const db = createRemoteDatabaseClient(dbInfo); + const appToken = resolveDbAppToken(flags, dbInfo.token); + const db = createRemoteDatabaseClient({ ...dbInfo, token: appToken }); const result = await db.run(sql.raw(query)); console.log(result); } else { diff --git a/packages/db/src/core/cli/index.ts b/packages/db/src/core/cli/index.ts index aa5e38af9c6c..435207d2c0f0 100644 --- a/packages/db/src/core/cli/index.ts +++ b/packages/db/src/core/cli/index.ts @@ -71,7 +71,7 @@ export async function cli({ } function validateDbAppTokenFlag(command: string | undefined, flags: Arguments) { - if (command !== 'execute' && command !== 'push' && command !== 'verify') return; + if (command !== 'execute' && command !== 'push' && command !== 'verify' && command !== 'shell') return; const dbAppToken = (flags as Arguments & { dbAppToken?: unknown }).dbAppToken; if (dbAppToken == null) return; From e69f7beaee487363629be9c13a77f5a8b5cda1d6 Mon Sep 17 00:00:00 2001 From: Kevin Brown Date: Tue, 23 Dec 2025 17:26:23 +0300 Subject: [PATCH 6/8] Update changeset --- .changeset/happy-rooms-scream.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/happy-rooms-scream.md b/.changeset/happy-rooms-scream.md index d115d47ad1a4..27b2b8abe3ca 100644 --- a/.changeset/happy-rooms-scream.md +++ b/.changeset/happy-rooms-scream.md @@ -2,4 +2,4 @@ '@astrojs/db': patch --- -Add --db-app-token CLI flag to astro db execute, push, and verify commands +Add --db-app-token CLI flag to astro db execute, push, query, and verify commands From 6365783db7a25bb2d3172bcaa13c52ecf3a4a472 Mon Sep 17 00:00:00 2001 From: Erika <3019731+Princesseuh@users.noreply.github.com> Date: Tue, 20 Jan 2026 17:49:05 +0100 Subject: [PATCH 7/8] Update .changeset/happy-rooms-scream.md --- .changeset/happy-rooms-scream.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/happy-rooms-scream.md b/.changeset/happy-rooms-scream.md index 27b2b8abe3ca..85ade0e7a79c 100644 --- a/.changeset/happy-rooms-scream.md +++ b/.changeset/happy-rooms-scream.md @@ -1,5 +1,5 @@ --- -'@astrojs/db': patch +'@astrojs/db': minor --- Add --db-app-token CLI flag to astro db execute, push, query, and verify commands From 2e4076187ca40a45d4e43b3c387d77be319eea2c Mon Sep 17 00:00:00 2001 From: Erika <3019731+Princesseuh@users.noreply.github.com> Date: Wed, 21 Jan 2026 18:02:27 +0100 Subject: [PATCH 8/8] Update .changeset/happy-rooms-scream.md Co-authored-by: Sarah Rainsberger <5098874+sarah11918@users.noreply.github.com> --- .changeset/happy-rooms-scream.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/.changeset/happy-rooms-scream.md b/.changeset/happy-rooms-scream.md index 85ade0e7a79c..e1a15e1934af 100644 --- a/.changeset/happy-rooms-scream.md +++ b/.changeset/happy-rooms-scream.md @@ -2,4 +2,14 @@ '@astrojs/db': minor --- -Add --db-app-token CLI flag to astro db execute, push, query, and verify commands +Adds a `--db-app-token` CLI flag to `astro db` commands `execute`, `push`, `query`, and `verify` + +The new Astro DB CLI flags allow you to provide a remote database app token directly instead of `ASTRO_DB_APP_TOKEN`. This ensures that no untrusted code (e.g. CI / CD workflows) has access to the secret that is only needed by the `astro db` commands. + +The following command can be used to safely push database configuration changes to your project database: + +``` +astro db push --db-app-token +``` + +See the [Astro DB integration documentation](https://docs.astro.build/en/guides/integrations-guide/db/#astro-db-cli-reference) for more information.