@jango-blockchained/hoox-cli 0.7.0 → 0.7.3
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 +602 -203
- package/package.json +5 -5
package/dist/index.js
CHANGED
|
@@ -14792,22 +14792,57 @@ function toError(err, fallback = "Unknown error") {
|
|
|
14792
14792
|
return fallback;
|
|
14793
14793
|
}
|
|
14794
14794
|
}
|
|
14795
|
+
function hasSensitiveFields(obj) {
|
|
14796
|
+
for (const key of Object.keys(obj)) {
|
|
14797
|
+
if (SENSITIVE_FIELDS.has(key))
|
|
14798
|
+
return true;
|
|
14799
|
+
}
|
|
14800
|
+
return false;
|
|
14801
|
+
}
|
|
14802
|
+
function hasErrorValues(obj) {
|
|
14803
|
+
for (const v of Object.values(obj)) {
|
|
14804
|
+
if (v instanceof Error)
|
|
14805
|
+
return true;
|
|
14806
|
+
}
|
|
14807
|
+
return false;
|
|
14808
|
+
}
|
|
14795
14809
|
function sanitizeOutput(data) {
|
|
14810
|
+
if (data === null || data === undefined)
|
|
14811
|
+
return data;
|
|
14812
|
+
const t = typeof data;
|
|
14813
|
+
if (t === "string" || t === "number" || t === "boolean")
|
|
14814
|
+
return data;
|
|
14796
14815
|
if (data instanceof Error) {
|
|
14797
14816
|
return { name: data.name, message: data.message };
|
|
14798
14817
|
}
|
|
14799
|
-
if (
|
|
14818
|
+
if (Array.isArray(data)) {
|
|
14819
|
+
return data.map(sanitizeOutput);
|
|
14820
|
+
}
|
|
14821
|
+
if (t === "object") {
|
|
14822
|
+
const obj = data;
|
|
14823
|
+
const keys = Object.keys(obj);
|
|
14824
|
+
if (keys.length < 10) {
|
|
14825
|
+
const sanitized2 = {};
|
|
14826
|
+
for (const k of keys) {
|
|
14827
|
+
if (k === "stack" || k === "cause")
|
|
14828
|
+
continue;
|
|
14829
|
+
const v = obj[k];
|
|
14830
|
+
sanitized2[k] = v instanceof Error ? { name: v.name, message: v.message } : sanitizeOutput(v);
|
|
14831
|
+
}
|
|
14832
|
+
return sanitized2;
|
|
14833
|
+
}
|
|
14834
|
+
if (!hasSensitiveFields(obj) && !hasErrorValues(obj)) {
|
|
14835
|
+
return obj;
|
|
14836
|
+
}
|
|
14800
14837
|
const sanitized = {};
|
|
14801
|
-
for (const
|
|
14838
|
+
for (const k of keys) {
|
|
14802
14839
|
if (k === "stack" || k === "cause")
|
|
14803
14840
|
continue;
|
|
14841
|
+
const v = obj[k];
|
|
14804
14842
|
sanitized[k] = v instanceof Error ? { name: v.name, message: v.message } : sanitizeOutput(v);
|
|
14805
14843
|
}
|
|
14806
14844
|
return sanitized;
|
|
14807
14845
|
}
|
|
14808
|
-
if (Array.isArray(data)) {
|
|
14809
|
-
return data.map(sanitizeOutput);
|
|
14810
|
-
}
|
|
14811
14846
|
return data;
|
|
14812
14847
|
}
|
|
14813
14848
|
function createJsonResponse(data, status = 200) {
|
|
@@ -14832,8 +14867,16 @@ function createErrorResponse(error51, status) {
|
|
|
14832
14867
|
headers: { "Content-Type": "application/json" }
|
|
14833
14868
|
});
|
|
14834
14869
|
}
|
|
14835
|
-
var Errors;
|
|
14870
|
+
var SENSITIVE_FIELDS, Errors;
|
|
14836
14871
|
var init_errors3 = __esm(() => {
|
|
14872
|
+
SENSITIVE_FIELDS = new Set([
|
|
14873
|
+
"password",
|
|
14874
|
+
"token",
|
|
14875
|
+
"secret",
|
|
14876
|
+
"api_key",
|
|
14877
|
+
"apiKey",
|
|
14878
|
+
"authorization"
|
|
14879
|
+
]);
|
|
14837
14880
|
Errors = {
|
|
14838
14881
|
badRequest: (message) => createErrorResponse({ message, status: 400, code: "BAD_REQUEST" }),
|
|
14839
14882
|
unauthorized: (message = "Unauthorized") => createErrorResponse({ message, status: 401, code: "UNAUTHORIZED" }),
|
|
@@ -15324,13 +15367,15 @@ var init_d1 = __esm(() => {
|
|
|
15324
15367
|
|
|
15325
15368
|
// ../shared/src/service-bindings.ts
|
|
15326
15369
|
function serviceFetch(binding, path2, body, options) {
|
|
15370
|
+
const timeout = options?.timeout ?? 30000;
|
|
15327
15371
|
return binding.fetch(`http://internal${path2}`, {
|
|
15328
15372
|
method: options?.method ?? "POST",
|
|
15329
15373
|
headers: {
|
|
15330
15374
|
"Content-Type": "application/json",
|
|
15331
15375
|
...options?.headers
|
|
15332
15376
|
},
|
|
15333
|
-
body: body != null ? JSON.stringify(body) : undefined
|
|
15377
|
+
body: body != null ? JSON.stringify(body) : undefined,
|
|
15378
|
+
signal: AbortSignal.timeout(timeout)
|
|
15334
15379
|
});
|
|
15335
15380
|
}
|
|
15336
15381
|
|
|
@@ -21388,18 +21433,15 @@ class KvSyncService {
|
|
|
21388
21433
|
return stdout2.trim() || null;
|
|
21389
21434
|
}
|
|
21390
21435
|
async set(namespaceId, key, value) {
|
|
21391
|
-
const proc = Bun.spawn([
|
|
21392
|
-
|
|
21393
|
-
|
|
21394
|
-
|
|
21395
|
-
|
|
21396
|
-
|
|
21397
|
-
|
|
21398
|
-
|
|
21399
|
-
value
|
|
21400
|
-
], { stdout: "pipe", stderr: "pipe" });
|
|
21436
|
+
const proc = Bun.spawn(["wrangler", "kv", "key", "put", "--namespace-id", namespaceId, key], { stdout: "pipe", stderr: "pipe", stdin: "pipe" });
|
|
21437
|
+
proc.stdin.write(value + `
|
|
21438
|
+
`);
|
|
21439
|
+
proc.stdin.end();
|
|
21440
|
+
const [, stderr] = await Promise.all([
|
|
21441
|
+
new Response(proc.stdout).text(),
|
|
21442
|
+
new Response(proc.stderr).text()
|
|
21443
|
+
]);
|
|
21401
21444
|
const exitCode = await proc.exited;
|
|
21402
|
-
const stderr = await new Response(proc.stderr).text();
|
|
21403
21445
|
if (exitCode !== 0) {
|
|
21404
21446
|
throw new Error(`Failed to set key "${key}": ${stderr.trim() || `exit ${exitCode}`}`);
|
|
21405
21447
|
}
|
|
@@ -21531,7 +21573,7 @@ var exports_schema_service = {};
|
|
|
21531
21573
|
__export(exports_schema_service, {
|
|
21532
21574
|
SchemaService: () => SchemaService
|
|
21533
21575
|
});
|
|
21534
|
-
import { readFileSync as readFileSync4 } from "fs";
|
|
21576
|
+
import { readFileSync as readFileSync4, existsSync as existsSync5 } from "fs";
|
|
21535
21577
|
import { resolve as resolve5 } from "path";
|
|
21536
21578
|
|
|
21537
21579
|
class SchemaService {
|
|
@@ -21565,34 +21607,41 @@ class SchemaService {
|
|
|
21565
21607
|
const workerWranglerPath = resolve5(workersDir, name, "wrangler.jsonc");
|
|
21566
21608
|
const devVarsPath = resolve5(workersDir, name, ".dev.vars");
|
|
21567
21609
|
const errors4 = [];
|
|
21610
|
+
const isHooxProject = existsSync5(resolve5(this.projectRoot, "wrangler.jsonc")) || existsSync5(resolve5(this.projectRoot, "workers"));
|
|
21568
21611
|
try {
|
|
21569
21612
|
const wranglerContent = readFileSync4(workerWranglerPath, "utf-8");
|
|
21570
21613
|
errors4.push(...validateWranglerJsonc(name, manifest, wranglerContent));
|
|
21571
21614
|
} catch (e) {
|
|
21615
|
+
const isErrnoException = e instanceof Error && "code" in e;
|
|
21616
|
+
const isEnoent = isErrnoException && e.code === "ENOENT";
|
|
21617
|
+
const errMessage = e instanceof Error ? e.message : String(e);
|
|
21618
|
+
const message = isEnoent && !isHooxProject ? `Cannot read ${workerWranglerPath}: File not found. Are you in the Hoox project root directory?` : `Cannot read ${workerWranglerPath}: ${errMessage}`;
|
|
21572
21619
|
errors4.push({
|
|
21573
21620
|
worker: name,
|
|
21574
21621
|
severity: "error",
|
|
21575
|
-
message
|
|
21622
|
+
message
|
|
21576
21623
|
});
|
|
21577
21624
|
}
|
|
21578
21625
|
try {
|
|
21579
21626
|
const rootContent = readFileSync4(rootWranglerPath, "utf-8");
|
|
21580
21627
|
errors4.push(...validateRootSecrets(name, manifest, rootContent));
|
|
21581
21628
|
} catch (e) {
|
|
21629
|
+
const errMessage = e instanceof Error ? e.message : String(e);
|
|
21582
21630
|
errors4.push({
|
|
21583
21631
|
worker: name,
|
|
21584
21632
|
severity: "warning",
|
|
21585
|
-
message: `Cannot read root wrangler.jsonc: ${
|
|
21633
|
+
message: `Cannot read root wrangler.jsonc: ${errMessage}`
|
|
21586
21634
|
});
|
|
21587
21635
|
}
|
|
21588
21636
|
try {
|
|
21589
21637
|
const devVarsContent = readFileSync4(devVarsPath, "utf-8");
|
|
21590
21638
|
errors4.push(...validateDevVars(name, manifest, devVarsContent));
|
|
21591
21639
|
} catch (e) {
|
|
21640
|
+
const errMessage = e instanceof Error ? e.message : String(e);
|
|
21592
21641
|
errors4.push({
|
|
21593
21642
|
worker: name,
|
|
21594
21643
|
severity: "warning",
|
|
21595
|
-
message: `Cannot read ${devVarsPath}: ${
|
|
21644
|
+
message: `Cannot read ${devVarsPath}: ${errMessage}`
|
|
21596
21645
|
});
|
|
21597
21646
|
}
|
|
21598
21647
|
return {
|
|
@@ -23674,11 +23723,13 @@ class CLIError extends Error {
|
|
|
23674
23723
|
code;
|
|
23675
23724
|
details;
|
|
23676
23725
|
recoverable;
|
|
23677
|
-
|
|
23726
|
+
hint;
|
|
23727
|
+
constructor(message, code = 1 /* ERROR */, details, recoverable = false, hint) {
|
|
23678
23728
|
super(message);
|
|
23679
23729
|
this.code = code;
|
|
23680
23730
|
this.details = details;
|
|
23681
23731
|
this.recoverable = recoverable;
|
|
23732
|
+
this.hint = hint;
|
|
23682
23733
|
this.name = "CLIError";
|
|
23683
23734
|
}
|
|
23684
23735
|
}
|
|
@@ -23746,7 +23797,63 @@ function stripAnsi(str) {
|
|
|
23746
23797
|
return ansis_default.strip(str);
|
|
23747
23798
|
}
|
|
23748
23799
|
|
|
23800
|
+
// src/utils/timer.ts
|
|
23801
|
+
function formatDuration2(ms) {
|
|
23802
|
+
if (!Number.isFinite(ms) || ms < 0)
|
|
23803
|
+
return "0ms";
|
|
23804
|
+
if (ms < 1000)
|
|
23805
|
+
return `${Math.round(ms)}ms`;
|
|
23806
|
+
if (ms < 60000)
|
|
23807
|
+
return `${(ms / 1000).toFixed(1)}s`;
|
|
23808
|
+
if (ms < 3600000) {
|
|
23809
|
+
const m2 = Math.floor(ms / 60000);
|
|
23810
|
+
const s = Math.floor(ms % 60000 / 1000);
|
|
23811
|
+
return `${m2}m ${String(s).padStart(2, "0")}s`;
|
|
23812
|
+
}
|
|
23813
|
+
const h = Math.floor(ms / 3600000);
|
|
23814
|
+
const m = Math.floor(ms % 3600000 / 60000);
|
|
23815
|
+
return `${h}h ${String(m).padStart(2, "0")}m`;
|
|
23816
|
+
}
|
|
23817
|
+
function startTimer() {
|
|
23818
|
+
const startedAt = Date.now();
|
|
23819
|
+
return {
|
|
23820
|
+
ms: () => Date.now() - startedAt,
|
|
23821
|
+
format: () => formatDuration2(Date.now() - startedAt)
|
|
23822
|
+
};
|
|
23823
|
+
}
|
|
23824
|
+
|
|
23825
|
+
// src/utils/format-mode.ts
|
|
23826
|
+
function isRichMode(opts) {
|
|
23827
|
+
if (opts?.json || opts?.quiet)
|
|
23828
|
+
return false;
|
|
23829
|
+
return Boolean(process.stdout.isTTY);
|
|
23830
|
+
}
|
|
23831
|
+
|
|
23749
23832
|
// src/utils/formatters.ts
|
|
23833
|
+
var formatDuration3 = formatDuration2;
|
|
23834
|
+
var BADGE_STYLE = {
|
|
23835
|
+
ok: { bg: ansis_default.bgGreen, fg: ansis_default.black },
|
|
23836
|
+
warn: { bg: ansis_default.bgYellow, fg: ansis_default.black },
|
|
23837
|
+
err: { bg: ansis_default.bgRed, fg: ansis_default.white },
|
|
23838
|
+
info: { bg: ansis_default.bgBlue, fg: ansis_default.white }
|
|
23839
|
+
};
|
|
23840
|
+
function formatBadge(level, text) {
|
|
23841
|
+
const content = (text ?? defaultBadgeLabel(level)).padEnd(4, " ");
|
|
23842
|
+
const { bg, fg } = BADGE_STYLE[level];
|
|
23843
|
+
return bg(fg(` ${content} `));
|
|
23844
|
+
}
|
|
23845
|
+
function defaultBadgeLabel(level) {
|
|
23846
|
+
switch (level) {
|
|
23847
|
+
case "ok":
|
|
23848
|
+
return "OK";
|
|
23849
|
+
case "warn":
|
|
23850
|
+
return "WARN";
|
|
23851
|
+
case "err":
|
|
23852
|
+
return "FAIL";
|
|
23853
|
+
case "info":
|
|
23854
|
+
return "INFO";
|
|
23855
|
+
}
|
|
23856
|
+
}
|
|
23750
23857
|
function formatSuccess(message, opts) {
|
|
23751
23858
|
if (opts?.quiet)
|
|
23752
23859
|
return;
|
|
@@ -23770,6 +23877,9 @@ function formatError2(error51, opts) {
|
|
|
23770
23877
|
if (cliError?.details) {
|
|
23771
23878
|
output.details = cliError.details;
|
|
23772
23879
|
}
|
|
23880
|
+
if (cliError?.hint) {
|
|
23881
|
+
output.hint = cliError.hint;
|
|
23882
|
+
}
|
|
23773
23883
|
process.stdout.write(JSON.stringify(output) + `
|
|
23774
23884
|
`);
|
|
23775
23885
|
return;
|
|
@@ -23783,6 +23893,10 @@ function formatError2(error51, opts) {
|
|
|
23783
23893
|
`);
|
|
23784
23894
|
if (cliError?.details) {
|
|
23785
23895
|
process.stdout.write(` ${theme.dim(cliError.details)}
|
|
23896
|
+
`);
|
|
23897
|
+
}
|
|
23898
|
+
if (cliError?.hint) {
|
|
23899
|
+
process.stdout.write(`${theme.dim("\u21B3 hint:")} ${theme.value(cliError.hint)}
|
|
23786
23900
|
`);
|
|
23787
23901
|
}
|
|
23788
23902
|
}
|
|
@@ -25455,6 +25569,16 @@ ${l2}
|
|
|
25455
25569
|
}).prompt();
|
|
25456
25570
|
};
|
|
25457
25571
|
var i = `${styleText2("gray", S_BAR)} `;
|
|
25572
|
+
var tasks = async (o2, e) => {
|
|
25573
|
+
for (const t2 of o2) {
|
|
25574
|
+
if (t2.enabled === false)
|
|
25575
|
+
continue;
|
|
25576
|
+
const s = spinner(e);
|
|
25577
|
+
s.start(t2.title);
|
|
25578
|
+
const n2 = await t2.task(s.message);
|
|
25579
|
+
s.stop(n2 || t2.title);
|
|
25580
|
+
}
|
|
25581
|
+
};
|
|
25458
25582
|
var text = (t2) => new n({
|
|
25459
25583
|
validate: t2.validate,
|
|
25460
25584
|
placeholder: t2.placeholder,
|
|
@@ -25540,7 +25664,12 @@ class CloudflareService {
|
|
|
25540
25664
|
};
|
|
25541
25665
|
} catch (err) {
|
|
25542
25666
|
const message = err instanceof Error ? err.message : String(err);
|
|
25543
|
-
|
|
25667
|
+
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;
|
|
25668
|
+
return {
|
|
25669
|
+
ok: false,
|
|
25670
|
+
error: hint ? `Failed to spawn wrangler: ${message}
|
|
25671
|
+
\u21B3 hint: ${hint}` : `Failed to spawn wrangler: ${message}`
|
|
25672
|
+
};
|
|
25544
25673
|
}
|
|
25545
25674
|
}
|
|
25546
25675
|
async whoami() {
|
|
@@ -25752,7 +25881,32 @@ class CloudflareService {
|
|
|
25752
25881
|
]);
|
|
25753
25882
|
}
|
|
25754
25883
|
async zonesList() {
|
|
25755
|
-
|
|
25884
|
+
const token = process.env.CLOUDFLARE_API_TOKEN;
|
|
25885
|
+
if (!token) {
|
|
25886
|
+
return {
|
|
25887
|
+
ok: false,
|
|
25888
|
+
error: "CLOUDFLARE_API_TOKEN environment variable is not set. Set it or run `wrangler login`."
|
|
25889
|
+
};
|
|
25890
|
+
}
|
|
25891
|
+
try {
|
|
25892
|
+
const response = await fetch("https://api.cloudflare.com/client/v4/zones?per_page=50", {
|
|
25893
|
+
headers: {
|
|
25894
|
+
Authorization: `Bearer ${token}`,
|
|
25895
|
+
"Content-Type": "application/json"
|
|
25896
|
+
}
|
|
25897
|
+
});
|
|
25898
|
+
const json2 = await response.json();
|
|
25899
|
+
if (!response.ok || !json2.success) {
|
|
25900
|
+
const errorMsg = json2.errors?.map((e) => e.message).join("; ") || `HTTP ${response.status}`;
|
|
25901
|
+
return { ok: false, error: `Failed to list zones: ${errorMsg}` };
|
|
25902
|
+
}
|
|
25903
|
+
const lines = json2.result.map((z2) => `${z2.name} (${z2.id})`);
|
|
25904
|
+
return { ok: true, value: lines.join(`
|
|
25905
|
+
`) };
|
|
25906
|
+
} catch (err) {
|
|
25907
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
25908
|
+
return { ok: false, error: `Cloudflare API request failed: ${message}` };
|
|
25909
|
+
}
|
|
25756
25910
|
}
|
|
25757
25911
|
extractUrl(stdout2) {
|
|
25758
25912
|
for (const line of stdout2.split(`
|
|
@@ -25976,8 +26130,8 @@ OPTIONS:
|
|
|
25976
26130
|
|
|
25977
26131
|
EXAMPLES:
|
|
25978
26132
|
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(
|
|
26133
|
+
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) => {
|
|
26134
|
+
const globalOpts = getFormatOptions(cmd);
|
|
25981
26135
|
const isNonInteractive = Boolean(options.token && options.account);
|
|
25982
26136
|
await runInitCommand(options, globalOpts, isNonInteractive);
|
|
25983
26137
|
}, { service: "init" }));
|
|
@@ -26642,11 +26796,53 @@ class DockerService {
|
|
|
26642
26796
|
}
|
|
26643
26797
|
isCommandAvailable(cmd) {
|
|
26644
26798
|
return new Promise((resolve2) => {
|
|
26645
|
-
const
|
|
26646
|
-
|
|
26647
|
-
|
|
26799
|
+
const parts = cmd.split(/\s+/).filter(Boolean);
|
|
26800
|
+
const binary = parts[0];
|
|
26801
|
+
const hasSubcommand = parts.length > 1;
|
|
26802
|
+
let proc;
|
|
26803
|
+
try {
|
|
26804
|
+
proc = Bun.spawn(["which", binary], {
|
|
26805
|
+
stdout: "pipe",
|
|
26806
|
+
stderr: "pipe"
|
|
26807
|
+
});
|
|
26808
|
+
} catch {
|
|
26809
|
+
resolve2(false);
|
|
26810
|
+
return;
|
|
26811
|
+
}
|
|
26812
|
+
const timer = setTimeout(() => {
|
|
26813
|
+
try {
|
|
26814
|
+
proc.kill();
|
|
26815
|
+
} catch {}
|
|
26816
|
+
resolve2(false);
|
|
26817
|
+
}, 5000);
|
|
26818
|
+
proc.exited.then((code) => {
|
|
26819
|
+
clearTimeout(timer);
|
|
26820
|
+
if (code !== 0) {
|
|
26821
|
+
resolve2(false);
|
|
26822
|
+
return;
|
|
26823
|
+
}
|
|
26824
|
+
if (!hasSubcommand) {
|
|
26825
|
+
resolve2(true);
|
|
26826
|
+
return;
|
|
26827
|
+
}
|
|
26828
|
+
try {
|
|
26829
|
+
const subproc = Bun.spawn([binary, ...parts.slice(1), "version"], {
|
|
26830
|
+
stdout: "pipe",
|
|
26831
|
+
stderr: "pipe"
|
|
26832
|
+
});
|
|
26833
|
+
subproc.exited.then((subcode) => {
|
|
26834
|
+
try {
|
|
26835
|
+
subproc.kill();
|
|
26836
|
+
} catch {}
|
|
26837
|
+
resolve2(subcode === 0);
|
|
26838
|
+
}).catch(() => resolve2(true));
|
|
26839
|
+
} catch {
|
|
26840
|
+
resolve2(true);
|
|
26841
|
+
}
|
|
26842
|
+
}).catch(() => {
|
|
26843
|
+
clearTimeout(timer);
|
|
26844
|
+
resolve2(false);
|
|
26648
26845
|
});
|
|
26649
|
-
proc.exited.then((code) => resolve2(code === 0)).catch(() => resolve2(false));
|
|
26650
26846
|
});
|
|
26651
26847
|
}
|
|
26652
26848
|
}
|
|
@@ -26684,8 +26880,8 @@ OPTIONS:
|
|
|
26684
26880
|
EXAMPLES:
|
|
26685
26881
|
hoox dev start
|
|
26686
26882
|
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(
|
|
26883
|
+
hoox dev start --runtime docker`).option("--runtime <native|docker>", "Choose dev runtime (overrides saved preference)").action(withErrorHandling(async (options, cmd) => {
|
|
26884
|
+
const fmt = getFormatOptions(cmd);
|
|
26689
26885
|
const configService = new ConfigService;
|
|
26690
26886
|
const prereqs = new PrerequisitesService;
|
|
26691
26887
|
const docker = new DockerService;
|
|
@@ -26803,8 +26999,8 @@ OPTIONS:
|
|
|
26803
26999
|
EXAMPLES:
|
|
26804
27000
|
hoox dev worker trade-worker
|
|
26805
27001
|
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(
|
|
27002
|
+
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) => {
|
|
27003
|
+
const fmt = getFormatOptions(cmd);
|
|
26808
27004
|
const configService = new ConfigService;
|
|
26809
27005
|
await configService.load();
|
|
26810
27006
|
const worker = configService.getWorker(name);
|
|
@@ -26839,8 +27035,8 @@ OPTIONS:
|
|
|
26839
27035
|
|
|
26840
27036
|
EXAMPLES:
|
|
26841
27037
|
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(
|
|
27038
|
+
hoox dev dashboard --runtime docker`).option("--runtime <native|docker>", "Choose dev runtime (overrides saved preference)").action(withErrorHandling(async (_options, cmd) => {
|
|
27039
|
+
const fmt = getFormatOptions(cmd);
|
|
26844
27040
|
const dashboardPath = path3.resolve(process.cwd(), "workers/dashboard");
|
|
26845
27041
|
const dashboardDir = Bun.file(dashboardPath);
|
|
26846
27042
|
if (!await dashboardDir.exists()) {
|
|
@@ -26859,6 +27055,131 @@ EXAMPLES:
|
|
|
26859
27055
|
}
|
|
26860
27056
|
// src/commands/deploy/deploy-command.ts
|
|
26861
27057
|
init_config2();
|
|
27058
|
+
|
|
27059
|
+
// src/utils/rich.ts
|
|
27060
|
+
async function runRichTasks(tasks2, options = {}) {
|
|
27061
|
+
const rich = isRichMode(options.format);
|
|
27062
|
+
const results = [];
|
|
27063
|
+
if (tasks2.length === 0)
|
|
27064
|
+
return results;
|
|
27065
|
+
const silent = options.format?.json === true;
|
|
27066
|
+
if (rich && !silent) {
|
|
27067
|
+
if (options.title)
|
|
27068
|
+
log.step(theme.heading(options.title));
|
|
27069
|
+
await tasks(tasks2.map((t2) => ({
|
|
27070
|
+
title: t2.title,
|
|
27071
|
+
task: async (message) => {
|
|
27072
|
+
const timer = startTimer();
|
|
27073
|
+
try {
|
|
27074
|
+
const value = await t2.run();
|
|
27075
|
+
const result = {
|
|
27076
|
+
title: t2.title,
|
|
27077
|
+
ok: true,
|
|
27078
|
+
ms: timer.ms(),
|
|
27079
|
+
value
|
|
27080
|
+
};
|
|
27081
|
+
if (t2.details) {
|
|
27082
|
+
result.details = await t2.details(value);
|
|
27083
|
+
}
|
|
27084
|
+
results.push(result);
|
|
27085
|
+
const dur = formatDuration2(result.ms);
|
|
27086
|
+
if (result.details) {
|
|
27087
|
+
const summary = Object.entries(result.details).map(([k, v]) => `${theme.dim(k)}=${theme.value(v)}`).join(" ");
|
|
27088
|
+
message(`${theme.success(icons.success)} ${dur} ${summary}`);
|
|
27089
|
+
} else {
|
|
27090
|
+
message(`${theme.success(icons.success)} ${dur}`);
|
|
27091
|
+
}
|
|
27092
|
+
} catch (err) {
|
|
27093
|
+
const message2 = err instanceof CLIError ? err.message : err instanceof Error ? err.message : String(err);
|
|
27094
|
+
const result = {
|
|
27095
|
+
title: t2.title,
|
|
27096
|
+
ok: false,
|
|
27097
|
+
ms: timer.ms(),
|
|
27098
|
+
error: message2
|
|
27099
|
+
};
|
|
27100
|
+
results.push(result);
|
|
27101
|
+
message(`${theme.error(icons.error)} ${formatDuration2(result.ms)} ${theme.dim(message2)}`);
|
|
27102
|
+
}
|
|
27103
|
+
}
|
|
27104
|
+
})));
|
|
27105
|
+
} else if (silent) {
|
|
27106
|
+
for (const t2 of tasks2) {
|
|
27107
|
+
const timer = startTimer();
|
|
27108
|
+
try {
|
|
27109
|
+
const value = await t2.run();
|
|
27110
|
+
const result = {
|
|
27111
|
+
title: t2.title,
|
|
27112
|
+
ok: true,
|
|
27113
|
+
ms: timer.ms(),
|
|
27114
|
+
value
|
|
27115
|
+
};
|
|
27116
|
+
if (t2.details) {
|
|
27117
|
+
result.details = await t2.details(value);
|
|
27118
|
+
}
|
|
27119
|
+
results.push(result);
|
|
27120
|
+
} catch (err) {
|
|
27121
|
+
const message2 = err instanceof CLIError ? err.message : err instanceof Error ? err.message : String(err);
|
|
27122
|
+
const result = {
|
|
27123
|
+
title: t2.title,
|
|
27124
|
+
ok: false,
|
|
27125
|
+
ms: timer.ms(),
|
|
27126
|
+
error: message2
|
|
27127
|
+
};
|
|
27128
|
+
results.push(result);
|
|
27129
|
+
}
|
|
27130
|
+
}
|
|
27131
|
+
} else {
|
|
27132
|
+
for (const t2 of tasks2) {
|
|
27133
|
+
log.step(t2.title);
|
|
27134
|
+
const timer = startTimer();
|
|
27135
|
+
try {
|
|
27136
|
+
const value = await t2.run();
|
|
27137
|
+
const result = {
|
|
27138
|
+
title: t2.title,
|
|
27139
|
+
ok: true,
|
|
27140
|
+
ms: timer.ms(),
|
|
27141
|
+
value
|
|
27142
|
+
};
|
|
27143
|
+
if (t2.details) {
|
|
27144
|
+
result.details = await t2.details(value);
|
|
27145
|
+
}
|
|
27146
|
+
results.push(result);
|
|
27147
|
+
log.success(`${t2.title} ${theme.dim(formatDuration2(result.ms))}`);
|
|
27148
|
+
} catch (err) {
|
|
27149
|
+
const message2 = err instanceof CLIError ? err.message : err instanceof Error ? err.message : String(err);
|
|
27150
|
+
const result = {
|
|
27151
|
+
title: t2.title,
|
|
27152
|
+
ok: false,
|
|
27153
|
+
ms: timer.ms(),
|
|
27154
|
+
error: message2
|
|
27155
|
+
};
|
|
27156
|
+
results.push(result);
|
|
27157
|
+
log.error(`${t2.title} ${theme.dim(message2)}`);
|
|
27158
|
+
}
|
|
27159
|
+
}
|
|
27160
|
+
}
|
|
27161
|
+
if (results.some((r2) => !r2.ok)) {
|
|
27162
|
+
process.exitCode = 1;
|
|
27163
|
+
}
|
|
27164
|
+
if (options.onSummary) {
|
|
27165
|
+
options.onSummary(results);
|
|
27166
|
+
} else if (!options.format?.json && !options.format?.quiet) {
|
|
27167
|
+
renderDefaultSummary(results);
|
|
27168
|
+
}
|
|
27169
|
+
return results;
|
|
27170
|
+
}
|
|
27171
|
+
function renderDefaultSummary(results) {
|
|
27172
|
+
if (results.length === 0)
|
|
27173
|
+
return;
|
|
27174
|
+
const rows = results.map((r2) => ({
|
|
27175
|
+
Task: r2.title,
|
|
27176
|
+
Status: r2.ok ? theme.success(icons.success) : theme.error(icons.error),
|
|
27177
|
+
Duration: formatDuration2(r2.ms)
|
|
27178
|
+
}));
|
|
27179
|
+
formatTable(rows, {});
|
|
27180
|
+
}
|
|
27181
|
+
|
|
27182
|
+
// src/commands/deploy/deploy-command.ts
|
|
26862
27183
|
import { statSync, existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
26863
27184
|
import { resolve as resolve2 } from "path";
|
|
26864
27185
|
|
|
@@ -26937,13 +27258,6 @@ class EnvService {
|
|
|
26937
27258
|
default: "cryptolinx",
|
|
26938
27259
|
hint: "Prefix for worker URLs"
|
|
26939
27260
|
},
|
|
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
27261
|
{
|
|
26948
27262
|
name: "TRADE_INTERNAL_KEY",
|
|
26949
27263
|
required: true,
|
|
@@ -27340,62 +27654,61 @@ var DEPLOY_ORDER = [
|
|
|
27340
27654
|
"hoox",
|
|
27341
27655
|
"dashboard"
|
|
27342
27656
|
];
|
|
27343
|
-
async function deployAll(configService, cf, env, forceRebuildDashboard = false, autoMode = false) {
|
|
27657
|
+
async function deployAll(configService, cf, env, forceRebuildDashboard = false, autoMode = false, format3 = {}) {
|
|
27344
27658
|
const enabled = configService.listEnabledWorkers();
|
|
27345
27659
|
const workers = DEPLOY_ORDER.filter((w) => w !== "dashboard" && enabled.includes(w));
|
|
27346
27660
|
const unknown2 = enabled.filter((w) => w !== "dashboard" && !DEPLOY_ORDER.includes(w));
|
|
27347
27661
|
const allItems = [...workers, ...unknown2, "dashboard"];
|
|
27348
|
-
const results = [];
|
|
27349
27662
|
if (allItems.length === 0) {
|
|
27350
|
-
return
|
|
27663
|
+
return [];
|
|
27351
27664
|
}
|
|
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}`);
|
|
27665
|
+
const tasks2 = allItems.map((name) => ({
|
|
27666
|
+
title: name,
|
|
27667
|
+
run: async () => {
|
|
27668
|
+
if (name === "dashboard") {
|
|
27669
|
+
return await deployDashboard(cf, forceRebuildDashboard, true, autoMode);
|
|
27376
27670
|
}
|
|
27377
|
-
|
|
27378
|
-
|
|
27379
|
-
|
|
27380
|
-
if (
|
|
27381
|
-
|
|
27382
|
-
}
|
|
27383
|
-
if (
|
|
27384
|
-
|
|
27385
|
-
|
|
27386
|
-
|
|
27387
|
-
|
|
27388
|
-
|
|
27389
|
-
|
|
27390
|
-
|
|
27671
|
+
return await deploySingle(configService, cf, name, env);
|
|
27672
|
+
},
|
|
27673
|
+
details: (r2) => {
|
|
27674
|
+
if (!r2.success)
|
|
27675
|
+
return { error: r2.error ?? "unknown" };
|
|
27676
|
+
const d = {};
|
|
27677
|
+
if (r2.url)
|
|
27678
|
+
d.URL = r2.url;
|
|
27679
|
+
if (r2.size)
|
|
27680
|
+
d.Size = r2.size;
|
|
27681
|
+
if (r2.startupTime)
|
|
27682
|
+
d.Startup = r2.startupTime;
|
|
27683
|
+
if (r2.versionId)
|
|
27684
|
+
d.Version = r2.versionId.slice(0, 8) + "\u2026";
|
|
27685
|
+
if (Object.keys(d).length === 0 && r2.rawOutput) {
|
|
27686
|
+
d.Output = r2.rawOutput.slice(0, 80);
|
|
27687
|
+
}
|
|
27688
|
+
return d;
|
|
27391
27689
|
}
|
|
27392
|
-
}
|
|
27393
|
-
const
|
|
27394
|
-
|
|
27395
|
-
|
|
27396
|
-
|
|
27397
|
-
}
|
|
27398
|
-
return
|
|
27690
|
+
}));
|
|
27691
|
+
const richResults = await runRichTasks(tasks2, {
|
|
27692
|
+
title: `Deploying ${allItems.length} item(s)`,
|
|
27693
|
+
format: format3,
|
|
27694
|
+
onSummary: (results) => renderDeploySummary(results, format3)
|
|
27695
|
+
});
|
|
27696
|
+
return richResults.map((r2) => r2.value).filter(Boolean);
|
|
27697
|
+
}
|
|
27698
|
+
function renderDeploySummary(results, format3) {
|
|
27699
|
+
if (format3.json || format3.quiet)
|
|
27700
|
+
return;
|
|
27701
|
+
const rows = results.map((r2) => {
|
|
27702
|
+
const d = r2.value;
|
|
27703
|
+
return {
|
|
27704
|
+
Worker: r2.title,
|
|
27705
|
+
Status: r2.ok ? formatBadge("ok", "DEPLOYED") : formatBadge("err", "FAILED"),
|
|
27706
|
+
URL: d?.url ?? theme.dim("\u2014"),
|
|
27707
|
+
Size: d?.size ?? theme.dim("\u2014"),
|
|
27708
|
+
Duration: formatDuration3(r2.ms)
|
|
27709
|
+
};
|
|
27710
|
+
});
|
|
27711
|
+
formatTable(rows, {});
|
|
27399
27712
|
}
|
|
27400
27713
|
async function deployDashboard(_cf, forceRebuild = false, silentMode = false, autoMode = false) {
|
|
27401
27714
|
const dashboardPath = "workers/dashboard";
|
|
@@ -27803,9 +28116,10 @@ EXAMPLES:
|
|
|
27803
28116
|
hoox deploy all
|
|
27804
28117
|
hoox deploy all --env production
|
|
27805
28118
|
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) => {
|
|
28119
|
+
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
28120
|
const configService = new ConfigService;
|
|
27808
28121
|
await configService.load();
|
|
28122
|
+
const format3 = getFormatOptions(cmd);
|
|
27809
28123
|
if (options.dryRun) {
|
|
27810
28124
|
const enabled = configService.listEnabledWorkers();
|
|
27811
28125
|
const workers = DEPLOY_ORDER.filter((w) => w !== "dashboard" && enabled.includes(w));
|
|
@@ -27838,7 +28152,7 @@ ${theme.dim("Run without --dry-run to deploy.")}
|
|
|
27838
28152
|
return;
|
|
27839
28153
|
}
|
|
27840
28154
|
const cf = new CloudflareService;
|
|
27841
|
-
await deployAll(configService, cf, options.env, options.rebuild ?? false, options.auto ?? false);
|
|
28155
|
+
await deployAll(configService, cf, options.env, options.rebuild ?? false, options.auto ?? false, format3);
|
|
27842
28156
|
}, { service: "deploy" }));
|
|
27843
28157
|
deployCmd.command("workers").summary("Deploy all enabled workers to Cloudflare").description(`Deploy all enabled workers to Cloudflare Workers.
|
|
27844
28158
|
|
|
@@ -27927,8 +28241,8 @@ OPTIONS:
|
|
|
27927
28241
|
|
|
27928
28242
|
EXAMPLES:
|
|
27929
28243
|
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(
|
|
28244
|
+
hoox deploy worker agent-worker --env production`).option("--env <env>", "Cloudflare environment (e.g. production, staging)").action(withErrorHandling(async (name, options, cmd) => {
|
|
28245
|
+
const fmt = getFormatOptions(cmd);
|
|
27932
28246
|
const configService = new ConfigService;
|
|
27933
28247
|
await configService.load();
|
|
27934
28248
|
const cf = new CloudflareService;
|
|
@@ -27950,8 +28264,8 @@ OPTIONS:
|
|
|
27950
28264
|
|
|
27951
28265
|
EXAMPLES:
|
|
27952
28266
|
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(
|
|
28267
|
+
hoox deploy dashboard --rebuild Force rebuild before deploying`).option("--rebuild", "Force rebuild of dashboard before deploying").action(withErrorHandling(async (options, cmd) => {
|
|
28268
|
+
const fmt = getFormatOptions(cmd);
|
|
27955
28269
|
const cf = new CloudflareService;
|
|
27956
28270
|
const result = await deployDashboard(cf, options.rebuild);
|
|
27957
28271
|
if (result.success) {
|
|
@@ -27977,8 +28291,8 @@ Use --token and --secret-token to override.
|
|
|
27977
28291
|
EXAMPLES:
|
|
27978
28292
|
hoox deploy telegram-webhook
|
|
27979
28293
|
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(
|
|
28294
|
+
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) => {
|
|
28295
|
+
const fmt = getFormatOptions(cmd);
|
|
27982
28296
|
await doTelegramWebhook(fmt, options.token, options.secretToken, options.subdomain);
|
|
27983
28297
|
}, { service: "deploy" }));
|
|
27984
28298
|
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 +28301,8 @@ This is a post-deployment step that ensures the dashboard has correct
|
|
|
27987
28301
|
service binding URLs for all workers.
|
|
27988
28302
|
|
|
27989
28303
|
EXAMPLES:
|
|
27990
|
-
hoox deploy update-internal-urls`).action(withErrorHandling(async () => {
|
|
27991
|
-
const fmt = getFormatOptions(
|
|
28304
|
+
hoox deploy update-internal-urls`).action(withErrorHandling(async (_options, cmd) => {
|
|
28305
|
+
const fmt = getFormatOptions(cmd);
|
|
27992
28306
|
await doUpdateInternalUrls(fmt);
|
|
27993
28307
|
}, { service: "deploy" }));
|
|
27994
28308
|
deployCmd.command("kv-config").summary("Apply KV manifest keys post-deployment").description(`Apply the KV manifest key-value pairs after deploying workers.
|
|
@@ -27997,8 +28311,8 @@ Sets all KV keys from the manifest to their default values.
|
|
|
27997
28311
|
This post-deployment step initializes the CONFIG_KV namespace.
|
|
27998
28312
|
|
|
27999
28313
|
EXAMPLES:
|
|
28000
|
-
hoox deploy kv-config`).action(withErrorHandling(async () => {
|
|
28001
|
-
const fmt = getFormatOptions(
|
|
28314
|
+
hoox deploy kv-config`).action(withErrorHandling(async (_options, cmd) => {
|
|
28315
|
+
const fmt = getFormatOptions(cmd);
|
|
28002
28316
|
await doKvConfig(fmt);
|
|
28003
28317
|
}, { service: "deploy" }));
|
|
28004
28318
|
deployCmd.command("history <worker>").summary("Show deployment version history for a worker").description(`Display the deployment version history for a Cloudflare Worker.
|
|
@@ -28010,8 +28324,8 @@ ARGUMENTS:
|
|
|
28010
28324
|
|
|
28011
28325
|
EXAMPLES:
|
|
28012
28326
|
hoox deploy history trade-worker
|
|
28013
|
-
hoox deploy history hoox --json`).action(withErrorHandling(async (worker) => {
|
|
28014
|
-
const fmt = getFormatOptions(
|
|
28327
|
+
hoox deploy history hoox --json`).action(withErrorHandling(async (worker, _options, cmd) => {
|
|
28328
|
+
const fmt = getFormatOptions(cmd);
|
|
28015
28329
|
await doVersionHistory(worker, fmt);
|
|
28016
28330
|
}, { service: "deploy" }));
|
|
28017
28331
|
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 +28343,8 @@ OPTIONS:
|
|
|
28029
28343
|
EXAMPLES:
|
|
28030
28344
|
hoox deploy rollback trade-worker
|
|
28031
28345
|
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(
|
|
28346
|
+
hoox deploy rollback trade-worker <version-id> --yes`).option("--yes", "Skip confirmation prompt").action(withErrorHandling(async (worker, version2, options, cmd) => {
|
|
28347
|
+
const fmt = getFormatOptions(cmd);
|
|
28034
28348
|
await doVersionRollback(worker, version2, fmt, options.yes ?? false);
|
|
28035
28349
|
}, { service: "deploy" }));
|
|
28036
28350
|
}
|
|
@@ -30101,27 +30415,40 @@ ${summary.failed > 0 ? theme.error(icons.error) : theme.success(icons.success)}
|
|
|
30101
30415
|
`);
|
|
30102
30416
|
}
|
|
30103
30417
|
async function handleSetup(opts) {
|
|
30104
|
-
const s = spinner();
|
|
30105
30418
|
try {
|
|
30106
30419
|
const configService = new ConfigService;
|
|
30107
|
-
s.start("Loading config...");
|
|
30108
30420
|
await configService.load();
|
|
30109
|
-
s.stop("Config loaded");
|
|
30110
30421
|
const cf = new CloudflareService;
|
|
30111
30422
|
const secretsService = await SecretsService.create();
|
|
30112
30423
|
const categories = [];
|
|
30113
|
-
|
|
30114
|
-
|
|
30115
|
-
|
|
30116
|
-
|
|
30117
|
-
|
|
30118
|
-
|
|
30119
|
-
|
|
30120
|
-
|
|
30121
|
-
|
|
30122
|
-
|
|
30123
|
-
|
|
30124
|
-
|
|
30424
|
+
const tasks2 = [
|
|
30425
|
+
{
|
|
30426
|
+
title: "Validating config",
|
|
30427
|
+
run: async () => await runConfigChecks(configService)
|
|
30428
|
+
},
|
|
30429
|
+
{
|
|
30430
|
+
title: "Checking infrastructure (D1, KV, R2, Queues)",
|
|
30431
|
+
run: async () => await runInfraChecks(cf)
|
|
30432
|
+
},
|
|
30433
|
+
{
|
|
30434
|
+
title: "Checking secrets",
|
|
30435
|
+
run: async () => await runSecretsChecks(secretsService, configService, cf)
|
|
30436
|
+
},
|
|
30437
|
+
{
|
|
30438
|
+
title: "Checking database",
|
|
30439
|
+
run: async () => await runDatabaseChecks(cf, configService)
|
|
30440
|
+
}
|
|
30441
|
+
];
|
|
30442
|
+
const results = await runRichTasks(tasks2, {
|
|
30443
|
+
title: "Running diagnostics",
|
|
30444
|
+
format: opts,
|
|
30445
|
+
onSummary: (rows) => {
|
|
30446
|
+
for (const r2 of rows) {
|
|
30447
|
+
if (r2.ok && r2.value)
|
|
30448
|
+
categories.push(r2.value);
|
|
30449
|
+
}
|
|
30450
|
+
}
|
|
30451
|
+
});
|
|
30125
30452
|
const report = buildReport(categories);
|
|
30126
30453
|
if (opts.json) {
|
|
30127
30454
|
process.stdout.write(JSON.stringify(report, null, 2) + `
|
|
@@ -30132,6 +30459,9 @@ async function handleSetup(opts) {
|
|
|
30132
30459
|
if (!report.success) {
|
|
30133
30460
|
process.exitCode = 1 /* ERROR */;
|
|
30134
30461
|
}
|
|
30462
|
+
if (results.some((r2) => !r2.ok)) {
|
|
30463
|
+
process.exitCode = 1 /* ERROR */;
|
|
30464
|
+
}
|
|
30135
30465
|
} catch (err) {
|
|
30136
30466
|
const message = err instanceof Error ? err.message : String(err);
|
|
30137
30467
|
formatError2(message, opts);
|
|
@@ -30139,7 +30469,6 @@ async function handleSetup(opts) {
|
|
|
30139
30469
|
}
|
|
30140
30470
|
}
|
|
30141
30471
|
async function handleHealth(opts, autoFix) {
|
|
30142
|
-
const s = spinner();
|
|
30143
30472
|
const results = [];
|
|
30144
30473
|
try {
|
|
30145
30474
|
const configService = new ConfigService;
|
|
@@ -30150,29 +30479,37 @@ async function handleHealth(opts, autoFix) {
|
|
|
30150
30479
|
formatSuccess("No enabled workers to check", opts);
|
|
30151
30480
|
return;
|
|
30152
30481
|
}
|
|
30153
|
-
|
|
30154
|
-
|
|
30155
|
-
|
|
30156
|
-
|
|
30157
|
-
|
|
30158
|
-
|
|
30159
|
-
|
|
30160
|
-
|
|
30161
|
-
|
|
30162
|
-
|
|
30163
|
-
|
|
30164
|
-
|
|
30165
|
-
|
|
30166
|
-
|
|
30167
|
-
|
|
30168
|
-
|
|
30169
|
-
|
|
30170
|
-
|
|
30171
|
-
|
|
30172
|
-
|
|
30482
|
+
const tasks2 = enabledWorkers.map((workerName) => ({
|
|
30483
|
+
title: `Probe ${workerName}`,
|
|
30484
|
+
run: async () => {
|
|
30485
|
+
try {
|
|
30486
|
+
const tailResult = await cf.tail(workerName);
|
|
30487
|
+
const healthy = tailResult.ok;
|
|
30488
|
+
return {
|
|
30489
|
+
worker: workerName,
|
|
30490
|
+
status: healthy ? "healthy" : "degraded",
|
|
30491
|
+
connectivity: healthy,
|
|
30492
|
+
error: tailResult.ok ? undefined : tailResult.error ?? "Unknown error"
|
|
30493
|
+
};
|
|
30494
|
+
} catch (err) {
|
|
30495
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
30496
|
+
return {
|
|
30497
|
+
worker: workerName,
|
|
30498
|
+
status: "down",
|
|
30499
|
+
connectivity: false,
|
|
30500
|
+
error: message
|
|
30501
|
+
};
|
|
30502
|
+
}
|
|
30173
30503
|
}
|
|
30504
|
+
}));
|
|
30505
|
+
const taskResults = await runRichTasks(tasks2, {
|
|
30506
|
+
title: `Health-checking ${enabledWorkers.length} worker(s)`,
|
|
30507
|
+
format: opts
|
|
30508
|
+
});
|
|
30509
|
+
for (const r2 of taskResults) {
|
|
30510
|
+
if (r2.value)
|
|
30511
|
+
results.push(r2.value);
|
|
30174
30512
|
}
|
|
30175
|
-
s.stop("Health check complete");
|
|
30176
30513
|
if (opts.json) {
|
|
30177
30514
|
process.stdout.write(JSON.stringify(results, null, 2) + `
|
|
30178
30515
|
`);
|
|
@@ -30197,7 +30534,6 @@ ${theme.warning(icons.warning)} Auto-fix flag set but health issues require manu
|
|
|
30197
30534
|
process.exitCode = 1 /* ERROR */;
|
|
30198
30535
|
}
|
|
30199
30536
|
} catch (err) {
|
|
30200
|
-
s.stop("Health check failed");
|
|
30201
30537
|
const message = err instanceof Error ? err.message : String(err);
|
|
30202
30538
|
formatError2(message, opts);
|
|
30203
30539
|
process.exitCode = 1 /* ERROR */;
|
|
@@ -30771,8 +31107,8 @@ async function tailAllWorkers(opts, fmt) {
|
|
|
30771
31107
|
}
|
|
30772
31108
|
function registerLogsCommand(program2) {
|
|
30773
31109
|
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(
|
|
31110
|
+
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) => {
|
|
31111
|
+
const fmt = getFormatOptions(cmd);
|
|
30776
31112
|
const workerOpts = {
|
|
30777
31113
|
level: normalizeLevel(options.level ?? "all"),
|
|
30778
31114
|
follow: options.follow !== false,
|
|
@@ -30786,8 +31122,8 @@ function registerLogsCommand(program2) {
|
|
|
30786
31122
|
process.exitCode = 1 /* ERROR */;
|
|
30787
31123
|
}
|
|
30788
31124
|
});
|
|
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(
|
|
31125
|
+
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) => {
|
|
31126
|
+
const fmt = getFormatOptions(cmd);
|
|
30791
31127
|
const workerOpts = {
|
|
30792
31128
|
level: normalizeLevel(options.level ?? "all"),
|
|
30793
31129
|
follow: options.follow !== false,
|
|
@@ -30896,21 +31232,22 @@ var PIPELINE_STEPS = [
|
|
|
30896
31232
|
];
|
|
30897
31233
|
function registerTestCommand(program2) {
|
|
30898
31234
|
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(
|
|
31235
|
+
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) => {
|
|
31236
|
+
const fmt = getFormatOptions(cmd);
|
|
30901
31237
|
const useJson = options.json || fmt.json;
|
|
30902
31238
|
const opts = { json: useJson, quiet: fmt.quiet };
|
|
30903
31239
|
const results = [];
|
|
30904
31240
|
const s = spinner();
|
|
31241
|
+
const pipelineTimer = startTimer();
|
|
30905
31242
|
s.start("Running CI pipeline...");
|
|
30906
31243
|
for (const step of PIPELINE_STEPS) {
|
|
30907
31244
|
s.message(`${theme.info(icons.info)} ${step.label}...`);
|
|
30908
31245
|
const result = await runStep(step.args, process.cwd());
|
|
30909
31246
|
results.push({ ...result, step: step.label });
|
|
30910
31247
|
if (result.success) {
|
|
30911
|
-
s.message(` ${theme.success(icons.success)} ${step.label} passed (${result.duration}
|
|
31248
|
+
s.message(` ${theme.success(icons.success)} ${step.label} passed (${formatDuration3(result.duration)})`);
|
|
30912
31249
|
} else {
|
|
30913
|
-
s.message(` ${theme.error(icons.error)} ${step.label} failed (${result.duration}
|
|
31250
|
+
s.message(` ${theme.error(icons.error)} ${step.label} failed (${formatDuration3(result.duration)})`);
|
|
30914
31251
|
if (result.error) {
|
|
30915
31252
|
s.message(theme.dim(result.error.slice(0, 500)));
|
|
30916
31253
|
}
|
|
@@ -30923,14 +31260,14 @@ function registerTestCommand(program2) {
|
|
|
30923
31260
|
failed: results.filter((r2) => !r2.success).length,
|
|
30924
31261
|
results
|
|
30925
31262
|
};
|
|
30926
|
-
s.stop(summary.failed > 0 ? `Pipeline complete: ${summary.passed} passed, ${summary.failed} failed` :
|
|
31263
|
+
s.stop(summary.failed > 0 ? `Pipeline complete: ${summary.passed} passed, ${summary.failed} failed (${formatDuration3(pipelineTimer.ms())})` : `Pipeline complete (${formatDuration3(pipelineTimer.ms())})`);
|
|
30927
31264
|
printSummary(summary, opts);
|
|
30928
31265
|
if (summary.failed > 0) {
|
|
30929
31266
|
process.exitCode = 1 /* ERROR */;
|
|
30930
31267
|
}
|
|
30931
31268
|
}, { 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(
|
|
31269
|
+
testCmd.command("unit").description("Run unit tests with bun test").option("--coverage", "Run with coverage reporting").action(async (options, cmd) => {
|
|
31270
|
+
const fmt = getFormatOptions(cmd);
|
|
30934
31271
|
const args = ["bun", "test"];
|
|
30935
31272
|
if (options.coverage)
|
|
30936
31273
|
args.push("--coverage");
|
|
@@ -30942,8 +31279,8 @@ function registerTestCommand(program2) {
|
|
|
30942
31279
|
process.exitCode = 1 /* ERROR */;
|
|
30943
31280
|
}
|
|
30944
31281
|
});
|
|
30945
|
-
testCmd.command("integration").description("Run integration tests with vitest").option("--coverage", "Run with coverage reporting").action(async (options) => {
|
|
30946
|
-
const fmt = getFormatOptions(
|
|
31282
|
+
testCmd.command("integration").description("Run integration tests with vitest").option("--coverage", "Run with coverage reporting").action(async (options, cmd) => {
|
|
31283
|
+
const fmt = getFormatOptions(cmd);
|
|
30947
31284
|
const args = ["vitest", "run", "--config", "vitest.config.ts"];
|
|
30948
31285
|
if (options.coverage)
|
|
30949
31286
|
args.push("--coverage");
|
|
@@ -30955,8 +31292,8 @@ function registerTestCommand(program2) {
|
|
|
30955
31292
|
process.exitCode = 1 /* ERROR */;
|
|
30956
31293
|
}
|
|
30957
31294
|
});
|
|
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(
|
|
31295
|
+
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) => {
|
|
31296
|
+
const fmt = getFormatOptions(cmd);
|
|
30960
31297
|
const configService = new ConfigService;
|
|
30961
31298
|
await configService.load();
|
|
30962
31299
|
const workerConfig = configService.getWorker(name);
|
|
@@ -30979,14 +31316,16 @@ function registerTestCommand(program2) {
|
|
|
30979
31316
|
}, { service: "test" }));
|
|
30980
31317
|
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
31318
|
const s = spinner();
|
|
31319
|
+
const liveTimer = startTimer();
|
|
30982
31320
|
let filePattern = options.service ? `tests/live/${options.service}.test.ts` : "tests/live/";
|
|
30983
31321
|
s.start(`Running live tests${options.service ? ` for ${options.service}` : ""}...`);
|
|
30984
31322
|
const args = ["bun", "test", filePattern, "--jobs", "1"];
|
|
30985
31323
|
const result = await runWithInherit(args, process.cwd());
|
|
31324
|
+
const dur = formatDuration3(liveTimer.ms());
|
|
30986
31325
|
if (result.success) {
|
|
30987
|
-
s.stop(
|
|
31326
|
+
s.stop(`Live tests complete (${dur})`);
|
|
30988
31327
|
} else {
|
|
30989
|
-
s.stop(`Live tests: some failures (exit code ${result.exitCode})`);
|
|
31328
|
+
s.stop(`Live tests: some failures (exit code ${result.exitCode}, ${dur})`);
|
|
30990
31329
|
process.exitCode = 1 /* ERROR */;
|
|
30991
31330
|
}
|
|
30992
31331
|
}, { service: "test" }));
|
|
@@ -31344,8 +31683,8 @@ EXAMPLES:
|
|
|
31344
31683
|
hoox clone --all --home Clone all workers to $HOME/.hoox/workers
|
|
31345
31684
|
hoox clone trade-worker Clone specific worker
|
|
31346
31685
|
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(
|
|
31686
|
+
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) => {
|
|
31687
|
+
const fmt = getFormatOptions(cmd);
|
|
31349
31688
|
const configService = new ConfigService;
|
|
31350
31689
|
await configService.load();
|
|
31351
31690
|
const workers = configService.listWorkers();
|
|
@@ -31380,6 +31719,7 @@ EXAMPLES:
|
|
|
31380
31719
|
}
|
|
31381
31720
|
const repoBase = options.org ? `https://github.com/${options.org}` : await resolveRepoBase();
|
|
31382
31721
|
const s = spinner();
|
|
31722
|
+
const cloneTimer = startTimer();
|
|
31383
31723
|
const location = options.home ? "$HOME/.hoox/workers" : "workers/";
|
|
31384
31724
|
s.start(`Cloning ${workers.length} worker(s) to ${location}...`);
|
|
31385
31725
|
const results = [];
|
|
@@ -31419,10 +31759,10 @@ EXAMPLES:
|
|
|
31419
31759
|
const succeeded = results.filter((r2) => r2.cloned).length;
|
|
31420
31760
|
const failed = results.filter((r2) => !r2.cloned).length;
|
|
31421
31761
|
if (failed > 0) {
|
|
31422
|
-
s.stop(`Clone complete: ${succeeded} succeeded, ${failed} failed`);
|
|
31762
|
+
s.stop(`Clone complete: ${succeeded} succeeded, ${failed} failed (${formatDuration3(cloneTimer.ms())})`);
|
|
31423
31763
|
process.exitCode = 1 /* ERROR */;
|
|
31424
31764
|
} else {
|
|
31425
|
-
s.stop(`All ${succeeded} worker(s) cloned successfully`);
|
|
31765
|
+
s.stop(`All ${succeeded} worker(s) cloned successfully (${formatDuration3(cloneTimer.ms())})`);
|
|
31426
31766
|
}
|
|
31427
31767
|
if (!fmt.quiet) {
|
|
31428
31768
|
const rows = results.map((r2) => ({
|
|
@@ -31450,6 +31790,7 @@ EXAMPLES:
|
|
|
31450
31790
|
}
|
|
31451
31791
|
const repoBase = options.org ? `https://github.com/${options.org}` : await resolveRepoBase();
|
|
31452
31792
|
const s = spinner();
|
|
31793
|
+
const singleTimer = startTimer();
|
|
31453
31794
|
const location = options.home ? "$HOME/.hoox/workers" : "workers/";
|
|
31454
31795
|
s.start(`Cloning ${name} to ${location}...`);
|
|
31455
31796
|
const result = await cloneWorker(getRepoUrl(repoBase, name), workerConfig.path, name, process.cwd(), homeDir);
|
|
@@ -31458,11 +31799,11 @@ EXAMPLES:
|
|
|
31458
31799
|
s.message("Updating submodules...");
|
|
31459
31800
|
await updateSubmodules(process.cwd());
|
|
31460
31801
|
} catch {}
|
|
31461
|
-
s.stop(`Successfully cloned ${name}`);
|
|
31802
|
+
s.stop(`Successfully cloned ${name} (${formatDuration3(singleTimer.ms())})`);
|
|
31462
31803
|
const locationText = options.home ? " at $HOME/.hoox/workers" : "";
|
|
31463
31804
|
formatSuccess(`Cloned ${name}${locationText} from ${result.repo}`, fmt);
|
|
31464
31805
|
} else {
|
|
31465
|
-
s.stop(`Failed to clone ${name}`);
|
|
31806
|
+
s.stop(`Failed to clone ${name} (${formatDuration3(singleTimer.ms())})`);
|
|
31466
31807
|
formatError2(new CLIError(`Failed to clone "${name}": ${result.error}`, 1 /* ERROR */), fmt);
|
|
31467
31808
|
process.exitCode = 1 /* ERROR */;
|
|
31468
31809
|
}
|
|
@@ -31650,16 +31991,23 @@ class DbService {
|
|
|
31650
31991
|
return stdout2.trim();
|
|
31651
31992
|
}
|
|
31652
31993
|
async readMigrationSql() {
|
|
31994
|
+
const path5 = this.resolveMigrationScriptPath();
|
|
31995
|
+
let file2;
|
|
31653
31996
|
try {
|
|
31654
|
-
|
|
31655
|
-
|
|
31656
|
-
|
|
31657
|
-
|
|
31658
|
-
|
|
31659
|
-
|
|
31660
|
-
}
|
|
31661
|
-
}
|
|
31662
|
-
|
|
31997
|
+
file2 = Bun.file(path5);
|
|
31998
|
+
} catch (e) {
|
|
31999
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
32000
|
+
throw new Error(`Migration script "${path5}" could not be read: ${message}. ` + `Refusing to run no-op migration.`, { cause: e });
|
|
32001
|
+
}
|
|
32002
|
+
if (!await file2.exists()) {
|
|
32003
|
+
throw new Error(`Migration script "${path5}" not found. ` + `Refusing to run no-op migration. Create the script or run \`hoox setup\` first.`);
|
|
32004
|
+
}
|
|
32005
|
+
const content = await file2.text();
|
|
32006
|
+
const match = content.match(/d1\s+execute\s+\S+\s+--command=["'](.+?)["']/s);
|
|
32007
|
+
if (!match) {
|
|
32008
|
+
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.`);
|
|
32009
|
+
}
|
|
32010
|
+
return match[1];
|
|
31663
32011
|
}
|
|
31664
32012
|
}
|
|
31665
32013
|
// src/commands/db/db-command.ts
|
|
@@ -31670,34 +32018,36 @@ async function resolveDb(cmd, svc) {
|
|
|
31670
32018
|
async function handleApply(opts, dbName, remote, file2) {
|
|
31671
32019
|
const svc = new DbService;
|
|
31672
32020
|
const s = spinner();
|
|
32021
|
+
const t2 = startTimer();
|
|
31673
32022
|
s.start("Applying schema...");
|
|
31674
32023
|
try {
|
|
31675
32024
|
const output = await svc.apply(dbName, remote, file2);
|
|
31676
|
-
s.stop(
|
|
32025
|
+
s.stop(`Schema applied (${formatDuration3(t2.ms())})`);
|
|
31677
32026
|
formatSuccess(`Schema applied to ${dbName}${remote ? " (remote)" : " (local)"}`, opts);
|
|
31678
32027
|
if (!opts.quiet && output) {
|
|
31679
32028
|
process.stdout.write(`${output}
|
|
31680
32029
|
`);
|
|
31681
32030
|
}
|
|
31682
32031
|
} catch (err) {
|
|
31683
|
-
s.stop(
|
|
32032
|
+
s.stop(`Schema apply failed (${formatDuration3(t2.ms())})`);
|
|
31684
32033
|
throw err;
|
|
31685
32034
|
}
|
|
31686
32035
|
}
|
|
31687
32036
|
async function handleMigrate(opts, dbName, remote) {
|
|
31688
32037
|
const svc = new DbService;
|
|
31689
32038
|
const s = spinner();
|
|
32039
|
+
const t2 = startTimer();
|
|
31690
32040
|
s.start("Running migrations...");
|
|
31691
32041
|
try {
|
|
31692
32042
|
const output = await svc.migrate(dbName, remote);
|
|
31693
|
-
s.stop(
|
|
32043
|
+
s.stop(`Migrations complete (${formatDuration3(t2.ms())})`);
|
|
31694
32044
|
formatSuccess(`Migrations applied to ${dbName}${remote ? " (remote)" : " (local)"}`, opts);
|
|
31695
32045
|
if (!opts.quiet && output) {
|
|
31696
32046
|
process.stdout.write(`${output}
|
|
31697
32047
|
`);
|
|
31698
32048
|
}
|
|
31699
32049
|
} catch (err) {
|
|
31700
|
-
s.stop(
|
|
32050
|
+
s.stop(`Migrations failed (${formatDuration3(t2.ms())})`);
|
|
31701
32051
|
throw err;
|
|
31702
32052
|
}
|
|
31703
32053
|
}
|
|
@@ -31716,7 +32066,37 @@ async function handleList2(opts, dbName, remote) {
|
|
|
31716
32066
|
formatTable(rows, opts);
|
|
31717
32067
|
}
|
|
31718
32068
|
}
|
|
31719
|
-
|
|
32069
|
+
var DESTRUCTIVE_SQL_KEYWORDS = [
|
|
32070
|
+
"DROP",
|
|
32071
|
+
"TRUNCATE",
|
|
32072
|
+
"ALTER",
|
|
32073
|
+
"DELETE",
|
|
32074
|
+
"UPDATE",
|
|
32075
|
+
"REPLACE",
|
|
32076
|
+
"INSERT",
|
|
32077
|
+
"CREATE",
|
|
32078
|
+
"ATTACH",
|
|
32079
|
+
"DETACH",
|
|
32080
|
+
"REINDEX",
|
|
32081
|
+
"VACUUM"
|
|
32082
|
+
];
|
|
32083
|
+
function findDestructiveKeywords(sql) {
|
|
32084
|
+
const upper = sql.toUpperCase();
|
|
32085
|
+
const found = [];
|
|
32086
|
+
for (const kw of DESTRUCTIVE_SQL_KEYWORDS) {
|
|
32087
|
+
const re = new RegExp(`(^|[^A-Z0-9_])${kw}([^A-Z0-9_]|$)`, "g");
|
|
32088
|
+
if (re.test(upper))
|
|
32089
|
+
found.push(kw);
|
|
32090
|
+
}
|
|
32091
|
+
return found;
|
|
32092
|
+
}
|
|
32093
|
+
async function handleQuery(opts, dbName, sql, remote, allowDestructive) {
|
|
32094
|
+
if (!allowDestructive) {
|
|
32095
|
+
const matches = findDestructiveKeywords(sql);
|
|
32096
|
+
if (matches.length > 0) {
|
|
32097
|
+
throw new Error(`Destructive SQL keywords detected: ${matches.join(", ")}. ` + `Re-run with --allow-destructive to confirm. ` + `(Keywords: ${DESTRUCTIVE_SQL_KEYWORDS.join(", ")})`);
|
|
32098
|
+
}
|
|
32099
|
+
}
|
|
31720
32100
|
const svc = new DbService;
|
|
31721
32101
|
const output = await svc.query(dbName, sql, remote);
|
|
31722
32102
|
if (opts.json) {
|
|
@@ -31735,13 +32115,14 @@ async function handleQuery(opts, dbName, sql, remote) {
|
|
|
31735
32115
|
async function handleExport(opts, dbName, outputPath) {
|
|
31736
32116
|
const svc = new DbService;
|
|
31737
32117
|
const s = spinner();
|
|
32118
|
+
const t2 = startTimer();
|
|
31738
32119
|
s.start("Exporting database...");
|
|
31739
32120
|
try {
|
|
31740
32121
|
const path5 = await svc.export(dbName, outputPath);
|
|
31741
|
-
s.stop(
|
|
32122
|
+
s.stop(`Database exported (${formatDuration3(t2.ms())})`);
|
|
31742
32123
|
formatSuccess(`Database exported to ${path5}`, opts);
|
|
31743
32124
|
} catch (err) {
|
|
31744
|
-
s.stop(
|
|
32125
|
+
s.stop(`Export failed (${formatDuration3(t2.ms())})`);
|
|
31745
32126
|
throw err;
|
|
31746
32127
|
}
|
|
31747
32128
|
}
|
|
@@ -31758,17 +32139,18 @@ async function handleReset(opts, dbName, confirmed) {
|
|
|
31758
32139
|
}
|
|
31759
32140
|
const svc = new DbService;
|
|
31760
32141
|
const s = spinner();
|
|
32142
|
+
const t2 = startTimer();
|
|
31761
32143
|
s.start("Resetting database...");
|
|
31762
32144
|
try {
|
|
31763
32145
|
const output = await svc.reset(dbName);
|
|
31764
|
-
s.stop(
|
|
32146
|
+
s.stop(`Database reset (${formatDuration3(t2.ms())})`);
|
|
31765
32147
|
formatSuccess(`Database "${dbName}" has been recreated`, opts);
|
|
31766
32148
|
if (!opts.quiet && output) {
|
|
31767
32149
|
process.stdout.write(`${output}
|
|
31768
32150
|
`);
|
|
31769
32151
|
}
|
|
31770
32152
|
} catch (err) {
|
|
31771
|
-
s.stop(
|
|
32153
|
+
s.stop(`Reset failed (${formatDuration3(t2.ms())})`);
|
|
31772
32154
|
throw err;
|
|
31773
32155
|
}
|
|
31774
32156
|
}
|
|
@@ -31816,12 +32198,13 @@ EXAMPLES:
|
|
|
31816
32198
|
const remote = Boolean(cmd.optsWithGlobals().remote);
|
|
31817
32199
|
await handleList2(opts, dbName, remote);
|
|
31818
32200
|
}, { service: "db" }));
|
|
31819
|
-
dbCmd.command("query <sql>").description("Execute a SQL query").action(withErrorHandling(async (sql,
|
|
32201
|
+
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
32202
|
const opts = getFormatOptions(cmd);
|
|
31821
32203
|
const svc = new DbService;
|
|
31822
32204
|
const dbName = await resolveDb(cmd, svc);
|
|
31823
32205
|
const remote = Boolean(cmd.optsWithGlobals().remote);
|
|
31824
|
-
|
|
32206
|
+
const allowDestructive = Boolean(options.allowDestructive);
|
|
32207
|
+
await handleQuery(opts, dbName, sql, remote, allowDestructive);
|
|
31825
32208
|
}, { service: "db" }));
|
|
31826
32209
|
dbCmd.command("export").description("Export database to .sql file").option("--output <path>", "Output file path").action(withErrorHandling(async (options, cmd) => {
|
|
31827
32210
|
const opts = getFormatOptions(cmd);
|
|
@@ -31916,11 +32299,20 @@ ${theme.heading("Summary:")} ${result.healthyCount} healthy, ${result.degradedCo
|
|
|
31916
32299
|
process.exitCode = 1 /* ERROR */;
|
|
31917
32300
|
}
|
|
31918
32301
|
}
|
|
32302
|
+
function assertSafeInteger(value, name, max) {
|
|
32303
|
+
if (!Number.isInteger(value) || value < 0 || value > max || !Number.isSafeInteger(value)) {
|
|
32304
|
+
throw new Error(`Invalid ${name}: ${value} (must be a non-negative integer \u2264 ${max})`);
|
|
32305
|
+
}
|
|
32306
|
+
}
|
|
32307
|
+
function escapeSqlString(value) {
|
|
32308
|
+
return value.replace(/'/g, "''");
|
|
32309
|
+
}
|
|
31919
32310
|
async function doMonitorTrades(limit, fmt) {
|
|
31920
32311
|
try {
|
|
32312
|
+
assertSafeInteger(limit, "limit", 100);
|
|
31921
32313
|
const db = new DbService;
|
|
31922
32314
|
const dbName = await db.resolveDbName();
|
|
31923
|
-
const sql = `SELECT * FROM trades ORDER BY timestamp DESC LIMIT ${
|
|
32315
|
+
const sql = `SELECT * FROM trades ORDER BY timestamp DESC LIMIT ${limit}`;
|
|
31924
32316
|
const output = await db.query(dbName, sql, true);
|
|
31925
32317
|
process.stdout.write(output + `
|
|
31926
32318
|
`);
|
|
@@ -31938,7 +32330,7 @@ async function doMonitorLogs(workerName, fmt) {
|
|
|
31938
32330
|
if (!/^[a-zA-Z0-9_-]+$/.test(workerName)) {
|
|
31939
32331
|
throw new Error(`Invalid worker name: "${workerName}"`);
|
|
31940
32332
|
}
|
|
31941
|
-
sql = `SELECT * FROM system_logs WHERE worker = '${workerName}' ORDER BY timestamp DESC LIMIT 20`;
|
|
32333
|
+
sql = `SELECT * FROM system_logs WHERE worker = '${escapeSqlString(workerName)}' ORDER BY timestamp DESC LIMIT 20`;
|
|
31942
32334
|
} else {
|
|
31943
32335
|
sql = "SELECT * FROM system_logs ORDER BY timestamp DESC LIMIT 20";
|
|
31944
32336
|
}
|
|
@@ -32052,6 +32444,7 @@ async function doMonitorAnalyticsSummary(fmt) {
|
|
|
32052
32444
|
}
|
|
32053
32445
|
async function doMonitorAnalyticsErrors(hours, fmt) {
|
|
32054
32446
|
try {
|
|
32447
|
+
assertSafeInteger(hours, "hours", 8760);
|
|
32055
32448
|
const db = new DbService;
|
|
32056
32449
|
const dbName = await db.resolveDbName();
|
|
32057
32450
|
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 +33106,18 @@ EXAMPLES:
|
|
|
32713
33106
|
throw new CLIError(`"${submodulePath}" is not a git submodule \u2014 nothing to update`, 2 /* INVALID_USAGE */);
|
|
32714
33107
|
}
|
|
32715
33108
|
const s = spinner();
|
|
33109
|
+
const t2 = startTimer();
|
|
32716
33110
|
s.start(`Updating ${target}...`);
|
|
32717
33111
|
const output = await gitSubmoduleUpdate(cwd, submodulePath);
|
|
32718
|
-
|
|
33112
|
+
const dur = formatDuration3(t2.ms());
|
|
33113
|
+
s.stop(output ? theme.success(`Updated ${target} (${dur})`) : theme.muted(`${target} already up to date (${dur})`));
|
|
32719
33114
|
} else {
|
|
32720
33115
|
const s = spinner();
|
|
33116
|
+
const t2 = startTimer();
|
|
32721
33117
|
s.start("Pulling latest from remote...");
|
|
32722
33118
|
const output = await gitPull(cwd);
|
|
32723
|
-
|
|
33119
|
+
const dur = formatDuration3(t2.ms());
|
|
33120
|
+
s.stop(output ? theme.success(`Repository up to date (${dur})`) : theme.muted(`Already up to date (${dur})`));
|
|
32724
33121
|
}
|
|
32725
33122
|
}, { service: "update" }));
|
|
32726
33123
|
}
|
|
@@ -32807,7 +33204,7 @@ function registerSchemaCommand(program2) {
|
|
|
32807
33204
|
import { spawn } from "child_process";
|
|
32808
33205
|
import { resolve as resolve7, dirname } from "path";
|
|
32809
33206
|
import { fileURLToPath } from "url";
|
|
32810
|
-
import { existsSync as
|
|
33207
|
+
import { existsSync as existsSync6 } from "fs";
|
|
32811
33208
|
var __dirname2 = dirname(fileURLToPath(import.meta.url));
|
|
32812
33209
|
function resolveTUIEntry() {
|
|
32813
33210
|
const candidates = [
|
|
@@ -32816,7 +33213,7 @@ function resolveTUIEntry() {
|
|
|
32816
33213
|
resolve7(process.cwd(), "../tui/src/main.tsx")
|
|
32817
33214
|
];
|
|
32818
33215
|
for (const path5 of candidates) {
|
|
32819
|
-
if (
|
|
33216
|
+
if (existsSync6(path5))
|
|
32820
33217
|
return path5;
|
|
32821
33218
|
}
|
|
32822
33219
|
throw new CLIError("Could not find TUI entry point. Ensure packages/tui/src/main.tsx exists.", 1 /* ERROR */);
|
|
@@ -33005,12 +33402,13 @@ EXAMPLES:
|
|
|
33005
33402
|
}
|
|
33006
33403
|
// src/services/setup/setup-service.ts
|
|
33007
33404
|
import {
|
|
33008
|
-
existsSync as
|
|
33405
|
+
existsSync as existsSync7,
|
|
33009
33406
|
mkdirSync as mkdirSync3,
|
|
33010
33407
|
readFileSync as readFileSync6,
|
|
33011
33408
|
statSync as statSync2,
|
|
33012
33409
|
writeFileSync as writeFileSync4
|
|
33013
33410
|
} from "fs";
|
|
33411
|
+
import { homedir as homedir4 } from "os";
|
|
33014
33412
|
import { join as join6 } from "path";
|
|
33015
33413
|
var ALL_WORKERS = [
|
|
33016
33414
|
"d1-worker",
|
|
@@ -33072,7 +33470,7 @@ class SetupService {
|
|
|
33072
33470
|
WEBHOOK_API_KEY_BINDING: this._randomHex(32),
|
|
33073
33471
|
TELEGRAM_INTERNAL_KEY_BINDING: internalKey
|
|
33074
33472
|
};
|
|
33075
|
-
if (!
|
|
33473
|
+
if (!existsSync7(KEYS_DIR)) {
|
|
33076
33474
|
mkdirSync3(KEYS_DIR, { recursive: true });
|
|
33077
33475
|
} else if (!statSync2(KEYS_DIR).isDirectory()) {
|
|
33078
33476
|
mkdirSync3(KEYS_DIR, { recursive: true });
|
|
@@ -33377,8 +33775,7 @@ class SetupService {
|
|
|
33377
33775
|
}
|
|
33378
33776
|
const fallbacks = [
|
|
33379
33777
|
"/usr/local/bin/wrangler",
|
|
33380
|
-
"
|
|
33381
|
-
join6(process.env.HOME || "~", ".bun", "bin", "wrangler")
|
|
33778
|
+
join6(homedir4(), ".bun", "bin", "wrangler")
|
|
33382
33779
|
];
|
|
33383
33780
|
for (const fb of fallbacks) {
|
|
33384
33781
|
try {
|
|
@@ -33451,7 +33848,7 @@ class SetupService {
|
|
|
33451
33848
|
step: "workers",
|
|
33452
33849
|
message: `${missing.length} worker(s) not cloned: ${missing.join(", ")}`
|
|
33453
33850
|
});
|
|
33454
|
-
if (!
|
|
33851
|
+
if (!existsSync7(gitmodulesPath)) {
|
|
33455
33852
|
this.onProgress({
|
|
33456
33853
|
type: "step-error",
|
|
33457
33854
|
step: "workers",
|
|
@@ -34052,7 +34449,7 @@ EXAMPLES:
|
|
|
34052
34449
|
const authOk = await tempSvc.checkAuth();
|
|
34053
34450
|
if (!authOk) {
|
|
34054
34451
|
authCheck.stop("Authentication failed");
|
|
34055
|
-
formatError2(new CLIError("Not authenticated with Cloudflare. Run 'wrangler login' first.", 1 /* ERROR
|
|
34452
|
+
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
34453
|
outro(theme.error("Setup aborted"));
|
|
34057
34454
|
process.exitCode = 1 /* ERROR */;
|
|
34058
34455
|
return;
|
|
@@ -34091,12 +34488,14 @@ EXAMPLES:
|
|
|
34091
34488
|
currentSpinner.stop(msg);
|
|
34092
34489
|
currentSpinner = null;
|
|
34093
34490
|
}));
|
|
34491
|
+
const totalTimer = startTimer();
|
|
34094
34492
|
const result = await setupSvc.runAll(opts);
|
|
34493
|
+
const totalMs = totalTimer.ms();
|
|
34095
34494
|
if (isInteractive) {
|
|
34096
|
-
log.step(
|
|
34495
|
+
log.step(`\u2500\u2500 Summary (total ${formatDuration3(totalMs)}) \u2500\u2500`);
|
|
34097
34496
|
const rows = result.steps.map((s) => ({
|
|
34098
34497
|
Step: s.step,
|
|
34099
|
-
Status: s.success ?
|
|
34498
|
+
Status: s.success ? formatBadge("ok", "DONE") : formatBadge("err", "FAIL"),
|
|
34100
34499
|
Message: s.message
|
|
34101
34500
|
}));
|
|
34102
34501
|
formatTable(rows, globalOpts);
|