@solcreek/cli 0.4.21 → 0.4.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +40 -0
- package/dist/commands/dashboard.d.ts +21 -0
- package/dist/commands/dashboard.js +72 -0
- package/dist/commands/deploy.d.ts +10 -0
- package/dist/commands/deploy.js +252 -0
- package/dist/commands/dev.d.ts +13 -0
- package/dist/commands/dev.js +77 -2
- package/dist/commands/doctor.js +9 -0
- package/dist/commands/init.d.ts +10 -0
- package/dist/commands/init.js +158 -2
- package/dist/commands/logs.d.ts +12 -0
- package/dist/commands/logs.js +69 -1
- package/dist/commands/resource-cmd.js +1 -1
- package/dist/commands/restart.d.ts +26 -0
- package/dist/commands/restart.js +55 -0
- package/dist/commands/rollback.d.ts +13 -0
- package/dist/commands/rollback.js +188 -1
- package/dist/commands/stop.d.ts +26 -0
- package/dist/commands/stop.js +65 -0
- package/dist/commands/top.d.ts +28 -0
- package/dist/commands/top.js +171 -0
- package/dist/dev/creekd-runner.d.ts +22 -0
- package/dist/dev/creekd-runner.js +188 -0
- package/dist/index.js +8 -0
- package/dist/utils/creekd-client.d.ts +152 -0
- package/dist/utils/creekd-client.js +144 -0
- package/dist/utils/gitignore.d.ts +2 -0
- package/dist/utils/gitignore.js +32 -0
- package/dist/utils/hostkey.d.ts +39 -0
- package/dist/utils/hostkey.js +84 -0
- package/dist/utils/hosts.d.ts +70 -0
- package/dist/utils/hosts.js +90 -0
- package/dist/utils/local-cache.d.ts +69 -0
- package/dist/utils/local-cache.js +100 -0
- package/dist/utils/nextjs.d.ts +4 -2
- package/dist/utils/nextjs.js +107 -38
- package/dist/utils/prepare-bundle.js +1 -1
- package/dist/utils/top-format.d.ts +4 -0
- package/dist/utils/top-format.js +32 -0
- package/dist/utils/watch.d.ts +81 -0
- package/dist/utils/watch.js +87 -0
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,45 @@
|
|
|
1
1
|
# @solcreek/cli
|
|
2
2
|
|
|
3
|
+
## 0.4.23
|
|
4
|
+
|
|
5
|
+
### Fixes / DX
|
|
6
|
+
|
|
7
|
+
- **`creek init` help now lists what it creates** — creek.toml (project
|
|
8
|
+
name, build command/output, detected framework) plus a worker/index.ts
|
|
9
|
+
example when you add a database — so first-timers know what to expect.
|
|
10
|
+
|
|
11
|
+
- **`creek doctor --json` prints a one-line summary to stderr** when a human
|
|
12
|
+
is watching, instead of only a wall of JSON. stdout stays pure JSON for
|
|
13
|
+
agents and pipes; CI / redirected runs stay silent.
|
|
14
|
+
|
|
15
|
+
- **The two SQLite doctor findings cross-reference each other.** A project
|
|
16
|
+
with both better-sqlite3 and Prisma no longer reads as two unrelated
|
|
17
|
+
problems — each notes it's the same Cloudflare-Workers SQLite migration.
|
|
18
|
+
|
|
19
|
+
- **`creek db/storage/cache attach --to` shows a value placeholder** in its
|
|
20
|
+
usage (`--to=<project>`) instead of a bare `--to`.
|
|
21
|
+
|
|
22
|
+
## 0.4.22
|
|
23
|
+
|
|
24
|
+
### Fixes / DX
|
|
25
|
+
|
|
26
|
+
- **Next.js deploys now set up the Creek adapter automatically.** Deploying
|
|
27
|
+
a Next.js (≥ 16.2.3) project no longer requires installing or configuring
|
|
28
|
+
anything — `creek deploy` fetches and runs the adapter on first use.
|
|
29
|
+
Projects outside the Creek repo previously fell back to an older build
|
|
30
|
+
path without it.
|
|
31
|
+
|
|
32
|
+
- **`creek deploy` no longer publishes from a non-interactive shell without
|
|
33
|
+
`--yes`.** In CI, an AI coding agent, or a pipe there is no prompt to
|
|
34
|
+
confirm, so a bare `creek deploy` now refuses and points you at
|
|
35
|
+
`--dry-run` (preview the plan) or `--yes` (confirm and deploy) instead of
|
|
36
|
+
shipping on its own. Interactive (terminal) use is unchanged.
|
|
37
|
+
|
|
38
|
+
- **Clearer Next.js diagnostics.** `creek doctor` and `creek deploy
|
|
39
|
+
--dry-run` no longer tell you to run `next build` to produce output that
|
|
40
|
+
Creek generates itself at deploy time, and the reported build-output path
|
|
41
|
+
now matches what actually ships.
|
|
42
|
+
|
|
3
43
|
## 0.4.6
|
|
4
44
|
|
|
5
45
|
### Features
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export declare const dashboardCommand: import("citty").CommandDef<{
|
|
2
|
+
json: {
|
|
3
|
+
type: "boolean";
|
|
4
|
+
description: string;
|
|
5
|
+
default: boolean;
|
|
6
|
+
};
|
|
7
|
+
yes: {
|
|
8
|
+
type: "boolean";
|
|
9
|
+
description: string;
|
|
10
|
+
default: boolean;
|
|
11
|
+
};
|
|
12
|
+
server: {
|
|
13
|
+
type: "string";
|
|
14
|
+
description: string;
|
|
15
|
+
};
|
|
16
|
+
token: {
|
|
17
|
+
type: "string";
|
|
18
|
+
description: string;
|
|
19
|
+
};
|
|
20
|
+
}>;
|
|
21
|
+
//# sourceMappingURL=dashboard.d.ts.map
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { defineCommand } from "citty";
|
|
2
|
+
import consola from "consola";
|
|
3
|
+
import { execSync } from "node:child_process";
|
|
4
|
+
import { globalArgs, resolveJsonMode, jsonOutput } from "../utils/output.js";
|
|
5
|
+
import { CreekdClient, CreekdApiError, getCreekdUrl } from "../utils/creekd-client.js";
|
|
6
|
+
export const dashboardCommand = defineCommand({
|
|
7
|
+
meta: {
|
|
8
|
+
name: "dashboard",
|
|
9
|
+
description: "Open the creekd web dashboard in your browser",
|
|
10
|
+
},
|
|
11
|
+
args: {
|
|
12
|
+
server: {
|
|
13
|
+
type: "string",
|
|
14
|
+
description: "creekd admin API URL (or $CREEKD_URL)",
|
|
15
|
+
},
|
|
16
|
+
token: {
|
|
17
|
+
type: "string",
|
|
18
|
+
description: "Bearer token (or $CREEKD_TOKEN)",
|
|
19
|
+
},
|
|
20
|
+
...globalArgs,
|
|
21
|
+
},
|
|
22
|
+
async run({ args }) {
|
|
23
|
+
const jsonMode = resolveJsonMode(args);
|
|
24
|
+
const url = args.server || getCreekdUrl();
|
|
25
|
+
const client = new CreekdClient(url, args.token || process.env.CREEKD_TOKEN || process.env.CREEKCTL_TOKEN || "");
|
|
26
|
+
// Verify creekd is reachable
|
|
27
|
+
try {
|
|
28
|
+
await client.listApps();
|
|
29
|
+
}
|
|
30
|
+
catch (err) {
|
|
31
|
+
if (err instanceof CreekdApiError && err.status === 401) {
|
|
32
|
+
if (jsonMode)
|
|
33
|
+
jsonOutput({ ok: false, error: "unauthorized", message: "Authentication required" }, 1);
|
|
34
|
+
consola.error("Authentication failed. Set CREEKD_TOKEN or use --token.");
|
|
35
|
+
process.exit(1);
|
|
36
|
+
}
|
|
37
|
+
if (jsonMode)
|
|
38
|
+
jsonOutput({ ok: false, error: "unreachable", message: `Cannot reach creekd at ${url}` }, 1, [
|
|
39
|
+
{ command: "creek top --server <url>", description: "Check connection" },
|
|
40
|
+
]);
|
|
41
|
+
consola.error(`Cannot reach creekd at ${url}`);
|
|
42
|
+
consola.info("Is creekd running? Start it with: creekd");
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|
|
45
|
+
// In production, dashboard is served at the same URL as creekd (via Caddy).
|
|
46
|
+
// In dev, it's typically at localhost:3000 (Vite).
|
|
47
|
+
const dashboardUrl = url.includes(":9080")
|
|
48
|
+
? url.replace(":9080", ":3000")
|
|
49
|
+
: url;
|
|
50
|
+
if (jsonMode) {
|
|
51
|
+
jsonOutput({ ok: true, url: dashboardUrl }, 0, [
|
|
52
|
+
{ command: "creek top", description: "CLI monitoring alternative" },
|
|
53
|
+
]);
|
|
54
|
+
}
|
|
55
|
+
consola.success(`Opening dashboard: ${dashboardUrl}`);
|
|
56
|
+
openBrowser(dashboardUrl);
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
function openBrowser(url) {
|
|
60
|
+
try {
|
|
61
|
+
const cmd = process.platform === "darwin"
|
|
62
|
+
? `open "${url}"`
|
|
63
|
+
: process.platform === "win32"
|
|
64
|
+
? `start "" "${url}"`
|
|
65
|
+
: `xdg-open "${url}"`;
|
|
66
|
+
execSync(cmd, { stdio: "ignore" });
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
consola.info(`Open manually: ${url}`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=dashboard.js.map
|
|
@@ -29,6 +29,16 @@ export declare const deployCommand: import("citty").CommandDef<{
|
|
|
29
29
|
description: string;
|
|
30
30
|
required: false;
|
|
31
31
|
};
|
|
32
|
+
watch: {
|
|
33
|
+
type: "boolean";
|
|
34
|
+
description: string;
|
|
35
|
+
default: false;
|
|
36
|
+
};
|
|
37
|
+
"watch-timeout-ms": {
|
|
38
|
+
type: "string";
|
|
39
|
+
description: string;
|
|
40
|
+
required: false;
|
|
41
|
+
};
|
|
32
42
|
json: {
|
|
33
43
|
type: "boolean";
|
|
34
44
|
description: string;
|
package/dist/commands/deploy.js
CHANGED
|
@@ -16,6 +16,8 @@ import { ensureTosAccepted } from "../utils/tos.js";
|
|
|
16
16
|
import { hasAdapterOutput } from "../utils/nextjs.js";
|
|
17
17
|
import { isRepoUrl, parseRepoUrl, validateRepoUrl, validateSubpath, RepoUrlError } from "../utils/repo-url.js";
|
|
18
18
|
import { checkGitInstalled, cloneRepo, detectPackageManager, installDependencies, cleanupDir as cleanupCloneDir, GitCloneError } from "../utils/git-clone.js";
|
|
19
|
+
import { CreekdClient, getCreekdUrl } from "../utils/creekd-client.js";
|
|
20
|
+
import { watchDeploy } from "../utils/watch.js";
|
|
19
21
|
function section(name) {
|
|
20
22
|
consola.log(`\n \x1b[2m[${name}]\x1b[0m`);
|
|
21
23
|
}
|
|
@@ -243,6 +245,16 @@ export const deployCommand = defineCommand({
|
|
|
243
245
|
description: "Target project slug or UUID. Required with --from-github when not run inside a project directory.",
|
|
244
246
|
required: false,
|
|
245
247
|
},
|
|
248
|
+
watch: {
|
|
249
|
+
type: "boolean",
|
|
250
|
+
description: "After deploy lands, poll status.conditions[] until Ready=True or Degraded reason=DeployTimeout (self-host creekd target only). Default poll = 1s, default budget = 5min.",
|
|
251
|
+
default: false,
|
|
252
|
+
},
|
|
253
|
+
"watch-timeout-ms": {
|
|
254
|
+
type: "string",
|
|
255
|
+
description: "Client-side watch budget in milliseconds (default 300000 = 5min). Independent of the daemon's progressing_timeout.",
|
|
256
|
+
required: false,
|
|
257
|
+
},
|
|
246
258
|
},
|
|
247
259
|
async run({ args }) {
|
|
248
260
|
const jsonMode = resolveJsonMode(args);
|
|
@@ -256,6 +268,27 @@ export const deployCommand = defineCommand({
|
|
|
256
268
|
: process.cwd();
|
|
257
269
|
return await dryRunPlan(dryCwd, args, jsonMode);
|
|
258
270
|
}
|
|
271
|
+
// --- Safety gate: no silent deploys in non-interactive environments ---
|
|
272
|
+
// Deploying publishes to a public URL — irreversible enough that we
|
|
273
|
+
// don't want it to happen by accident. In a TTY a human is present to
|
|
274
|
+
// see and abort; in a non-TTY (AI agent, CI, pipe) there is no prompt,
|
|
275
|
+
// so a bare `creek deploy` would otherwise just ship. Require an
|
|
276
|
+
// explicit --yes there instead. This is what makes the "safe to run
|
|
277
|
+
// from an AI coding agent" promise true: a bare call refuses and points
|
|
278
|
+
// at --dry-run / --yes rather than publishing on its own. Documented CI
|
|
279
|
+
// already passes --yes, so this doesn't regress automated deploys.
|
|
280
|
+
if (!isTTY && !args.yes) {
|
|
281
|
+
const breadcrumbs = [
|
|
282
|
+
{ command: "creek deploy --dry-run", description: "Preview the plan without executing (no network, no uploads)" },
|
|
283
|
+
{ command: "creek deploy --yes", description: "Confirm and deploy without an interactive prompt" },
|
|
284
|
+
];
|
|
285
|
+
const message = "Refusing to deploy from a non-interactive environment without confirmation. Re-run with --dry-run to preview the plan, or --yes to deploy.";
|
|
286
|
+
if (jsonMode) {
|
|
287
|
+
jsonOutput({ ok: false, error: "confirmation_required", message }, 1, breadcrumbs);
|
|
288
|
+
}
|
|
289
|
+
consola.error(message);
|
|
290
|
+
process.exit(1);
|
|
291
|
+
}
|
|
259
292
|
// --- Ensure ToS accepted ---
|
|
260
293
|
const autoConfirm = shouldAutoConfirm(args);
|
|
261
294
|
const tos = await ensureTosAccepted(autoConfirm);
|
|
@@ -318,6 +351,13 @@ export const deployCommand = defineCommand({
|
|
|
318
351
|
consola.warn(` Binding '${ub.name}' (${ub.type}) is not yet supported — will be skipped`);
|
|
319
352
|
}
|
|
320
353
|
}
|
|
354
|
+
// creekd target → deploy to local creekd via admin API
|
|
355
|
+
if (resolved.target === "creekd") {
|
|
356
|
+
return await deployCreekd(cwd, resolved, args["skip-build"], jsonMode, {
|
|
357
|
+
watch: args.watch === true,
|
|
358
|
+
watchTimeoutMs: parseWatchTimeoutMs(args["watch-timeout-ms"]),
|
|
359
|
+
});
|
|
360
|
+
}
|
|
321
361
|
if (token) {
|
|
322
362
|
return await deployAuthenticated(cwd, resolved, token, args["skip-build"], jsonMode, args["no-cache"]);
|
|
323
363
|
}
|
|
@@ -344,6 +384,203 @@ export const deployCommand = defineCommand({
|
|
|
344
384
|
process.exit(1);
|
|
345
385
|
},
|
|
346
386
|
});
|
|
387
|
+
async function deployCreekd(cwd, resolved, skipBuild, jsonMode, watchOpts = { watch: false }) {
|
|
388
|
+
const client = new CreekdClient();
|
|
389
|
+
// 1. Verify creekd is reachable
|
|
390
|
+
try {
|
|
391
|
+
await client.listApps();
|
|
392
|
+
}
|
|
393
|
+
catch {
|
|
394
|
+
const msg = `Cannot reach creekd at ${getCreekdUrl()}. Is it running?`;
|
|
395
|
+
if (jsonMode)
|
|
396
|
+
jsonOutput({ error: "creekd_unreachable", message: msg }, 1, [
|
|
397
|
+
{ command: "creekd", description: "Start the daemon" },
|
|
398
|
+
]);
|
|
399
|
+
consola.error(msg);
|
|
400
|
+
process.exit(1);
|
|
401
|
+
}
|
|
402
|
+
if (!jsonMode) {
|
|
403
|
+
section("Detect");
|
|
404
|
+
consola.info(` Target: creekd (local)`);
|
|
405
|
+
consola.info(` Project: ${resolved.projectName}`);
|
|
406
|
+
if (resolved.framework)
|
|
407
|
+
consola.info(` Framework: ${resolved.framework}`);
|
|
408
|
+
}
|
|
409
|
+
// 2. Build (unless skipped)
|
|
410
|
+
if (!skipBuild && resolved.buildCommand) {
|
|
411
|
+
if (!jsonMode) {
|
|
412
|
+
section("Build");
|
|
413
|
+
consola.start(` ${resolved.buildCommand}`);
|
|
414
|
+
}
|
|
415
|
+
try {
|
|
416
|
+
execSync(resolved.buildCommand, { cwd, stdio: jsonMode ? "pipe" : "inherit" });
|
|
417
|
+
if (!jsonMode)
|
|
418
|
+
consola.success(" Build complete");
|
|
419
|
+
}
|
|
420
|
+
catch (e) {
|
|
421
|
+
const msg = `Build failed: ${resolved.buildCommand}`;
|
|
422
|
+
if (jsonMode)
|
|
423
|
+
jsonOutput({ ok: false, error: "build_failed", message: msg }, 1, []);
|
|
424
|
+
consola.error(msg);
|
|
425
|
+
process.exit(1);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
// 3. Detect entry point + runtime
|
|
429
|
+
const entryPoint = resolved.workerEntry ?? resolved.buildOutput + "/index.js";
|
|
430
|
+
const runtime = "bun"; // default; could read from creek.toml [app].runtime
|
|
431
|
+
const port = 3000; // TODO: auto-assign or read from config
|
|
432
|
+
// 4. Deploy via CreekdClient (idempotent: spawn if absent, deploy if running)
|
|
433
|
+
if (!jsonMode) {
|
|
434
|
+
section("Deploy");
|
|
435
|
+
consola.start(" Deploying to creekd...");
|
|
436
|
+
}
|
|
437
|
+
const startTime = Date.now();
|
|
438
|
+
try {
|
|
439
|
+
const spawnReq = { id: resolved.projectName, runtime, entry: entryPoint, port };
|
|
440
|
+
let app;
|
|
441
|
+
try {
|
|
442
|
+
app = await client.spawnApp(spawnReq);
|
|
443
|
+
}
|
|
444
|
+
catch (e) {
|
|
445
|
+
if (e.code === "already_running") {
|
|
446
|
+
app = await client.deployApp(resolved.projectName, { runtime, entry: entryPoint, port });
|
|
447
|
+
}
|
|
448
|
+
else {
|
|
449
|
+
throw e;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
// Release phase (pre-traffic command, e.g. migrations)
|
|
453
|
+
if (resolved.releaseCommand) {
|
|
454
|
+
if (!jsonMode) {
|
|
455
|
+
section("Release");
|
|
456
|
+
consola.start(` ${resolved.releaseCommand}`);
|
|
457
|
+
}
|
|
458
|
+
try {
|
|
459
|
+
execSync(resolved.releaseCommand, {
|
|
460
|
+
cwd,
|
|
461
|
+
stdio: jsonMode ? "pipe" : "inherit",
|
|
462
|
+
timeout: (resolved.releaseTimeout ?? 300) * 1000,
|
|
463
|
+
});
|
|
464
|
+
if (!jsonMode)
|
|
465
|
+
consola.success(" Release complete");
|
|
466
|
+
}
|
|
467
|
+
catch (e) {
|
|
468
|
+
const msg = e.killed
|
|
469
|
+
? `Release command timed out after ${resolved.releaseTimeout ?? 300}s`
|
|
470
|
+
: `Release command failed: ${e.stderr?.toString() || e.message}`;
|
|
471
|
+
if (jsonMode)
|
|
472
|
+
jsonOutput({ ok: false, error: "release_failed", message: msg }, 1);
|
|
473
|
+
consola.error(msg);
|
|
474
|
+
try {
|
|
475
|
+
await client.stopApp(resolved.projectName);
|
|
476
|
+
}
|
|
477
|
+
catch { }
|
|
478
|
+
process.exit(1);
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
const elapsedS = ((Date.now() - startTime) / 1000).toFixed(1);
|
|
482
|
+
// --watch — poll status.conditions[] until Ready or
|
|
483
|
+
// deploy_stuck. Runs AFTER `creekctl ensure` returns success
|
|
484
|
+
// so the daemon already has the spec; we're observing the
|
|
485
|
+
// supervisor's convergence. Skipped when --watch is off.
|
|
486
|
+
let watchSummary;
|
|
487
|
+
if (watchOpts.watch) {
|
|
488
|
+
if (!jsonMode) {
|
|
489
|
+
section("Watch");
|
|
490
|
+
consola.start(" Polling status.conditions[]...");
|
|
491
|
+
}
|
|
492
|
+
watchSummary = await watchDeploy(new CreekdClient(), resolved.projectName, {
|
|
493
|
+
timeoutMs: watchOpts.watchTimeoutMs,
|
|
494
|
+
onPoll: jsonMode
|
|
495
|
+
? undefined
|
|
496
|
+
: (envelope) => {
|
|
497
|
+
const conds = envelope.status?.conditions ?? [];
|
|
498
|
+
const summary = conds.map((c) => `${c.type}=${c.status}`).join(" ");
|
|
499
|
+
consola.info(` ${summary}`);
|
|
500
|
+
},
|
|
501
|
+
});
|
|
502
|
+
}
|
|
503
|
+
if (jsonMode) {
|
|
504
|
+
jsonOutput({
|
|
505
|
+
ok: watchSummary ? watchSummary.ok : true,
|
|
506
|
+
url: `http://localhost:${port}`,
|
|
507
|
+
appId: resolved.projectName,
|
|
508
|
+
port,
|
|
509
|
+
runtime,
|
|
510
|
+
target: "creekd",
|
|
511
|
+
buildTimeS: parseFloat(elapsedS),
|
|
512
|
+
mode: "creekd-local",
|
|
513
|
+
...(watchSummary ? { watch: watchSummaryWire(watchSummary) } : {}),
|
|
514
|
+
}, watchSummary && !watchSummary.ok ? 1 : 0, [
|
|
515
|
+
{ command: `creek top ${resolved.projectName}`, description: "Check app status" },
|
|
516
|
+
{ command: `creek logs --server ${resolved.projectName}`, description: "View app logs" },
|
|
517
|
+
{ command: `creek exec -- bun run seed.ts`, description: "Run one-off command" },
|
|
518
|
+
]);
|
|
519
|
+
}
|
|
520
|
+
else {
|
|
521
|
+
if (watchSummary && !watchSummary.ok) {
|
|
522
|
+
consola.error(` Watch failed: ${watchSummary.reason}`);
|
|
523
|
+
if (watchSummary.reason === "deploy_stuck") {
|
|
524
|
+
consola.info(" The daemon flipped Degraded reason=DeployTimeout — supervisor did not converge.");
|
|
525
|
+
consola.info(` creek logs --server ${resolved.projectName} Inspect why`);
|
|
526
|
+
}
|
|
527
|
+
else if (watchSummary.reason === "watch_timeout") {
|
|
528
|
+
consola.info(` Client gave up after ${watchSummary.elapsedMs}ms. The daemon may still converge.`);
|
|
529
|
+
}
|
|
530
|
+
else if (watchSummary.reason === "fetch_failed") {
|
|
531
|
+
consola.info(` Polling failed: ${watchSummary.error.message}`);
|
|
532
|
+
}
|
|
533
|
+
process.exit(1);
|
|
534
|
+
}
|
|
535
|
+
consola.success(` Deployed to creekd (${elapsedS}s)`);
|
|
536
|
+
consola.info(` http://localhost:${port}`);
|
|
537
|
+
console.log("");
|
|
538
|
+
consola.info(" Next steps:");
|
|
539
|
+
consola.info(` creek top ${resolved.projectName} Check status`);
|
|
540
|
+
consola.info(` creek logs --server ${resolved.projectName} View logs`);
|
|
541
|
+
consola.info(` creekctl events ${resolved.projectName} Stream events`);
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
catch (e) {
|
|
545
|
+
const output = e.stdout?.toString() || e.stderr?.toString() || e.message;
|
|
546
|
+
const msg = `creekctl deploy failed: ${output}`;
|
|
547
|
+
if (jsonMode)
|
|
548
|
+
jsonOutput({ ok: false, error: "creekd_deploy_failed", message: msg }, 1, []);
|
|
549
|
+
consola.error(msg);
|
|
550
|
+
process.exit(1);
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
/** Parse the --watch-timeout-ms flag. Empty / unset → undefined
|
|
554
|
+
* so watchDeploy applies its own default. Non-numeric → exits. */
|
|
555
|
+
function parseWatchTimeoutMs(raw) {
|
|
556
|
+
if (raw === undefined || raw === null || raw === "")
|
|
557
|
+
return undefined;
|
|
558
|
+
const s = String(raw);
|
|
559
|
+
if (!/^\d+$/.test(s)) {
|
|
560
|
+
consola.error(`--watch-timeout-ms must be a positive integer (got "${raw}")`);
|
|
561
|
+
process.exit(1);
|
|
562
|
+
}
|
|
563
|
+
const n = Number.parseInt(s, 10);
|
|
564
|
+
if (!Number.isFinite(n) || n <= 0) {
|
|
565
|
+
consola.error(`--watch-timeout-ms must be a positive integer (got "${raw}")`);
|
|
566
|
+
process.exit(1);
|
|
567
|
+
}
|
|
568
|
+
return n;
|
|
569
|
+
}
|
|
570
|
+
/** Pluck the JSON-friendly fields out of a WatchResult — strips
|
|
571
|
+
* the AppEnvelope which is too noisy for the deploy summary. */
|
|
572
|
+
function watchSummaryWire(r) {
|
|
573
|
+
if (r.ok)
|
|
574
|
+
return { ok: true, reason: r.reason };
|
|
575
|
+
if (r.reason === "deploy_stuck") {
|
|
576
|
+
const conds = r.envelope.status?.conditions ?? [];
|
|
577
|
+
return { ok: false, reason: r.reason, conditions: conds };
|
|
578
|
+
}
|
|
579
|
+
if (r.reason === "watch_timeout") {
|
|
580
|
+
return { ok: false, reason: r.reason, elapsedMs: r.elapsedMs };
|
|
581
|
+
}
|
|
582
|
+
return { ok: false, reason: r.reason, error: r.error.message };
|
|
583
|
+
}
|
|
347
584
|
export const CLI_TERMINAL_STATUSES = new Set(["active", "failed", "cancelled"]);
|
|
348
585
|
export const CLI_IN_FLIGHT_STATUSES = new Set([
|
|
349
586
|
"queued",
|
|
@@ -514,12 +751,18 @@ async function deployFromGithub(options) {
|
|
|
514
751
|
// Report the outcome
|
|
515
752
|
if (targetDeployment.status === "active") {
|
|
516
753
|
if (jsonMode) {
|
|
754
|
+
const elapsedS = ((Date.now() - startedAt) / 1000).toFixed(1);
|
|
517
755
|
jsonOutput({
|
|
518
756
|
ok: true,
|
|
519
757
|
deploymentId: targetDeployment.id,
|
|
520
758
|
version: targetDeployment.version,
|
|
521
759
|
status: "active",
|
|
522
760
|
url: targetDeployment.url,
|
|
761
|
+
branch: targetDeployment.branch,
|
|
762
|
+
buildTimeS: parseFloat(elapsedS),
|
|
763
|
+
mode: "production",
|
|
764
|
+
source: "github",
|
|
765
|
+
rollbackCommand: `creek rollback --project ${projectSlug}`,
|
|
523
766
|
}, 0, NO_PROJECT_BREADCRUMBS);
|
|
524
767
|
}
|
|
525
768
|
else {
|
|
@@ -777,9 +1020,11 @@ async function deploySandbox(cwd, skipBuild, jsonMode = false, resolved, tos) {
|
|
|
777
1020
|
sandboxId: result.sandboxId,
|
|
778
1021
|
url: status.previewUrl,
|
|
779
1022
|
deployDurationMs: status.deployDurationMs,
|
|
1023
|
+
buildTimeS: status.deployDurationMs ? parseFloat((status.deployDurationMs / 1000).toFixed(1)) : null,
|
|
780
1024
|
expiresAt: result.expiresAt,
|
|
781
1025
|
expiresInMinutes: expiresInMinutes(result.expiresAt),
|
|
782
1026
|
framework: framework ?? null,
|
|
1027
|
+
renderMode: effectiveRenderMode,
|
|
783
1028
|
assetCount: fileList.length,
|
|
784
1029
|
mode: "sandbox",
|
|
785
1030
|
}, 0, [
|
|
@@ -1062,13 +1307,20 @@ async function deployAuthenticated(cwd, resolved, token, skipBuild, jsonMode = f
|
|
|
1062
1307
|
// Silent — build log is best-effort for now.
|
|
1063
1308
|
});
|
|
1064
1309
|
if (jsonMode) {
|
|
1310
|
+
const elapsedS = ((Date.now() - start) / 1000).toFixed(1);
|
|
1065
1311
|
jsonOutput({
|
|
1066
1312
|
ok: true,
|
|
1067
1313
|
url: res.url ?? res.previewUrl,
|
|
1068
1314
|
previewUrl: res.previewUrl,
|
|
1069
1315
|
deploymentId: deployment.id,
|
|
1316
|
+
version: res.deployment?.version ?? null,
|
|
1070
1317
|
project: project.slug,
|
|
1318
|
+
framework: framework ?? null,
|
|
1319
|
+
renderMode: effectiveRenderMode,
|
|
1320
|
+
assetCount: fileList.length,
|
|
1321
|
+
buildTimeS: parseFloat(elapsedS),
|
|
1071
1322
|
mode: "production",
|
|
1323
|
+
rollbackCommand: `creek rollback --project ${project.slug}`,
|
|
1072
1324
|
...(resolved.cron.length > 0 ? { cron: resolved.cron } : {}),
|
|
1073
1325
|
}, 0, [
|
|
1074
1326
|
{ command: `creek status`, description: "Check deployment status" },
|
package/dist/commands/dev.d.ts
CHANGED
|
@@ -4,10 +4,23 @@ export declare const devCommand: import("citty").CommandDef<{
|
|
|
4
4
|
description: string;
|
|
5
5
|
default: string;
|
|
6
6
|
};
|
|
7
|
+
target: {
|
|
8
|
+
type: "string";
|
|
9
|
+
description: string;
|
|
10
|
+
required: false;
|
|
11
|
+
};
|
|
7
12
|
reset: {
|
|
8
13
|
type: "boolean";
|
|
9
14
|
description: string;
|
|
10
15
|
};
|
|
16
|
+
dashboard: {
|
|
17
|
+
type: "boolean";
|
|
18
|
+
description: string;
|
|
19
|
+
};
|
|
20
|
+
top: {
|
|
21
|
+
type: "boolean";
|
|
22
|
+
description: string;
|
|
23
|
+
};
|
|
11
24
|
json: {
|
|
12
25
|
type: "boolean";
|
|
13
26
|
description: string;
|
package/dist/commands/dev.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { defineCommand } from "citty";
|
|
2
2
|
import { consola } from "consola";
|
|
3
|
-
import {
|
|
3
|
+
import { execSync } from "node:child_process";
|
|
4
|
+
import { resolveConfig, formatDetectionSummary, DEPLOY_TARGETS } from "@solcreek/sdk";
|
|
4
5
|
import { globalArgs } from "../utils/output.js";
|
|
5
6
|
import { DevServer } from "../dev/server.js";
|
|
6
7
|
export const devCommand = defineCommand({
|
|
7
8
|
meta: {
|
|
8
9
|
name: "dev",
|
|
9
|
-
description: "Start local development server",
|
|
10
|
+
description: "Start local development server. Auto-detects target from creek.toml (cf = Miniflare, creekd = sandbox VM with real Postgres/Redis).",
|
|
10
11
|
},
|
|
11
12
|
args: {
|
|
12
13
|
...globalArgs,
|
|
@@ -15,10 +16,23 @@ export const devCommand = defineCommand({
|
|
|
15
16
|
description: "Port number (default: 3000)",
|
|
16
17
|
default: "3000",
|
|
17
18
|
},
|
|
19
|
+
target: {
|
|
20
|
+
type: "string",
|
|
21
|
+
description: "Deploy target override: cf (Miniflare) or creekd (sandbox VM)",
|
|
22
|
+
required: false,
|
|
23
|
+
},
|
|
18
24
|
reset: {
|
|
19
25
|
type: "boolean",
|
|
20
26
|
description: "Clear local data before starting",
|
|
21
27
|
},
|
|
28
|
+
dashboard: {
|
|
29
|
+
type: "boolean",
|
|
30
|
+
description: "Open the web dashboard after server starts",
|
|
31
|
+
},
|
|
32
|
+
top: {
|
|
33
|
+
type: "boolean",
|
|
34
|
+
description: "Show creek top alongside the dev server",
|
|
35
|
+
},
|
|
22
36
|
},
|
|
23
37
|
async run({ args }) {
|
|
24
38
|
const cwd = process.cwd();
|
|
@@ -35,7 +49,36 @@ export const devCommand = defineCommand({
|
|
|
35
49
|
consola.error(`Invalid port: ${args.port}`);
|
|
36
50
|
process.exit(1);
|
|
37
51
|
}
|
|
52
|
+
// Target: CLI flag > creek.toml > auto-detect (already resolved)
|
|
53
|
+
const target = args.target ?? config.target;
|
|
54
|
+
if (args.target && !DEPLOY_TARGETS.includes(args.target)) {
|
|
55
|
+
consola.error(`Invalid target "${args.target}". Valid: ${DEPLOY_TARGETS.join(", ")}`);
|
|
56
|
+
process.exit(1);
|
|
57
|
+
}
|
|
38
58
|
consola.info(`Detected: ${formatDetectionSummary(config)}`);
|
|
59
|
+
consola.info(`Target: ${target}${args.target ? " (--target override)" : " (from config)"}`);
|
|
60
|
+
if (target === "creekd") {
|
|
61
|
+
const { CreekdDevServer } = await import("../dev/creekd-runner.js");
|
|
62
|
+
const server = new CreekdDevServer({ cwd, port, config, reset: !!args.reset });
|
|
63
|
+
const shutdown = async () => {
|
|
64
|
+
consola.info("Shutting down...");
|
|
65
|
+
await server.stop();
|
|
66
|
+
process.exit(0);
|
|
67
|
+
};
|
|
68
|
+
process.on("SIGINT", shutdown);
|
|
69
|
+
process.on("SIGTERM", shutdown);
|
|
70
|
+
try {
|
|
71
|
+
await server.start();
|
|
72
|
+
}
|
|
73
|
+
catch (e) {
|
|
74
|
+
consola.error(`Failed to start creekd dev server: ${e.message}`);
|
|
75
|
+
await server.stop();
|
|
76
|
+
process.exit(1);
|
|
77
|
+
}
|
|
78
|
+
afterStart(args, port);
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
// CF Workers path (existing)
|
|
39
82
|
const server = new DevServer({
|
|
40
83
|
cwd,
|
|
41
84
|
port,
|
|
@@ -58,6 +101,7 @@ export const devCommand = defineCommand({
|
|
|
58
101
|
await server.stop();
|
|
59
102
|
process.exit(1);
|
|
60
103
|
}
|
|
104
|
+
afterStart(args, port);
|
|
61
105
|
// Interactive trigger commands (only if cron/queue configured)
|
|
62
106
|
if (config.cron.length > 0 || config.queue) {
|
|
63
107
|
const { createInterface } = await import("node:readline");
|
|
@@ -99,4 +143,35 @@ export const devCommand = defineCommand({
|
|
|
99
143
|
}
|
|
100
144
|
},
|
|
101
145
|
});
|
|
146
|
+
function afterStart(args, port) {
|
|
147
|
+
if (args.dashboard) {
|
|
148
|
+
const url = `http://localhost:${port}`;
|
|
149
|
+
consola.info(`Opening dashboard: ${url}`);
|
|
150
|
+
openBrowser(url);
|
|
151
|
+
}
|
|
152
|
+
if (args.top) {
|
|
153
|
+
import("../utils/creekd-client.js").then(({ getCreekdUrl }) => {
|
|
154
|
+
const topUrl = getCreekdUrl();
|
|
155
|
+
consola.info(`Starting creek top (${topUrl})...`);
|
|
156
|
+
import("node:child_process").then(({ spawn }) => {
|
|
157
|
+
spawn(process.execPath, [process.argv[1], "top", "--server", topUrl], {
|
|
158
|
+
stdio: "inherit",
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
function openBrowser(url) {
|
|
165
|
+
try {
|
|
166
|
+
const cmd = process.platform === "darwin"
|
|
167
|
+
? `open "${url}"`
|
|
168
|
+
: process.platform === "win32"
|
|
169
|
+
? `start "" "${url}"`
|
|
170
|
+
: `xdg-open "${url}"`;
|
|
171
|
+
execSync(cmd, { stdio: "ignore" });
|
|
172
|
+
}
|
|
173
|
+
catch {
|
|
174
|
+
consola.info(`Open manually: ${url}`);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
102
177
|
//# sourceMappingURL=dev.js.map
|
package/dist/commands/doctor.js
CHANGED
|
@@ -55,6 +55,15 @@ export const doctorCommand = defineCommand({
|
|
|
55
55
|
const ctx = buildDoctorContext(cwd);
|
|
56
56
|
const report = runDoctor(ctx);
|
|
57
57
|
if (jsonMode) {
|
|
58
|
+
// A one-line human summary on stderr so stdout stays pure JSON for
|
|
59
|
+
// agents/pipes, while a person who pipes this in a terminal isn't
|
|
60
|
+
// staring at a wall of JSON. Only when stderr is a TTY (a human is
|
|
61
|
+
// watching) — fully-redirected/CI runs stay silent.
|
|
62
|
+
if (process.stderr.isTTY) {
|
|
63
|
+
const { error, warn, info } = report.summary;
|
|
64
|
+
const counts = `${error} error${s(error)}, ${warn} warning${s(warn)}, ${info} info`;
|
|
65
|
+
process.stderr.write(`creek doctor: ${report.ok ? "ok" : "issues found"} — ${counts}\n`);
|
|
66
|
+
}
|
|
58
67
|
jsonOutput({
|
|
59
68
|
ok: report.ok,
|
|
60
69
|
cwd,
|
package/dist/commands/init.d.ts
CHANGED
|
@@ -14,5 +14,15 @@ export declare const initCommand: import("citty").CommandDef<{
|
|
|
14
14
|
description: string;
|
|
15
15
|
required: false;
|
|
16
16
|
};
|
|
17
|
+
adopt: {
|
|
18
|
+
type: "string";
|
|
19
|
+
description: string;
|
|
20
|
+
required: false;
|
|
21
|
+
};
|
|
22
|
+
"hostkey-fingerprint": {
|
|
23
|
+
type: "string";
|
|
24
|
+
description: string;
|
|
25
|
+
required: false;
|
|
26
|
+
};
|
|
17
27
|
}>;
|
|
18
28
|
//# sourceMappingURL=init.d.ts.map
|