@@ -4,6 +4,7 @@ import { promisify } from "node:util";
44import { basename , join , resolve } from "node:path" ;
55import * as p from "@clack/prompts" ;
66import pc from "picocolors" ;
7+ import * as dotenv from "dotenv" ;
78import {
89 scaffoldApp ,
910 createDatabase ,
@@ -30,8 +31,9 @@ function isCwdEmpty(): boolean {
3031}
3132
3233/**
33- * Poll Tiger Cloud until the service is ready (status != "creating ").
34+ * Poll Tiger Cloud until the service is ready (status == "ready ").
3435 * Returns true if ready, false on timeout.
36+ * Throws an error if the service is not found or tiger CLI is unavailable.
3537 */
3638async function waitForDatabase (
3739 serviceId : string ,
@@ -48,17 +50,80 @@ async function waitForDatabase(
4850 stdio : [ "pipe" , "pipe" , "pipe" ] ,
4951 } ) ;
5052 const info = JSON . parse ( stdout ) as { status ?: string } ;
51- if ( info . status && info . status !== "creating" ) {
53+ // Only return true when database is actually ready
54+ if ( info . status === "ready" ) {
5255 return true ;
5356 }
54- } catch {
55- // tiger CLI not available or service not found — keep trying
57+ } catch ( error ) {
58+ const err = error as Error & { stderr ?: string } ;
59+ // Check if it's a "service not found" or "tiger CLI not available" error
60+ if ( err . message ?. includes ( "not found" ) || err . stderr ?. includes ( "not found" ) ) {
61+ throw new Error ( `Service ${ serviceId } not found. Please check the service ID.` ) ;
62+ }
63+ if ( err . message ?. includes ( "command not found" ) || err . message ?. includes ( "tiger" ) ) {
64+ throw new Error ( "Tiger CLI not available. Please install it from https://tiger.tigerdata.cloud" ) ;
65+ }
66+ // For other errors (network, etc.), continue retrying
5667 }
5768 await new Promise ( ( r ) => setTimeout ( r , intervalMs ) ) ;
5869 }
5970 return false ;
6071}
6172
73+ /**
74+ * Extract the service ID from a Tiger Cloud DATABASE_URL.
75+ * Tiger Cloud hostnames follow the pattern: <service_id>.<region>.aws.tsdb.cloud
76+ */
77+ function extractServiceIdFromUrl ( databaseUrl : string ) : string | null {
78+ try {
79+ const url = new URL ( databaseUrl ) ;
80+ const hostname = url . hostname ;
81+
82+ // Match Tiger Cloud hostname patterns like: abc123def4.us-east-1.aws.tsdb.cloud
83+ const match = hostname . match ( / ^ ( [ a - z 0 - 9 ] { 10 } ) \. / ) ;
84+ return match ? match [ 1 ] : null ;
85+ } catch {
86+ return null ;
87+ }
88+ }
89+
90+ /**
91+ * Check database status and start it if paused/stopped.
92+ * Returns the status: "started", "already_running", "not_found", or "creating"
93+ */
94+ function startDatabaseIfNeeded ( serviceId : string , noWait = false ) : string {
95+ try {
96+ const stdout = execSync ( `tiger service get ${ serviceId } -o json` , {
97+ encoding : "utf-8" ,
98+ stdio : [ "pipe" , "pipe" , "pipe" ] ,
99+ } ) ;
100+ const info = JSON . parse ( stdout ) as { status ?: string } ;
101+
102+ if ( ! info . status ) {
103+ return "not_found" ;
104+ }
105+
106+ // If database is paused or stopped, start it
107+ if ( info . status === "paused" || info . status === "stopped" ) {
108+ execSync ( `tiger service start ${ serviceId } ${ noWait ? " --no-wait" : "" } ` , {
109+ encoding : "utf-8" ,
110+ stdio : [ "pipe" , "pipe" , "pipe" ] ,
111+ } ) ;
112+ return "started" ;
113+ }
114+
115+ if ( info . status === "creating" ) {
116+ return "creating" ;
117+ }
118+
119+ // Already running
120+ return "already_running" ;
121+ } catch {
122+ // tiger CLI not available or service not found
123+ return "not_found" ;
124+ }
125+ }
126+
62127function isExisting0pflow ( ) : boolean {
63128 try {
64129 const pkgPath = join ( process . cwd ( ) , "package.json" ) ;
@@ -123,6 +188,35 @@ export async function runRun(): Promise<void> {
123188
124189 // ── Existing project → launch ───────────────────────────────────────
125190 if ( isExisting0pflow ( ) ) {
191+ // Check if database is paused and start it if needed
192+ try {
193+ const { findEnvFile } = await import ( "./env.js" ) ;
194+ const envPath = findEnvFile ( process . cwd ( ) ) ;
195+ if ( envPath ) {
196+ const envContent = readFileSync ( envPath , "utf-8" ) ;
197+ const parsed = dotenv . parse ( envContent ) ;
198+
199+ if ( parsed . DATABASE_URL ) {
200+ const serviceId = extractServiceIdFromUrl ( parsed . DATABASE_URL ) ;
201+ if ( serviceId ) {
202+ const status = startDatabaseIfNeeded ( serviceId , false ) ;
203+ if ( status === "started" ) {
204+ const s = p . spinner ( ) ;
205+ s . start ( "Database was paused, waiting for it to start..." ) ;
206+ const ready = await waitForDatabase ( serviceId , 3 * 60 * 1000 ) ;
207+ if ( ready ) {
208+ s . stop ( pc . green ( "Database is ready" ) ) ;
209+ } else {
210+ s . stop ( pc . yellow ( "Database is starting (taking longer than expected)" ) ) ;
211+ }
212+ }
213+ }
214+ }
215+ }
216+ } catch {
217+ // Continue without database check if env loading fails
218+ }
219+
126220 const mode = await p . select ( {
127221 message : "Launch mode" ,
128222 options : [
@@ -296,28 +390,34 @@ export async function runRun(): Promise<void> {
296390 }
297391 serviceId = sid ;
298392 }
393+
394+ // Start database if paused (async, don't wait)
395+ if ( serviceId ) {
396+ const status = startDatabaseIfNeeded ( serviceId , true ) ;
397+ if ( status === "started" ) {
398+ p . log . info ( "Database was paused, starting it in the background..." ) ;
399+ }
400+ }
299401 }
300402
301403 // ── Execute ─────────────────────────────────────────────────────────
302404 const s = p . spinner ( ) ;
303405
304- // Start database creation first (async) if creating new
305- let dbPromise : Promise < { service_id ?: string } > | undefined ;
406+ // Create database if needed (returns immediately with --no-wait)
306407 if ( dbChoice === "new" ) {
307408 s . start ( "Creating Tiger Cloud database..." ) ;
308- dbPromise = createDatabase ( { name : projectName } ) . then ( ( result ) => {
309- if ( ! result . success ) {
310- throw new Error ( result . error || "Failed to create database" ) ;
311- }
312- return result ;
313- } ) ;
314- }
315-
316- // Scaffold app (parallel with db provisioning)
317- if ( ! dbPromise ) {
318- s . start ( "Scaffolding project..." ) ;
409+ const dbResult = await createDatabase ( { name : projectName } ) ;
410+ if ( ! dbResult . success ) {
411+ s . stop ( pc . red ( "Database creation failed" ) ) ;
412+ p . log . error ( dbResult . error || "Failed to create database" ) ;
413+ process . exit ( 1 ) ;
414+ }
415+ serviceId = dbResult . service_id ;
416+ s . stop ( pc . green ( `Database creation initiated (${ serviceId } )` ) ) ;
319417 }
320418
419+ // Scaffold app
420+ s . start ( "Scaffolding project..." ) ;
321421 const scaffoldResult = await scaffoldApp ( {
322422 appName : projectName ,
323423 directory,
@@ -343,19 +443,16 @@ export async function runRun(): Promise<void> {
343443 s . stop ( pc . yellow ( "npm install failed (you can retry manually)" ) ) ;
344444 }
345445
346- // Wait for database and setup schema
347- if ( dbChoice === "new" && dbPromise ) {
348- s . start ( "Waiting for database to be ready..." ) ;
349- try {
350- const dbResult = await dbPromise ;
351- serviceId = dbResult . service_id ;
352- s . stop ( pc . green ( `Database created (${ serviceId } )` ) ) ;
353- } catch ( err ) {
354- s . stop ( pc . yellow ( "Database creation failed" ) ) ;
355- p . log . warn (
356- `You can create one later with: tiger service create --name ${ projectName } ` ,
357- ) ;
446+ // Wait for database to be ready (whether new or existing that was started)
447+ if ( serviceId ) {
448+ s . start ( "Checking database status..." ) ;
449+ const ready = await waitForDatabase ( serviceId , 3 * 60 * 1000 ) ;
450+ if ( ! ready ) {
451+ s . stop ( pc . red ( "Database failed to start" ) ) ;
452+ p . log . error ( "Database did not become ready in time. Please check Tiger Cloud dashboard." ) ;
453+ process . exit ( 1 ) ;
358454 }
455+ s . stop ( pc . green ( "Database is ready" ) ) ;
359456 }
360457
361458 if ( serviceId ) {
0 commit comments