@jango-blockchained/hoox-cli 0.7.0 → 0.7.2
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/README.md +42 -26
- package/dist/index.js +550 -196
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -21388,18 +21388,15 @@ class KvSyncService {
|
|
|
21388
21388
|
return stdout2.trim() || null;
|
|
21389
21389
|
}
|
|
21390
21390
|
async set(namespaceId, key, value) {
|
|
21391
|
-
const proc = Bun.spawn([
|
|
21392
|
-
|
|
21393
|
-
|
|
21394
|
-
|
|
21395
|
-
|
|
21396
|
-
|
|
21397
|
-
|
|
21398
|
-
|
|
21399
|
-
value
|
|
21400
|
-
], { stdout: "pipe", stderr: "pipe" });
|
|
21391
|
+
const proc = Bun.spawn(["wrangler", "kv", "key", "put", "--namespace-id", namespaceId, key], { stdout: "pipe", stderr: "pipe", stdin: "pipe" });
|
|
21392
|
+
proc.stdin.write(value + `
|
|
21393
|
+
`);
|
|
21394
|
+
proc.stdin.end();
|
|
21395
|
+
const [, stderr] = await Promise.all([
|
|
21396
|
+
new Response(proc.stdout).text(),
|
|
21397
|
+
new Response(proc.stderr).text()
|
|
21398
|
+
]);
|
|
21401
21399
|
const exitCode = await proc.exited;
|
|
21402
|
-
const stderr = await new Response(proc.stderr).text();
|
|
21403
21400
|
if (exitCode !== 0) {
|
|
21404
21401
|
throw new Error(`Failed to set key "${key}": ${stderr.trim() || `exit ${exitCode}`}`);
|
|
21405
21402
|
}
|
|
@@ -21531,7 +21528,7 @@ var exports_schema_service = {};
|
|
|
21531
21528
|
__export(exports_schema_service, {
|
|
21532
21529
|
SchemaService: () => SchemaService
|
|
21533
21530
|
});
|
|
21534
|
-
import { readFileSync as readFileSync4 } from "fs";
|
|
21531
|
+
import { readFileSync as readFileSync4, existsSync as existsSync5 } from "fs";
|
|
21535
21532
|
import { resolve as resolve5 } from "path";
|
|
21536
21533
|
|
|
21537
21534
|
class SchemaService {
|
|
@@ -21565,34 +21562,41 @@ class SchemaService {
|
|
|
21565
21562
|
const workerWranglerPath = resolve5(workersDir, name, "wrangler.jsonc");
|
|
21566
21563
|
const devVarsPath = resolve5(workersDir, name, ".dev.vars");
|
|
21567
21564
|
const errors4 = [];
|
|
21565
|
+
const isHooxProject = existsSync5(resolve5(this.projectRoot, "wrangler.jsonc")) || existsSync5(resolve5(this.projectRoot, "workers"));
|
|
21568
21566
|
try {
|
|
21569
21567
|
const wranglerContent = readFileSync4(workerWranglerPath, "utf-8");
|
|
21570
21568
|
errors4.push(...validateWranglerJsonc(name, manifest, wranglerContent));
|
|
21571
21569
|
} catch (e) {
|
|
21570
|
+
const isErrnoException = e instanceof Error && "code" in e;
|
|
21571
|
+
const isEnoent = isErrnoException && e.code === "ENOENT";
|
|
21572
|
+
const errMessage = e instanceof Error ? e.message : String(e);
|
|
21573
|
+
const message = isEnoent && !isHooxProject ? `Cannot read ${workerWranglerPath}: File not found. Are you in the Hoox project root directory?` : `Cannot read ${workerWranglerPath}: ${errMessage}`;
|
|
21572
21574
|
errors4.push({
|
|
21573
21575
|
worker: name,
|
|
21574
21576
|
severity: "error",
|
|
21575
|
-
message
|
|
21577
|
+
message
|
|
21576
21578
|
});
|
|
21577
21579
|
}
|
|
21578
21580
|
try {
|
|
21579
21581
|
const rootContent = readFileSync4(rootWranglerPath, "utf-8");
|
|
21580
21582
|
errors4.push(...validateRootSecrets(name, manifest, rootContent));
|
|
21581
21583
|
} catch (e) {
|
|
21584
|
+
const errMessage = e instanceof Error ? e.message : String(e);
|
|
21582
21585
|
errors4.push({
|
|
21583
21586
|
worker: name,
|
|
21584
21587
|
severity: "warning",
|
|
21585
|
-
message: `Cannot read root wrangler.jsonc: ${
|
|
21588
|
+
message: `Cannot read root wrangler.jsonc: ${errMessage}`
|
|
21586
21589
|
});
|
|
21587
21590
|
}
|
|
21588
21591
|
try {
|
|
21589
21592
|
const devVarsContent = readFileSync4(devVarsPath, "utf-8");
|
|
21590
21593
|
errors4.push(...validateDevVars(name, manifest, devVarsContent));
|
|
21591
21594
|
} catch (e) {
|
|
21595
|
+
const errMessage = e instanceof Error ? e.message : String(e);
|
|
21592
21596
|
errors4.push({
|
|
21593
21597
|
worker: name,
|
|
21594
21598
|
severity: "warning",
|
|
21595
|
-
message: `Cannot read ${devVarsPath}: ${
|
|
21599
|
+
message: `Cannot read ${devVarsPath}: ${errMessage}`
|
|
21596
21600
|
});
|
|
21597
21601
|
}
|
|
21598
21602
|
return {
|
|
@@ -23674,11 +23678,13 @@ class CLIError extends Error {
|
|
|
23674
23678
|
code;
|
|
23675
23679
|
details;
|
|
23676
23680
|
recoverable;
|
|
23677
|
-
|
|
23681
|
+
hint;
|
|
23682
|
+
constructor(message, code = 1 /* ERROR */, details, recoverable = false, hint) {
|
|
23678
23683
|
super(message);
|
|
23679
23684
|
this.code = code;
|
|
23680
23685
|
this.details = details;
|
|
23681
23686
|
this.recoverable = recoverable;
|
|
23687
|
+
this.hint = hint;
|
|
23682
23688
|
this.name = "CLIError";
|
|
23683
23689
|
}
|
|
23684
23690
|
}
|
|
@@ -23746,7 +23752,63 @@ function stripAnsi(str) {
|
|
|
23746
23752
|
return ansis_default.strip(str);
|
|
23747
23753
|
}
|
|
23748
23754
|
|
|
23755
|
+
// src/utils/timer.ts
|
|
23756
|
+
function formatDuration2(ms) {
|
|
23757
|
+
if (!Number.isFinite(ms) || ms < 0)
|
|
23758
|
+
return "0ms";
|
|
23759
|
+
if (ms < 1000)
|
|
23760
|
+
return `${Math.round(ms)}ms`;
|
|
23761
|
+
if (ms < 60000)
|
|
23762
|
+
return `${(ms / 1000).toFixed(1)}s`;
|
|
23763
|
+
if (ms < 3600000) {
|
|
23764
|
+
const m2 = Math.floor(ms / 60000);
|
|
23765
|
+
const s = Math.floor(ms % 60000 / 1000);
|
|
23766
|
+
return `${m2}m ${String(s).padStart(2, "0")}s`;
|
|
23767
|
+
}
|
|
23768
|
+
const h = Math.floor(ms / 3600000);
|
|
23769
|
+
const m = Math.floor(ms % 3600000 / 60000);
|
|
23770
|
+
return `${h}h ${String(m).padStart(2, "0")}m`;
|
|
23771
|
+
}
|
|
23772
|
+
function startTimer() {
|
|
23773
|
+
const startedAt = Date.now();
|
|
23774
|
+
return {
|
|
23775
|
+
ms: () => Date.now() - startedAt,
|
|
23776
|
+
format: () => formatDuration2(Date.now() - startedAt)
|
|
23777
|
+
};
|
|
23778
|
+
}
|
|
23779
|
+
|
|
23780
|
+
// src/utils/format-mode.ts
|
|
23781
|
+
function isRichMode(opts) {
|
|
23782
|
+
if (opts?.json || opts?.quiet)
|
|
23783
|
+
return false;
|
|
23784
|
+
return Boolean(process.stdout.isTTY);
|
|
23785
|
+
}
|
|
23786
|
+
|
|
23749
23787
|
// src/utils/formatters.ts
|
|
23788
|
+
var formatDuration3 = formatDuration2;
|
|
23789
|
+
var BADGE_STYLE = {
|
|
23790
|
+
ok: { bg: ansis_default.bgGreen, fg: ansis_default.black },
|
|
23791
|
+
warn: { bg: ansis_default.bgYellow, fg: ansis_default.black },
|
|
23792
|
+
err: { bg: ansis_default.bgRed, fg: ansis_default.white },
|
|
23793
|
+
info: { bg: ansis_default.bgBlue, fg: ansis_default.white }
|
|
23794
|
+
};
|
|
23795
|
+
function formatBadge(level, text) {
|
|
23796
|
+
const content = (text ?? defaultBadgeLabel(level)).padEnd(4, " ");
|
|
23797
|
+
const { bg, fg } = BADGE_STYLE[level];
|
|
23798
|
+
return bg(fg(` ${content} `));
|
|
23799
|
+
}
|
|
23800
|
+
function defaultBadgeLabel(level) {
|
|
23801
|
+
switch (level) {
|
|
23802
|
+
case "ok":
|
|
23803
|
+
return "OK";
|
|
23804
|
+
case "warn":
|
|
23805
|
+
return "WARN";
|
|
23806
|
+
case "err":
|
|
23807
|
+
return "FAIL";
|
|
23808
|
+
case "info":
|
|
23809
|
+
return "INFO";
|
|
23810
|
+
}
|
|
23811
|
+
}
|
|
23750
23812
|
function formatSuccess(message, opts) {
|
|
23751
23813
|
if (opts?.quiet)
|
|
23752
23814
|
return;
|
|
@@ -23770,6 +23832,9 @@ function formatError2(error51, opts) {
|
|
|
23770
23832
|
if (cliError?.details) {
|
|
23771
23833
|
output.details = cliError.details;
|
|
23772
23834
|
}
|
|
23835
|
+
if (cliError?.hint) {
|
|
23836
|
+
output.hint = cliError.hint;
|
|
23837
|
+
}
|
|
23773
23838
|
process.stdout.write(JSON.stringify(output) + `
|
|
23774
23839
|
`);
|
|
23775
23840
|
return;
|
|
@@ -23783,6 +23848,10 @@ function formatError2(error51, opts) {
|
|
|
23783
23848
|
`);
|
|
23784
23849
|
if (cliError?.details) {
|
|
23785
23850
|
process.stdout.write(` ${theme.dim(cliError.details)}
|
|
23851
|
+
`);
|
|
23852
|
+
}
|
|
23853
|
+
if (cliError?.hint) {
|
|
23854
|
+
process.stdout.write(`${theme.dim("\u21B3 hint:")} ${theme.value(cliError.hint)}
|
|
23786
23855
|
`);
|
|
23787
23856
|
}
|
|
23788
23857
|
}
|
|
@@ -25455,6 +25524,16 @@ ${l2}
|
|
|
25455
25524
|
}).prompt();
|
|
25456
25525
|
};
|
|
25457
25526
|
var i = `${styleText2("gray", S_BAR)} `;
|
|
25527
|
+
var tasks = async (o2, e) => {
|
|
25528
|
+
for (const t2 of o2) {
|
|
25529
|
+
if (t2.enabled === false)
|
|
25530
|
+
continue;
|
|
25531
|
+
const s = spinner(e);
|
|
25532
|
+
s.start(t2.title);
|
|
25533
|
+
const n2 = await t2.task(s.message);
|
|
25534
|
+
s.stop(n2 || t2.title);
|
|
25535
|
+
}
|
|
25536
|
+
};
|
|
25458
25537
|
var text = (t2) => new n({
|
|
25459
25538
|
validate: t2.validate,
|
|
25460
25539
|
placeholder: t2.placeholder,
|
|
@@ -25540,7 +25619,12 @@ class CloudflareService {
|
|
|
25540
25619
|
};
|
|
25541
25620
|
} catch (err) {
|
|
25542
25621
|
const message = err instanceof Error ? err.message : String(err);
|
|
25543
|
-
|
|
25622
|
+
const hint = /ENOENT|not found/i.test(message) ? "Install wrangler with `bun add -g wrangler` (or `npm i -g wrangler`), then run `wrangler login`." : undefined;
|
|
25623
|
+
return {
|
|
25624
|
+
ok: false,
|
|
25625
|
+
error: hint ? `Failed to spawn wrangler: ${message}
|
|
25626
|
+
\u21B3 hint: ${hint}` : `Failed to spawn wrangler: ${message}`
|
|
25627
|
+
};
|
|
25544
25628
|
}
|
|
25545
25629
|
}
|
|
25546
25630
|
async whoami() {
|
|
@@ -25752,7 +25836,32 @@ class CloudflareService {
|
|
|
25752
25836
|
]);
|
|
25753
25837
|
}
|
|
25754
25838
|
async zonesList() {
|
|
25755
|
-
|
|
25839
|
+
const token = process.env.CLOUDFLARE_API_TOKEN;
|
|
25840
|
+
if (!token) {
|
|
25841
|
+
return {
|
|
25842
|
+
ok: false,
|
|
25843
|
+
error: "CLOUDFLARE_API_TOKEN environment variable is not set. Set it or run `wrangler login`."
|
|
25844
|
+
};
|
|
25845
|
+
}
|
|
25846
|
+
try {
|
|
25847
|
+
const response = await fetch("https://api.cloudflare.com/client/v4/zones?per_page=50", {
|
|
25848
|
+
headers: {
|
|
25849
|
+
Authorization: `Bearer ${token}`,
|
|
25850
|
+
"Content-Type": "application/json"
|
|
25851
|
+
}
|
|
25852
|
+
});
|
|
25853
|
+
const json2 = await response.json();
|
|
25854
|
+
if (!response.ok || !json2.success) {
|
|
25855
|
+
const errorMsg = json2.errors?.map((e) => e.message).join("; ") || `HTTP ${response.status}`;
|
|
25856
|
+
return { ok: false, error: `Failed to list zones: ${errorMsg}` };
|
|
25857
|
+
}
|
|
25858
|
+
const lines = json2.result.map((z2) => `${z2.name} (${z2.id})`);
|
|
25859
|
+
return { ok: true, value: lines.join(`
|
|
25860
|
+
`) };
|
|
25861
|
+
} catch (err) {
|
|
25862
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
25863
|
+
return { ok: false, error: `Cloudflare API request failed: ${message}` };
|
|
25864
|
+
}
|
|
25756
25865
|
}
|
|
25757
25866
|
extractUrl(stdout2) {
|
|
25758
25867
|
for (const line of stdout2.split(`
|
|
@@ -25976,8 +26085,8 @@ OPTIONS:
|
|
|
25976
26085
|
|
|
25977
26086
|
EXAMPLES:
|
|
25978
26087
|
hoox init Interactive wizard
|
|
25979
|
-
hoox init --token cfut_xxx --account xxx Non-interactive`).option("--token <token>", "Cloudflare API token (non-interactive)").option("--account <id>", "Cloudflare Account ID (non-interactive)").option("--secret-store <id>", "Secret Store ID (non-interactive)").option("--prefix <prefix>", "Subdomain prefix (non-interactive, default: cryptolinx)").option("--preset <name>", "Worker preset (minimal, standard, full)").option("--resume", "Resume from saved wizard state").option("--accept-risk", "Skip the risk acknowledgment confirmation").action(withErrorHandling(async (options) => {
|
|
25980
|
-
const globalOpts = getFormatOptions(
|
|
26088
|
+
hoox init --token cfut_xxx --account xxx Non-interactive`).option("--token <token>", "Cloudflare API token (non-interactive)").option("--account <id>", "Cloudflare Account ID (non-interactive)").option("--secret-store <id>", "Secret Store ID (non-interactive)").option("--prefix <prefix>", "Subdomain prefix (non-interactive, default: cryptolinx)").option("--preset <name>", "Worker preset (minimal, standard, full)").option("--resume", "Resume from saved wizard state").option("--accept-risk", "Skip the risk acknowledgment confirmation").action(withErrorHandling(async (options, cmd) => {
|
|
26089
|
+
const globalOpts = getFormatOptions(cmd);
|
|
25981
26090
|
const isNonInteractive = Boolean(options.token && options.account);
|
|
25982
26091
|
await runInitCommand(options, globalOpts, isNonInteractive);
|
|
25983
26092
|
}, { service: "init" }));
|
|
@@ -26642,11 +26751,53 @@ class DockerService {
|
|
|
26642
26751
|
}
|
|
26643
26752
|
isCommandAvailable(cmd) {
|
|
26644
26753
|
return new Promise((resolve2) => {
|
|
26645
|
-
const
|
|
26646
|
-
|
|
26647
|
-
|
|
26754
|
+
const parts = cmd.split(/\s+/).filter(Boolean);
|
|
26755
|
+
const binary = parts[0];
|
|
26756
|
+
const hasSubcommand = parts.length > 1;
|
|
26757
|
+
let proc;
|
|
26758
|
+
try {
|
|
26759
|
+
proc = Bun.spawn(["which", binary], {
|
|
26760
|
+
stdout: "pipe",
|
|
26761
|
+
stderr: "pipe"
|
|
26762
|
+
});
|
|
26763
|
+
} catch {
|
|
26764
|
+
resolve2(false);
|
|
26765
|
+
return;
|
|
26766
|
+
}
|
|
26767
|
+
const timer = setTimeout(() => {
|
|
26768
|
+
try {
|
|
26769
|
+
proc.kill();
|
|
26770
|
+
} catch {}
|
|
26771
|
+
resolve2(false);
|
|
26772
|
+
}, 5000);
|
|
26773
|
+
proc.exited.then((code) => {
|
|
26774
|
+
clearTimeout(timer);
|
|
26775
|
+
if (code !== 0) {
|
|
26776
|
+
resolve2(false);
|
|
26777
|
+
return;
|
|
26778
|
+
}
|
|
26779
|
+
if (!hasSubcommand) {
|
|
26780
|
+
resolve2(true);
|
|
26781
|
+
return;
|
|
26782
|
+
}
|
|
26783
|
+
try {
|
|
26784
|
+
const subproc = Bun.spawn([binary, ...parts.slice(1), "version"], {
|
|
26785
|
+
stdout: "pipe",
|
|
26786
|
+
stderr: "pipe"
|
|
26787
|
+
});
|
|
26788
|
+
subproc.exited.then((subcode) => {
|
|
26789
|
+
try {
|
|
26790
|
+
subproc.kill();
|
|
26791
|
+
} catch {}
|
|
26792
|
+
resolve2(subcode === 0);
|
|
26793
|
+
}).catch(() => resolve2(true));
|
|
26794
|
+
} catch {
|
|
26795
|
+
resolve2(true);
|
|
26796
|
+
}
|
|
26797
|
+
}).catch(() => {
|
|
26798
|
+
clearTimeout(timer);
|
|
26799
|
+
resolve2(false);
|
|
26648
26800
|
});
|
|
26649
|
-
proc.exited.then((code) => resolve2(code === 0)).catch(() => resolve2(false));
|
|
26650
26801
|
});
|
|
26651
26802
|
}
|
|
26652
26803
|
}
|
|
@@ -26684,8 +26835,8 @@ OPTIONS:
|
|
|
26684
26835
|
EXAMPLES:
|
|
26685
26836
|
hoox dev start
|
|
26686
26837
|
hoox dev start --runtime native
|
|
26687
|
-
hoox dev start --runtime docker`).option("--runtime <native|docker>", "Choose dev runtime (overrides saved preference)").action(withErrorHandling(async (options) => {
|
|
26688
|
-
const fmt = getFormatOptions(
|
|
26838
|
+
hoox dev start --runtime docker`).option("--runtime <native|docker>", "Choose dev runtime (overrides saved preference)").action(withErrorHandling(async (options, cmd) => {
|
|
26839
|
+
const fmt = getFormatOptions(cmd);
|
|
26689
26840
|
const configService = new ConfigService;
|
|
26690
26841
|
const prereqs = new PrerequisitesService;
|
|
26691
26842
|
const docker = new DockerService;
|
|
@@ -26803,8 +26954,8 @@ OPTIONS:
|
|
|
26803
26954
|
EXAMPLES:
|
|
26804
26955
|
hoox dev worker trade-worker
|
|
26805
26956
|
hoox dev worker agent-worker --port 8788
|
|
26806
|
-
hoox dev worker hoox --runtime docker`).option("--port <port>", "Dev server port (default: 8787)", (v) => parseInt(v, 10)).option("--runtime <native|docker>", "Choose dev runtime (overrides saved preference)").action(withErrorHandling(async (name, options) => {
|
|
26807
|
-
const fmt = getFormatOptions(
|
|
26957
|
+
hoox dev worker hoox --runtime docker`).option("--port <port>", "Dev server port (default: 8787)", (v) => parseInt(v, 10)).option("--runtime <native|docker>", "Choose dev runtime (overrides saved preference)").action(withErrorHandling(async (name, options, cmd) => {
|
|
26958
|
+
const fmt = getFormatOptions(cmd);
|
|
26808
26959
|
const configService = new ConfigService;
|
|
26809
26960
|
await configService.load();
|
|
26810
26961
|
const worker = configService.getWorker(name);
|
|
@@ -26839,8 +26990,8 @@ OPTIONS:
|
|
|
26839
26990
|
|
|
26840
26991
|
EXAMPLES:
|
|
26841
26992
|
hoox dev dashboard
|
|
26842
|
-
hoox dev dashboard --runtime docker`).option("--runtime <native|docker>", "Choose dev runtime (overrides saved preference)").action(withErrorHandling(async () => {
|
|
26843
|
-
const fmt = getFormatOptions(
|
|
26993
|
+
hoox dev dashboard --runtime docker`).option("--runtime <native|docker>", "Choose dev runtime (overrides saved preference)").action(withErrorHandling(async (_options, cmd) => {
|
|
26994
|
+
const fmt = getFormatOptions(cmd);
|
|
26844
26995
|
const dashboardPath = path3.resolve(process.cwd(), "workers/dashboard");
|
|
26845
26996
|
const dashboardDir = Bun.file(dashboardPath);
|
|
26846
26997
|
if (!await dashboardDir.exists()) {
|
|
@@ -26859,6 +27010,131 @@ EXAMPLES:
|
|
|
26859
27010
|
}
|
|
26860
27011
|
// src/commands/deploy/deploy-command.ts
|
|
26861
27012
|
init_config2();
|
|
27013
|
+
|
|
27014
|
+
// src/utils/rich.ts
|
|
27015
|
+
async function runRichTasks(tasks2, options = {}) {
|
|
27016
|
+
const rich = isRichMode(options.format);
|
|
27017
|
+
const results = [];
|
|
27018
|
+
if (tasks2.length === 0)
|
|
27019
|
+
return results;
|
|
27020
|
+
const silent = options.format?.json === true;
|
|
27021
|
+
if (rich && !silent) {
|
|
27022
|
+
if (options.title)
|
|
27023
|
+
log.step(theme.heading(options.title));
|
|
27024
|
+
await tasks(tasks2.map((t2) => ({
|
|
27025
|
+
title: t2.title,
|
|
27026
|
+
task: async (message) => {
|
|
27027
|
+
const timer = startTimer();
|
|
27028
|
+
try {
|
|
27029
|
+
const value = await t2.run();
|
|
27030
|
+
const result = {
|
|
27031
|
+
title: t2.title,
|
|
27032
|
+
ok: true,
|
|
27033
|
+
ms: timer.ms(),
|
|
27034
|
+
value
|
|
27035
|
+
};
|
|
27036
|
+
if (t2.details) {
|
|
27037
|
+
result.details = await t2.details(value);
|
|
27038
|
+
}
|
|
27039
|
+
results.push(result);
|
|
27040
|
+
const dur = formatDuration2(result.ms);
|
|
27041
|
+
if (result.details) {
|
|
27042
|
+
const summary = Object.entries(result.details).map(([k, v]) => `${theme.dim(k)}=${theme.value(v)}`).join(" ");
|
|
27043
|
+
message(`${theme.success(icons.success)} ${dur} ${summary}`);
|
|
27044
|
+
} else {
|
|
27045
|
+
message(`${theme.success(icons.success)} ${dur}`);
|
|
27046
|
+
}
|
|
27047
|
+
} catch (err) {
|
|
27048
|
+
const message2 = err instanceof CLIError ? err.message : err instanceof Error ? err.message : String(err);
|
|
27049
|
+
const result = {
|
|
27050
|
+
title: t2.title,
|
|
27051
|
+
ok: false,
|
|
27052
|
+
ms: timer.ms(),
|
|
27053
|
+
error: message2
|
|
27054
|
+
};
|
|
27055
|
+
results.push(result);
|
|
27056
|
+
message(`${theme.error(icons.error)} ${formatDuration2(result.ms)} ${theme.dim(message2)}`);
|
|
27057
|
+
}
|
|
27058
|
+
}
|
|
27059
|
+
})));
|
|
27060
|
+
} else if (silent) {
|
|
27061
|
+
for (const t2 of tasks2) {
|
|
27062
|
+
const timer = startTimer();
|
|
27063
|
+
try {
|
|
27064
|
+
const value = await t2.run();
|
|
27065
|
+
const result = {
|
|
27066
|
+
title: t2.title,
|
|
27067
|
+
ok: true,
|
|
27068
|
+
ms: timer.ms(),
|
|
27069
|
+
value
|
|
27070
|
+
};
|
|
27071
|
+
if (t2.details) {
|
|
27072
|
+
result.details = await t2.details(value);
|
|
27073
|
+
}
|
|
27074
|
+
results.push(result);
|
|
27075
|
+
} catch (err) {
|
|
27076
|
+
const message2 = err instanceof CLIError ? err.message : err instanceof Error ? err.message : String(err);
|
|
27077
|
+
const result = {
|
|
27078
|
+
title: t2.title,
|
|
27079
|
+
ok: false,
|
|
27080
|
+
ms: timer.ms(),
|
|
27081
|
+
error: message2
|
|
27082
|
+
};
|
|
27083
|
+
results.push(result);
|
|
27084
|
+
}
|
|
27085
|
+
}
|
|
27086
|
+
} else {
|
|
27087
|
+
for (const t2 of tasks2) {
|
|
27088
|
+
log.step(t2.title);
|
|
27089
|
+
const timer = startTimer();
|
|
27090
|
+
try {
|
|
27091
|
+
const value = await t2.run();
|
|
27092
|
+
const result = {
|
|
27093
|
+
title: t2.title,
|
|
27094
|
+
ok: true,
|
|
27095
|
+
ms: timer.ms(),
|
|
27096
|
+
value
|
|
27097
|
+
};
|
|
27098
|
+
if (t2.details) {
|
|
27099
|
+
result.details = await t2.details(value);
|
|
27100
|
+
}
|
|
27101
|
+
results.push(result);
|
|
27102
|
+
log.success(`${t2.title} ${theme.dim(formatDuration2(result.ms))}`);
|
|
27103
|
+
} catch (err) {
|
|
27104
|
+
const message2 = err instanceof CLIError ? err.message : err instanceof Error ? err.message : String(err);
|
|
27105
|
+
const result = {
|
|
27106
|
+
title: t2.title,
|
|
27107
|
+
ok: false,
|
|
27108
|
+
ms: timer.ms(),
|
|
27109
|
+
error: message2
|
|
27110
|
+
};
|
|
27111
|
+
results.push(result);
|
|
27112
|
+
log.error(`${t2.title} ${theme.dim(message2)}`);
|
|
27113
|
+
}
|
|
27114
|
+
}
|
|
27115
|
+
}
|
|
27116
|
+
if (results.some((r2) => !r2.ok)) {
|
|
27117
|
+
process.exitCode = 1;
|
|
27118
|
+
}
|
|
27119
|
+
if (options.onSummary) {
|
|
27120
|
+
options.onSummary(results);
|
|
27121
|
+
} else if (!options.format?.json && !options.format?.quiet) {
|
|
27122
|
+
renderDefaultSummary(results);
|
|
27123
|
+
}
|
|
27124
|
+
return results;
|
|
27125
|
+
}
|
|
27126
|
+
function renderDefaultSummary(results) {
|
|
27127
|
+
if (results.length === 0)
|
|
27128
|
+
return;
|
|
27129
|
+
const rows = results.map((r2) => ({
|
|
27130
|
+
Task: r2.title,
|
|
27131
|
+
Status: r2.ok ? theme.success(icons.success) : theme.error(icons.error),
|
|
27132
|
+
Duration: formatDuration2(r2.ms)
|
|
27133
|
+
}));
|
|
27134
|
+
formatTable(rows, {});
|
|
27135
|
+
}
|
|
27136
|
+
|
|
27137
|
+
// src/commands/deploy/deploy-command.ts
|
|
26862
27138
|
import { statSync, existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
26863
27139
|
import { resolve as resolve2 } from "path";
|
|
26864
27140
|
|
|
@@ -26937,13 +27213,6 @@ class EnvService {
|
|
|
26937
27213
|
default: "cryptolinx",
|
|
26938
27214
|
hint: "Prefix for worker URLs"
|
|
26939
27215
|
},
|
|
26940
|
-
{
|
|
26941
|
-
name: "INTERNAL_KEY_BINDING",
|
|
26942
|
-
required: true,
|
|
26943
|
-
secret: true,
|
|
26944
|
-
section: "Internal Auth",
|
|
26945
|
-
hint: "Internal key for D1 worker auth"
|
|
26946
|
-
},
|
|
26947
27216
|
{
|
|
26948
27217
|
name: "TRADE_INTERNAL_KEY",
|
|
26949
27218
|
required: true,
|
|
@@ -27340,62 +27609,61 @@ var DEPLOY_ORDER = [
|
|
|
27340
27609
|
"hoox",
|
|
27341
27610
|
"dashboard"
|
|
27342
27611
|
];
|
|
27343
|
-
async function deployAll(configService, cf, env, forceRebuildDashboard = false, autoMode = false) {
|
|
27612
|
+
async function deployAll(configService, cf, env, forceRebuildDashboard = false, autoMode = false, format3 = {}) {
|
|
27344
27613
|
const enabled = configService.listEnabledWorkers();
|
|
27345
27614
|
const workers = DEPLOY_ORDER.filter((w) => w !== "dashboard" && enabled.includes(w));
|
|
27346
27615
|
const unknown2 = enabled.filter((w) => w !== "dashboard" && !DEPLOY_ORDER.includes(w));
|
|
27347
27616
|
const allItems = [...workers, ...unknown2, "dashboard"];
|
|
27348
|
-
const results = [];
|
|
27349
27617
|
if (allItems.length === 0) {
|
|
27350
|
-
return
|
|
27618
|
+
return [];
|
|
27351
27619
|
}
|
|
27352
|
-
const
|
|
27353
|
-
|
|
27354
|
-
|
|
27355
|
-
|
|
27356
|
-
|
|
27357
|
-
s.message(`[${i2 + 1}/${allItems.length}] ${name}...`);
|
|
27358
|
-
let result;
|
|
27359
|
-
if (isDashboard) {
|
|
27360
|
-
result = await deployDashboard(cf, forceRebuildDashboard, true, autoMode);
|
|
27361
|
-
} else {
|
|
27362
|
-
result = await deploySingle(configService, cf, name, env);
|
|
27363
|
-
}
|
|
27364
|
-
results.push(result);
|
|
27365
|
-
if (result.success) {
|
|
27366
|
-
s.stop(`${theme.success(icons.success)} ${name} deployed`);
|
|
27367
|
-
} else {
|
|
27368
|
-
s.stop(`${theme.error(icons.error)} ${name} failed`);
|
|
27369
|
-
}
|
|
27370
|
-
if (result.success) {
|
|
27371
|
-
if (result.url) {
|
|
27372
|
-
log.step(` ${theme.dim("URL:")} ${result.url}`);
|
|
27373
|
-
}
|
|
27374
|
-
if (result.size) {
|
|
27375
|
-
log.step(` ${theme.dim("Size:")} ${result.size}`);
|
|
27376
|
-
}
|
|
27377
|
-
if (result.startupTime) {
|
|
27378
|
-
log.step(` ${theme.dim("Startup:")} ${result.startupTime}`);
|
|
27620
|
+
const tasks2 = allItems.map((name) => ({
|
|
27621
|
+
title: name,
|
|
27622
|
+
run: async () => {
|
|
27623
|
+
if (name === "dashboard") {
|
|
27624
|
+
return await deployDashboard(cf, forceRebuildDashboard, true, autoMode);
|
|
27379
27625
|
}
|
|
27380
|
-
|
|
27381
|
-
|
|
27382
|
-
|
|
27383
|
-
if (!
|
|
27384
|
-
|
|
27385
|
-
}
|
|
27386
|
-
|
|
27387
|
-
|
|
27388
|
-
|
|
27389
|
-
|
|
27390
|
-
|
|
27626
|
+
return await deploySingle(configService, cf, name, env);
|
|
27627
|
+
},
|
|
27628
|
+
details: (r2) => {
|
|
27629
|
+
if (!r2.success)
|
|
27630
|
+
return { error: r2.error ?? "unknown" };
|
|
27631
|
+
const d = {};
|
|
27632
|
+
if (r2.url)
|
|
27633
|
+
d.URL = r2.url;
|
|
27634
|
+
if (r2.size)
|
|
27635
|
+
d.Size = r2.size;
|
|
27636
|
+
if (r2.startupTime)
|
|
27637
|
+
d.Startup = r2.startupTime;
|
|
27638
|
+
if (r2.versionId)
|
|
27639
|
+
d.Version = r2.versionId.slice(0, 8) + "\u2026";
|
|
27640
|
+
if (Object.keys(d).length === 0 && r2.rawOutput) {
|
|
27641
|
+
d.Output = r2.rawOutput.slice(0, 80);
|
|
27642
|
+
}
|
|
27643
|
+
return d;
|
|
27391
27644
|
}
|
|
27392
|
-
}
|
|
27393
|
-
const
|
|
27394
|
-
|
|
27395
|
-
|
|
27396
|
-
|
|
27397
|
-
}
|
|
27398
|
-
return
|
|
27645
|
+
}));
|
|
27646
|
+
const richResults = await runRichTasks(tasks2, {
|
|
27647
|
+
title: `Deploying ${allItems.length} item(s)`,
|
|
27648
|
+
format: format3,
|
|
27649
|
+
onSummary: (results) => renderDeploySummary(results, format3)
|
|
27650
|
+
});
|
|
27651
|
+
return richResults.map((r2) => r2.value).filter(Boolean);
|
|
27652
|
+
}
|
|
27653
|
+
function renderDeploySummary(results, format3) {
|
|
27654
|
+
if (format3.json || format3.quiet)
|
|
27655
|
+
return;
|
|
27656
|
+
const rows = results.map((r2) => {
|
|
27657
|
+
const d = r2.value;
|
|
27658
|
+
return {
|
|
27659
|
+
Worker: r2.title,
|
|
27660
|
+
Status: r2.ok ? formatBadge("ok", "DEPLOYED") : formatBadge("err", "FAILED"),
|
|
27661
|
+
URL: d?.url ?? theme.dim("\u2014"),
|
|
27662
|
+
Size: d?.size ?? theme.dim("\u2014"),
|
|
27663
|
+
Duration: formatDuration3(r2.ms)
|
|
27664
|
+
};
|
|
27665
|
+
});
|
|
27666
|
+
formatTable(rows, {});
|
|
27399
27667
|
}
|
|
27400
27668
|
async function deployDashboard(_cf, forceRebuild = false, silentMode = false, autoMode = false) {
|
|
27401
27669
|
const dashboardPath = "workers/dashboard";
|
|
@@ -27803,9 +28071,10 @@ EXAMPLES:
|
|
|
27803
28071
|
hoox deploy all
|
|
27804
28072
|
hoox deploy all --env production
|
|
27805
28073
|
hoox deploy all --rebuild
|
|
27806
|
-
hoox deploy all --auto`).option("--env <env>", "Cloudflare environment (e.g. production, staging)").option("--rebuild", "Force rebuild of dashboard before deploying").option("--auto", "Skip dashboard rebuild prompt, use existing build if available").option("--dry-run", "Preview deployment plan without executing").action(withErrorHandling(async (options) => {
|
|
28074
|
+
hoox deploy all --auto`).option("--env <env>", "Cloudflare environment (e.g. production, staging)").option("--rebuild", "Force rebuild of dashboard before deploying").option("--auto", "Skip dashboard rebuild prompt, use existing build if available").option("--dry-run", "Preview deployment plan without executing").action(withErrorHandling(async (options, cmd) => {
|
|
27807
28075
|
const configService = new ConfigService;
|
|
27808
28076
|
await configService.load();
|
|
28077
|
+
const format3 = getFormatOptions(cmd);
|
|
27809
28078
|
if (options.dryRun) {
|
|
27810
28079
|
const enabled = configService.listEnabledWorkers();
|
|
27811
28080
|
const workers = DEPLOY_ORDER.filter((w) => w !== "dashboard" && enabled.includes(w));
|
|
@@ -27838,7 +28107,7 @@ ${theme.dim("Run without --dry-run to deploy.")}
|
|
|
27838
28107
|
return;
|
|
27839
28108
|
}
|
|
27840
28109
|
const cf = new CloudflareService;
|
|
27841
|
-
await deployAll(configService, cf, options.env, options.rebuild ?? false, options.auto ?? false);
|
|
28110
|
+
await deployAll(configService, cf, options.env, options.rebuild ?? false, options.auto ?? false, format3);
|
|
27842
28111
|
}, { service: "deploy" }));
|
|
27843
28112
|
deployCmd.command("workers").summary("Deploy all enabled workers to Cloudflare").description(`Deploy all enabled workers to Cloudflare Workers.
|
|
27844
28113
|
|
|
@@ -27927,8 +28196,8 @@ OPTIONS:
|
|
|
27927
28196
|
|
|
27928
28197
|
EXAMPLES:
|
|
27929
28198
|
hoox deploy worker trade-worker
|
|
27930
|
-
hoox deploy worker agent-worker --env production`).option("--env <env>", "Cloudflare environment (e.g. production, staging)").action(withErrorHandling(async (name, options) => {
|
|
27931
|
-
const fmt = getFormatOptions(
|
|
28199
|
+
hoox deploy worker agent-worker --env production`).option("--env <env>", "Cloudflare environment (e.g. production, staging)").action(withErrorHandling(async (name, options, cmd) => {
|
|
28200
|
+
const fmt = getFormatOptions(cmd);
|
|
27932
28201
|
const configService = new ConfigService;
|
|
27933
28202
|
await configService.load();
|
|
27934
28203
|
const cf = new CloudflareService;
|
|
@@ -27950,8 +28219,8 @@ OPTIONS:
|
|
|
27950
28219
|
|
|
27951
28220
|
EXAMPLES:
|
|
27952
28221
|
hoox deploy dashboard Interactive: choose to rebuild or use existing
|
|
27953
|
-
hoox deploy dashboard --rebuild Force rebuild before deploying`).option("--rebuild", "Force rebuild of dashboard before deploying").action(withErrorHandling(async (options) => {
|
|
27954
|
-
const fmt = getFormatOptions(
|
|
28222
|
+
hoox deploy dashboard --rebuild Force rebuild before deploying`).option("--rebuild", "Force rebuild of dashboard before deploying").action(withErrorHandling(async (options, cmd) => {
|
|
28223
|
+
const fmt = getFormatOptions(cmd);
|
|
27955
28224
|
const cf = new CloudflareService;
|
|
27956
28225
|
const result = await deployDashboard(cf, options.rebuild);
|
|
27957
28226
|
if (result.success) {
|
|
@@ -27977,8 +28246,8 @@ Use --token and --secret-token to override.
|
|
|
27977
28246
|
EXAMPLES:
|
|
27978
28247
|
hoox deploy telegram-webhook
|
|
27979
28248
|
hoox deploy telegram-webhook --token 123456:ABC-DEF1234
|
|
27980
|
-
hoox deploy telegram-webhook --subdomain myapp`).option("--token <token>", "Telegram bot token (from @BotFather)").option("--secret-token <secret>", "Telegram webhook secret token").option("--subdomain <prefix>", "Worker subdomain prefix (default: from config)").action(withErrorHandling(async (options) => {
|
|
27981
|
-
const fmt = getFormatOptions(
|
|
28249
|
+
hoox deploy telegram-webhook --subdomain myapp`).option("--token <token>", "Telegram bot token (from @BotFather)").option("--secret-token <secret>", "Telegram webhook secret token").option("--subdomain <prefix>", "Worker subdomain prefix (default: from config)").action(withErrorHandling(async (options, cmd) => {
|
|
28250
|
+
const fmt = getFormatOptions(cmd);
|
|
27982
28251
|
await doTelegramWebhook(fmt, options.token, options.secretToken, options.subdomain);
|
|
27983
28252
|
}, { service: "deploy" }));
|
|
27984
28253
|
deployCmd.command("update-internal-urls").summary("Update dashboard wrangler.jsonc with current service URLs").description(`Update the dashboard's wrangler.jsonc with the current service URLs.
|
|
@@ -27987,8 +28256,8 @@ This is a post-deployment step that ensures the dashboard has correct
|
|
|
27987
28256
|
service binding URLs for all workers.
|
|
27988
28257
|
|
|
27989
28258
|
EXAMPLES:
|
|
27990
|
-
hoox deploy update-internal-urls`).action(withErrorHandling(async () => {
|
|
27991
|
-
const fmt = getFormatOptions(
|
|
28259
|
+
hoox deploy update-internal-urls`).action(withErrorHandling(async (_options, cmd) => {
|
|
28260
|
+
const fmt = getFormatOptions(cmd);
|
|
27992
28261
|
await doUpdateInternalUrls(fmt);
|
|
27993
28262
|
}, { service: "deploy" }));
|
|
27994
28263
|
deployCmd.command("kv-config").summary("Apply KV manifest keys post-deployment").description(`Apply the KV manifest key-value pairs after deploying workers.
|
|
@@ -27997,8 +28266,8 @@ Sets all KV keys from the manifest to their default values.
|
|
|
27997
28266
|
This post-deployment step initializes the CONFIG_KV namespace.
|
|
27998
28267
|
|
|
27999
28268
|
EXAMPLES:
|
|
28000
|
-
hoox deploy kv-config`).action(withErrorHandling(async () => {
|
|
28001
|
-
const fmt = getFormatOptions(
|
|
28269
|
+
hoox deploy kv-config`).action(withErrorHandling(async (_options, cmd) => {
|
|
28270
|
+
const fmt = getFormatOptions(cmd);
|
|
28002
28271
|
await doKvConfig(fmt);
|
|
28003
28272
|
}, { service: "deploy" }));
|
|
28004
28273
|
deployCmd.command("history <worker>").summary("Show deployment version history for a worker").description(`Display the deployment version history for a Cloudflare Worker.
|
|
@@ -28010,8 +28279,8 @@ ARGUMENTS:
|
|
|
28010
28279
|
|
|
28011
28280
|
EXAMPLES:
|
|
28012
28281
|
hoox deploy history trade-worker
|
|
28013
|
-
hoox deploy history hoox --json`).action(withErrorHandling(async (worker) => {
|
|
28014
|
-
const fmt = getFormatOptions(
|
|
28282
|
+
hoox deploy history hoox --json`).action(withErrorHandling(async (worker, _options, cmd) => {
|
|
28283
|
+
const fmt = getFormatOptions(cmd);
|
|
28015
28284
|
await doVersionHistory(worker, fmt);
|
|
28016
28285
|
}, { service: "deploy" }));
|
|
28017
28286
|
deployCmd.command("rollback <worker>").argument("[version]", "Version ID to rollback to (if omitted, prompts to select)").summary("Rollback a worker to a previous version").description(`Rollback a Cloudflare Worker to a previous version.
|
|
@@ -28029,8 +28298,8 @@ OPTIONS:
|
|
|
28029
28298
|
EXAMPLES:
|
|
28030
28299
|
hoox deploy rollback trade-worker
|
|
28031
28300
|
hoox deploy rollback trade-worker <version-id>
|
|
28032
|
-
hoox deploy rollback trade-worker <version-id> --yes`).option("--yes", "Skip confirmation prompt").action(withErrorHandling(async (worker, version2, options) => {
|
|
28033
|
-
const fmt = getFormatOptions(
|
|
28301
|
+
hoox deploy rollback trade-worker <version-id> --yes`).option("--yes", "Skip confirmation prompt").action(withErrorHandling(async (worker, version2, options, cmd) => {
|
|
28302
|
+
const fmt = getFormatOptions(cmd);
|
|
28034
28303
|
await doVersionRollback(worker, version2, fmt, options.yes ?? false);
|
|
28035
28304
|
}, { service: "deploy" }));
|
|
28036
28305
|
}
|
|
@@ -30101,27 +30370,40 @@ ${summary.failed > 0 ? theme.error(icons.error) : theme.success(icons.success)}
|
|
|
30101
30370
|
`);
|
|
30102
30371
|
}
|
|
30103
30372
|
async function handleSetup(opts) {
|
|
30104
|
-
const s = spinner();
|
|
30105
30373
|
try {
|
|
30106
30374
|
const configService = new ConfigService;
|
|
30107
|
-
s.start("Loading config...");
|
|
30108
30375
|
await configService.load();
|
|
30109
|
-
s.stop("Config loaded");
|
|
30110
30376
|
const cf = new CloudflareService;
|
|
30111
30377
|
const secretsService = await SecretsService.create();
|
|
30112
30378
|
const categories = [];
|
|
30113
|
-
|
|
30114
|
-
|
|
30115
|
-
|
|
30116
|
-
|
|
30117
|
-
|
|
30118
|
-
|
|
30119
|
-
|
|
30120
|
-
|
|
30121
|
-
|
|
30122
|
-
|
|
30123
|
-
|
|
30124
|
-
|
|
30379
|
+
const tasks2 = [
|
|
30380
|
+
{
|
|
30381
|
+
title: "Validating config",
|
|
30382
|
+
run: async () => await runConfigChecks(configService)
|
|
30383
|
+
},
|
|
30384
|
+
{
|
|
30385
|
+
title: "Checking infrastructure (D1, KV, R2, Queues)",
|
|
30386
|
+
run: async () => await runInfraChecks(cf)
|
|
30387
|
+
},
|
|
30388
|
+
{
|
|
30389
|
+
title: "Checking secrets",
|
|
30390
|
+
run: async () => await runSecretsChecks(secretsService, configService, cf)
|
|
30391
|
+
},
|
|
30392
|
+
{
|
|
30393
|
+
title: "Checking database",
|
|
30394
|
+
run: async () => await runDatabaseChecks(cf, configService)
|
|
30395
|
+
}
|
|
30396
|
+
];
|
|
30397
|
+
const results = await runRichTasks(tasks2, {
|
|
30398
|
+
title: "Running diagnostics",
|
|
30399
|
+
format: opts,
|
|
30400
|
+
onSummary: (rows) => {
|
|
30401
|
+
for (const r2 of rows) {
|
|
30402
|
+
if (r2.ok && r2.value)
|
|
30403
|
+
categories.push(r2.value);
|
|
30404
|
+
}
|
|
30405
|
+
}
|
|
30406
|
+
});
|
|
30125
30407
|
const report = buildReport(categories);
|
|
30126
30408
|
if (opts.json) {
|
|
30127
30409
|
process.stdout.write(JSON.stringify(report, null, 2) + `
|
|
@@ -30132,6 +30414,9 @@ async function handleSetup(opts) {
|
|
|
30132
30414
|
if (!report.success) {
|
|
30133
30415
|
process.exitCode = 1 /* ERROR */;
|
|
30134
30416
|
}
|
|
30417
|
+
if (results.some((r2) => !r2.ok)) {
|
|
30418
|
+
process.exitCode = 1 /* ERROR */;
|
|
30419
|
+
}
|
|
30135
30420
|
} catch (err) {
|
|
30136
30421
|
const message = err instanceof Error ? err.message : String(err);
|
|
30137
30422
|
formatError2(message, opts);
|
|
@@ -30139,7 +30424,6 @@ async function handleSetup(opts) {
|
|
|
30139
30424
|
}
|
|
30140
30425
|
}
|
|
30141
30426
|
async function handleHealth(opts, autoFix) {
|
|
30142
|
-
const s = spinner();
|
|
30143
30427
|
const results = [];
|
|
30144
30428
|
try {
|
|
30145
30429
|
const configService = new ConfigService;
|
|
@@ -30150,29 +30434,37 @@ async function handleHealth(opts, autoFix) {
|
|
|
30150
30434
|
formatSuccess("No enabled workers to check", opts);
|
|
30151
30435
|
return;
|
|
30152
30436
|
}
|
|
30153
|
-
|
|
30154
|
-
|
|
30155
|
-
|
|
30156
|
-
|
|
30157
|
-
|
|
30158
|
-
|
|
30159
|
-
|
|
30160
|
-
|
|
30161
|
-
|
|
30162
|
-
|
|
30163
|
-
|
|
30164
|
-
|
|
30165
|
-
|
|
30166
|
-
|
|
30167
|
-
|
|
30168
|
-
|
|
30169
|
-
|
|
30170
|
-
|
|
30171
|
-
|
|
30172
|
-
|
|
30437
|
+
const tasks2 = enabledWorkers.map((workerName) => ({
|
|
30438
|
+
title: `Probe ${workerName}`,
|
|
30439
|
+
run: async () => {
|
|
30440
|
+
try {
|
|
30441
|
+
const tailResult = await cf.tail(workerName);
|
|
30442
|
+
const healthy = tailResult.ok;
|
|
30443
|
+
return {
|
|
30444
|
+
worker: workerName,
|
|
30445
|
+
status: healthy ? "healthy" : "degraded",
|
|
30446
|
+
connectivity: healthy,
|
|
30447
|
+
error: tailResult.ok ? undefined : tailResult.error ?? "Unknown error"
|
|
30448
|
+
};
|
|
30449
|
+
} catch (err) {
|
|
30450
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
30451
|
+
return {
|
|
30452
|
+
worker: workerName,
|
|
30453
|
+
status: "down",
|
|
30454
|
+
connectivity: false,
|
|
30455
|
+
error: message
|
|
30456
|
+
};
|
|
30457
|
+
}
|
|
30173
30458
|
}
|
|
30459
|
+
}));
|
|
30460
|
+
const taskResults = await runRichTasks(tasks2, {
|
|
30461
|
+
title: `Health-checking ${enabledWorkers.length} worker(s)`,
|
|
30462
|
+
format: opts
|
|
30463
|
+
});
|
|
30464
|
+
for (const r2 of taskResults) {
|
|
30465
|
+
if (r2.value)
|
|
30466
|
+
results.push(r2.value);
|
|
30174
30467
|
}
|
|
30175
|
-
s.stop("Health check complete");
|
|
30176
30468
|
if (opts.json) {
|
|
30177
30469
|
process.stdout.write(JSON.stringify(results, null, 2) + `
|
|
30178
30470
|
`);
|
|
@@ -30197,7 +30489,6 @@ ${theme.warning(icons.warning)} Auto-fix flag set but health issues require manu
|
|
|
30197
30489
|
process.exitCode = 1 /* ERROR */;
|
|
30198
30490
|
}
|
|
30199
30491
|
} catch (err) {
|
|
30200
|
-
s.stop("Health check failed");
|
|
30201
30492
|
const message = err instanceof Error ? err.message : String(err);
|
|
30202
30493
|
formatError2(message, opts);
|
|
30203
30494
|
process.exitCode = 1 /* ERROR */;
|
|
@@ -30771,8 +31062,8 @@ async function tailAllWorkers(opts, fmt) {
|
|
|
30771
31062
|
}
|
|
30772
31063
|
function registerLogsCommand(program2) {
|
|
30773
31064
|
const logsCmd = program2.command("logs").description("Tail and view Cloudflare Worker logs in real-time");
|
|
30774
|
-
logsCmd.command("worker <name>").description("Tail logs for a specific Cloudflare Worker").option("--level <level>", "Filter by log level (log, info, warn, error, debug, all)", "all").option("--follow", "Continuously stream logs (default)", true).option("--no-follow", "Show recent logs and exit after 5 seconds").option("--json", "Output logs in JSON format").action(async (name, options) => {
|
|
30775
|
-
const fmt = getFormatOptions(
|
|
31065
|
+
logsCmd.command("worker <name>").description("Tail logs for a specific Cloudflare Worker").option("--level <level>", "Filter by log level (log, info, warn, error, debug, all)", "all").option("--follow", "Continuously stream logs (default)", true).option("--no-follow", "Show recent logs and exit after 5 seconds").option("--json", "Output logs in JSON format").action(async (name, options, cmd) => {
|
|
31066
|
+
const fmt = getFormatOptions(cmd);
|
|
30776
31067
|
const workerOpts = {
|
|
30777
31068
|
level: normalizeLevel(options.level ?? "all"),
|
|
30778
31069
|
follow: options.follow !== false,
|
|
@@ -30786,8 +31077,8 @@ function registerLogsCommand(program2) {
|
|
|
30786
31077
|
process.exitCode = 1 /* ERROR */;
|
|
30787
31078
|
}
|
|
30788
31079
|
});
|
|
30789
|
-
logsCmd.command("all").description("Tail logs from all enabled workers concurrently").option("--level <level>", "Filter by log level (log, info, warn, error, debug, all)", "all").option("--follow", "Continuously stream logs (default)", true).option("--no-follow", "Show recent logs and exit after 5 seconds").option("--json", "Output logs in JSON format").action(async (options) => {
|
|
30790
|
-
const fmt = getFormatOptions(
|
|
31080
|
+
logsCmd.command("all").description("Tail logs from all enabled workers concurrently").option("--level <level>", "Filter by log level (log, info, warn, error, debug, all)", "all").option("--follow", "Continuously stream logs (default)", true).option("--no-follow", "Show recent logs and exit after 5 seconds").option("--json", "Output logs in JSON format").action(async (options, cmd) => {
|
|
31081
|
+
const fmt = getFormatOptions(cmd);
|
|
30791
31082
|
const workerOpts = {
|
|
30792
31083
|
level: normalizeLevel(options.level ?? "all"),
|
|
30793
31084
|
follow: options.follow !== false,
|
|
@@ -30896,21 +31187,22 @@ var PIPELINE_STEPS = [
|
|
|
30896
31187
|
];
|
|
30897
31188
|
function registerTestCommand(program2) {
|
|
30898
31189
|
const testCmd = program2.command("test").description("Run tests and CI pipeline");
|
|
30899
|
-
testCmd.command("all").description("Run full CI pipeline: lint \u2192 typecheck \u2192 unit tests \u2192 integration tests").option("--json", "Output results as JSON").action(withErrorHandling(async (options) => {
|
|
30900
|
-
const fmt = getFormatOptions(
|
|
31190
|
+
testCmd.command("all").description("Run full CI pipeline: lint \u2192 typecheck \u2192 unit tests \u2192 integration tests").option("--json", "Output results as JSON").action(withErrorHandling(async (options, cmd) => {
|
|
31191
|
+
const fmt = getFormatOptions(cmd);
|
|
30901
31192
|
const useJson = options.json || fmt.json;
|
|
30902
31193
|
const opts = { json: useJson, quiet: fmt.quiet };
|
|
30903
31194
|
const results = [];
|
|
30904
31195
|
const s = spinner();
|
|
31196
|
+
const pipelineTimer = startTimer();
|
|
30905
31197
|
s.start("Running CI pipeline...");
|
|
30906
31198
|
for (const step of PIPELINE_STEPS) {
|
|
30907
31199
|
s.message(`${theme.info(icons.info)} ${step.label}...`);
|
|
30908
31200
|
const result = await runStep(step.args, process.cwd());
|
|
30909
31201
|
results.push({ ...result, step: step.label });
|
|
30910
31202
|
if (result.success) {
|
|
30911
|
-
s.message(` ${theme.success(icons.success)} ${step.label} passed (${result.duration}
|
|
31203
|
+
s.message(` ${theme.success(icons.success)} ${step.label} passed (${formatDuration3(result.duration)})`);
|
|
30912
31204
|
} else {
|
|
30913
|
-
s.message(` ${theme.error(icons.error)} ${step.label} failed (${result.duration}
|
|
31205
|
+
s.message(` ${theme.error(icons.error)} ${step.label} failed (${formatDuration3(result.duration)})`);
|
|
30914
31206
|
if (result.error) {
|
|
30915
31207
|
s.message(theme.dim(result.error.slice(0, 500)));
|
|
30916
31208
|
}
|
|
@@ -30923,14 +31215,14 @@ function registerTestCommand(program2) {
|
|
|
30923
31215
|
failed: results.filter((r2) => !r2.success).length,
|
|
30924
31216
|
results
|
|
30925
31217
|
};
|
|
30926
|
-
s.stop(summary.failed > 0 ? `Pipeline complete: ${summary.passed} passed, ${summary.failed} failed` :
|
|
31218
|
+
s.stop(summary.failed > 0 ? `Pipeline complete: ${summary.passed} passed, ${summary.failed} failed (${formatDuration3(pipelineTimer.ms())})` : `Pipeline complete (${formatDuration3(pipelineTimer.ms())})`);
|
|
30927
31219
|
printSummary(summary, opts);
|
|
30928
31220
|
if (summary.failed > 0) {
|
|
30929
31221
|
process.exitCode = 1 /* ERROR */;
|
|
30930
31222
|
}
|
|
30931
31223
|
}, { service: "test" }));
|
|
30932
|
-
testCmd.command("unit").description("Run unit tests with bun test").option("--coverage", "Run with coverage reporting").action(async (options) => {
|
|
30933
|
-
const fmt = getFormatOptions(
|
|
31224
|
+
testCmd.command("unit").description("Run unit tests with bun test").option("--coverage", "Run with coverage reporting").action(async (options, cmd) => {
|
|
31225
|
+
const fmt = getFormatOptions(cmd);
|
|
30934
31226
|
const args = ["bun", "test"];
|
|
30935
31227
|
if (options.coverage)
|
|
30936
31228
|
args.push("--coverage");
|
|
@@ -30942,8 +31234,8 @@ function registerTestCommand(program2) {
|
|
|
30942
31234
|
process.exitCode = 1 /* ERROR */;
|
|
30943
31235
|
}
|
|
30944
31236
|
});
|
|
30945
|
-
testCmd.command("integration").description("Run integration tests with vitest").option("--coverage", "Run with coverage reporting").action(async (options) => {
|
|
30946
|
-
const fmt = getFormatOptions(
|
|
31237
|
+
testCmd.command("integration").description("Run integration tests with vitest").option("--coverage", "Run with coverage reporting").action(async (options, cmd) => {
|
|
31238
|
+
const fmt = getFormatOptions(cmd);
|
|
30947
31239
|
const args = ["vitest", "run", "--config", "vitest.config.ts"];
|
|
30948
31240
|
if (options.coverage)
|
|
30949
31241
|
args.push("--coverage");
|
|
@@ -30955,8 +31247,8 @@ function registerTestCommand(program2) {
|
|
|
30955
31247
|
process.exitCode = 1 /* ERROR */;
|
|
30956
31248
|
}
|
|
30957
31249
|
});
|
|
30958
|
-
testCmd.command("worker <name>").description("Run tests for a specific worker by name").option("--coverage", "Run with coverage reporting").action(withErrorHandling(async (name, options) => {
|
|
30959
|
-
const fmt = getFormatOptions(
|
|
31250
|
+
testCmd.command("worker <name>").description("Run tests for a specific worker by name").option("--coverage", "Run with coverage reporting").action(withErrorHandling(async (name, options, cmd) => {
|
|
31251
|
+
const fmt = getFormatOptions(cmd);
|
|
30960
31252
|
const configService = new ConfigService;
|
|
30961
31253
|
await configService.load();
|
|
30962
31254
|
const workerConfig = configService.getWorker(name);
|
|
@@ -30979,14 +31271,16 @@ function registerTestCommand(program2) {
|
|
|
30979
31271
|
}, { service: "test" }));
|
|
30980
31272
|
testCmd.command("live").description("Run live Cloudflare service integration tests (no mocks)").option("--service <name>", "Test a specific service: d1, kv, r2, queues, ai, api, secrets, durable-objects").action(withErrorHandling(async (options) => {
|
|
30981
31273
|
const s = spinner();
|
|
31274
|
+
const liveTimer = startTimer();
|
|
30982
31275
|
let filePattern = options.service ? `tests/live/${options.service}.test.ts` : "tests/live/";
|
|
30983
31276
|
s.start(`Running live tests${options.service ? ` for ${options.service}` : ""}...`);
|
|
30984
31277
|
const args = ["bun", "test", filePattern, "--jobs", "1"];
|
|
30985
31278
|
const result = await runWithInherit(args, process.cwd());
|
|
31279
|
+
const dur = formatDuration3(liveTimer.ms());
|
|
30986
31280
|
if (result.success) {
|
|
30987
|
-
s.stop(
|
|
31281
|
+
s.stop(`Live tests complete (${dur})`);
|
|
30988
31282
|
} else {
|
|
30989
|
-
s.stop(`Live tests: some failures (exit code ${result.exitCode})`);
|
|
31283
|
+
s.stop(`Live tests: some failures (exit code ${result.exitCode}, ${dur})`);
|
|
30990
31284
|
process.exitCode = 1 /* ERROR */;
|
|
30991
31285
|
}
|
|
30992
31286
|
}, { service: "test" }));
|
|
@@ -31344,8 +31638,8 @@ EXAMPLES:
|
|
|
31344
31638
|
hoox clone --all --home Clone all workers to $HOME/.hoox/workers
|
|
31345
31639
|
hoox clone trade-worker Clone specific worker
|
|
31346
31640
|
hoox clone trade-worker --home Clone specific worker to $HOME/.hoox/workers
|
|
31347
|
-
hoox clone --org my-org Clone from specific org`).option("--all", "Clone all worker repositories").option("--home", "Clone into $HOME/.hoox/workers instead of default location").option("--org <org>", "GitHub organization (derived from git remote by default)").argument("[name]", "Worker name to clone (omit to list status)").action(withErrorHandling(async (name, options) => {
|
|
31348
|
-
const fmt = getFormatOptions(
|
|
31641
|
+
hoox clone --org my-org Clone from specific org`).option("--all", "Clone all worker repositories").option("--home", "Clone into $HOME/.hoox/workers instead of default location").option("--org <org>", "GitHub organization (derived from git remote by default)").argument("[name]", "Worker name to clone (omit to list status)").action(withErrorHandling(async (name, options, cmd) => {
|
|
31642
|
+
const fmt = getFormatOptions(cmd);
|
|
31349
31643
|
const configService = new ConfigService;
|
|
31350
31644
|
await configService.load();
|
|
31351
31645
|
const workers = configService.listWorkers();
|
|
@@ -31380,6 +31674,7 @@ EXAMPLES:
|
|
|
31380
31674
|
}
|
|
31381
31675
|
const repoBase = options.org ? `https://github.com/${options.org}` : await resolveRepoBase();
|
|
31382
31676
|
const s = spinner();
|
|
31677
|
+
const cloneTimer = startTimer();
|
|
31383
31678
|
const location = options.home ? "$HOME/.hoox/workers" : "workers/";
|
|
31384
31679
|
s.start(`Cloning ${workers.length} worker(s) to ${location}...`);
|
|
31385
31680
|
const results = [];
|
|
@@ -31419,10 +31714,10 @@ EXAMPLES:
|
|
|
31419
31714
|
const succeeded = results.filter((r2) => r2.cloned).length;
|
|
31420
31715
|
const failed = results.filter((r2) => !r2.cloned).length;
|
|
31421
31716
|
if (failed > 0) {
|
|
31422
|
-
s.stop(`Clone complete: ${succeeded} succeeded, ${failed} failed`);
|
|
31717
|
+
s.stop(`Clone complete: ${succeeded} succeeded, ${failed} failed (${formatDuration3(cloneTimer.ms())})`);
|
|
31423
31718
|
process.exitCode = 1 /* ERROR */;
|
|
31424
31719
|
} else {
|
|
31425
|
-
s.stop(`All ${succeeded} worker(s) cloned successfully`);
|
|
31720
|
+
s.stop(`All ${succeeded} worker(s) cloned successfully (${formatDuration3(cloneTimer.ms())})`);
|
|
31426
31721
|
}
|
|
31427
31722
|
if (!fmt.quiet) {
|
|
31428
31723
|
const rows = results.map((r2) => ({
|
|
@@ -31450,6 +31745,7 @@ EXAMPLES:
|
|
|
31450
31745
|
}
|
|
31451
31746
|
const repoBase = options.org ? `https://github.com/${options.org}` : await resolveRepoBase();
|
|
31452
31747
|
const s = spinner();
|
|
31748
|
+
const singleTimer = startTimer();
|
|
31453
31749
|
const location = options.home ? "$HOME/.hoox/workers" : "workers/";
|
|
31454
31750
|
s.start(`Cloning ${name} to ${location}...`);
|
|
31455
31751
|
const result = await cloneWorker(getRepoUrl(repoBase, name), workerConfig.path, name, process.cwd(), homeDir);
|
|
@@ -31458,11 +31754,11 @@ EXAMPLES:
|
|
|
31458
31754
|
s.message("Updating submodules...");
|
|
31459
31755
|
await updateSubmodules(process.cwd());
|
|
31460
31756
|
} catch {}
|
|
31461
|
-
s.stop(`Successfully cloned ${name}`);
|
|
31757
|
+
s.stop(`Successfully cloned ${name} (${formatDuration3(singleTimer.ms())})`);
|
|
31462
31758
|
const locationText = options.home ? " at $HOME/.hoox/workers" : "";
|
|
31463
31759
|
formatSuccess(`Cloned ${name}${locationText} from ${result.repo}`, fmt);
|
|
31464
31760
|
} else {
|
|
31465
|
-
s.stop(`Failed to clone ${name}`);
|
|
31761
|
+
s.stop(`Failed to clone ${name} (${formatDuration3(singleTimer.ms())})`);
|
|
31466
31762
|
formatError2(new CLIError(`Failed to clone "${name}": ${result.error}`, 1 /* ERROR */), fmt);
|
|
31467
31763
|
process.exitCode = 1 /* ERROR */;
|
|
31468
31764
|
}
|
|
@@ -31650,16 +31946,23 @@ class DbService {
|
|
|
31650
31946
|
return stdout2.trim();
|
|
31651
31947
|
}
|
|
31652
31948
|
async readMigrationSql() {
|
|
31949
|
+
const path5 = this.resolveMigrationScriptPath();
|
|
31950
|
+
let file2;
|
|
31653
31951
|
try {
|
|
31654
|
-
|
|
31655
|
-
|
|
31656
|
-
|
|
31657
|
-
|
|
31658
|
-
|
|
31659
|
-
|
|
31660
|
-
}
|
|
31661
|
-
}
|
|
31662
|
-
|
|
31952
|
+
file2 = Bun.file(path5);
|
|
31953
|
+
} catch (e) {
|
|
31954
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
31955
|
+
throw new Error(`Migration script "${path5}" could not be read: ${message}. ` + `Refusing to run no-op migration.`, { cause: e });
|
|
31956
|
+
}
|
|
31957
|
+
if (!await file2.exists()) {
|
|
31958
|
+
throw new Error(`Migration script "${path5}" not found. ` + `Refusing to run no-op migration. Create the script or run \`hoox setup\` first.`);
|
|
31959
|
+
}
|
|
31960
|
+
const content = await file2.text();
|
|
31961
|
+
const match = content.match(/d1\s+execute\s+\S+\s+--command=["'](.+?)["']/s);
|
|
31962
|
+
if (!match) {
|
|
31963
|
+
throw new Error(`Migration script "${path5}" was found but no \`d1 execute \u2026 --command="\u2026"\` line could be extracted. ` + `Refusing to fall back to a no-op migration \u2014 please check the script format.`);
|
|
31964
|
+
}
|
|
31965
|
+
return match[1];
|
|
31663
31966
|
}
|
|
31664
31967
|
}
|
|
31665
31968
|
// src/commands/db/db-command.ts
|
|
@@ -31670,34 +31973,36 @@ async function resolveDb(cmd, svc) {
|
|
|
31670
31973
|
async function handleApply(opts, dbName, remote, file2) {
|
|
31671
31974
|
const svc = new DbService;
|
|
31672
31975
|
const s = spinner();
|
|
31976
|
+
const t2 = startTimer();
|
|
31673
31977
|
s.start("Applying schema...");
|
|
31674
31978
|
try {
|
|
31675
31979
|
const output = await svc.apply(dbName, remote, file2);
|
|
31676
|
-
s.stop(
|
|
31980
|
+
s.stop(`Schema applied (${formatDuration3(t2.ms())})`);
|
|
31677
31981
|
formatSuccess(`Schema applied to ${dbName}${remote ? " (remote)" : " (local)"}`, opts);
|
|
31678
31982
|
if (!opts.quiet && output) {
|
|
31679
31983
|
process.stdout.write(`${output}
|
|
31680
31984
|
`);
|
|
31681
31985
|
}
|
|
31682
31986
|
} catch (err) {
|
|
31683
|
-
s.stop(
|
|
31987
|
+
s.stop(`Schema apply failed (${formatDuration3(t2.ms())})`);
|
|
31684
31988
|
throw err;
|
|
31685
31989
|
}
|
|
31686
31990
|
}
|
|
31687
31991
|
async function handleMigrate(opts, dbName, remote) {
|
|
31688
31992
|
const svc = new DbService;
|
|
31689
31993
|
const s = spinner();
|
|
31994
|
+
const t2 = startTimer();
|
|
31690
31995
|
s.start("Running migrations...");
|
|
31691
31996
|
try {
|
|
31692
31997
|
const output = await svc.migrate(dbName, remote);
|
|
31693
|
-
s.stop(
|
|
31998
|
+
s.stop(`Migrations complete (${formatDuration3(t2.ms())})`);
|
|
31694
31999
|
formatSuccess(`Migrations applied to ${dbName}${remote ? " (remote)" : " (local)"}`, opts);
|
|
31695
32000
|
if (!opts.quiet && output) {
|
|
31696
32001
|
process.stdout.write(`${output}
|
|
31697
32002
|
`);
|
|
31698
32003
|
}
|
|
31699
32004
|
} catch (err) {
|
|
31700
|
-
s.stop(
|
|
32005
|
+
s.stop(`Migrations failed (${formatDuration3(t2.ms())})`);
|
|
31701
32006
|
throw err;
|
|
31702
32007
|
}
|
|
31703
32008
|
}
|
|
@@ -31716,7 +32021,37 @@ async function handleList2(opts, dbName, remote) {
|
|
|
31716
32021
|
formatTable(rows, opts);
|
|
31717
32022
|
}
|
|
31718
32023
|
}
|
|
31719
|
-
|
|
32024
|
+
var DESTRUCTIVE_SQL_KEYWORDS = [
|
|
32025
|
+
"DROP",
|
|
32026
|
+
"TRUNCATE",
|
|
32027
|
+
"ALTER",
|
|
32028
|
+
"DELETE",
|
|
32029
|
+
"UPDATE",
|
|
32030
|
+
"REPLACE",
|
|
32031
|
+
"INSERT",
|
|
32032
|
+
"CREATE",
|
|
32033
|
+
"ATTACH",
|
|
32034
|
+
"DETACH",
|
|
32035
|
+
"REINDEX",
|
|
32036
|
+
"VACUUM"
|
|
32037
|
+
];
|
|
32038
|
+
function findDestructiveKeywords(sql) {
|
|
32039
|
+
const upper = sql.toUpperCase();
|
|
32040
|
+
const found = [];
|
|
32041
|
+
for (const kw of DESTRUCTIVE_SQL_KEYWORDS) {
|
|
32042
|
+
const re = new RegExp(`(^|[^A-Z0-9_])${kw}([^A-Z0-9_]|$)`, "g");
|
|
32043
|
+
if (re.test(upper))
|
|
32044
|
+
found.push(kw);
|
|
32045
|
+
}
|
|
32046
|
+
return found;
|
|
32047
|
+
}
|
|
32048
|
+
async function handleQuery(opts, dbName, sql, remote, allowDestructive) {
|
|
32049
|
+
if (!allowDestructive) {
|
|
32050
|
+
const matches = findDestructiveKeywords(sql);
|
|
32051
|
+
if (matches.length > 0) {
|
|
32052
|
+
throw new Error(`Destructive SQL keywords detected: ${matches.join(", ")}. ` + `Re-run with --allow-destructive to confirm. ` + `(Keywords: ${DESTRUCTIVE_SQL_KEYWORDS.join(", ")})`);
|
|
32053
|
+
}
|
|
32054
|
+
}
|
|
31720
32055
|
const svc = new DbService;
|
|
31721
32056
|
const output = await svc.query(dbName, sql, remote);
|
|
31722
32057
|
if (opts.json) {
|
|
@@ -31735,13 +32070,14 @@ async function handleQuery(opts, dbName, sql, remote) {
|
|
|
31735
32070
|
async function handleExport(opts, dbName, outputPath) {
|
|
31736
32071
|
const svc = new DbService;
|
|
31737
32072
|
const s = spinner();
|
|
32073
|
+
const t2 = startTimer();
|
|
31738
32074
|
s.start("Exporting database...");
|
|
31739
32075
|
try {
|
|
31740
32076
|
const path5 = await svc.export(dbName, outputPath);
|
|
31741
|
-
s.stop(
|
|
32077
|
+
s.stop(`Database exported (${formatDuration3(t2.ms())})`);
|
|
31742
32078
|
formatSuccess(`Database exported to ${path5}`, opts);
|
|
31743
32079
|
} catch (err) {
|
|
31744
|
-
s.stop(
|
|
32080
|
+
s.stop(`Export failed (${formatDuration3(t2.ms())})`);
|
|
31745
32081
|
throw err;
|
|
31746
32082
|
}
|
|
31747
32083
|
}
|
|
@@ -31758,17 +32094,18 @@ async function handleReset(opts, dbName, confirmed) {
|
|
|
31758
32094
|
}
|
|
31759
32095
|
const svc = new DbService;
|
|
31760
32096
|
const s = spinner();
|
|
32097
|
+
const t2 = startTimer();
|
|
31761
32098
|
s.start("Resetting database...");
|
|
31762
32099
|
try {
|
|
31763
32100
|
const output = await svc.reset(dbName);
|
|
31764
|
-
s.stop(
|
|
32101
|
+
s.stop(`Database reset (${formatDuration3(t2.ms())})`);
|
|
31765
32102
|
formatSuccess(`Database "${dbName}" has been recreated`, opts);
|
|
31766
32103
|
if (!opts.quiet && output) {
|
|
31767
32104
|
process.stdout.write(`${output}
|
|
31768
32105
|
`);
|
|
31769
32106
|
}
|
|
31770
32107
|
} catch (err) {
|
|
31771
|
-
s.stop(
|
|
32108
|
+
s.stop(`Reset failed (${formatDuration3(t2.ms())})`);
|
|
31772
32109
|
throw err;
|
|
31773
32110
|
}
|
|
31774
32111
|
}
|
|
@@ -31816,12 +32153,13 @@ EXAMPLES:
|
|
|
31816
32153
|
const remote = Boolean(cmd.optsWithGlobals().remote);
|
|
31817
32154
|
await handleList2(opts, dbName, remote);
|
|
31818
32155
|
}, { service: "db" }));
|
|
31819
|
-
dbCmd.command("query <sql>").description("Execute a SQL query").action(withErrorHandling(async (sql,
|
|
32156
|
+
dbCmd.command("query <sql>").description("Execute a SQL query").option("--allow-destructive", "Allow destructive keywords (DROP, DELETE, TRUNCATE, ALTER, UPDATE)").action(withErrorHandling(async (sql, options, cmd) => {
|
|
31820
32157
|
const opts = getFormatOptions(cmd);
|
|
31821
32158
|
const svc = new DbService;
|
|
31822
32159
|
const dbName = await resolveDb(cmd, svc);
|
|
31823
32160
|
const remote = Boolean(cmd.optsWithGlobals().remote);
|
|
31824
|
-
|
|
32161
|
+
const allowDestructive = Boolean(options.allowDestructive);
|
|
32162
|
+
await handleQuery(opts, dbName, sql, remote, allowDestructive);
|
|
31825
32163
|
}, { service: "db" }));
|
|
31826
32164
|
dbCmd.command("export").description("Export database to .sql file").option("--output <path>", "Output file path").action(withErrorHandling(async (options, cmd) => {
|
|
31827
32165
|
const opts = getFormatOptions(cmd);
|
|
@@ -31916,11 +32254,20 @@ ${theme.heading("Summary:")} ${result.healthyCount} healthy, ${result.degradedCo
|
|
|
31916
32254
|
process.exitCode = 1 /* ERROR */;
|
|
31917
32255
|
}
|
|
31918
32256
|
}
|
|
32257
|
+
function assertSafeInteger(value, name, max) {
|
|
32258
|
+
if (!Number.isInteger(value) || value < 0 || value > max || !Number.isSafeInteger(value)) {
|
|
32259
|
+
throw new Error(`Invalid ${name}: ${value} (must be a non-negative integer \u2264 ${max})`);
|
|
32260
|
+
}
|
|
32261
|
+
}
|
|
32262
|
+
function escapeSqlString(value) {
|
|
32263
|
+
return value.replace(/'/g, "''");
|
|
32264
|
+
}
|
|
31919
32265
|
async function doMonitorTrades(limit, fmt) {
|
|
31920
32266
|
try {
|
|
32267
|
+
assertSafeInteger(limit, "limit", 100);
|
|
31921
32268
|
const db = new DbService;
|
|
31922
32269
|
const dbName = await db.resolveDbName();
|
|
31923
|
-
const sql = `SELECT * FROM trades ORDER BY timestamp DESC LIMIT ${
|
|
32270
|
+
const sql = `SELECT * FROM trades ORDER BY timestamp DESC LIMIT ${limit}`;
|
|
31924
32271
|
const output = await db.query(dbName, sql, true);
|
|
31925
32272
|
process.stdout.write(output + `
|
|
31926
32273
|
`);
|
|
@@ -31938,7 +32285,7 @@ async function doMonitorLogs(workerName, fmt) {
|
|
|
31938
32285
|
if (!/^[a-zA-Z0-9_-]+$/.test(workerName)) {
|
|
31939
32286
|
throw new Error(`Invalid worker name: "${workerName}"`);
|
|
31940
32287
|
}
|
|
31941
|
-
sql = `SELECT * FROM system_logs WHERE worker = '${workerName}' ORDER BY timestamp DESC LIMIT 20`;
|
|
32288
|
+
sql = `SELECT * FROM system_logs WHERE worker = '${escapeSqlString(workerName)}' ORDER BY timestamp DESC LIMIT 20`;
|
|
31942
32289
|
} else {
|
|
31943
32290
|
sql = "SELECT * FROM system_logs ORDER BY timestamp DESC LIMIT 20";
|
|
31944
32291
|
}
|
|
@@ -32052,6 +32399,7 @@ async function doMonitorAnalyticsSummary(fmt) {
|
|
|
32052
32399
|
}
|
|
32053
32400
|
async function doMonitorAnalyticsErrors(hours, fmt) {
|
|
32054
32401
|
try {
|
|
32402
|
+
assertSafeInteger(hours, "hours", 8760);
|
|
32055
32403
|
const db = new DbService;
|
|
32056
32404
|
const dbName = await db.resolveDbName();
|
|
32057
32405
|
const sql = `SELECT level, COUNT(*) as count FROM system_logs WHERE level IN ('error', 'warn') AND timestamp > unixepoch('now', '-${hours} hours') GROUP BY level`;
|
|
@@ -32713,14 +33061,18 @@ EXAMPLES:
|
|
|
32713
33061
|
throw new CLIError(`"${submodulePath}" is not a git submodule \u2014 nothing to update`, 2 /* INVALID_USAGE */);
|
|
32714
33062
|
}
|
|
32715
33063
|
const s = spinner();
|
|
33064
|
+
const t2 = startTimer();
|
|
32716
33065
|
s.start(`Updating ${target}...`);
|
|
32717
33066
|
const output = await gitSubmoduleUpdate(cwd, submodulePath);
|
|
32718
|
-
|
|
33067
|
+
const dur = formatDuration3(t2.ms());
|
|
33068
|
+
s.stop(output ? theme.success(`Updated ${target} (${dur})`) : theme.muted(`${target} already up to date (${dur})`));
|
|
32719
33069
|
} else {
|
|
32720
33070
|
const s = spinner();
|
|
33071
|
+
const t2 = startTimer();
|
|
32721
33072
|
s.start("Pulling latest from remote...");
|
|
32722
33073
|
const output = await gitPull(cwd);
|
|
32723
|
-
|
|
33074
|
+
const dur = formatDuration3(t2.ms());
|
|
33075
|
+
s.stop(output ? theme.success(`Repository up to date (${dur})`) : theme.muted(`Already up to date (${dur})`));
|
|
32724
33076
|
}
|
|
32725
33077
|
}, { service: "update" }));
|
|
32726
33078
|
}
|
|
@@ -32807,7 +33159,7 @@ function registerSchemaCommand(program2) {
|
|
|
32807
33159
|
import { spawn } from "child_process";
|
|
32808
33160
|
import { resolve as resolve7, dirname } from "path";
|
|
32809
33161
|
import { fileURLToPath } from "url";
|
|
32810
|
-
import { existsSync as
|
|
33162
|
+
import { existsSync as existsSync6 } from "fs";
|
|
32811
33163
|
var __dirname2 = dirname(fileURLToPath(import.meta.url));
|
|
32812
33164
|
function resolveTUIEntry() {
|
|
32813
33165
|
const candidates = [
|
|
@@ -32816,7 +33168,7 @@ function resolveTUIEntry() {
|
|
|
32816
33168
|
resolve7(process.cwd(), "../tui/src/main.tsx")
|
|
32817
33169
|
];
|
|
32818
33170
|
for (const path5 of candidates) {
|
|
32819
|
-
if (
|
|
33171
|
+
if (existsSync6(path5))
|
|
32820
33172
|
return path5;
|
|
32821
33173
|
}
|
|
32822
33174
|
throw new CLIError("Could not find TUI entry point. Ensure packages/tui/src/main.tsx exists.", 1 /* ERROR */);
|
|
@@ -33005,12 +33357,13 @@ EXAMPLES:
|
|
|
33005
33357
|
}
|
|
33006
33358
|
// src/services/setup/setup-service.ts
|
|
33007
33359
|
import {
|
|
33008
|
-
existsSync as
|
|
33360
|
+
existsSync as existsSync7,
|
|
33009
33361
|
mkdirSync as mkdirSync3,
|
|
33010
33362
|
readFileSync as readFileSync6,
|
|
33011
33363
|
statSync as statSync2,
|
|
33012
33364
|
writeFileSync as writeFileSync4
|
|
33013
33365
|
} from "fs";
|
|
33366
|
+
import { homedir as homedir4 } from "os";
|
|
33014
33367
|
import { join as join6 } from "path";
|
|
33015
33368
|
var ALL_WORKERS = [
|
|
33016
33369
|
"d1-worker",
|
|
@@ -33072,7 +33425,7 @@ class SetupService {
|
|
|
33072
33425
|
WEBHOOK_API_KEY_BINDING: this._randomHex(32),
|
|
33073
33426
|
TELEGRAM_INTERNAL_KEY_BINDING: internalKey
|
|
33074
33427
|
};
|
|
33075
|
-
if (!
|
|
33428
|
+
if (!existsSync7(KEYS_DIR)) {
|
|
33076
33429
|
mkdirSync3(KEYS_DIR, { recursive: true });
|
|
33077
33430
|
} else if (!statSync2(KEYS_DIR).isDirectory()) {
|
|
33078
33431
|
mkdirSync3(KEYS_DIR, { recursive: true });
|
|
@@ -33377,8 +33730,7 @@ class SetupService {
|
|
|
33377
33730
|
}
|
|
33378
33731
|
const fallbacks = [
|
|
33379
33732
|
"/usr/local/bin/wrangler",
|
|
33380
|
-
"
|
|
33381
|
-
join6(process.env.HOME || "~", ".bun", "bin", "wrangler")
|
|
33733
|
+
join6(homedir4(), ".bun", "bin", "wrangler")
|
|
33382
33734
|
];
|
|
33383
33735
|
for (const fb of fallbacks) {
|
|
33384
33736
|
try {
|
|
@@ -33451,7 +33803,7 @@ class SetupService {
|
|
|
33451
33803
|
step: "workers",
|
|
33452
33804
|
message: `${missing.length} worker(s) not cloned: ${missing.join(", ")}`
|
|
33453
33805
|
});
|
|
33454
|
-
if (!
|
|
33806
|
+
if (!existsSync7(gitmodulesPath)) {
|
|
33455
33807
|
this.onProgress({
|
|
33456
33808
|
type: "step-error",
|
|
33457
33809
|
step: "workers",
|
|
@@ -34052,7 +34404,7 @@ EXAMPLES:
|
|
|
34052
34404
|
const authOk = await tempSvc.checkAuth();
|
|
34053
34405
|
if (!authOk) {
|
|
34054
34406
|
authCheck.stop("Authentication failed");
|
|
34055
|
-
formatError2(new CLIError("Not authenticated with Cloudflare. Run 'wrangler login' first.", 1 /* ERROR
|
|
34407
|
+
formatError2(new CLIError("Not authenticated with Cloudflare. Run 'wrangler login' first.", 1 /* ERROR */, undefined, false, "Run `wrangler login` interactively, or set CLOUDFLARE_API_TOKEN and CLOUDFLARE_ACCOUNT_ID environment variables for CI / non-interactive use."), globalOpts);
|
|
34056
34408
|
outro(theme.error("Setup aborted"));
|
|
34057
34409
|
process.exitCode = 1 /* ERROR */;
|
|
34058
34410
|
return;
|
|
@@ -34091,12 +34443,14 @@ EXAMPLES:
|
|
|
34091
34443
|
currentSpinner.stop(msg);
|
|
34092
34444
|
currentSpinner = null;
|
|
34093
34445
|
}));
|
|
34446
|
+
const totalTimer = startTimer();
|
|
34094
34447
|
const result = await setupSvc.runAll(opts);
|
|
34448
|
+
const totalMs = totalTimer.ms();
|
|
34095
34449
|
if (isInteractive) {
|
|
34096
|
-
log.step(
|
|
34450
|
+
log.step(`\u2500\u2500 Summary (total ${formatDuration3(totalMs)}) \u2500\u2500`);
|
|
34097
34451
|
const rows = result.steps.map((s) => ({
|
|
34098
34452
|
Step: s.step,
|
|
34099
|
-
Status: s.success ?
|
|
34453
|
+
Status: s.success ? formatBadge("ok", "DONE") : formatBadge("err", "FAIL"),
|
|
34100
34454
|
Message: s.message
|
|
34101
34455
|
}));
|
|
34102
34456
|
formatTable(rows, globalOpts);
|