@solcreek/cli 0.4.21 → 0.4.22
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 +21 -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/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/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
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { defineCommand } from "citty";
|
|
2
|
+
import consola from "consola";
|
|
3
|
+
import { globalArgs, resolveJsonMode, jsonOutput, shouldAutoConfirm } from "../utils/output.js";
|
|
4
|
+
import { CreekdClient, CreekdApiError, getCreekdUrl } from "../utils/creekd-client.js";
|
|
5
|
+
export const stopCommand = defineCommand({
|
|
6
|
+
meta: {
|
|
7
|
+
name: "stop",
|
|
8
|
+
description: "Stop an app on a creekd instance",
|
|
9
|
+
},
|
|
10
|
+
args: {
|
|
11
|
+
id: {
|
|
12
|
+
type: "positional",
|
|
13
|
+
description: "App ID to stop",
|
|
14
|
+
required: true,
|
|
15
|
+
},
|
|
16
|
+
server: {
|
|
17
|
+
type: "string",
|
|
18
|
+
description: "creekd admin API URL (or $CREEKD_URL)",
|
|
19
|
+
},
|
|
20
|
+
token: {
|
|
21
|
+
type: "string",
|
|
22
|
+
description: "Bearer token (or $CREEKD_TOKEN)",
|
|
23
|
+
},
|
|
24
|
+
...globalArgs,
|
|
25
|
+
},
|
|
26
|
+
async run({ args }) {
|
|
27
|
+
const jsonMode = resolveJsonMode(args);
|
|
28
|
+
const client = new CreekdClient(args.server || getCreekdUrl(), args.token || process.env.CREEKD_TOKEN || process.env.CREEKCTL_TOKEN || "");
|
|
29
|
+
const id = args.id;
|
|
30
|
+
if (!shouldAutoConfirm(args) && !jsonMode) {
|
|
31
|
+
const readline = await import("node:readline/promises");
|
|
32
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
33
|
+
const answer = await rl.question(`Stop app "${id}"? [y/N] `);
|
|
34
|
+
rl.close();
|
|
35
|
+
if (answer.toLowerCase() !== "y") {
|
|
36
|
+
consola.info("Aborted.");
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
try {
|
|
41
|
+
await client.stopApp(id);
|
|
42
|
+
if (jsonMode) {
|
|
43
|
+
jsonOutput({ ok: true, stopped: id }, 0, [
|
|
44
|
+
{ command: `creek top`, description: "Live process overview" },
|
|
45
|
+
]);
|
|
46
|
+
}
|
|
47
|
+
consola.success(`Stopped ${id}`);
|
|
48
|
+
}
|
|
49
|
+
catch (err) {
|
|
50
|
+
if (err instanceof CreekdApiError) {
|
|
51
|
+
if (jsonMode)
|
|
52
|
+
jsonOutput({ ok: false, error: err.code, message: err.message }, 1);
|
|
53
|
+
if (err.status === 404) {
|
|
54
|
+
consola.error(`App "${id}" not found.`);
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
consola.error(`Stop failed: ${err.message}`);
|
|
58
|
+
}
|
|
59
|
+
process.exit(1);
|
|
60
|
+
}
|
|
61
|
+
throw err;
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
//# sourceMappingURL=stop.js.map
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export declare const topCommand: import("citty").CommandDef<{
|
|
2
|
+
server: {
|
|
3
|
+
type: "string";
|
|
4
|
+
description: string;
|
|
5
|
+
required: false;
|
|
6
|
+
};
|
|
7
|
+
token: {
|
|
8
|
+
type: "string";
|
|
9
|
+
description: string;
|
|
10
|
+
required: false;
|
|
11
|
+
};
|
|
12
|
+
interval: {
|
|
13
|
+
type: "string";
|
|
14
|
+
description: string;
|
|
15
|
+
default: string;
|
|
16
|
+
};
|
|
17
|
+
json: {
|
|
18
|
+
type: "boolean";
|
|
19
|
+
description: string;
|
|
20
|
+
default: boolean;
|
|
21
|
+
};
|
|
22
|
+
yes: {
|
|
23
|
+
type: "boolean";
|
|
24
|
+
description: string;
|
|
25
|
+
default: boolean;
|
|
26
|
+
};
|
|
27
|
+
}>;
|
|
28
|
+
//# sourceMappingURL=top.d.ts.map
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { defineCommand } from "citty";
|
|
2
|
+
import consola from "consola";
|
|
3
|
+
import { globalArgs, resolveJsonMode, jsonOutput, isTTY } from "../utils/output.js";
|
|
4
|
+
import { CreekdClient, CreekdApiError, getCreekdUrl, } from "../utils/creekd-client.js";
|
|
5
|
+
import { fmtBytes, fmtDuration, calcCpuPercent } from "../utils/top-format.js";
|
|
6
|
+
export const topCommand = defineCommand({
|
|
7
|
+
meta: {
|
|
8
|
+
name: "top",
|
|
9
|
+
description: "Live view of apps on a creekd instance",
|
|
10
|
+
},
|
|
11
|
+
args: {
|
|
12
|
+
...globalArgs,
|
|
13
|
+
server: {
|
|
14
|
+
type: "string",
|
|
15
|
+
description: "creekd admin API URL (or $CREEKD_URL)",
|
|
16
|
+
required: false,
|
|
17
|
+
},
|
|
18
|
+
token: {
|
|
19
|
+
type: "string",
|
|
20
|
+
description: "Bearer token (or $CREEKD_TOKEN)",
|
|
21
|
+
required: false,
|
|
22
|
+
},
|
|
23
|
+
interval: {
|
|
24
|
+
type: "string",
|
|
25
|
+
description: "Refresh interval in seconds (default 2)",
|
|
26
|
+
default: "2",
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
async run({ args }) {
|
|
30
|
+
const jsonMode = resolveJsonMode(args);
|
|
31
|
+
const client = new CreekdClient(args.server || getCreekdUrl(), args.token || process.env.CREEKD_TOKEN || process.env.CREEKCTL_TOKEN || "");
|
|
32
|
+
const intervalMs = Math.max(500, parseFloat(args.interval || "2") * 1000);
|
|
33
|
+
if (jsonMode) {
|
|
34
|
+
const snapshot = await collectSnapshot(client);
|
|
35
|
+
jsonOutput({ ok: true, ...snapshot }, 0, [
|
|
36
|
+
{ command: "creek top --json", description: "Refresh snapshot" },
|
|
37
|
+
{ command: "creek logs <app-id>", description: "Stream app logs" },
|
|
38
|
+
]);
|
|
39
|
+
}
|
|
40
|
+
await liveTop(client, intervalMs);
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
let prevCpu = new Map();
|
|
44
|
+
async function collectSnapshot(client) {
|
|
45
|
+
const apps = await client.listApps();
|
|
46
|
+
const now = Date.now();
|
|
47
|
+
const rows = [];
|
|
48
|
+
const statsResults = await Promise.allSettled(apps.map((app) => client.getStats(app.id)));
|
|
49
|
+
for (let i = 0; i < apps.length; i++) {
|
|
50
|
+
const app = apps[i];
|
|
51
|
+
const stats = statsResults[i].status === "fulfilled"
|
|
52
|
+
? statsResults[i].value
|
|
53
|
+
: null;
|
|
54
|
+
let cpuStr = "—";
|
|
55
|
+
if (stats?.cgroup_enabled && stats.cpu_usage_usec != null) {
|
|
56
|
+
const prev = prevCpu.get(app.id);
|
|
57
|
+
if (prev) {
|
|
58
|
+
const pct = calcCpuPercent(prev.usec, prev.ts, stats.cpu_usage_usec, now);
|
|
59
|
+
if (pct !== null)
|
|
60
|
+
cpuStr = pct.toFixed(1) + "%";
|
|
61
|
+
}
|
|
62
|
+
prevCpu.set(app.id, { usec: stats.cpu_usage_usec, ts: now });
|
|
63
|
+
}
|
|
64
|
+
rows.push({
|
|
65
|
+
id: app.id,
|
|
66
|
+
status: app.status,
|
|
67
|
+
cpu: cpuStr,
|
|
68
|
+
mem: stats?.memory_current_bytes != null ? fmtBytes(stats.memory_current_bytes) : "—",
|
|
69
|
+
memLimit: stats?.memory_max_bytes != null && stats.memory_max_bytes > 0
|
|
70
|
+
? fmtBytes(stats.memory_max_bytes)
|
|
71
|
+
: "—",
|
|
72
|
+
pids: stats?.pids_current != null ? String(stats.pids_current) : "—",
|
|
73
|
+
restarts: app.restart_count,
|
|
74
|
+
uptime: fmtDuration(app.uptime_ms),
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
const running = apps.filter((a) => a.status === "running").length;
|
|
78
|
+
const crashed = apps.filter((a) => a.status === "crash_loop").length;
|
|
79
|
+
return {
|
|
80
|
+
apps: rows,
|
|
81
|
+
summary: { total: apps.length, running, crashed },
|
|
82
|
+
timestamp: new Date().toISOString(),
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
async function liveTop(client, intervalMs) {
|
|
86
|
+
const url = client instanceof CreekdClient
|
|
87
|
+
? getCreekdUrl()
|
|
88
|
+
: "creekd";
|
|
89
|
+
let first = true;
|
|
90
|
+
// eslint-disable-next-line no-constant-condition
|
|
91
|
+
while (true) {
|
|
92
|
+
try {
|
|
93
|
+
const snap = await collectSnapshot(client);
|
|
94
|
+
if (isTTY)
|
|
95
|
+
process.stdout.write("\x1b[2J\x1b[H");
|
|
96
|
+
render(snap, url);
|
|
97
|
+
first = false;
|
|
98
|
+
}
|
|
99
|
+
catch (err) {
|
|
100
|
+
if (first) {
|
|
101
|
+
if (err instanceof CreekdApiError && err.status === 401) {
|
|
102
|
+
consola.error("Authentication failed. Set CREEKD_TOKEN or use --token.");
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
consola.error(`Cannot reach creekd at ${url}`);
|
|
106
|
+
consola.info("Is creekd running? Check with: systemctl status creekd");
|
|
107
|
+
}
|
|
108
|
+
process.exit(1);
|
|
109
|
+
}
|
|
110
|
+
if (isTTY)
|
|
111
|
+
process.stdout.write("\x1b[2J\x1b[H");
|
|
112
|
+
consola.warn(`Refresh failed: ${err.message}`);
|
|
113
|
+
}
|
|
114
|
+
await sleep(intervalMs);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
function render(snap, url) {
|
|
118
|
+
const { apps, summary } = snap;
|
|
119
|
+
const dim = "\x1b[2m";
|
|
120
|
+
const reset = "\x1b[0m";
|
|
121
|
+
const bold = "\x1b[1m";
|
|
122
|
+
const green = "\x1b[32m";
|
|
123
|
+
const red = "\x1b[31m";
|
|
124
|
+
const yellow = "\x1b[33m";
|
|
125
|
+
const header = `${bold}creek top${reset}${dim} — ${url}${reset} ` +
|
|
126
|
+
`${summary.total} apps, ${green}${summary.running} running${reset}` +
|
|
127
|
+
(summary.crashed > 0 ? `, ${red}${summary.crashed} crashed${reset}` : "");
|
|
128
|
+
process.stdout.write(header + "\n\n");
|
|
129
|
+
if (apps.length === 0) {
|
|
130
|
+
process.stdout.write(`${dim} No apps running.${reset}\n`);
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
const cols = ["APP", "STATUS", "CPU", "MEM", "LIMIT", "PIDS", "RESTARTS", "UPTIME"];
|
|
134
|
+
const widths = cols.map((c, i) => {
|
|
135
|
+
const dataMax = Math.max(...apps.map((r) => String(cellValue(r, i)).length), 0);
|
|
136
|
+
return Math.max(c.length, dataMax);
|
|
137
|
+
});
|
|
138
|
+
const headerLine = cols.map((c, i) => c.padEnd(widths[i])).join(" ");
|
|
139
|
+
process.stdout.write(`${dim} ${headerLine}${reset}\n`);
|
|
140
|
+
for (const row of apps) {
|
|
141
|
+
const statusColor = row.status === "running" ? green
|
|
142
|
+
: row.status === "crash_loop" ? red
|
|
143
|
+
: row.status === "starting" ? yellow
|
|
144
|
+
: dim;
|
|
145
|
+
const cells = cols.map((_, i) => {
|
|
146
|
+
const val = String(cellValue(row, i));
|
|
147
|
+
if (i === 1)
|
|
148
|
+
return `${statusColor}${val.padEnd(widths[i])}${reset}`;
|
|
149
|
+
return val.padEnd(widths[i]);
|
|
150
|
+
});
|
|
151
|
+
process.stdout.write(" " + cells.join(" ") + "\n");
|
|
152
|
+
}
|
|
153
|
+
process.stdout.write(`\n${dim} Refreshing every ${snap._intervalS || 2}s — Ctrl+C to quit${reset}\n`);
|
|
154
|
+
}
|
|
155
|
+
function cellValue(row, col) {
|
|
156
|
+
switch (col) {
|
|
157
|
+
case 0: return row.id;
|
|
158
|
+
case 1: return row.status;
|
|
159
|
+
case 2: return row.cpu;
|
|
160
|
+
case 3: return row.mem;
|
|
161
|
+
case 4: return row.memLimit;
|
|
162
|
+
case 5: return row.pids;
|
|
163
|
+
case 6: return row.restarts;
|
|
164
|
+
case 7: return row.uptime;
|
|
165
|
+
default: return "";
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
function sleep(ms) {
|
|
169
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
170
|
+
}
|
|
171
|
+
//# sourceMappingURL=top.js.map
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { ResolvedConfig } from "@solcreek/sdk";
|
|
2
|
+
export interface CreekdDevServerOptions {
|
|
3
|
+
cwd: string;
|
|
4
|
+
port: number;
|
|
5
|
+
config: ResolvedConfig;
|
|
6
|
+
reset: boolean;
|
|
7
|
+
}
|
|
8
|
+
export declare class CreekdDevServer {
|
|
9
|
+
private options;
|
|
10
|
+
private sandboxProcess;
|
|
11
|
+
constructor(options: CreekdDevServerOptions);
|
|
12
|
+
start(): Promise<void>;
|
|
13
|
+
stop(): Promise<void>;
|
|
14
|
+
private requireCreekd;
|
|
15
|
+
private ensureSandbox;
|
|
16
|
+
private buildEnvVars;
|
|
17
|
+
private detectDevCommand;
|
|
18
|
+
/** For compatibility with DevServer interface */
|
|
19
|
+
triggerScheduled(): Promise<void>;
|
|
20
|
+
sendQueueMessage(_payload: unknown): Promise<void>;
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=creekd-runner.d.ts.map
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
// CreekdDevServer for `creek dev --target creekd`.
|
|
2
|
+
//
|
|
3
|
+
// Orchestrates `creekd sandbox` (Go binary) instead of Miniflare.
|
|
4
|
+
// Real Postgres, Redis, SeaweedFS run inside a Lima VM.
|
|
5
|
+
// The app process runs inside the VM with env vars injected.
|
|
6
|
+
//
|
|
7
|
+
// Requirements:
|
|
8
|
+
// - `creekd` binary in PATH
|
|
9
|
+
// - Lima (`limactl`) for macOS/Linux sandbox VM
|
|
10
|
+
import { execSync, spawn } from "node:child_process";
|
|
11
|
+
import { existsSync } from "node:fs";
|
|
12
|
+
import { join } from "node:path";
|
|
13
|
+
import { consola } from "consola";
|
|
14
|
+
export class CreekdDevServer {
|
|
15
|
+
options;
|
|
16
|
+
sandboxProcess = null;
|
|
17
|
+
constructor(options) {
|
|
18
|
+
this.options = options;
|
|
19
|
+
}
|
|
20
|
+
async start() {
|
|
21
|
+
const { cwd, port, config, reset } = this.options;
|
|
22
|
+
const startTime = Date.now();
|
|
23
|
+
// 1. Check creekd is installed
|
|
24
|
+
this.requireCreekd();
|
|
25
|
+
// 2. Start sandbox (provisions Lima VM + primitives from creek.toml)
|
|
26
|
+
consola.info("Starting creekd sandbox...");
|
|
27
|
+
const status = await this.ensureSandbox(cwd);
|
|
28
|
+
// 3. Build env var map from sandbox primitives
|
|
29
|
+
const env = this.buildEnvVars(config, status, port);
|
|
30
|
+
// 4. Load .env.local if present
|
|
31
|
+
const envLocalPath = join(cwd, ".env.local");
|
|
32
|
+
if (existsSync(envLocalPath)) {
|
|
33
|
+
const { readFileSync } = await import("node:fs");
|
|
34
|
+
const lines = readFileSync(envLocalPath, "utf-8").split("\n");
|
|
35
|
+
for (const line of lines) {
|
|
36
|
+
const trimmed = line.trim();
|
|
37
|
+
if (!trimmed || trimmed.startsWith("#"))
|
|
38
|
+
continue;
|
|
39
|
+
const eqIdx = trimmed.indexOf("=");
|
|
40
|
+
if (eqIdx > 0) {
|
|
41
|
+
const key = trimmed.slice(0, eqIdx);
|
|
42
|
+
const val = trimmed.slice(eqIdx + 1).replace(/^["']|["']$/g, "");
|
|
43
|
+
if (!env[key])
|
|
44
|
+
env[key] = val;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
consola.info(`.env.local loaded`);
|
|
48
|
+
}
|
|
49
|
+
// 5. Detect runtime and dev command
|
|
50
|
+
const devCmd = this.detectDevCommand(cwd, config);
|
|
51
|
+
// 6. Print status
|
|
52
|
+
const elapsed = Date.now() - startTime;
|
|
53
|
+
console.log("");
|
|
54
|
+
consola.success("⬡ creek dev (creekd sandbox)\n");
|
|
55
|
+
consola.info(`App: http://localhost:${port}`);
|
|
56
|
+
for (const p of status.ports) {
|
|
57
|
+
if (p.name !== "app") {
|
|
58
|
+
consola.info(`${p.name.padEnd(12)}localhost:${p.host}`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
console.log("");
|
|
62
|
+
for (const [k, v] of Object.entries(env)) {
|
|
63
|
+
if (k.startsWith("DATABASE") || k.startsWith("REDIS") || k.startsWith("S3_") || k.startsWith("SMTP")) {
|
|
64
|
+
const masked = v.replace(/:[^:@]+@/, ":***@");
|
|
65
|
+
consola.info(` ${k}=${masked}`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
console.log("");
|
|
69
|
+
consola.info(`Running: ${devCmd.join(" ")}`);
|
|
70
|
+
consola.info(`Ready in ${elapsed}ms`);
|
|
71
|
+
console.log("");
|
|
72
|
+
// 7. Start the dev server process with env vars
|
|
73
|
+
this.sandboxProcess = spawn(devCmd[0], devCmd.slice(1), {
|
|
74
|
+
cwd,
|
|
75
|
+
env: { ...process.env, ...env },
|
|
76
|
+
stdio: "inherit",
|
|
77
|
+
});
|
|
78
|
+
this.sandboxProcess.on("exit", (code) => {
|
|
79
|
+
if (code !== null && code !== 0) {
|
|
80
|
+
consola.error(`Dev server exited with code ${code}`);
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
async stop() {
|
|
85
|
+
if (this.sandboxProcess) {
|
|
86
|
+
this.sandboxProcess.kill("SIGTERM");
|
|
87
|
+
this.sandboxProcess = null;
|
|
88
|
+
}
|
|
89
|
+
// Don't stop the sandbox VM — it persists for fast restarts
|
|
90
|
+
}
|
|
91
|
+
// --- Internals ---
|
|
92
|
+
requireCreekd() {
|
|
93
|
+
try {
|
|
94
|
+
execSync("creekd --version", { stdio: "pipe" });
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
throw new Error([
|
|
98
|
+
"creekd is not installed.",
|
|
99
|
+
"",
|
|
100
|
+
" Install with: curl -fsSL https://install.creek.dev | sh",
|
|
101
|
+
"",
|
|
102
|
+
" Or use --target cf to develop with Miniflare (CF Workers local).",
|
|
103
|
+
].join("\n"));
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
async ensureSandbox(cwd) {
|
|
107
|
+
try {
|
|
108
|
+
const output = execSync(`creekd sandbox --non-interactive --json "${cwd}"`, { encoding: "utf-8", timeout: 300_000 });
|
|
109
|
+
// Find the JSON line in output (creekd may print logs before JSON)
|
|
110
|
+
const lines = output.trim().split("\n");
|
|
111
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
112
|
+
try {
|
|
113
|
+
return JSON.parse(lines[i]);
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
throw new Error("No JSON status from creekd sandbox");
|
|
120
|
+
}
|
|
121
|
+
catch (e) {
|
|
122
|
+
if (e.message?.includes("not installed"))
|
|
123
|
+
throw e;
|
|
124
|
+
throw new Error(`creekd sandbox failed: ${e.message}`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
buildEnvVars(config, status, port) {
|
|
128
|
+
const env = {
|
|
129
|
+
PORT: String(port),
|
|
130
|
+
};
|
|
131
|
+
// Map sandbox ports to standard env vars
|
|
132
|
+
for (const p of status.ports) {
|
|
133
|
+
switch (p.name) {
|
|
134
|
+
case "postgres":
|
|
135
|
+
env.DATABASE_URL = `postgresql://creek:creek_sandbox@127.0.0.1:${p.host}/app`;
|
|
136
|
+
break;
|
|
137
|
+
case "mysql":
|
|
138
|
+
env.DATABASE_URL = `mysql://creek:creek_sandbox@127.0.0.1:${p.host}/app`;
|
|
139
|
+
break;
|
|
140
|
+
case "redis":
|
|
141
|
+
env.REDIS_URL = `redis://127.0.0.1:${p.host}/0`;
|
|
142
|
+
break;
|
|
143
|
+
case "s3":
|
|
144
|
+
env.S3_ENDPOINT = `http://127.0.0.1:${p.host}`;
|
|
145
|
+
env.S3_BUCKET = config.projectName;
|
|
146
|
+
env.AWS_ACCESS_KEY_ID = "creek";
|
|
147
|
+
env.AWS_SECRET_ACCESS_KEY = "creek_sandbox";
|
|
148
|
+
break;
|
|
149
|
+
case "smtp":
|
|
150
|
+
env.SMTP_URL = `smtp://127.0.0.1:${p.host}`;
|
|
151
|
+
break;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
// SQLite fallback for database if no postgres/mysql port
|
|
155
|
+
if (!env.DATABASE_URL) {
|
|
156
|
+
const dbDir = join(this.options.cwd, ".creek", "dev");
|
|
157
|
+
env.DATABASE_URL = `sqlite://${dbDir}/dev.db`;
|
|
158
|
+
}
|
|
159
|
+
return env;
|
|
160
|
+
}
|
|
161
|
+
detectDevCommand(cwd, config) {
|
|
162
|
+
// Check package.json for dev script
|
|
163
|
+
const pkgPath = join(cwd, "package.json");
|
|
164
|
+
if (existsSync(pkgPath)) {
|
|
165
|
+
const { readFileSync } = require("node:fs");
|
|
166
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
167
|
+
if (pkg.scripts?.dev) {
|
|
168
|
+
return ["npm", "run", "dev"];
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
// Fallback to bun --watch or node --watch
|
|
172
|
+
const entryFiles = ["src/index.ts", "src/index.mjs", "src/index.js", "index.ts", "index.mjs", "index.js"];
|
|
173
|
+
for (const entry of entryFiles) {
|
|
174
|
+
if (existsSync(join(cwd, entry))) {
|
|
175
|
+
return ["bun", "--watch", entry];
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
return ["bun", "--watch", "."];
|
|
179
|
+
}
|
|
180
|
+
/** For compatibility with DevServer interface */
|
|
181
|
+
async triggerScheduled() {
|
|
182
|
+
consola.warn("Scheduled triggers not yet supported in creekd dev mode");
|
|
183
|
+
}
|
|
184
|
+
async sendQueueMessage(_payload) {
|
|
185
|
+
consola.warn("Queue messages not yet supported in creekd dev mode");
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
//# sourceMappingURL=creekd-runner.js.map
|
package/dist/index.js
CHANGED
|
@@ -24,6 +24,10 @@ import { doctorCommand } from "./commands/doctor.js";
|
|
|
24
24
|
import { dbCommand } from "./commands/db.js";
|
|
25
25
|
import { storageCommand } from "./commands/storage.js";
|
|
26
26
|
import { cacheCommand } from "./commands/cache.js";
|
|
27
|
+
import { topCommand } from "./commands/top.js";
|
|
28
|
+
import { restartCommand } from "./commands/restart.js";
|
|
29
|
+
import { stopCommand } from "./commands/stop.js";
|
|
30
|
+
import { dashboardCommand } from "./commands/dashboard.js";
|
|
27
31
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
28
32
|
const cliPkg = JSON.parse(readFileSync(join(__dirname, "..", "package.json"), "utf-8"));
|
|
29
33
|
// Read version from the "creek" facade package (what users install),
|
|
@@ -63,6 +67,10 @@ const main = defineCommand({
|
|
|
63
67
|
cache: cacheCommand,
|
|
64
68
|
domains: domainsCommand,
|
|
65
69
|
rollback: rollbackCommand,
|
|
70
|
+
top: topCommand,
|
|
71
|
+
dashboard: dashboardCommand,
|
|
72
|
+
restart: restartCommand,
|
|
73
|
+
stop: stopCommand,
|
|
66
74
|
ops: opsCommand,
|
|
67
75
|
},
|
|
68
76
|
});
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lightweight creekd admin API client for the CLI.
|
|
3
|
+
*
|
|
4
|
+
* Uses plain fetch (no openapi-fetch dep) with types matching
|
|
5
|
+
* the OpenAPI spec. The CLI only needs a handful of endpoints.
|
|
6
|
+
*/
|
|
7
|
+
export interface AppView {
|
|
8
|
+
id: string;
|
|
9
|
+
runtime?: string;
|
|
10
|
+
command: string;
|
|
11
|
+
args?: string[];
|
|
12
|
+
env?: string[];
|
|
13
|
+
port: number;
|
|
14
|
+
status: "starting" | "running" | "crash_loop" | "stopping" | "stopped";
|
|
15
|
+
pid: number;
|
|
16
|
+
uptime_ms: number;
|
|
17
|
+
restart_count: number;
|
|
18
|
+
health_failures: number;
|
|
19
|
+
net_ip?: string;
|
|
20
|
+
}
|
|
21
|
+
export interface StatsView {
|
|
22
|
+
id: string;
|
|
23
|
+
cgroup_enabled: boolean;
|
|
24
|
+
memory_current_bytes?: number;
|
|
25
|
+
memory_max_bytes?: number;
|
|
26
|
+
pids_current?: number;
|
|
27
|
+
cpu_usage_usec?: number;
|
|
28
|
+
oom_kills?: number;
|
|
29
|
+
read_err?: string;
|
|
30
|
+
}
|
|
31
|
+
export interface AppEnvelope {
|
|
32
|
+
apiVersion: string;
|
|
33
|
+
kind: string;
|
|
34
|
+
metadata: {
|
|
35
|
+
name: string;
|
|
36
|
+
uid: string;
|
|
37
|
+
generation: number;
|
|
38
|
+
resourceVersion: string;
|
|
39
|
+
creationTimestamp: string;
|
|
40
|
+
};
|
|
41
|
+
spec: {
|
|
42
|
+
runtime?: string;
|
|
43
|
+
command?: string;
|
|
44
|
+
args?: string[];
|
|
45
|
+
env?: string[];
|
|
46
|
+
port?: number;
|
|
47
|
+
};
|
|
48
|
+
status: {
|
|
49
|
+
observedGeneration: number;
|
|
50
|
+
conditions: Array<{
|
|
51
|
+
type: string;
|
|
52
|
+
status: string;
|
|
53
|
+
lastTransitionTime: string;
|
|
54
|
+
reason: string;
|
|
55
|
+
message?: string;
|
|
56
|
+
}>;
|
|
57
|
+
currentPid: number;
|
|
58
|
+
currentPort: number;
|
|
59
|
+
restartCount: number;
|
|
60
|
+
healthFailures: number;
|
|
61
|
+
uptimeMs: number;
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
export interface ListAppsResponse {
|
|
65
|
+
apps: AppView[];
|
|
66
|
+
}
|
|
67
|
+
/** Release ledger entry returned by POST /v1/apps/{id}/rollback. */
|
|
68
|
+
export interface Release {
|
|
69
|
+
uid: string;
|
|
70
|
+
phase: "Active" | "Superseded" | "RolledBack";
|
|
71
|
+
creationTimestamp: string;
|
|
72
|
+
spec: {
|
|
73
|
+
appUid: string;
|
|
74
|
+
releaseSeq: number;
|
|
75
|
+
gitSha?: string;
|
|
76
|
+
image?: string;
|
|
77
|
+
envHash?: string;
|
|
78
|
+
createdBy?: string;
|
|
79
|
+
rolledBackFrom?: number;
|
|
80
|
+
originalArtifactRelease?: number;
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
export interface ErrorResponse {
|
|
84
|
+
code: string;
|
|
85
|
+
error: string;
|
|
86
|
+
}
|
|
87
|
+
export declare class CreekdApiError extends Error {
|
|
88
|
+
status: number;
|
|
89
|
+
code: string;
|
|
90
|
+
constructor(status: number, code: string);
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Thrown specifically on 412 Precondition Failed (If-Match
|
|
94
|
+
* mismatch). Carries the daemon's CURRENT rv so the caller can
|
|
95
|
+
* decide between (a) prompting the user to refresh, (b) auto-
|
|
96
|
+
* retrying with the fresh rv when --bypass-rv was passed, or
|
|
97
|
+
* (c) emitting a structured machine-readable error to JSON mode.
|
|
98
|
+
*
|
|
99
|
+
* Per DESIGN-self-host-state.md §"First-party CLI MUST send
|
|
100
|
+
* If-Match": "On 412, CLI surfaces a structured prompt and does
|
|
101
|
+
* NOT auto-retry by default."
|
|
102
|
+
*/
|
|
103
|
+
export declare class CreekdResourceVersionMismatchError extends CreekdApiError {
|
|
104
|
+
currentResourceVersion: string;
|
|
105
|
+
attemptedResourceVersion: string;
|
|
106
|
+
constructor(currentResourceVersion: string, attemptedResourceVersion: string);
|
|
107
|
+
}
|
|
108
|
+
/** Options that apply to every mutating call. */
|
|
109
|
+
export interface MutateOptions {
|
|
110
|
+
/**
|
|
111
|
+
* If-Match value to send as the precondition header. Daemon
|
|
112
|
+
* returns 412 → CreekdResourceVersionMismatchError if it doesn't
|
|
113
|
+
* match the daemon's current rv. Omit (or pass undefined) for
|
|
114
|
+
* unconditional writes; the daemon then attaches
|
|
115
|
+
* `Warning: 299 - "unconditional-write"` to the response.
|
|
116
|
+
*/
|
|
117
|
+
ifMatch?: string;
|
|
118
|
+
}
|
|
119
|
+
export declare function getCreekdUrl(): string;
|
|
120
|
+
export declare function getCreekdToken(): string;
|
|
121
|
+
export declare class CreekdClient {
|
|
122
|
+
private token;
|
|
123
|
+
private baseUrl;
|
|
124
|
+
constructor(baseUrl?: string, token?: string);
|
|
125
|
+
listApps(): Promise<AppView[]>;
|
|
126
|
+
getApp(id: string): Promise<AppEnvelope>;
|
|
127
|
+
getStats(id: string): Promise<StatsView>;
|
|
128
|
+
getAppLogs(id: string, tail?: number): Promise<string>;
|
|
129
|
+
stopApp(id: string, opts?: MutateOptions): Promise<void>;
|
|
130
|
+
/**
|
|
131
|
+
* Spawn a brand-new app. POST /v1/apps. Creation is not
|
|
132
|
+
* spec-mutating in the rv sense — there's no prior version to
|
|
133
|
+
* If-Match against — so ifMatch is intentionally NOT a parameter.
|
|
134
|
+
*/
|
|
135
|
+
spawnApp(body: unknown): Promise<AppView>;
|
|
136
|
+
/**
|
|
137
|
+
* Blue-green deploy of an existing app. POST /v1/apps/{id}/deploy.
|
|
138
|
+
* Spec-mutating; pass ifMatch sourced from the local cache (or a
|
|
139
|
+
* fresh getApp) — 412 surfaces as CreekdResourceVersionMismatchError.
|
|
140
|
+
*/
|
|
141
|
+
deployApp(id: string, body: unknown, opts?: MutateOptions): Promise<AppView>;
|
|
142
|
+
restartApp(id: string): Promise<AppView>;
|
|
143
|
+
/**
|
|
144
|
+
* Roll back to the target release seq. Spec-mutating — accepts
|
|
145
|
+
* If-Match. Throws CreekdResourceVersionMismatchError on 412.
|
|
146
|
+
*/
|
|
147
|
+
rollbackApp(id: string, toSeq: number, opts?: MutateOptions): Promise<Release>;
|
|
148
|
+
private get;
|
|
149
|
+
private post;
|
|
150
|
+
private request;
|
|
151
|
+
}
|
|
152
|
+
//# sourceMappingURL=creekd-client.d.ts.map
|