@@ -16,38 +16,43 @@ import {
1616 formatMtimeGap ,
1717} from '../utils/dev-restart.js' ;
1818import { readEnvWithDeprecation , isMcpServerEnabled } from '@objectstack/types' ;
19+ import type { ResolvedProjectDatabaseUrl } from '@objectstack/runtime' ;
1920
2021/**
21- * Resolve the persistent default database URL for `objectstack dev`.
22+ * Resolve the database URL for `objectstack dev` — dev's flag surface mapped
23+ * onto the ONE shared resolution (`resolveProjectDatabaseUrl`, #6469) that
24+ * `os start` and `os migrate` resolve through too. Priority: `--database` /
25+ * `--fresh`'s ephemeral file → `OS_DATABASE_URL` / `DATABASE_URL` /
26+ * `TURSO_DATABASE_URL` → explicit in-memory driver (`--database-driver memory`
27+ * / `OS_DATABASE_DRIVER=memory`) → the config-declared default datasource →
28+ * the unified default `<state dir>/data/objectstack.db` (legacy `dev.db` /
29+ * `standalone.db` still compat-read, with the loud `notice` line).
2230 *
23- * `dev` should keep your work between restarts — the historical serve default
31+ * `dev` keeps a persistent default on purpose — the historical serve default
2432 * of `:memory:` wipes all data (and AI-authored metadata) on every restart,
25- * which makes local app-building unusable. So when the user has NOT chosen a
26- * database another way, default to a project-anchored sqlite file at
27- * `<cwd>/.objectstack/data/dev.db` (gitignored, per-project).
33+ * which makes local app-building unusable.
2834 *
29- * Returns `undefined` (i.e. "don't impose a default") when the user already
30- * selected a database, so the existing resolution wins:
31- * - `--database <url>` flag
32- * - `--fresh` (its own ephemeral temp DB)
33- * - `OS_DATABASE_URL` / `DATABASE_URL` env
34- * - an explicit in-memory driver (`--database-driver memory` or
35- * `OS_DATABASE_DRIVER=memory`)
35+ * This wrapper is dev's ONE resolution seam (pinned, together with start's and
36+ * migrate's, by `unified-db-resolution.pin.test.ts`): it maps inputs, it never
37+ * re-implements any fallback. The runtime import is lazy so oclif's
38+ * import-every-command startup (#5726) does not pay for the runtime graph.
3639 */
37- export function resolveDefaultDevDbUrl ( opts : {
40+ export async function resolveDevDatabase ( opts : {
3841 databaseFlag ?: string ;
3942 freshDbUrl ?: string ;
4043 databaseDriverFlag ?: string ;
4144 env : Record < string , string | undefined > ;
4245 cwd : string ;
43- } ) : string | undefined {
44- if ( opts . databaseFlag || opts . freshDbUrl ) return undefined ;
45- const envDbUrl = ( opts . env . OS_DATABASE_URL ?? opts . env . DATABASE_URL ) ?. trim ( ) ;
46- if ( envDbUrl ) return undefined ;
47- const forcedMemory =
48- opts . databaseDriverFlag === 'memory' || opts . env . OS_DATABASE_DRIVER ?. trim ( ) === 'memory' ;
49- if ( forcedMemory ) return undefined ;
50- return `file:${ path . join ( opts . cwd , '.objectstack' , 'data' , 'dev.db' ) } ` ;
46+ artifactPath ?: string ;
47+ } ) : Promise < ResolvedProjectDatabaseUrl > {
48+ const { resolveProjectDatabaseUrl } = await import ( '@objectstack/runtime' ) ;
49+ return resolveProjectDatabaseUrl ( {
50+ explicitUrl : opts . databaseFlag ?? opts . freshDbUrl ,
51+ explicitDriver : opts . databaseDriverFlag ,
52+ env : opts . env ,
53+ projectRoot : opts . cwd ,
54+ artifactPath : opts . artifactPath ,
55+ } ) ;
5156}
5257
5358export default class Dev extends Command {
@@ -228,7 +233,7 @@ export default class Dev extends Command {
228233 // Creates a unique scratch dir that owns the state this command can
229234 // actually place, and nothing more. What it covers, exactly:
230235 // - the dev SQLite DB the CLI resolves for the run
231- // (OS_HOME → <home>/data/dev .db, published as OS_DATABASE_URL),
236+ // (OS_HOME → <home>/data/objectstack .db, published as OS_DATABASE_URL),
232237 // - the storage-service uploads root, published on the settings
233238 // service's own env name OS_STORAGE_LOCAL_ROOT (#4968),
234239 // - any other state a plugin keys off OS_HOME.
@@ -262,7 +267,9 @@ export default class Dev extends Command {
262267 if ( flags . fresh ) {
263268 freshHome = fs . mkdtempSync ( path . join ( os . tmpdir ( ) , 'objectstack-dev-' ) ) ;
264269 fs . mkdirSync ( path . join ( freshHome , 'data' ) , { recursive : true } ) ;
265- freshDbUrl = `file:${ path . join ( freshHome , 'data' , 'dev.db' ) } ` ;
270+ // The unified default filename (#6469) — the same name every command
271+ // resolves, just anchored on this run's ephemeral OS_HOME.
272+ freshDbUrl = `file:${ path . join ( freshHome , 'data' , 'objectstack.db' ) } ` ;
266273 freshStorageRoot = path . join ( freshHome , 'uploads' ) ;
267274 fs . mkdirSync ( freshStorageRoot , { recursive : true } ) ;
268275 printKV ( 'Fresh OS_HOME' , freshHome , '🧪' ) ;
@@ -292,23 +299,31 @@ export default class Dev extends Command {
292299 // idempotent (empty-DB only) and never overwrites an existing account.
293300 const seedAdmin = flags [ 'seed-admin' ] ?? true ;
294301
295- // Default `dev` to a PERSISTENT, project-anchored sqlite database so
296- // AI-authored metadata and records survive restarts. The historical
297- // serve default is `:memory:`, which silently wipes everything on every
298- // restart — fine for throwaway demos, but it makes local app-building
299- // unusable (build an app, restart, it's gone). See {@link resolveDefaultDevDbUrl}
300- // for the opt-out matrix (--fresh / --database / OS_DATABASE_URL / memory driver).
301- const defaultDevDb = resolveDefaultDevDbUrl ( {
302+ // Resolve the database through the ONE shared resolution (#6469) —
303+ // `os dev`, `os start` and `os migrate` all land on the same URL for the
304+ // same project directory. `dev` keeps a PERSISTENT default on purpose:
305+ // the historical serve default is `:memory:`, which silently wipes
306+ // everything on every restart — fine for throwaway demos, but it makes
307+ // local app-building unusable (build an app, restart, it's gone). See
308+ // {@link resolveDevDatabase } for the priority ladder.
309+ const resolvedDb = await resolveDevDatabase ( {
302310 databaseFlag : flags . database ,
303311 freshDbUrl,
304312 databaseDriverFlag : flags [ 'database-driver' ] ,
305313 env : process . env ,
306314 cwd : process . cwd ( ) ,
315+ artifactPath,
307316 } ) ;
308- if ( defaultDevDb ) {
309- fs . mkdirSync ( path . dirname ( defaultDevDb . replace ( / ^ f i l e : / , '' ) ) , { recursive : true } ) ;
317+ if ( resolvedDb . notice ) {
318+ // Legacy-file compat-read — one loud line naming the file being read
319+ // and how to converge on the unified default. Never an interactive
320+ // prompt (CI-safe), never silent (#6469).
321+ console . log ( chalk . yellow ( ` ⚠ ${ resolvedDb . notice } ` ) ) ;
310322 }
311- const effectiveDb = flags . database ?? freshDbUrl ?? defaultDevDb ;
323+ if ( resolvedDb . source === 'unified-default' ) {
324+ fs . mkdirSync ( path . dirname ( resolvedDb . url . replace ( / ^ f i l e : / , '' ) ) , { recursive : true } ) ;
325+ }
326+ const effectiveDb = resolvedDb . url ;
312327 const localEnv : NodeJS . ProcessEnv = {
313328 ...process . env ,
314329 OS_ENVIRONMENT_ID : environmentId ,
@@ -318,14 +333,14 @@ export default class Dev extends Command {
318333 ...( seedAdmin && flags [ 'admin-password' ] ? { OS_SEED_ADMIN_PASSWORD : flags [ 'admin-password' ] } : { } ) ,
319334 ...( freshHome ? { OS_HOME : freshHome } : { } ) ,
320335 ...( freshStorageRoot ? { OS_STORAGE_LOCAL_ROOT : freshStorageRoot } : { } ) ,
321- ... ( effectiveDb ? { OS_DATABASE_URL : effectiveDb } : { } ) ,
336+ OS_DATABASE_URL : effectiveDb ,
322337 ...( flags [ 'database-driver' ] ? { OS_DATABASE_DRIVER : flags [ 'database-driver' ] } : { } ) ,
323338 ...( flags [ 'database-auth-token' ] ? { OS_DATABASE_AUTH_TOKEN : flags [ 'database-auth-token' ] } : { } ) ,
324339 ...( flags [ 'auth-secret' ] ? { OS_AUTH_SECRET : flags [ 'auth-secret' ] } : { } ) ,
325340 } ;
326341 printKV ( 'Environment ID' , environmentId , '🎯' ) ;
327342 printKV ( 'Artifact' , isUrl ? artifactPath : path . relative ( process . cwd ( ) , artifactPath ) , '📦' ) ;
328- if ( effectiveDb ) printKV ( 'Database' , redactConnectionUrl ( effectiveDb ) , '🗄️' ) ;
343+ printKV ( 'Database' , redactConnectionUrl ( effectiveDb ) , '🗄️' ) ;
329344
330345 const port = flags . port ?? readEnvWithDeprecation ( 'OS_PORT' , 'PORT' , { silent : true } ) ;
331346 const binPath = process . argv [ 1 ] ;
0 commit comments