Skip to content

Commit 2580522

Browse files
cevianclaude
andcommitted
feat(cli): unpause database automatically in run command
- Add extractServiceIdFromUrl() to parse service ID from DATABASE_URL - Add startDatabaseIfNeeded() to check and start paused/stopped databases - When launching existing project: wait synchronously for database to start - When selecting existing database during creation: start asynchronously - Fix waitForDatabase() to only return true when status is "ready" - Add error handling to fail fast on "service not found" errors - Simplify database creation flow by removing confusing promise wrapper - Exit immediately if database creation fails Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
1 parent 582d64d commit 2580522

File tree

1 file changed

+126
-29
lines changed

1 file changed

+126
-29
lines changed

packages/core/src/cli/run.ts

Lines changed: 126 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { promisify } from "node:util";
44
import { basename, join, resolve } from "node:path";
55
import * as p from "@clack/prompts";
66
import pc from "picocolors";
7+
import * as dotenv from "dotenv";
78
import {
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
*/
3638
async 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-z0-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+
62127
function 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

Comments
 (0)