@jango-blockchained/hoox-cli 0.5.6 → 0.5.8
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/dist/index.js +1373 -929
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -23437,6 +23437,9 @@ var init_schema_service = __esm(() => {
|
|
|
23437
23437
|
init_src();
|
|
23438
23438
|
});
|
|
23439
23439
|
|
|
23440
|
+
// src/index.ts
|
|
23441
|
+
import { readFileSync as readFileSync5 } from "fs";
|
|
23442
|
+
|
|
23440
23443
|
// ../../node_modules/.bun/commander@14.0.3/node_modules/commander/esm.mjs
|
|
23441
23444
|
var import__ = __toESM(require_commander(), 1);
|
|
23442
23445
|
var {
|
|
@@ -25147,6 +25150,52 @@ class CloudflareService {
|
|
|
25147
25150
|
}
|
|
25148
25151
|
return { ok: true, value: deployResult };
|
|
25149
25152
|
}
|
|
25153
|
+
async versionsList(workerName) {
|
|
25154
|
+
const result = await this.runWrangler([
|
|
25155
|
+
"versions",
|
|
25156
|
+
"list",
|
|
25157
|
+
"--name",
|
|
25158
|
+
workerName,
|
|
25159
|
+
"--json"
|
|
25160
|
+
]);
|
|
25161
|
+
if (!result.ok) {
|
|
25162
|
+
return result;
|
|
25163
|
+
}
|
|
25164
|
+
try {
|
|
25165
|
+
const parsed = JSON.parse(result.value);
|
|
25166
|
+
if (!Array.isArray(parsed)) {
|
|
25167
|
+
return {
|
|
25168
|
+
ok: false,
|
|
25169
|
+
error: `Expected array from wrangler versions list, got ${typeof parsed}`
|
|
25170
|
+
};
|
|
25171
|
+
}
|
|
25172
|
+
const versions2 = parsed.map((v2) => ({
|
|
25173
|
+
id: String(v2.id ?? ""),
|
|
25174
|
+
number: typeof v2.number === "number" ? v2.number : undefined,
|
|
25175
|
+
created_on: typeof v2.metadata === "object" && v2.metadata !== null ? String(v2.metadata.created_on ?? "") : undefined,
|
|
25176
|
+
author: typeof v2.metadata === "object" && v2.metadata !== null ? String(v2.metadata.author ?? "") : undefined,
|
|
25177
|
+
message: typeof v2.metadata === "object" && v2.metadata !== null ? String(v2.metadata.message ?? "") : undefined,
|
|
25178
|
+
source: typeof v2.metadata === "object" && v2.metadata !== null ? String(v2.metadata.source ?? "") : undefined
|
|
25179
|
+
}));
|
|
25180
|
+
return { ok: true, value: versions2 };
|
|
25181
|
+
} catch (err) {
|
|
25182
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
25183
|
+
return {
|
|
25184
|
+
ok: false,
|
|
25185
|
+
error: `Failed to parse versions list: ${message}`
|
|
25186
|
+
};
|
|
25187
|
+
}
|
|
25188
|
+
}
|
|
25189
|
+
async versionsRollback(workerName, versionId) {
|
|
25190
|
+
return this.runWrangler([
|
|
25191
|
+
"versions",
|
|
25192
|
+
"rollback",
|
|
25193
|
+
"--name",
|
|
25194
|
+
workerName,
|
|
25195
|
+
"--version-id",
|
|
25196
|
+
versionId
|
|
25197
|
+
]);
|
|
25198
|
+
}
|
|
25150
25199
|
async dev(workerPath, port) {
|
|
25151
25200
|
const devPort = port ?? 8787;
|
|
25152
25201
|
const resolvedPath = path.resolve(this.cwd, workerPath);
|
|
@@ -25291,6 +25340,27 @@ class CloudflareService {
|
|
|
25291
25340
|
return;
|
|
25292
25341
|
}
|
|
25293
25342
|
}
|
|
25343
|
+
// src/utils/error-handler.ts
|
|
25344
|
+
function withErrorHandling(handler, options) {
|
|
25345
|
+
return async (...args) => {
|
|
25346
|
+
try {
|
|
25347
|
+
await handler(...args);
|
|
25348
|
+
} catch (error51) {
|
|
25349
|
+
const service = options?.service ?? "cli";
|
|
25350
|
+
if (error51 instanceof CLIError) {
|
|
25351
|
+
formatError2(error51, options?.opts);
|
|
25352
|
+
process.exitCode = error51.code;
|
|
25353
|
+
} else if (error51 instanceof Error) {
|
|
25354
|
+
formatError2(`[${service}] ${error51.message}`, options?.opts);
|
|
25355
|
+
process.exitCode = 1 /* ERROR */;
|
|
25356
|
+
} else {
|
|
25357
|
+
formatError2(`[${service}] Unknown error: ${String(error51)}`, options?.opts);
|
|
25358
|
+
process.exitCode = -1 /* CommandFailed */;
|
|
25359
|
+
}
|
|
25360
|
+
}
|
|
25361
|
+
};
|
|
25362
|
+
}
|
|
25363
|
+
|
|
25294
25364
|
// src/commands/init/cli-provisioner.ts
|
|
25295
25365
|
class CLIProvisioner {
|
|
25296
25366
|
async provision(plan) {
|
|
@@ -25341,9 +25411,7 @@ class CLIProvisioner {
|
|
|
25341
25411
|
async check(plan) {
|
|
25342
25412
|
const created = [
|
|
25343
25413
|
...plan.d1Databases.map((d) => `D1:${d}`),
|
|
25344
|
-
...plan.kvNamespaces.map((k2) => `KV:${k2}`)
|
|
25345
|
-
...plan.r2Buckets.map((b2) => `R2:${b2}`),
|
|
25346
|
-
...plan.queues.map((q2) => `Queue:${q2}`)
|
|
25414
|
+
...plan.kvNamespaces.map((k2) => `KV:${k2}`)
|
|
25347
25415
|
];
|
|
25348
25416
|
return { success: true, created, errors: [] };
|
|
25349
25417
|
}
|
|
@@ -25483,18 +25551,11 @@ OPTIONS:
|
|
|
25483
25551
|
|
|
25484
25552
|
EXAMPLES:
|
|
25485
25553
|
hoox init Interactive wizard
|
|
25486
|
-
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(async (options) => {
|
|
25554
|
+
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) => {
|
|
25487
25555
|
const globalOpts = getFormatOptions(program2);
|
|
25488
25556
|
const isNonInteractive = Boolean(options.token && options.account);
|
|
25489
|
-
|
|
25490
|
-
|
|
25491
|
-
} catch (err) {
|
|
25492
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
25493
|
-
R2.error(message);
|
|
25494
|
-
formatError2(new CLIError(message, 1 /* ERROR */), globalOpts);
|
|
25495
|
-
process.exitCode = 1 /* ERROR */;
|
|
25496
|
-
}
|
|
25497
|
-
});
|
|
25557
|
+
await runInitCommand(options, globalOpts, isNonInteractive);
|
|
25558
|
+
}, { service: "init" }));
|
|
25498
25559
|
}
|
|
25499
25560
|
async function runInitCommand(options, globalOpts, isNonInteractive) {
|
|
25500
25561
|
if (isNonInteractive) {
|
|
@@ -26188,119 +26249,113 @@ OPTIONS:
|
|
|
26188
26249
|
EXAMPLES:
|
|
26189
26250
|
hoox dev start
|
|
26190
26251
|
hoox dev start --runtime native
|
|
26191
|
-
hoox dev start --runtime docker`).option("--runtime <native|docker>", "Choose dev runtime (overrides saved preference)").action(async (options) => {
|
|
26252
|
+
hoox dev start --runtime docker`).option("--runtime <native|docker>", "Choose dev runtime (overrides saved preference)").action(withErrorHandling(async (options) => {
|
|
26192
26253
|
const fmt = getFormatOptions(program2);
|
|
26193
|
-
|
|
26194
|
-
|
|
26195
|
-
|
|
26196
|
-
|
|
26197
|
-
|
|
26198
|
-
|
|
26199
|
-
|
|
26200
|
-
process.stdout.write(`
|
|
26254
|
+
const configService = new ConfigService;
|
|
26255
|
+
const prereqs = new PrerequisitesService;
|
|
26256
|
+
const docker = new DockerService;
|
|
26257
|
+
await configService.load();
|
|
26258
|
+
const wranglerCheck = await prereqs.checkWranglerVersion();
|
|
26259
|
+
if (wranglerCheck.outdated) {
|
|
26260
|
+
process.stdout.write(`
|
|
26201
26261
|
! wrangler is outdated (${wranglerCheck.current} < ${wranglerCheck.minimum})
|
|
26202
26262
|
` + ` Run \`bunx wrangler update\` to update.
|
|
26203
26263
|
` + ` Press Enter to continue or Ctrl+C to abort.
|
|
26204
26264
|
|
|
26205
26265
|
`);
|
|
26206
|
-
|
|
26207
|
-
|
|
26208
|
-
|
|
26209
|
-
|
|
26210
|
-
|
|
26211
|
-
|
|
26212
|
-
|
|
26213
|
-
|
|
26214
|
-
|
|
26215
|
-
|
|
26216
|
-
|
|
26217
|
-
|
|
26218
|
-
|
|
26219
|
-
|
|
26220
|
-
|
|
26221
|
-
|
|
26222
|
-
|
|
26223
|
-
|
|
26224
|
-
|
|
26225
|
-
|
|
26226
|
-
|
|
26227
|
-
|
|
26228
|
-
|
|
26229
|
-
|
|
26230
|
-
|
|
26231
|
-
|
|
26232
|
-
|
|
26233
|
-
|
|
26234
|
-
|
|
26235
|
-
|
|
26236
|
-
}
|
|
26266
|
+
}
|
|
26267
|
+
const dockerStatus = await docker.checkAvailability();
|
|
26268
|
+
const dockerViable = dockerStatus.docker && dockerStatus.compose;
|
|
26269
|
+
const composeExists = await docker.composeFileExists();
|
|
26270
|
+
let runtime = "native";
|
|
26271
|
+
if (options.runtime === "native" || options.runtime === "docker") {
|
|
26272
|
+
runtime = options.runtime;
|
|
26273
|
+
} else {
|
|
26274
|
+
const savedRuntime = configService.getDevRuntime();
|
|
26275
|
+
if (savedRuntime) {
|
|
26276
|
+
runtime = savedRuntime;
|
|
26277
|
+
} else if (dockerViable && composeExists) {
|
|
26278
|
+
const choice = await Ee({
|
|
26279
|
+
message: "Which runtime would you like to use?",
|
|
26280
|
+
options: [
|
|
26281
|
+
{
|
|
26282
|
+
value: "native",
|
|
26283
|
+
label: "Native (wrangler dev)",
|
|
26284
|
+
hint: "run locally with wrangler"
|
|
26285
|
+
},
|
|
26286
|
+
{
|
|
26287
|
+
value: "docker",
|
|
26288
|
+
label: "Docker",
|
|
26289
|
+
hint: "run via docker compose"
|
|
26290
|
+
}
|
|
26291
|
+
]
|
|
26292
|
+
});
|
|
26293
|
+
if (choice === "native" || choice === "docker") {
|
|
26294
|
+
runtime = choice;
|
|
26295
|
+
await configService.setDevRuntime(runtime);
|
|
26237
26296
|
}
|
|
26238
26297
|
}
|
|
26239
|
-
|
|
26240
|
-
|
|
26241
|
-
|
|
26298
|
+
}
|
|
26299
|
+
const validation = configService.validate();
|
|
26300
|
+
if (!validation.valid) {
|
|
26301
|
+
formatError2(new CLIError(`Invalid configuration:
|
|
26242
26302
|
${validation.errors.map((e2) => ` - ${e2}`).join(`
|
|
26243
26303
|
`)}`, 2 /* INVALID_USAGE */), fmt);
|
|
26244
|
-
|
|
26245
|
-
|
|
26246
|
-
|
|
26247
|
-
|
|
26248
|
-
|
|
26249
|
-
|
|
26250
|
-
|
|
26251
|
-
|
|
26252
|
-
|
|
26253
|
-
|
|
26254
|
-
}
|
|
26255
|
-
return;
|
|
26256
|
-
}
|
|
26257
|
-
const enabledWorkers = configService.listEnabledWorkers();
|
|
26258
|
-
if (enabledWorkers.length === 0) {
|
|
26259
|
-
formatError2(new CLIError("No enabled workers found in wrangler.jsonc. Enable at least one worker.", 2 /* INVALID_USAGE */), fmt);
|
|
26260
|
-
process.exitCode = 2 /* INVALID_USAGE */;
|
|
26261
|
-
return;
|
|
26262
|
-
}
|
|
26263
|
-
const LOCAL_PORTS = {
|
|
26264
|
-
hoox: 8787,
|
|
26265
|
-
"trade-worker": 8788,
|
|
26266
|
-
"d1-worker": 8789,
|
|
26267
|
-
"telegram-worker": 8790,
|
|
26268
|
-
"web3-wallet-worker": 8792
|
|
26269
|
-
};
|
|
26270
|
-
let nextFallbackPort = 8800;
|
|
26271
|
-
const cf = new CloudflareService;
|
|
26272
|
-
let started = 0;
|
|
26273
|
-
let failed = 0;
|
|
26274
|
-
for (const name of enabledWorkers) {
|
|
26275
|
-
const worker = configService.getWorker(name);
|
|
26276
|
-
if (!worker) {
|
|
26277
|
-
formatError2(`Worker "${name}" not found in config, skipping.`, fmt);
|
|
26278
|
-
failed++;
|
|
26279
|
-
continue;
|
|
26280
|
-
}
|
|
26281
|
-
const port = LOCAL_PORTS[name] ?? nextFallbackPort++;
|
|
26282
|
-
formatSuccess(`Starting worker "${name}" (${worker.path}) on port ${port}...`, fmt);
|
|
26283
|
-
const result = await cf.dev(worker.path, port);
|
|
26284
|
-
if (!result.ok) {
|
|
26285
|
-
formatError2(`Failed to start worker "${name}": ${result.error}`, fmt);
|
|
26286
|
-
failed++;
|
|
26287
|
-
} else {
|
|
26288
|
-
formatSuccess(`Worker "${name}" running on http://localhost:${result.value.port}`, fmt);
|
|
26289
|
-
started++;
|
|
26290
|
-
}
|
|
26304
|
+
process.exitCode = 2 /* INVALID_USAGE */;
|
|
26305
|
+
return;
|
|
26306
|
+
}
|
|
26307
|
+
if (runtime === "docker") {
|
|
26308
|
+
const profiles = ["workers"];
|
|
26309
|
+
formatSuccess(`Starting workers via Docker Compose (profiles: ${profiles.join(", ")})...`, fmt);
|
|
26310
|
+
const result = await docker.composeUp(profiles, false);
|
|
26311
|
+
if (!result.ok) {
|
|
26312
|
+
formatError2(new CLIError(result.error ?? "Docker compose failed", 1 /* ERROR */), fmt);
|
|
26313
|
+
process.exitCode = 1 /* ERROR */;
|
|
26291
26314
|
}
|
|
26292
|
-
|
|
26293
|
-
|
|
26315
|
+
return;
|
|
26316
|
+
}
|
|
26317
|
+
const enabledWorkers = configService.listEnabledWorkers();
|
|
26318
|
+
if (enabledWorkers.length === 0) {
|
|
26319
|
+
formatError2(new CLIError("No enabled workers found in wrangler.jsonc. Enable at least one worker.", 2 /* INVALID_USAGE */), fmt);
|
|
26320
|
+
process.exitCode = 2 /* INVALID_USAGE */;
|
|
26321
|
+
return;
|
|
26322
|
+
}
|
|
26323
|
+
const LOCAL_PORTS = {
|
|
26324
|
+
hoox: 8787,
|
|
26325
|
+
"trade-worker": 8788,
|
|
26326
|
+
"d1-worker": 8789,
|
|
26327
|
+
"telegram-worker": 8790,
|
|
26328
|
+
"web3-wallet-worker": 8792
|
|
26329
|
+
};
|
|
26330
|
+
let nextFallbackPort = 8800;
|
|
26331
|
+
const cf = new CloudflareService;
|
|
26332
|
+
let started = 0;
|
|
26333
|
+
let failed = 0;
|
|
26334
|
+
for (const name of enabledWorkers) {
|
|
26335
|
+
const worker = configService.getWorker(name);
|
|
26336
|
+
if (!worker) {
|
|
26337
|
+
formatError2(`Worker "${name}" not found in config, skipping.`, fmt);
|
|
26338
|
+
failed++;
|
|
26339
|
+
continue;
|
|
26294
26340
|
}
|
|
26295
|
-
|
|
26296
|
-
|
|
26341
|
+
const port = LOCAL_PORTS[name] ?? nextFallbackPort++;
|
|
26342
|
+
formatSuccess(`Starting worker "${name}" (${worker.path}) on port ${port}...`, fmt);
|
|
26343
|
+
const result = await cf.dev(worker.path, port);
|
|
26344
|
+
if (!result.ok) {
|
|
26345
|
+
formatError2(`Failed to start worker "${name}": ${result.error}`, fmt);
|
|
26346
|
+
failed++;
|
|
26347
|
+
} else {
|
|
26348
|
+
formatSuccess(`Worker "${name}" running on http://localhost:${result.value.port}`, fmt);
|
|
26349
|
+
started++;
|
|
26297
26350
|
}
|
|
26298
|
-
}
|
|
26299
|
-
|
|
26300
|
-
|
|
26351
|
+
}
|
|
26352
|
+
if (started > 0) {
|
|
26353
|
+
formatSuccess(`Started ${started} worker(s) \u2014 http://localhost:8787 (and adjacent ports)`, fmt);
|
|
26354
|
+
}
|
|
26355
|
+
if (failed > 0) {
|
|
26301
26356
|
process.exitCode = 1 /* ERROR */;
|
|
26302
26357
|
}
|
|
26303
|
-
});
|
|
26358
|
+
}, { service: "dev" }));
|
|
26304
26359
|
devCmd.command("worker <name>").summary("Start a single worker for local development").description(`Start a specific worker using wrangler dev.
|
|
26305
26360
|
|
|
26306
26361
|
ARGUMENTS:
|
|
@@ -26313,39 +26368,33 @@ OPTIONS:
|
|
|
26313
26368
|
EXAMPLES:
|
|
26314
26369
|
hoox dev worker trade-worker
|
|
26315
26370
|
hoox dev worker agent-worker --port 8788
|
|
26316
|
-
hoox dev worker hoox --runtime docker`).option("--port <port>", "Dev server port (default: 8787)", (v2) => parseInt(v2, 10)).option("--runtime <native|docker>", "Choose dev runtime (overrides saved preference)").action(async (name, options) => {
|
|
26371
|
+
hoox dev worker hoox --runtime docker`).option("--port <port>", "Dev server port (default: 8787)", (v2) => parseInt(v2, 10)).option("--runtime <native|docker>", "Choose dev runtime (overrides saved preference)").action(withErrorHandling(async (name, options) => {
|
|
26317
26372
|
const fmt = getFormatOptions(program2);
|
|
26318
|
-
|
|
26319
|
-
|
|
26320
|
-
|
|
26321
|
-
|
|
26322
|
-
|
|
26323
|
-
formatError2(new CLIError(`Worker "${name}" not found in wrangler.jsonc.
|
|
26373
|
+
const configService = new ConfigService;
|
|
26374
|
+
await configService.load();
|
|
26375
|
+
const worker = configService.getWorker(name);
|
|
26376
|
+
if (!worker) {
|
|
26377
|
+
formatError2(new CLIError(`Worker "${name}" not found in wrangler.jsonc.
|
|
26324
26378
|
` + `Available workers: ${configService.listWorkers().join(", ")}`, 2 /* INVALID_USAGE */), fmt);
|
|
26325
|
-
|
|
26326
|
-
|
|
26327
|
-
|
|
26328
|
-
|
|
26329
|
-
|
|
26330
|
-
|
|
26331
|
-
|
|
26332
|
-
|
|
26333
|
-
|
|
26334
|
-
|
|
26335
|
-
|
|
26336
|
-
|
|
26337
|
-
|
|
26338
|
-
|
|
26339
|
-
process.exitCode = 1 /* ERROR */;
|
|
26340
|
-
return;
|
|
26341
|
-
}
|
|
26342
|
-
formatSuccess(`Worker "${name}" running on http://localhost:${result.value.port}`, fmt);
|
|
26343
|
-
} catch (err) {
|
|
26344
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
26345
|
-
formatError2(message, fmt);
|
|
26379
|
+
process.exitCode = 2 /* INVALID_USAGE */;
|
|
26380
|
+
return;
|
|
26381
|
+
}
|
|
26382
|
+
if (!worker.enabled) {
|
|
26383
|
+
formatError2(new CLIError(`Worker "${name}" is disabled. Enable it with the config command first.`, 2 /* INVALID_USAGE */), fmt);
|
|
26384
|
+
process.exitCode = 2 /* INVALID_USAGE */;
|
|
26385
|
+
return;
|
|
26386
|
+
}
|
|
26387
|
+
const port = options.port ?? 8787;
|
|
26388
|
+
formatSuccess(`Starting worker "${name}" (${worker.path}) on port ${port}...`, fmt);
|
|
26389
|
+
const cf = new CloudflareService;
|
|
26390
|
+
const result = await cf.dev(worker.path, port);
|
|
26391
|
+
if (!result.ok) {
|
|
26392
|
+
formatError2(new CLIError(`Failed to start worker "${name}": ${result.error}`, 1 /* ERROR */), fmt);
|
|
26346
26393
|
process.exitCode = 1 /* ERROR */;
|
|
26394
|
+
return;
|
|
26347
26395
|
}
|
|
26348
|
-
|
|
26396
|
+
formatSuccess(`Worker "${name}" running on http://localhost:${result.value.port}`, fmt);
|
|
26397
|
+
}, { service: "dev" }));
|
|
26349
26398
|
devCmd.command("dashboard").summary("Start the Next.js dashboard dev server").description(`Start the Hoox dashboard development server using Next.js.
|
|
26350
26399
|
|
|
26351
26400
|
The dashboard runs on http://localhost:3000 with hot-reloading enabled.
|
|
@@ -26355,29 +26404,23 @@ OPTIONS:
|
|
|
26355
26404
|
|
|
26356
26405
|
EXAMPLES:
|
|
26357
26406
|
hoox dev dashboard
|
|
26358
|
-
hoox dev dashboard --runtime docker`).option("--runtime <native|docker>", "Choose dev runtime (overrides saved preference)").action(async () => {
|
|
26407
|
+
hoox dev dashboard --runtime docker`).option("--runtime <native|docker>", "Choose dev runtime (overrides saved preference)").action(withErrorHandling(async () => {
|
|
26359
26408
|
const fmt = getFormatOptions(program2);
|
|
26360
|
-
|
|
26361
|
-
|
|
26362
|
-
|
|
26363
|
-
|
|
26364
|
-
|
|
26365
|
-
|
|
26366
|
-
return;
|
|
26367
|
-
}
|
|
26368
|
-
formatSuccess("Starting Next.js dashboard dev server...", fmt);
|
|
26369
|
-
Bun.spawn(["bun", "run", "dev"], {
|
|
26370
|
-
cwd: dashboardPath,
|
|
26371
|
-
stdout: "inherit",
|
|
26372
|
-
stderr: "inherit"
|
|
26373
|
-
});
|
|
26374
|
-
formatSuccess("Dashboard starting on http://localhost:3000", fmt);
|
|
26375
|
-
} catch (err) {
|
|
26376
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
26377
|
-
formatError2(message, fmt);
|
|
26378
|
-
process.exitCode = 1 /* ERROR */;
|
|
26409
|
+
const dashboardPath = path2.resolve(process.cwd(), "workers/dashboard");
|
|
26410
|
+
const dashboardDir = Bun.file(dashboardPath);
|
|
26411
|
+
if (!await dashboardDir.exists()) {
|
|
26412
|
+
formatError2(new CLIError(`Dashboard directory not found: ${dashboardPath}`, 2 /* INVALID_USAGE */), fmt);
|
|
26413
|
+
process.exitCode = 2 /* INVALID_USAGE */;
|
|
26414
|
+
return;
|
|
26379
26415
|
}
|
|
26380
|
-
|
|
26416
|
+
formatSuccess("Starting Next.js dashboard dev server...", fmt);
|
|
26417
|
+
Bun.spawn(["bun", "run", "dev"], {
|
|
26418
|
+
cwd: dashboardPath,
|
|
26419
|
+
stdout: "inherit",
|
|
26420
|
+
stderr: "inherit"
|
|
26421
|
+
});
|
|
26422
|
+
formatSuccess("Dashboard starting on http://localhost:3000", fmt);
|
|
26423
|
+
}, { service: "dev" }));
|
|
26381
26424
|
}
|
|
26382
26425
|
// src/commands/deploy/deploy-command.ts
|
|
26383
26426
|
init_config2();
|
|
@@ -26997,7 +27040,7 @@ ${theme.heading("Deploying Dashboard")}
|
|
|
26997
27040
|
}
|
|
26998
27041
|
return {
|
|
26999
27042
|
worker: "dashboard",
|
|
27000
|
-
url: urlMatch?.[0]
|
|
27043
|
+
url: urlMatch?.[0],
|
|
27001
27044
|
success: true,
|
|
27002
27045
|
size: sizeMatch ? `${sizeMatch[1]} ${sizeMatch[2]}` : undefined,
|
|
27003
27046
|
startupTime: startupMatch ? `${startupMatch[1]} ms` : undefined
|
|
@@ -27034,7 +27077,7 @@ ${theme.heading("Deploying Dashboard")}
|
|
|
27034
27077
|
}
|
|
27035
27078
|
return {
|
|
27036
27079
|
worker: "dashboard",
|
|
27037
|
-
url: urlMatch?.[0]
|
|
27080
|
+
url: urlMatch?.[0],
|
|
27038
27081
|
success: true
|
|
27039
27082
|
};
|
|
27040
27083
|
}
|
|
@@ -27116,12 +27159,9 @@ async function doUpdateInternalUrls(fmt) {
|
|
|
27116
27159
|
const global = config2.getGlobal();
|
|
27117
27160
|
const prefix = global.subdomain_prefix ?? "hoox";
|
|
27118
27161
|
const workers = config2.listEnabledWorkers();
|
|
27119
|
-
|
|
27120
|
-
if (!existsSync2(filePath)) {
|
|
27121
|
-
filePath = resolve(process.cwd(), "workers", "dashboard", "wrangler.jsonc");
|
|
27122
|
-
}
|
|
27162
|
+
const filePath = resolve(process.cwd(), "workers", "dashboard", "wrangler.jsonc");
|
|
27123
27163
|
if (!existsSync2(filePath)) {
|
|
27124
|
-
formatError2(new CLIError("Dashboard wrangler.jsonc not found
|
|
27164
|
+
formatError2(new CLIError("Dashboard wrangler.jsonc not found at workers/dashboard/wrangler.jsonc", 1 /* ERROR */), fmt);
|
|
27125
27165
|
process.exitCode = 1 /* ERROR */;
|
|
27126
27166
|
return;
|
|
27127
27167
|
}
|
|
@@ -27204,6 +27244,100 @@ ${setCount} key(s) set, ${errorCount} error(s)
|
|
|
27204
27244
|
process.exitCode = 1 /* ERROR */;
|
|
27205
27245
|
}
|
|
27206
27246
|
}
|
|
27247
|
+
async function doVersionHistory(worker, fmt) {
|
|
27248
|
+
const s = vt();
|
|
27249
|
+
s.start(`Fetching deployment history for ${worker}...`);
|
|
27250
|
+
const cf = new CloudflareService;
|
|
27251
|
+
const result = await cf.versionsList(worker);
|
|
27252
|
+
if (!result.ok) {
|
|
27253
|
+
s.stop(`${theme.error(icons.error)} Failed to fetch history`);
|
|
27254
|
+
formatError2(new CLIError(result.error, 1 /* ERROR */), fmt);
|
|
27255
|
+
process.exitCode = 1 /* ERROR */;
|
|
27256
|
+
return;
|
|
27257
|
+
}
|
|
27258
|
+
const versions2 = result.value;
|
|
27259
|
+
s.stop(`${theme.success(icons.success)} ${versions2.length} version(s) found`);
|
|
27260
|
+
if (versions2.length === 0) {
|
|
27261
|
+
if (!fmt.quiet) {
|
|
27262
|
+
process.stdout.write(`${theme.dim(`No deployment history found.
|
|
27263
|
+
`)}`);
|
|
27264
|
+
}
|
|
27265
|
+
return;
|
|
27266
|
+
}
|
|
27267
|
+
const rows = versions2.map((v2) => ({
|
|
27268
|
+
"Version ID": v2.id,
|
|
27269
|
+
Number: v2.number !== undefined ? String(v2.number) : "-",
|
|
27270
|
+
Created: v2.created_on ? new Date(v2.created_on).toLocaleString() : "-",
|
|
27271
|
+
Author: v2.author ?? "-",
|
|
27272
|
+
Source: v2.source ?? "-"
|
|
27273
|
+
}));
|
|
27274
|
+
formatTable(rows, fmt);
|
|
27275
|
+
}
|
|
27276
|
+
async function doVersionRollback(worker, version2, fmt, yes) {
|
|
27277
|
+
const cf = new CloudflareService;
|
|
27278
|
+
let targetVersion = version2;
|
|
27279
|
+
if (!targetVersion) {
|
|
27280
|
+
const s2 = vt();
|
|
27281
|
+
s2.start(`Fetching recent versions for ${worker}...`);
|
|
27282
|
+
const result = await cf.versionsList(worker);
|
|
27283
|
+
if (!result.ok) {
|
|
27284
|
+
s2.stop(`${theme.error(icons.error)} Failed to fetch versions`);
|
|
27285
|
+
formatError2(new CLIError(result.error, 1 /* ERROR */), fmt);
|
|
27286
|
+
process.exitCode = 1 /* ERROR */;
|
|
27287
|
+
return;
|
|
27288
|
+
}
|
|
27289
|
+
const versions2 = result.value;
|
|
27290
|
+
if (versions2.length === 0) {
|
|
27291
|
+
s2.stop(`${theme.dim("No versions found")}`);
|
|
27292
|
+
process.stdout.write(`${theme.dim(`No deployment history found for this worker.
|
|
27293
|
+
`)}`);
|
|
27294
|
+
return;
|
|
27295
|
+
}
|
|
27296
|
+
s2.stop(`${theme.success(icons.success)} Versions fetched`);
|
|
27297
|
+
const recent = versions2.slice(0, 5);
|
|
27298
|
+
const selected = await Ee({
|
|
27299
|
+
message: `Select a version to roll ${worker} back to:`,
|
|
27300
|
+
options: recent.map((v2) => ({
|
|
27301
|
+
value: v2.id,
|
|
27302
|
+
label: `v${v2.number ?? "?"} ${v2.id.slice(0, 8)}... ${v2.created_on ? new Date(v2.created_on).toLocaleDateString() : "?"}`,
|
|
27303
|
+
hint: v2.source ? `via ${v2.source}` : undefined
|
|
27304
|
+
}))
|
|
27305
|
+
});
|
|
27306
|
+
if (R(selected)) {
|
|
27307
|
+
process.stdout.write(`${theme.dim(`Rollback cancelled.
|
|
27308
|
+
`)}`);
|
|
27309
|
+
return;
|
|
27310
|
+
}
|
|
27311
|
+
targetVersion = selected;
|
|
27312
|
+
}
|
|
27313
|
+
if (!yes) {
|
|
27314
|
+
const confirmed = await le({
|
|
27315
|
+
message: `Roll back ${worker} to version ${targetVersion}? This will replace the current deployment.`
|
|
27316
|
+
});
|
|
27317
|
+
if (R(confirmed) || !confirmed) {
|
|
27318
|
+
process.stdout.write(`${theme.dim(`Rollback cancelled.
|
|
27319
|
+
`)}`);
|
|
27320
|
+
return;
|
|
27321
|
+
}
|
|
27322
|
+
}
|
|
27323
|
+
const s = vt();
|
|
27324
|
+
s.start(`Rolling back ${worker} to version ${targetVersion}...`);
|
|
27325
|
+
const rollResult = await cf.versionsRollback(worker, targetVersion);
|
|
27326
|
+
if (!rollResult.ok) {
|
|
27327
|
+
s.stop(`${theme.error(icons.error)} Rollback failed`);
|
|
27328
|
+
formatError2(new CLIError(rollResult.error, 1 /* ERROR */), fmt);
|
|
27329
|
+
process.exitCode = 1 /* ERROR */;
|
|
27330
|
+
return;
|
|
27331
|
+
}
|
|
27332
|
+
s.stop(`${theme.success(icons.success)} ${worker} rolled back to ${targetVersion.slice(0, 8)}...`);
|
|
27333
|
+
if (rollResult.value && !fmt.quiet) {
|
|
27334
|
+
const snippet = rollResult.value.split(`
|
|
27335
|
+
`).find((l) => l.trim())?.trim().slice(0, 120);
|
|
27336
|
+
if (snippet) {
|
|
27337
|
+
R2.step(` ${theme.dim("Output:")} ${snippet}`);
|
|
27338
|
+
}
|
|
27339
|
+
}
|
|
27340
|
+
}
|
|
27207
27341
|
function registerDeployCommand(program2) {
|
|
27208
27342
|
const deployCmd = program2.command("deploy").summary("Deploy workers and/or dashboard to Cloudflare Workers").description(`Deploy your Hoox trading system to Cloudflare's edge network.
|
|
27209
27343
|
|
|
@@ -27234,19 +27368,43 @@ EXAMPLES:
|
|
|
27234
27368
|
hoox deploy all
|
|
27235
27369
|
hoox deploy all --env production
|
|
27236
27370
|
hoox deploy all --rebuild
|
|
27237
|
-
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").action(async (options) => {
|
|
27238
|
-
const
|
|
27239
|
-
|
|
27240
|
-
|
|
27241
|
-
|
|
27242
|
-
const
|
|
27243
|
-
|
|
27244
|
-
|
|
27245
|
-
|
|
27246
|
-
|
|
27247
|
-
|
|
27371
|
+
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) => {
|
|
27372
|
+
const configService = new ConfigService;
|
|
27373
|
+
await configService.load();
|
|
27374
|
+
if (options.dryRun) {
|
|
27375
|
+
const enabled = configService.listEnabledWorkers();
|
|
27376
|
+
const workers = DEPLOY_ORDER.filter((w3) => w3 !== "dashboard" && enabled.includes(w3));
|
|
27377
|
+
const unknown2 = enabled.filter((w3) => w3 !== "dashboard" && !DEPLOY_ORDER.includes(w3));
|
|
27378
|
+
const ordered = [...workers, ...unknown2];
|
|
27379
|
+
process.stdout.write(`
|
|
27380
|
+
${theme.heading("Deployment Plan (dry-run)")}
|
|
27381
|
+
`);
|
|
27382
|
+
process.stdout.write(`${theme.dim("\u2500".repeat(50))}
|
|
27383
|
+
`);
|
|
27384
|
+
process.stdout.write(`Workers to deploy (${ordered.length + unknown2.length}):
|
|
27385
|
+
`);
|
|
27386
|
+
for (const w3 of [...ordered, ...unknown2]) {
|
|
27387
|
+
process.stdout.write(` ${theme.dim("\u25CB")} ${w3}
|
|
27388
|
+
`);
|
|
27389
|
+
}
|
|
27390
|
+
const buildInfo = getDashboardBuildInfo("workers/dashboard");
|
|
27391
|
+
if (buildInfo.exists) {
|
|
27392
|
+
process.stdout.write(`
|
|
27393
|
+
Dashboard: ${buildInfo.age ? `build exists (${buildInfo.age})` : "build exists"}
|
|
27394
|
+
`);
|
|
27395
|
+
} else {
|
|
27396
|
+
process.stdout.write(`
|
|
27397
|
+
Dashboard: no build found (will be built)
|
|
27398
|
+
`);
|
|
27399
|
+
}
|
|
27400
|
+
process.stdout.write(`
|
|
27401
|
+
${theme.dim("Run without --dry-run to deploy.")}
|
|
27402
|
+
`);
|
|
27403
|
+
return;
|
|
27248
27404
|
}
|
|
27249
|
-
|
|
27405
|
+
const cf = new CloudflareService;
|
|
27406
|
+
await deployAll(configService, cf, options.env, options.rebuild ?? false, options.auto ?? false);
|
|
27407
|
+
}, { service: "deploy" }));
|
|
27250
27408
|
deployCmd.command("workers").summary("Deploy all enabled workers to Cloudflare").description(`Deploy all enabled workers to Cloudflare Workers.
|
|
27251
27409
|
|
|
27252
27410
|
Workers are deployed in the correct order based on their dependencies. This command skips the dashboard deployment.
|
|
@@ -27256,81 +27414,74 @@ OPTIONS:
|
|
|
27256
27414
|
|
|
27257
27415
|
EXAMPLES:
|
|
27258
27416
|
hoox deploy workers
|
|
27259
|
-
hoox deploy workers --env staging`).option("--env <env>", "Cloudflare environment (e.g. production, staging)").action(async (options) => {
|
|
27260
|
-
|
|
27261
|
-
|
|
27262
|
-
|
|
27263
|
-
|
|
27264
|
-
|
|
27265
|
-
|
|
27266
|
-
|
|
27267
|
-
|
|
27268
|
-
|
|
27269
|
-
|
|
27270
|
-
process.stdout.write(`${theme.dim(`No enabled workers to deploy
|
|
27417
|
+
hoox deploy workers --env staging`).option("--env <env>", "Cloudflare environment (e.g. production, staging)").action(withErrorHandling(async (options) => {
|
|
27418
|
+
const configService = new ConfigService;
|
|
27419
|
+
await configService.load();
|
|
27420
|
+
const cf = new CloudflareService;
|
|
27421
|
+
const enabled = configService.listEnabledWorkers();
|
|
27422
|
+
const workers = DEPLOY_ORDER.filter((w3) => w3 !== "dashboard" && enabled.includes(w3));
|
|
27423
|
+
const unknown2 = enabled.filter((w3) => w3 !== "dashboard" && !DEPLOY_ORDER.includes(w3));
|
|
27424
|
+
const ordered = [...workers, ...unknown2];
|
|
27425
|
+
const results = [];
|
|
27426
|
+
if (ordered.length === 0) {
|
|
27427
|
+
process.stdout.write(`${theme.dim(`No enabled workers to deploy
|
|
27271
27428
|
`)}`);
|
|
27272
|
-
|
|
27273
|
-
|
|
27274
|
-
|
|
27429
|
+
return;
|
|
27430
|
+
}
|
|
27431
|
+
process.stdout.write(`
|
|
27275
27432
|
${theme.heading("Deploying Workers")}
|
|
27276
27433
|
`);
|
|
27277
|
-
|
|
27434
|
+
process.stdout.write(`${theme.dim("\u2500".repeat(60))}
|
|
27278
27435
|
`);
|
|
27279
|
-
|
|
27280
|
-
|
|
27436
|
+
for (const name of ordered) {
|
|
27437
|
+
process.stdout.write(`${theme.dim("\u25CB")} ${name.padEnd(25)} pending
|
|
27281
27438
|
`);
|
|
27282
|
-
|
|
27283
|
-
|
|
27439
|
+
}
|
|
27440
|
+
process.stdout.write(`${theme.dim("\u2500".repeat(60))}
|
|
27284
27441
|
|
|
27285
27442
|
`);
|
|
27286
|
-
|
|
27287
|
-
|
|
27288
|
-
|
|
27289
|
-
|
|
27290
|
-
|
|
27291
|
-
|
|
27292
|
-
|
|
27293
|
-
|
|
27294
|
-
|
|
27295
|
-
`);
|
|
27296
|
-
if (result.size)
|
|
27297
|
-
process.stdout.write(` ${theme.dim("Size:")} ${result.size}
|
|
27443
|
+
for (const name of ordered) {
|
|
27444
|
+
const s = vt();
|
|
27445
|
+
s.start(`Deploying ${name}...`);
|
|
27446
|
+
const result = await deploySingle(configService, cf, name, options.env);
|
|
27447
|
+
results.push(result);
|
|
27448
|
+
if (result.success) {
|
|
27449
|
+
s.stop(`${theme.success(icons.success)} ${name} deployed`);
|
|
27450
|
+
if (result.url)
|
|
27451
|
+
process.stdout.write(` ${theme.dim("URL:")} ${result.url}
|
|
27298
27452
|
`);
|
|
27299
|
-
|
|
27300
|
-
|
|
27453
|
+
if (result.size)
|
|
27454
|
+
process.stdout.write(` ${theme.dim("Size:")} ${result.size}
|
|
27301
27455
|
`);
|
|
27302
|
-
|
|
27303
|
-
|
|
27456
|
+
if (result.startupTime)
|
|
27457
|
+
process.stdout.write(` ${theme.dim("Startup:")} ${result.startupTime}
|
|
27304
27458
|
`);
|
|
27305
|
-
|
|
27306
|
-
|
|
27459
|
+
if (result.versionId)
|
|
27460
|
+
process.stdout.write(` ${theme.dim("Version:")} ${result.versionId.slice(0, 8)}...
|
|
27307
27461
|
`);
|
|
27308
|
-
|
|
27309
|
-
|
|
27310
|
-
s.stop(`${theme.error(icons.error)} ${name} failed`);
|
|
27311
|
-
if (result.error)
|
|
27312
|
-
process.stdout.write(` ${theme.error("Error:")} ${result.error}
|
|
27462
|
+
if (!result.url && !result.size && !result.startupTime && !result.versionId && result.rawOutput) {
|
|
27463
|
+
process.stdout.write(` ${theme.dim("Output:")} ${result.rawOutput.slice(0, 80)}
|
|
27313
27464
|
`);
|
|
27314
27465
|
}
|
|
27466
|
+
} else {
|
|
27467
|
+
s.stop(`${theme.error(icons.error)} ${name} failed`);
|
|
27468
|
+
if (result.error)
|
|
27469
|
+
process.stdout.write(` ${theme.error("Error:")} ${result.error}
|
|
27470
|
+
`);
|
|
27315
27471
|
}
|
|
27316
|
-
|
|
27317
|
-
|
|
27318
|
-
|
|
27472
|
+
}
|
|
27473
|
+
const succeeded = results.filter((r) => r.success).length;
|
|
27474
|
+
const failed = results.filter((r) => !r.success).length;
|
|
27475
|
+
process.stdout.write(`
|
|
27319
27476
|
${theme.heading("Summary:")} ${succeeded}/${ordered.length} deployed`);
|
|
27320
|
-
|
|
27321
|
-
|
|
27477
|
+
if (failed > 0)
|
|
27478
|
+
process.stdout.write(` ${theme.error(`(${failed} failed)`)}
|
|
27322
27479
|
`);
|
|
27323
|
-
|
|
27324
|
-
|
|
27480
|
+
else
|
|
27481
|
+
process.stdout.write(` ${theme.success(" \u2713")}
|
|
27325
27482
|
|
|
27326
27483
|
`);
|
|
27327
|
-
|
|
27328
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
27329
|
-
process.stdout.write(`${theme.error(`Error: ${message}`)}
|
|
27330
|
-
`);
|
|
27331
|
-
process.exitCode = 1 /* ERROR */;
|
|
27332
|
-
}
|
|
27333
|
-
});
|
|
27484
|
+
}, { service: "deploy" }));
|
|
27334
27485
|
deployCmd.command("worker <name>").summary("Deploy a single worker by name").description(`Deploy a specific worker to Cloudflare Workers.
|
|
27335
27486
|
|
|
27336
27487
|
ARGUMENTS:
|
|
@@ -27341,26 +27492,20 @@ OPTIONS:
|
|
|
27341
27492
|
|
|
27342
27493
|
EXAMPLES:
|
|
27343
27494
|
hoox deploy worker trade-worker
|
|
27344
|
-
hoox deploy worker agent-worker --env production`).option("--env <env>", "Cloudflare environment (e.g. production, staging)").action(async (name, options) => {
|
|
27495
|
+
hoox deploy worker agent-worker --env production`).option("--env <env>", "Cloudflare environment (e.g. production, staging)").action(withErrorHandling(async (name, options) => {
|
|
27345
27496
|
const fmt = getFormatOptions(program2);
|
|
27346
|
-
|
|
27347
|
-
|
|
27348
|
-
|
|
27349
|
-
|
|
27350
|
-
|
|
27351
|
-
|
|
27352
|
-
|
|
27353
|
-
|
|
27354
|
-
}
|
|
27355
|
-
formatError2(new CLIError(`Failed to deploy "${name}": ${result.error}`, 1 /* ERROR */), fmt);
|
|
27356
|
-
process.exitCode = 1 /* ERROR */;
|
|
27357
|
-
}
|
|
27358
|
-
} catch (err) {
|
|
27359
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
27360
|
-
formatError2(message, fmt);
|
|
27497
|
+
const configService = new ConfigService;
|
|
27498
|
+
await configService.load();
|
|
27499
|
+
const cf = new CloudflareService;
|
|
27500
|
+
const result = await deploySingle(configService, cf, name, options.env);
|
|
27501
|
+
if (result.success) {
|
|
27502
|
+
const url2 = result.url ? ` \u2014 ${result.url}` : "";
|
|
27503
|
+
formatSuccess(`Deployed ${name}${url2}`, fmt);
|
|
27504
|
+
} else {
|
|
27505
|
+
formatError2(new CLIError(`Failed to deploy "${name}": ${result.error}`, 1 /* ERROR */), fmt);
|
|
27361
27506
|
process.exitCode = 1 /* ERROR */;
|
|
27362
27507
|
}
|
|
27363
|
-
});
|
|
27508
|
+
}, { service: "deploy" }));
|
|
27364
27509
|
deployCmd.command("dashboard").summary("Build and deploy the Next.js dashboard").description(`Build and deploy the Hoox dashboard to Cloudflare Workers.
|
|
27365
27510
|
|
|
27366
27511
|
The dashboard is built using OpenNext which converts Next.js to Cloudflare Workers format. Before deploying, the CLI checks for an existing build and prompts you to rebuild or use the existing build.
|
|
@@ -27370,24 +27515,18 @@ OPTIONS:
|
|
|
27370
27515
|
|
|
27371
27516
|
EXAMPLES:
|
|
27372
27517
|
hoox deploy dashboard Interactive: choose to rebuild or use existing
|
|
27373
|
-
hoox deploy dashboard --rebuild Force rebuild before deploying`).option("--rebuild", "Force rebuild of dashboard before deploying").action(async (options) => {
|
|
27518
|
+
hoox deploy dashboard --rebuild Force rebuild before deploying`).option("--rebuild", "Force rebuild of dashboard before deploying").action(withErrorHandling(async (options) => {
|
|
27374
27519
|
const fmt = getFormatOptions(program2);
|
|
27375
|
-
|
|
27376
|
-
|
|
27377
|
-
|
|
27378
|
-
|
|
27379
|
-
|
|
27380
|
-
|
|
27381
|
-
}
|
|
27382
|
-
formatError2(new CLIError(`Dashboard deployment failed: ${result.error}`, 1 /* ERROR */), fmt);
|
|
27383
|
-
process.exitCode = 1 /* ERROR */;
|
|
27384
|
-
}
|
|
27385
|
-
} catch (err) {
|
|
27386
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
27387
|
-
formatError2(message, fmt);
|
|
27520
|
+
const cf = new CloudflareService;
|
|
27521
|
+
const result = await deployDashboard(cf, options.rebuild);
|
|
27522
|
+
if (result.success) {
|
|
27523
|
+
const url2 = result.url ? ` \u2014 ${result.url}` : "";
|
|
27524
|
+
formatSuccess(`Dashboard deployed${url2}`, fmt);
|
|
27525
|
+
} else {
|
|
27526
|
+
formatError2(new CLIError(`Dashboard deployment failed: ${result.error}`, 1 /* ERROR */), fmt);
|
|
27388
27527
|
process.exitCode = 1 /* ERROR */;
|
|
27389
27528
|
}
|
|
27390
|
-
});
|
|
27529
|
+
}, { service: "deploy" }));
|
|
27391
27530
|
deployCmd.command("telegram-webhook").summary("Set Telegram bot webhook (post-deploy step)").description(`Configure the Telegram bot webhook after deploying telegram-worker.
|
|
27392
27531
|
|
|
27393
27532
|
Calls the Telegram Bot API to set the webhook URL.
|
|
@@ -27403,30 +27542,62 @@ Use --token and --secret-token to override.
|
|
|
27403
27542
|
EXAMPLES:
|
|
27404
27543
|
hoox deploy telegram-webhook
|
|
27405
27544
|
hoox deploy telegram-webhook --token 123456:ABC-DEF1234
|
|
27406
|
-
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(async (options) => {
|
|
27545
|
+
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) => {
|
|
27407
27546
|
const fmt = getFormatOptions(program2);
|
|
27408
27547
|
await doTelegramWebhook(fmt, options.token, options.secretToken, options.subdomain);
|
|
27409
|
-
});
|
|
27548
|
+
}, { service: "deploy" }));
|
|
27410
27549
|
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.
|
|
27411
27550
|
|
|
27412
27551
|
This is a post-deployment step that ensures the dashboard has correct
|
|
27413
27552
|
service binding URLs for all workers.
|
|
27414
27553
|
|
|
27415
27554
|
EXAMPLES:
|
|
27416
|
-
hoox deploy update-internal-urls`).action(async () => {
|
|
27555
|
+
hoox deploy update-internal-urls`).action(withErrorHandling(async () => {
|
|
27417
27556
|
const fmt = getFormatOptions(program2);
|
|
27418
27557
|
await doUpdateInternalUrls(fmt);
|
|
27419
|
-
});
|
|
27558
|
+
}, { service: "deploy" }));
|
|
27420
27559
|
deployCmd.command("kv-config").summary("Apply KV manifest keys post-deployment").description(`Apply the KV manifest key-value pairs after deploying workers.
|
|
27421
27560
|
|
|
27422
27561
|
Sets all KV keys from the manifest to their default values.
|
|
27423
27562
|
This post-deployment step initializes the CONFIG_KV namespace.
|
|
27424
27563
|
|
|
27425
27564
|
EXAMPLES:
|
|
27426
|
-
hoox deploy kv-config`).action(async () => {
|
|
27565
|
+
hoox deploy kv-config`).action(withErrorHandling(async () => {
|
|
27427
27566
|
const fmt = getFormatOptions(program2);
|
|
27428
27567
|
await doKvConfig(fmt);
|
|
27429
|
-
});
|
|
27568
|
+
}, { service: "deploy" }));
|
|
27569
|
+
deployCmd.command("history <worker>").summary("Show deployment version history for a worker").description(`Display the deployment version history for a Cloudflare Worker.
|
|
27570
|
+
|
|
27571
|
+
Shows version ID, version number, creation date, author, and source for each deployment.
|
|
27572
|
+
|
|
27573
|
+
ARGUMENTS:
|
|
27574
|
+
name Worker name (e.g., trade-worker, agent-worker)
|
|
27575
|
+
|
|
27576
|
+
EXAMPLES:
|
|
27577
|
+
hoox deploy history trade-worker
|
|
27578
|
+
hoox deploy history hoox --json`).action(withErrorHandling(async (worker) => {
|
|
27579
|
+
const fmt = getFormatOptions(program2);
|
|
27580
|
+
await doVersionHistory(worker, fmt);
|
|
27581
|
+
}, { service: "deploy" }));
|
|
27582
|
+
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.
|
|
27583
|
+
|
|
27584
|
+
If no version is specified, the CLI fetches the 5 most recent versions
|
|
27585
|
+
and prompts you to select one.
|
|
27586
|
+
|
|
27587
|
+
ARGUMENTS:
|
|
27588
|
+
name Worker name (e.g., trade-worker, agent-worker)
|
|
27589
|
+
version Version ID to rollback to (optional)
|
|
27590
|
+
|
|
27591
|
+
OPTIONS:
|
|
27592
|
+
--yes Skip confirmation prompt
|
|
27593
|
+
|
|
27594
|
+
EXAMPLES:
|
|
27595
|
+
hoox deploy rollback trade-worker
|
|
27596
|
+
hoox deploy rollback trade-worker <version-id>
|
|
27597
|
+
hoox deploy rollback trade-worker <version-id> --yes`).option("--yes", "Skip confirmation prompt").action(withErrorHandling(async (worker, version2, options) => {
|
|
27598
|
+
const fmt = getFormatOptions(program2);
|
|
27599
|
+
await doVersionRollback(worker, version2, fmt, options.yes ?? false);
|
|
27600
|
+
}, { service: "deploy" }));
|
|
27430
27601
|
}
|
|
27431
27602
|
// src/commands/infra/infra-command.ts
|
|
27432
27603
|
init_main();
|
|
@@ -27558,6 +27729,95 @@ async function doQueueDelete(name, opts, cf) {
|
|
|
27558
27729
|
const cloudflare = cf ?? new CloudflareService;
|
|
27559
27730
|
await handleDelete(name, "Queue", (n) => cloudflare.queueDelete(n), opts);
|
|
27560
27731
|
}
|
|
27732
|
+
async function doProvisionDryRun(opts) {
|
|
27733
|
+
const configService = new ConfigService;
|
|
27734
|
+
try {
|
|
27735
|
+
await configService.load();
|
|
27736
|
+
} catch (err) {
|
|
27737
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
27738
|
+
formatError2(new CLIError(message, 1 /* ERROR */), opts);
|
|
27739
|
+
return;
|
|
27740
|
+
}
|
|
27741
|
+
const enabledWorkers = configService.listEnabledWorkers();
|
|
27742
|
+
if (enabledWorkers.length === 0) {
|
|
27743
|
+
if (!opts.quiet) {
|
|
27744
|
+
process.stdout.write(theme.dim(`No enabled workers found in wrangler.jsonc.
|
|
27745
|
+
`));
|
|
27746
|
+
}
|
|
27747
|
+
return;
|
|
27748
|
+
}
|
|
27749
|
+
const plan = [];
|
|
27750
|
+
for (const workerName of enabledWorkers) {
|
|
27751
|
+
const workerConfig = configService.getWorker(workerName);
|
|
27752
|
+
if (!workerConfig?.path)
|
|
27753
|
+
continue;
|
|
27754
|
+
const wranglerPath = resolve2(workerConfig.path, "wrangler.jsonc");
|
|
27755
|
+
const file2 = Bun.file(wranglerPath);
|
|
27756
|
+
if (!await file2.exists())
|
|
27757
|
+
continue;
|
|
27758
|
+
let wranglerConfig;
|
|
27759
|
+
try {
|
|
27760
|
+
const content = await file2.text();
|
|
27761
|
+
wranglerConfig = parse6(content) ?? {};
|
|
27762
|
+
} catch {
|
|
27763
|
+
continue;
|
|
27764
|
+
}
|
|
27765
|
+
const resources = [];
|
|
27766
|
+
const d1Databases = Array.isArray(wranglerConfig.d1_databases) ? wranglerConfig.d1_databases : [];
|
|
27767
|
+
for (const db of d1Databases) {
|
|
27768
|
+
const dbName = db.database_name ?? db.binding;
|
|
27769
|
+
if (dbName)
|
|
27770
|
+
resources.push(`D1: ${dbName}`);
|
|
27771
|
+
}
|
|
27772
|
+
const kvNamespaces = Array.isArray(wranglerConfig.kv_namespaces) ? wranglerConfig.kv_namespaces : [];
|
|
27773
|
+
for (const kv of kvNamespaces) {
|
|
27774
|
+
const kvBinding = kv.binding ?? `kv-${workerName}`;
|
|
27775
|
+
resources.push(`KV: ${kvBinding}`);
|
|
27776
|
+
}
|
|
27777
|
+
const r2Buckets = Array.isArray(wranglerConfig.r2_buckets) ? wranglerConfig.r2_buckets : [];
|
|
27778
|
+
for (const bucket of r2Buckets) {
|
|
27779
|
+
const bucketName = bucket.bucket_name ?? bucket.binding;
|
|
27780
|
+
if (bucketName)
|
|
27781
|
+
resources.push(`R2: ${bucketName}`);
|
|
27782
|
+
}
|
|
27783
|
+
let queueNames = [];
|
|
27784
|
+
if (wranglerConfig.queues && typeof wranglerConfig.queues === "object") {
|
|
27785
|
+
const queues = wranglerConfig.queues;
|
|
27786
|
+
const producers = Array.isArray(queues.producers) ? queues.producers : [];
|
|
27787
|
+
const consumers = Array.isArray(queues.consumers) ? queues.consumers : [];
|
|
27788
|
+
queueNames = [
|
|
27789
|
+
...producers.map((q2) => q2.queue),
|
|
27790
|
+
...consumers.map((q2) => q2.queue)
|
|
27791
|
+
].filter(Boolean);
|
|
27792
|
+
}
|
|
27793
|
+
for (const qName of [...new Set(queueNames)]) {
|
|
27794
|
+
resources.push(`Queue: ${qName}`);
|
|
27795
|
+
}
|
|
27796
|
+
if (resources.length > 0) {
|
|
27797
|
+
plan.push({ worker: workerName, resources });
|
|
27798
|
+
}
|
|
27799
|
+
}
|
|
27800
|
+
if (plan.length === 0) {
|
|
27801
|
+
process.stdout.write(theme.dim(`No provisionable resources found in any worker config.
|
|
27802
|
+
`));
|
|
27803
|
+
return;
|
|
27804
|
+
}
|
|
27805
|
+
process.stdout.write(theme.heading(`Resources to provision:
|
|
27806
|
+
|
|
27807
|
+
`));
|
|
27808
|
+
for (const entry of plan) {
|
|
27809
|
+
process.stdout.write(`${theme.bold(entry.worker)}
|
|
27810
|
+
`);
|
|
27811
|
+
for (const r of entry.resources) {
|
|
27812
|
+
process.stdout.write(`${theme.dim(" \u2500")} ${r}
|
|
27813
|
+
`);
|
|
27814
|
+
}
|
|
27815
|
+
process.stdout.write(`
|
|
27816
|
+
`);
|
|
27817
|
+
}
|
|
27818
|
+
process.stdout.write(`${theme.dim(`Run without --dry-run to provision these resources.
|
|
27819
|
+
`)}`);
|
|
27820
|
+
}
|
|
27561
27821
|
async function doProvision(opts, cf, config2) {
|
|
27562
27822
|
const cloudflare = cf ?? new CloudflareService;
|
|
27563
27823
|
const configService = config2 ?? new ConfigService;
|
|
@@ -27787,10 +28047,19 @@ This reads each worker's wrangler.jsonc and creates:
|
|
|
27787
28047
|
- R2 buckets (from r2_buckets)
|
|
27788
28048
|
- Queues (from queues)
|
|
27789
28049
|
|
|
28050
|
+
OPTIONS:
|
|
28051
|
+
--dry-run Preview what resources would be created without creating them
|
|
28052
|
+
|
|
27790
28053
|
EXAMPLES:
|
|
27791
|
-
hoox infra provision
|
|
27792
|
-
|
|
27793
|
-
|
|
28054
|
+
hoox infra provision
|
|
28055
|
+
hoox infra provision --dry-run`).option("--dry-run", "Preview what resources would be created without creating them").action(async (_, cmd) => {
|
|
28056
|
+
const opts = getOptions(cmd);
|
|
28057
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
28058
|
+
if (globalOpts.dryRun) {
|
|
28059
|
+
await doProvisionDryRun(opts);
|
|
28060
|
+
} else {
|
|
28061
|
+
await doProvision(opts);
|
|
28062
|
+
}
|
|
27794
28063
|
});
|
|
27795
28064
|
const d1Cmd = infraCmd.command("d1").summary("Manage D1 SQL databases").description(`D1 is Cloudflare's serverless SQL database.
|
|
27796
28065
|
|
|
@@ -27801,8 +28070,8 @@ EXAMPLES:
|
|
|
27801
28070
|
d1Cmd.command("list").summary("List all D1 databases").description(`List all D1 databases in your Cloudflare account.
|
|
27802
28071
|
|
|
27803
28072
|
EXAMPLES:
|
|
27804
|
-
hoox infra d1 list`).action(async
|
|
27805
|
-
const opts = getOptions(
|
|
28073
|
+
hoox infra d1 list`).action(async (_, cmd) => {
|
|
28074
|
+
const opts = getOptions(cmd);
|
|
27806
28075
|
await doD1List(opts);
|
|
27807
28076
|
});
|
|
27808
28077
|
d1Cmd.command("create <name>").summary("Create a new D1 database").description(`Create a new D1 database.
|
|
@@ -27811,8 +28080,8 @@ ARGUMENTS:
|
|
|
27811
28080
|
name Database name
|
|
27812
28081
|
|
|
27813
28082
|
EXAMPLES:
|
|
27814
|
-
hoox infra d1 create trade-data-db`).action(async
|
|
27815
|
-
const opts = getOptions(
|
|
28083
|
+
hoox infra d1 create trade-data-db`).action(async (name, _, cmd) => {
|
|
28084
|
+
const opts = getOptions(cmd);
|
|
27816
28085
|
await doD1Create(name, opts);
|
|
27817
28086
|
});
|
|
27818
28087
|
d1Cmd.command("delete <name>").summary("Delete a D1 database").description(`Delete a D1 database (WARNING: destructive operation).
|
|
@@ -27821,8 +28090,8 @@ ARGUMENTS:
|
|
|
27821
28090
|
name Database name
|
|
27822
28091
|
|
|
27823
28092
|
EXAMPLES:
|
|
27824
|
-
hoox infra d1 delete trade-data-db`).action(async
|
|
27825
|
-
const opts = getOptions(
|
|
28093
|
+
hoox infra d1 delete trade-data-db`).action(async (name, _, cmd) => {
|
|
28094
|
+
const opts = getOptions(cmd);
|
|
27826
28095
|
await doD1Delete(name, opts);
|
|
27827
28096
|
});
|
|
27828
28097
|
const kvCmd = infraCmd.command("kv").summary("Manage KV namespaces").description(`KV (Key-Value) namespaces for configuration and caching.
|
|
@@ -27834,8 +28103,8 @@ EXAMPLES:
|
|
|
27834
28103
|
kvCmd.command("list").summary("List all KV namespaces").description(`List all KV namespaces in your Cloudflare account.
|
|
27835
28104
|
|
|
27836
28105
|
EXAMPLES:
|
|
27837
|
-
hoox infra kv list`).action(async
|
|
27838
|
-
const opts = getOptions(
|
|
28106
|
+
hoox infra kv list`).action(async (_, cmd) => {
|
|
28107
|
+
const opts = getOptions(cmd);
|
|
27839
28108
|
await doKvList(opts);
|
|
27840
28109
|
});
|
|
27841
28110
|
kvCmd.command("create <name>").summary("Create a new KV namespace").description(`Create a new KV namespace.
|
|
@@ -27844,8 +28113,8 @@ ARGUMENTS:
|
|
|
27844
28113
|
name KV namespace title
|
|
27845
28114
|
|
|
27846
28115
|
EXAMPLES:
|
|
27847
|
-
hoox infra kv create CONFIG_KV`).action(async
|
|
27848
|
-
const opts = getOptions(
|
|
28116
|
+
hoox infra kv create CONFIG_KV`).action(async (name, _, cmd) => {
|
|
28117
|
+
const opts = getOptions(cmd);
|
|
27849
28118
|
await doKvCreate(name, opts);
|
|
27850
28119
|
});
|
|
27851
28120
|
kvCmd.command("delete <id>").summary("Delete a KV namespace by ID").description(`Delete a KV namespace by its ID (WARNING: destructive operation).
|
|
@@ -27854,8 +28123,8 @@ ARGUMENTS:
|
|
|
27854
28123
|
id KV namespace ID (get from 'kv list')
|
|
27855
28124
|
|
|
27856
28125
|
EXAMPLES:
|
|
27857
|
-
hoox infra kv delete c5917667a21745e390ff969f32b1847d`).action(async
|
|
27858
|
-
const opts = getOptions(
|
|
28126
|
+
hoox infra kv delete c5917667a21745e390ff969f32b1847d`).action(async (id, _, cmd) => {
|
|
28127
|
+
const opts = getOptions(cmd);
|
|
27859
28128
|
await doKvDelete(id, opts);
|
|
27860
28129
|
});
|
|
27861
28130
|
const r2Cmd = infraCmd.command("r2").summary("Manage R2 object storage buckets").description(`R2 is Cloudflare's S3-compatible object storage.
|
|
@@ -27867,8 +28136,8 @@ EXAMPLES:
|
|
|
27867
28136
|
r2Cmd.command("list").summary("List all R2 buckets").description(`List all R2 buckets in your Cloudflare account.
|
|
27868
28137
|
|
|
27869
28138
|
EXAMPLES:
|
|
27870
|
-
hoox infra r2 list`).action(async
|
|
27871
|
-
const opts = getOptions(
|
|
28139
|
+
hoox infra r2 list`).action(async (_, cmd) => {
|
|
28140
|
+
const opts = getOptions(cmd);
|
|
27872
28141
|
await doR2List(opts);
|
|
27873
28142
|
});
|
|
27874
28143
|
r2Cmd.command("create <name>").summary("Create a new R2 bucket").description(`Create a new R2 bucket.
|
|
@@ -27877,8 +28146,8 @@ ARGUMENTS:
|
|
|
27877
28146
|
name Bucket name
|
|
27878
28147
|
|
|
27879
28148
|
EXAMPLES:
|
|
27880
|
-
hoox infra r2 create trade-reports`).action(async
|
|
27881
|
-
const opts = getOptions(
|
|
28149
|
+
hoox infra r2 create trade-reports`).action(async (name, _, cmd) => {
|
|
28150
|
+
const opts = getOptions(cmd);
|
|
27882
28151
|
await doR2Create(name, opts);
|
|
27883
28152
|
});
|
|
27884
28153
|
r2Cmd.command("delete <name>").summary("Delete an R2 bucket").description(`Delete an R2 bucket (WARNING: destructive operation).
|
|
@@ -27887,8 +28156,8 @@ ARGUMENTS:
|
|
|
27887
28156
|
name Bucket name
|
|
27888
28157
|
|
|
27889
28158
|
EXAMPLES:
|
|
27890
|
-
hoox infra r2 delete trade-reports`).action(async
|
|
27891
|
-
const opts = getOptions(
|
|
28159
|
+
hoox infra r2 delete trade-reports`).action(async (name, _, cmd) => {
|
|
28160
|
+
const opts = getOptions(cmd);
|
|
27892
28161
|
await doR2Delete(name, opts);
|
|
27893
28162
|
});
|
|
27894
28163
|
const queuesCmd = infraCmd.command("queues").summary("Manage Cloudflare Queues").description(`Queues for asynchronous message processing between workers.
|
|
@@ -27900,8 +28169,8 @@ EXAMPLES:
|
|
|
27900
28169
|
queuesCmd.command("list").summary("List all Queues").description(`List all queues in your Cloudflare account.
|
|
27901
28170
|
|
|
27902
28171
|
EXAMPLES:
|
|
27903
|
-
hoox infra queues list`).action(async
|
|
27904
|
-
const opts = getOptions(
|
|
28172
|
+
hoox infra queues list`).action(async (_, cmd) => {
|
|
28173
|
+
const opts = getOptions(cmd);
|
|
27905
28174
|
await doQueueList(opts);
|
|
27906
28175
|
});
|
|
27907
28176
|
queuesCmd.command("create <name>").summary("Create a new Queue").description(`Create a new queue.
|
|
@@ -27910,8 +28179,8 @@ ARGUMENTS:
|
|
|
27910
28179
|
name Queue name
|
|
27911
28180
|
|
|
27912
28181
|
EXAMPLES:
|
|
27913
|
-
hoox infra queues create trade-execution`).action(async
|
|
27914
|
-
const opts = getOptions(
|
|
28182
|
+
hoox infra queues create trade-execution`).action(async (name, _, cmd) => {
|
|
28183
|
+
const opts = getOptions(cmd);
|
|
27915
28184
|
await doQueueCreate(name, opts);
|
|
27916
28185
|
});
|
|
27917
28186
|
queuesCmd.command("delete <name>").summary("Delete a Queue").description(`Delete a queue (WARNING: destructive operation).
|
|
@@ -27920,8 +28189,8 @@ ARGUMENTS:
|
|
|
27920
28189
|
name Queue name
|
|
27921
28190
|
|
|
27922
28191
|
EXAMPLES:
|
|
27923
|
-
hoox infra queues delete trade-execution`).action(async
|
|
27924
|
-
const opts = getOptions(
|
|
28192
|
+
hoox infra queues delete trade-execution`).action(async (name, _, cmd) => {
|
|
28193
|
+
const opts = getOptions(cmd);
|
|
27925
28194
|
await doQueueDelete(name, opts);
|
|
27926
28195
|
});
|
|
27927
28196
|
const vectorizeCmd = infraCmd.command("vectorize").summary("Manage Vectorize indexes").description(`Vectorize is Cloudflare's vector database for AI-powered search.
|
|
@@ -27930,16 +28199,16 @@ EXAMPLES:
|
|
|
27930
28199
|
hoox infra vectorize list
|
|
27931
28200
|
hoox infra vectorize create my-rag-index
|
|
27932
28201
|
hoox infra vectorize delete my-rag-index`);
|
|
27933
|
-
vectorizeCmd.command("list").summary("List all Vectorize indexes").description("List all Vectorize indexes in your Cloudflare account.").action(async
|
|
27934
|
-
const opts = getOptions(
|
|
28202
|
+
vectorizeCmd.command("list").summary("List all Vectorize indexes").description("List all Vectorize indexes in your Cloudflare account.").action(async (_, cmd) => {
|
|
28203
|
+
const opts = getOptions(cmd);
|
|
27935
28204
|
await doVectorizeList(opts);
|
|
27936
28205
|
});
|
|
27937
|
-
vectorizeCmd.command("create <name>").summary("Create a new Vectorize index").description("Create a new Vectorize index with default dimensions (768) and cosine metric.").action(async
|
|
27938
|
-
const opts = getOptions(
|
|
28206
|
+
vectorizeCmd.command("create <name>").summary("Create a new Vectorize index").description("Create a new Vectorize index with default dimensions (768) and cosine metric.").action(async (name, _, cmd) => {
|
|
28207
|
+
const opts = getOptions(cmd);
|
|
27939
28208
|
await doVectorizeCreate(name, opts);
|
|
27940
28209
|
});
|
|
27941
|
-
vectorizeCmd.command("delete <name>").summary("Delete a Vectorize index").description("Delete a Vectorize index (WARNING: destructive operation).").action(async
|
|
27942
|
-
const opts = getOptions(
|
|
28210
|
+
vectorizeCmd.command("delete <name>").summary("Delete a Vectorize index").description("Delete a Vectorize index (WARNING: destructive operation).").action(async (name, _, cmd) => {
|
|
28211
|
+
const opts = getOptions(cmd);
|
|
27943
28212
|
await doVectorizeDelete(name, opts);
|
|
27944
28213
|
});
|
|
27945
28214
|
const analyticsCmd = infraCmd.command("analytics").summary("Manage Analytics Engine datasets").description(`Analytics Engine for storing and querying time-series data.
|
|
@@ -27950,12 +28219,12 @@ The CLI provides creation instructions.
|
|
|
27950
28219
|
EXAMPLES:
|
|
27951
28220
|
hoox infra analytics list
|
|
27952
28221
|
hoox infra analytics create hoox-analytics`);
|
|
27953
|
-
analyticsCmd.command("list").summary("List all Analytics Engine datasets").description("List all Analytics Engine datasets in your account.").action(async
|
|
27954
|
-
const opts = getOptions(
|
|
28222
|
+
analyticsCmd.command("list").summary("List all Analytics Engine datasets").description("List all Analytics Engine datasets in your account.").action(async (_, cmd) => {
|
|
28223
|
+
const opts = getOptions(cmd);
|
|
27955
28224
|
await doAnalyticsList(opts);
|
|
27956
28225
|
});
|
|
27957
|
-
analyticsCmd.command("create <name>").summary("Show instructions for creating an Analytics Engine dataset").description("Analytics Engine datasets must be created via Cloudflare Dashboard.").action(async
|
|
27958
|
-
const opts = getOptions(
|
|
28226
|
+
analyticsCmd.command("create <name>").summary("Show instructions for creating an Analytics Engine dataset").description("Analytics Engine datasets must be created via Cloudflare Dashboard.").action(async (name, _, cmd) => {
|
|
28227
|
+
const opts = getOptions(cmd);
|
|
27959
28228
|
await doAnalyticsCreate(name, opts);
|
|
27960
28229
|
});
|
|
27961
28230
|
}
|
|
@@ -28134,32 +28403,7 @@ class SecretsService {
|
|
|
28134
28403
|
}
|
|
28135
28404
|
}
|
|
28136
28405
|
}
|
|
28137
|
-
// src/utils/error-handler.ts
|
|
28138
|
-
function withErrorHandling(handler, options) {
|
|
28139
|
-
return async (...args) => {
|
|
28140
|
-
try {
|
|
28141
|
-
await handler(...args);
|
|
28142
|
-
} catch (error51) {
|
|
28143
|
-
const service = options?.service ?? "cli";
|
|
28144
|
-
if (error51 instanceof CLIError) {
|
|
28145
|
-
formatError2(error51, options?.opts);
|
|
28146
|
-
process.exit(error51.code);
|
|
28147
|
-
} else if (error51 instanceof Error) {
|
|
28148
|
-
formatError2(`[${service}] ${error51.message}`, options?.opts);
|
|
28149
|
-
process.exit(1 /* ERROR */);
|
|
28150
|
-
} else {
|
|
28151
|
-
formatError2(`[${service}] Unknown error: ${String(error51)}`, options?.opts);
|
|
28152
|
-
process.exit(-1 /* CommandFailed */);
|
|
28153
|
-
}
|
|
28154
|
-
}
|
|
28155
|
-
};
|
|
28156
|
-
}
|
|
28157
|
-
|
|
28158
28406
|
// src/commands/config/env-command.ts
|
|
28159
|
-
function formatOpts(cmd) {
|
|
28160
|
-
const opts = cmd.optsWithGlobals();
|
|
28161
|
-
return { json: Boolean(opts.json), quiet: Boolean(opts.quiet) };
|
|
28162
|
-
}
|
|
28163
28407
|
async function handleInit(opts) {
|
|
28164
28408
|
ye("Environment Setup");
|
|
28165
28409
|
const existingEnv = Bun.file(".env.local");
|
|
@@ -28313,27 +28557,23 @@ EXAMPLES:
|
|
|
28313
28557
|
hoox config env validate
|
|
28314
28558
|
hoox config env generate-dev-vars`);
|
|
28315
28559
|
envCmd.command("init").description("Interactive wizard to generate .env.local and .dev.vars").action(withErrorHandling(async (_, cmd) => {
|
|
28316
|
-
const opts =
|
|
28560
|
+
const opts = getFormatOptions(cmd);
|
|
28317
28561
|
await handleInit(opts);
|
|
28318
28562
|
}, { service: "env" }));
|
|
28319
28563
|
envCmd.command("show").description("Display current .env.local (secrets redacted)").action(withErrorHandling(async (_, cmd) => {
|
|
28320
|
-
const opts =
|
|
28564
|
+
const opts = getFormatOptions(cmd);
|
|
28321
28565
|
await handleShow(opts);
|
|
28322
28566
|
}, { service: "env" }));
|
|
28323
28567
|
envCmd.command("validate").description("Check required environment variables").action(withErrorHandling(async (_, cmd) => {
|
|
28324
|
-
const opts =
|
|
28568
|
+
const opts = getFormatOptions(cmd);
|
|
28325
28569
|
await handleValidate(opts);
|
|
28326
28570
|
}, { service: "env" }));
|
|
28327
28571
|
envCmd.command("generate-dev-vars").description("Create per-worker .dev.vars from .env.local").action(withErrorHandling(async (_, cmd) => {
|
|
28328
|
-
const opts =
|
|
28572
|
+
const opts = getFormatOptions(cmd);
|
|
28329
28573
|
await handleGenerateDevVars(opts);
|
|
28330
28574
|
}, { service: "env" }));
|
|
28331
28575
|
}
|
|
28332
28576
|
// src/commands/config/kv-command.ts
|
|
28333
|
-
function formatOpts2(cmd) {
|
|
28334
|
-
const opts = cmd.optsWithGlobals();
|
|
28335
|
-
return { json: Boolean(opts.json), quiet: Boolean(opts.quiet) };
|
|
28336
|
-
}
|
|
28337
28577
|
async function resolveNs(cmd) {
|
|
28338
28578
|
const svc = new KvSyncService;
|
|
28339
28579
|
const opts = cmd.optsWithGlobals();
|
|
@@ -28434,32 +28674,32 @@ EXAMPLES:
|
|
|
28434
28674
|
hoox config kv apply-manifest
|
|
28435
28675
|
hoox config kv manifest`).option("--namespace-id <id>", "KV namespace ID (auto-detected if omitted)");
|
|
28436
28676
|
kvCmd.command("list").description("List all keys in the KV namespace").action(withErrorHandling(async (_, cmd) => {
|
|
28437
|
-
const opts =
|
|
28677
|
+
const opts = getFormatOptions(cmd);
|
|
28438
28678
|
const nsId = await resolveNs(cmd);
|
|
28439
28679
|
await handleList(opts, nsId);
|
|
28440
28680
|
}, { service: "kv" }));
|
|
28441
28681
|
kvCmd.command("get <key>").description("Get a key's value from the KV namespace").action(withErrorHandling(async (key, _, cmd) => {
|
|
28442
|
-
const opts =
|
|
28682
|
+
const opts = getFormatOptions(cmd);
|
|
28443
28683
|
const nsId = await resolveNs(cmd);
|
|
28444
28684
|
await handleGet(opts, nsId, key);
|
|
28445
28685
|
}, { service: "kv" }));
|
|
28446
28686
|
kvCmd.command("set <key> <value>").description("Set a key's value in the KV namespace").action(withErrorHandling(async (key, value, _, cmd) => {
|
|
28447
|
-
const opts =
|
|
28687
|
+
const opts = getFormatOptions(cmd);
|
|
28448
28688
|
const nsId = await resolveNs(cmd);
|
|
28449
28689
|
await handleSet(opts, nsId, key, value);
|
|
28450
28690
|
}, { service: "kv" }));
|
|
28451
28691
|
kvCmd.command("delete <key>").description("Delete a key from the KV namespace").action(withErrorHandling(async (key, _, cmd) => {
|
|
28452
|
-
const opts =
|
|
28692
|
+
const opts = getFormatOptions(cmd);
|
|
28453
28693
|
const nsId = await resolveNs(cmd);
|
|
28454
28694
|
await handleDelete2(opts, nsId, key);
|
|
28455
28695
|
}, { service: "kv" }));
|
|
28456
28696
|
kvCmd.command("apply-manifest").description("Apply manifest key defaults to the KV namespace").action(withErrorHandling(async (_, cmd) => {
|
|
28457
|
-
const opts =
|
|
28697
|
+
const opts = getFormatOptions(cmd);
|
|
28458
28698
|
const nsId = await resolveNs(cmd);
|
|
28459
28699
|
await handleApplyManifest(opts, nsId);
|
|
28460
28700
|
}, { service: "kv" }));
|
|
28461
28701
|
kvCmd.command("manifest").description("Show expected KV keys from manifest").action(withErrorHandling(async (_, cmd) => {
|
|
28462
|
-
const opts =
|
|
28702
|
+
const opts = getFormatOptions(cmd);
|
|
28463
28703
|
await handleManifest(opts);
|
|
28464
28704
|
}, { service: "kv" }));
|
|
28465
28705
|
}
|
|
@@ -28486,10 +28726,6 @@ async function writeConfigRaw(content, configPath) {
|
|
|
28486
28726
|
const path3 = configPath ?? "wrangler.jsonc";
|
|
28487
28727
|
await Bun.write(path3, content);
|
|
28488
28728
|
}
|
|
28489
|
-
function formatOpts3(program2) {
|
|
28490
|
-
const opts = program2.optsWithGlobals();
|
|
28491
|
-
return { json: Boolean(opts.json), quiet: Boolean(opts.quiet) };
|
|
28492
|
-
}
|
|
28493
28729
|
async function promptSecret(promptText) {
|
|
28494
28730
|
process.stdout.write(`${theme.info(icons.info)} ${promptText}: `);
|
|
28495
28731
|
if (process.stdin.isTTY) {
|
|
@@ -28589,48 +28825,43 @@ OPTIONS:
|
|
|
28589
28825
|
|
|
28590
28826
|
EXAMPLES:
|
|
28591
28827
|
hoox config show
|
|
28592
|
-
hoox config show --json`).action(async () => {
|
|
28593
|
-
const opts =
|
|
28594
|
-
|
|
28595
|
-
|
|
28596
|
-
|
|
28597
|
-
|
|
28598
|
-
|
|
28599
|
-
|
|
28600
|
-
|
|
28601
|
-
|
|
28602
|
-
|
|
28603
|
-
|
|
28604
|
-
|
|
28605
|
-
|
|
28606
|
-
|
|
28607
|
-
|
|
28608
|
-
process.stdout.write(`${theme.heading(`
|
|
28828
|
+
hoox config show --json`).action(withErrorHandling(async (_, cmd) => {
|
|
28829
|
+
const opts = getFormatOptions(cmd);
|
|
28830
|
+
if (opts.json) {
|
|
28831
|
+
const raw = await readConfigRaw();
|
|
28832
|
+
const parsed = parse6(raw);
|
|
28833
|
+
formatJson(parsed, opts);
|
|
28834
|
+
return;
|
|
28835
|
+
}
|
|
28836
|
+
const svc = new ConfigService;
|
|
28837
|
+
await svc.load();
|
|
28838
|
+
const global = svc.getGlobal();
|
|
28839
|
+
const globalPairs = {};
|
|
28840
|
+
for (const [k2, v2] of Object.entries(global)) {
|
|
28841
|
+
globalPairs[k2] = v2 ?? "(not set)";
|
|
28842
|
+
}
|
|
28843
|
+
process.stdout.write(`${theme.heading(`
|
|
28609
28844
|
Global Configuration`)}
|
|
28610
28845
|
`);
|
|
28611
|
-
|
|
28612
|
-
|
|
28613
|
-
|
|
28614
|
-
|
|
28846
|
+
formatKeyValue(globalPairs, opts);
|
|
28847
|
+
const workers = svc.listWorkers();
|
|
28848
|
+
if (workers.length > 0) {
|
|
28849
|
+
process.stdout.write(`
|
|
28615
28850
|
${theme.heading("Workers")}
|
|
28616
28851
|
`);
|
|
28617
|
-
|
|
28618
|
-
|
|
28619
|
-
|
|
28620
|
-
|
|
28621
|
-
|
|
28622
|
-
|
|
28623
|
-
|
|
28624
|
-
|
|
28625
|
-
|
|
28626
|
-
|
|
28627
|
-
|
|
28628
|
-
}
|
|
28629
|
-
} catch (err) {
|
|
28630
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
28631
|
-
formatError2(new CLIError(`Failed to show config: ${message}`, 1 /* ERROR */), opts);
|
|
28852
|
+
const rows = workers.map((name) => {
|
|
28853
|
+
const w3 = svc.getWorker(name);
|
|
28854
|
+
return {
|
|
28855
|
+
Worker: name,
|
|
28856
|
+
Enabled: w3.enabled ? `${theme.success("\u2022")} yes` : "-",
|
|
28857
|
+
Path: w3.path,
|
|
28858
|
+
Secrets: w3.secrets?.length ? String(w3.secrets.length) : "-",
|
|
28859
|
+
Vars: w3.vars && Object.keys(w3.vars).length ? String(Object.keys(w3.vars).length) : "-"
|
|
28860
|
+
};
|
|
28861
|
+
});
|
|
28862
|
+
formatTable(rows, opts);
|
|
28632
28863
|
}
|
|
28633
|
-
});
|
|
28864
|
+
}, { service: "config" }));
|
|
28634
28865
|
configCmd.command("set <key> <value>").summary("Update a config value in wrangler.jsonc").description(`Update a configuration value in wrangler.jsonc.
|
|
28635
28866
|
|
|
28636
28867
|
ARGUMENTS:
|
|
@@ -28645,32 +28876,23 @@ PATH EXAMPLES:
|
|
|
28645
28876
|
EXAMPLES:
|
|
28646
28877
|
hoox config set global.subdomain_prefix myapp
|
|
28647
28878
|
hoox config set workers.trade-worker.enabled false
|
|
28648
|
-
hoox config set workers.agent-worker.vars.interval 5`).action(async (key, value) => {
|
|
28649
|
-
const opts =
|
|
28879
|
+
hoox config set workers.agent-worker.vars.interval 5`).action(withErrorHandling(async (key, value, _, cmd) => {
|
|
28880
|
+
const opts = getFormatOptions(cmd);
|
|
28881
|
+
const raw = await readConfigRaw();
|
|
28882
|
+
const jsonPath = keyToPath(key);
|
|
28883
|
+
let edits;
|
|
28650
28884
|
try {
|
|
28651
|
-
|
|
28652
|
-
|
|
28653
|
-
|
|
28654
|
-
try {
|
|
28655
|
-
edits = modify(raw, jsonPath, parseValue(value), {
|
|
28656
|
-
formattingOptions: FORMATTING_OPTIONS
|
|
28657
|
-
});
|
|
28658
|
-
} catch (err) {
|
|
28659
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
28660
|
-
throw new CLIError(`Invalid key path "${key}": ${msg}`, 2 /* INVALID_USAGE */);
|
|
28661
|
-
}
|
|
28662
|
-
const updated = applyEdits(raw, edits);
|
|
28663
|
-
await writeConfigRaw(updated);
|
|
28664
|
-
formatSuccess(`Updated "${key}" = "${value}" in wrangler.jsonc`, opts);
|
|
28885
|
+
edits = modify(raw, jsonPath, parseValue(value), {
|
|
28886
|
+
formattingOptions: FORMATTING_OPTIONS
|
|
28887
|
+
});
|
|
28665
28888
|
} catch (err) {
|
|
28666
|
-
|
|
28667
|
-
|
|
28668
|
-
} else {
|
|
28669
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
28670
|
-
formatError2(new CLIError(`Failed to set config: ${message}`, 1 /* ERROR */), opts);
|
|
28671
|
-
}
|
|
28889
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
28890
|
+
throw new CLIError(`Invalid key path "${key}": ${msg}`, 2 /* INVALID_USAGE */);
|
|
28672
28891
|
}
|
|
28673
|
-
|
|
28892
|
+
const updated = applyEdits(raw, edits);
|
|
28893
|
+
await writeConfigRaw(updated);
|
|
28894
|
+
formatSuccess(`Updated "${key}" = "${value}" in wrangler.jsonc`, opts);
|
|
28895
|
+
}, { service: "config" }));
|
|
28674
28896
|
const secretsCmd = configCmd.command("secrets").summary("Manage Cloudflare Worker secrets").description(`Manage secrets for your Cloudflare Workers.
|
|
28675
28897
|
|
|
28676
28898
|
Secrets are defined in wrangler.jsonc under each worker's 'secrets' array.
|
|
@@ -28693,58 +28915,53 @@ ARGUMENTS:
|
|
|
28693
28915
|
|
|
28694
28916
|
EXAMPLES:
|
|
28695
28917
|
hoox config secrets list
|
|
28696
|
-
hoox config secrets list trade-worker`).action(async (worker) => {
|
|
28697
|
-
const opts =
|
|
28698
|
-
|
|
28699
|
-
|
|
28700
|
-
|
|
28701
|
-
|
|
28702
|
-
|
|
28703
|
-
process.stdout.write(`${theme.dim(`No secrets declared for worker "${worker}".`)}
|
|
28918
|
+
hoox config secrets list trade-worker`).action(withErrorHandling(async (worker, _, cmd) => {
|
|
28919
|
+
const opts = getFormatOptions(cmd);
|
|
28920
|
+
const svc = await SecretsService.create();
|
|
28921
|
+
if (worker) {
|
|
28922
|
+
const secrets = svc.listSecrets(worker);
|
|
28923
|
+
if (secrets.length === 0) {
|
|
28924
|
+
process.stdout.write(`${theme.dim(`No secrets declared for worker "${worker}".`)}
|
|
28704
28925
|
`);
|
|
28705
|
-
|
|
28706
|
-
|
|
28707
|
-
|
|
28708
|
-
|
|
28709
|
-
|
|
28710
|
-
|
|
28926
|
+
return;
|
|
28927
|
+
}
|
|
28928
|
+
if (opts.json) {
|
|
28929
|
+
formatJson({ worker, secrets }, opts);
|
|
28930
|
+
} else {
|
|
28931
|
+
process.stdout.write(`${theme.heading(`
|
|
28711
28932
|
Secrets for ${worker}`)}
|
|
28712
28933
|
`);
|
|
28713
|
-
|
|
28714
|
-
|
|
28934
|
+
for (const s of secrets) {
|
|
28935
|
+
process.stdout.write(` ${theme.label("\u2022")} ${s}
|
|
28715
28936
|
`);
|
|
28716
|
-
}
|
|
28717
28937
|
}
|
|
28718
|
-
}
|
|
28719
|
-
|
|
28720
|
-
|
|
28721
|
-
|
|
28722
|
-
|
|
28938
|
+
}
|
|
28939
|
+
} else {
|
|
28940
|
+
const all = svc.listAllSecrets();
|
|
28941
|
+
const workers = Object.keys(all);
|
|
28942
|
+
if (workers.length === 0) {
|
|
28943
|
+
process.stdout.write(`${theme.dim("No secrets declared for any worker.")}
|
|
28723
28944
|
`);
|
|
28724
|
-
|
|
28725
|
-
|
|
28726
|
-
|
|
28727
|
-
|
|
28728
|
-
|
|
28729
|
-
|
|
28945
|
+
return;
|
|
28946
|
+
}
|
|
28947
|
+
if (opts.json) {
|
|
28948
|
+
formatJson(all, opts);
|
|
28949
|
+
} else {
|
|
28950
|
+
process.stdout.write(`${theme.heading(`
|
|
28730
28951
|
Secrets by Worker`)}
|
|
28731
28952
|
`);
|
|
28732
|
-
|
|
28733
|
-
|
|
28953
|
+
for (const [name, secrets] of Object.entries(all)) {
|
|
28954
|
+
process.stdout.write(`
|
|
28734
28955
|
${theme.bold(name)} (${secrets.length})
|
|
28735
28956
|
`);
|
|
28736
|
-
|
|
28737
|
-
|
|
28957
|
+
for (const s of secrets) {
|
|
28958
|
+
process.stdout.write(` ${theme.dim("\u2022")} ${s}
|
|
28738
28959
|
`);
|
|
28739
|
-
}
|
|
28740
28960
|
}
|
|
28741
28961
|
}
|
|
28742
28962
|
}
|
|
28743
|
-
} catch (err) {
|
|
28744
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
28745
|
-
formatError2(new CLIError(`Failed to list secrets: ${message}`, 1 /* ERROR */), opts);
|
|
28746
28963
|
}
|
|
28747
|
-
});
|
|
28964
|
+
}, { service: "config" }));
|
|
28748
28965
|
secretsCmd.command("set <worker> <name>").summary("Set a secret value for a worker").description(`Set a secret value for a worker and sync to Cloudflare.
|
|
28749
28966
|
|
|
28750
28967
|
ARGUMENTS:
|
|
@@ -28756,39 +28973,30 @@ It writes to the worker's .dev.vars file and syncs to Cloudflare.
|
|
|
28756
28973
|
|
|
28757
28974
|
EXAMPLES:
|
|
28758
28975
|
hoox config secrets set trade-worker BINANCE_KEY_BINDING
|
|
28759
|
-
hoox config secrets set agent-worker OPENAI_KEY`).action(async (workerName, secretName) => {
|
|
28760
|
-
const opts =
|
|
28761
|
-
|
|
28762
|
-
|
|
28763
|
-
|
|
28764
|
-
|
|
28765
|
-
|
|
28766
|
-
|
|
28767
|
-
|
|
28768
|
-
|
|
28769
|
-
|
|
28770
|
-
|
|
28771
|
-
|
|
28772
|
-
|
|
28773
|
-
|
|
28774
|
-
|
|
28775
|
-
|
|
28776
|
-
|
|
28777
|
-
|
|
28778
|
-
|
|
28779
|
-
|
|
28780
|
-
|
|
28781
|
-
formatError2(new CLIError(`Sync partial: ${result.error ?? "unknown error"}`, 1 /* ERROR */), opts);
|
|
28782
|
-
}
|
|
28783
|
-
} catch (err) {
|
|
28784
|
-
if (err instanceof CLIError) {
|
|
28785
|
-
formatError2(err, opts);
|
|
28786
|
-
} else {
|
|
28787
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
28788
|
-
formatError2(new CLIError(`Failed to set secret: ${message}`, 1 /* ERROR */), opts);
|
|
28789
|
-
}
|
|
28976
|
+
hoox config secrets set agent-worker OPENAI_KEY`).action(withErrorHandling(async (workerName, secretName, _, cmd) => {
|
|
28977
|
+
const opts = getFormatOptions(cmd);
|
|
28978
|
+
const svc = await SecretsService.create();
|
|
28979
|
+
const declared = svc.listSecrets(workerName);
|
|
28980
|
+
if (!declared.includes(secretName) && declared.length > 0) {
|
|
28981
|
+
throw new CLIError(`Secret "${secretName}" is not declared for worker "${workerName}". ` + `Declared secrets: ${declared.join(", ")}`, 2 /* INVALID_USAGE */);
|
|
28982
|
+
}
|
|
28983
|
+
const value = await promptSecret(`Enter value for "${secretName}"`);
|
|
28984
|
+
if (!value) {
|
|
28985
|
+
throw new CLIError("Secret value cannot be empty", 2 /* INVALID_USAGE */);
|
|
28986
|
+
}
|
|
28987
|
+
const devVarsPath = `workers/${workerName}/.dev.vars`;
|
|
28988
|
+
await updateDevVars(devVarsPath, secretName, value);
|
|
28989
|
+
formatSuccess(`Secret "${secretName}" updated in ${devVarsPath}`, opts);
|
|
28990
|
+
const syncSpin = vt();
|
|
28991
|
+
syncSpin.start("Syncing to Cloudflare...");
|
|
28992
|
+
const result = await svc.syncToCloudflare(workerName);
|
|
28993
|
+
if (result.ok) {
|
|
28994
|
+
syncSpin.stop(`Secret "${secretName}" synced to Cloudflare`);
|
|
28995
|
+
} else {
|
|
28996
|
+
syncSpin.stop(`Sync partial: ${result.error ?? "unknown error"}`);
|
|
28997
|
+
formatError2(new CLIError(`Sync partial: ${result.error ?? "unknown error"}`, 1 /* ERROR */), opts);
|
|
28790
28998
|
}
|
|
28791
|
-
});
|
|
28999
|
+
}, { service: "config" }));
|
|
28792
29000
|
secretsCmd.command("delete <worker> <name>").summary("Delete a secret from Cloudflare").description(`Delete a secret from Cloudflare Workers.
|
|
28793
29001
|
|
|
28794
29002
|
ARGUMENTS:
|
|
@@ -28798,45 +29006,36 @@ ARGUMENTS:
|
|
|
28798
29006
|
This removes the secret from Cloudflare and from the worker's .dev.vars file.
|
|
28799
29007
|
|
|
28800
29008
|
EXAMPLES:
|
|
28801
|
-
hoox config secrets delete trade-worker BINANCE_KEY_BINDING`).action(async (workerName, secretName) => {
|
|
28802
|
-
const opts =
|
|
28803
|
-
|
|
28804
|
-
|
|
28805
|
-
|
|
28806
|
-
|
|
28807
|
-
|
|
28808
|
-
|
|
28809
|
-
|
|
28810
|
-
|
|
28811
|
-
|
|
28812
|
-
|
|
28813
|
-
|
|
28814
|
-
|
|
28815
|
-
|
|
28816
|
-
|
|
28817
|
-
|
|
28818
|
-
|
|
28819
|
-
|
|
28820
|
-
|
|
28821
|
-
|
|
28822
|
-
|
|
28823
|
-
const lines = content.split(`
|
|
29009
|
+
hoox config secrets delete trade-worker BINANCE_KEY_BINDING`).action(withErrorHandling(async (workerName, secretName, _, cmd) => {
|
|
29010
|
+
const opts = getFormatOptions(cmd);
|
|
29011
|
+
const svc = await SecretsService.create();
|
|
29012
|
+
const declared = svc.listSecrets(workerName);
|
|
29013
|
+
if (!declared.includes(secretName)) {
|
|
29014
|
+
throw new CLIError(`Secret "${secretName}" is not declared for worker "${workerName}".`, 2 /* INVALID_USAGE */);
|
|
29015
|
+
}
|
|
29016
|
+
const proc = Bun.spawn(["wrangler", "secret", "delete", secretName], {
|
|
29017
|
+
cwd: `workers/${workerName}`,
|
|
29018
|
+
stdout: "pipe",
|
|
29019
|
+
stderr: "pipe"
|
|
29020
|
+
});
|
|
29021
|
+
const exitCode = await proc.exited;
|
|
29022
|
+
if (exitCode !== 0) {
|
|
29023
|
+
const stderrText = await new Response(proc.stderr).text();
|
|
29024
|
+
throw new CLIError(`wrangler exited with code ${exitCode}: ${stderrText.trim()}`, 1 /* ERROR */);
|
|
29025
|
+
}
|
|
29026
|
+
const devVarsPath = `workers/${workerName}/.dev.vars`;
|
|
29027
|
+
const devFile = Bun.file(devVarsPath);
|
|
29028
|
+
if (await devFile.exists()) {
|
|
29029
|
+
let content = await devFile.text();
|
|
29030
|
+
const lines = content.split(`
|
|
28824
29031
|
`);
|
|
28825
|
-
|
|
28826
|
-
|
|
29032
|
+
const filtered = lines.filter((line) => !line.startsWith(`${secretName}=`) && line.trim() !== "");
|
|
29033
|
+
await Bun.write(devVarsPath, filtered.join(`
|
|
28827
29034
|
`) + (filtered.length > 0 ? `
|
|
28828
29035
|
` : ""));
|
|
28829
|
-
}
|
|
28830
|
-
formatSuccess(`Secret "${secretName}" deleted from Cloudflare`, opts);
|
|
28831
|
-
} catch (err) {
|
|
28832
|
-
if (err instanceof CLIError) {
|
|
28833
|
-
formatError2(err, opts);
|
|
28834
|
-
} else {
|
|
28835
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
28836
|
-
formatError2(new CLIError(`Failed to delete secret: ${message}`, 1 /* ERROR */), opts);
|
|
28837
|
-
}
|
|
28838
29036
|
}
|
|
28839
|
-
|
|
29037
|
+
formatSuccess(`Secret "${secretName}" deleted from Cloudflare`, opts);
|
|
29038
|
+
}, { service: "config" }));
|
|
28840
29039
|
secretsCmd.command("sync [worker]").summary("Sync secrets to Cloudflare").description(`Sync secrets from .dev.vars files to Cloudflare Workers.
|
|
28841
29040
|
|
|
28842
29041
|
ARGUMENTS:
|
|
@@ -28846,56 +29045,47 @@ This reads .dev.vars files and uploads secrets to Cloudflare via wrangler.
|
|
|
28846
29045
|
|
|
28847
29046
|
EXAMPLES:
|
|
28848
29047
|
hoox config secrets sync Sync all workers
|
|
28849
|
-
hoox config secrets sync trade-worker Sync specific worker`).action(async (workerName) => {
|
|
28850
|
-
const opts =
|
|
28851
|
-
|
|
28852
|
-
|
|
28853
|
-
|
|
28854
|
-
|
|
28855
|
-
|
|
28856
|
-
|
|
28857
|
-
|
|
28858
|
-
syncSpin.stop(`Synced ${result.value?.length ?? 0} secrets for "${workerName}"`);
|
|
28859
|
-
} else {
|
|
28860
|
-
syncSpin.stop(`Sync failed: ${result.error ?? "unknown error"}`);
|
|
28861
|
-
formatError2(new CLIError(`Sync failed: ${result.error ?? "unknown error"}`, 1 /* ERROR */), opts);
|
|
28862
|
-
}
|
|
29048
|
+
hoox config secrets sync trade-worker Sync specific worker`).action(withErrorHandling(async (workerName, _, cmd) => {
|
|
29049
|
+
const opts = getFormatOptions(cmd);
|
|
29050
|
+
const svc = await SecretsService.create();
|
|
29051
|
+
if (workerName) {
|
|
29052
|
+
const syncSpin = vt();
|
|
29053
|
+
syncSpin.start(`Syncing secrets for "${workerName}"...`);
|
|
29054
|
+
const result = await svc.syncToCloudflare(workerName);
|
|
29055
|
+
if (result.ok) {
|
|
29056
|
+
syncSpin.stop(`Synced ${result.value?.length ?? 0} secrets for "${workerName}"`);
|
|
28863
29057
|
} else {
|
|
28864
|
-
|
|
28865
|
-
|
|
28866
|
-
|
|
28867
|
-
|
|
28868
|
-
|
|
28869
|
-
|
|
28870
|
-
|
|
28871
|
-
|
|
28872
|
-
|
|
28873
|
-
|
|
28874
|
-
|
|
28875
|
-
|
|
28876
|
-
|
|
28877
|
-
|
|
28878
|
-
|
|
28879
|
-
|
|
28880
|
-
|
|
28881
|
-
|
|
28882
|
-
|
|
28883
|
-
}
|
|
28884
|
-
if (failed === 0) {
|
|
28885
|
-
formatSuccess(`All ${synced} workers synced successfully`, opts);
|
|
29058
|
+
syncSpin.stop(`Sync failed: ${result.error ?? "unknown error"}`);
|
|
29059
|
+
formatError2(new CLIError(`Sync failed: ${result.error ?? "unknown error"}`, 1 /* ERROR */), opts);
|
|
29060
|
+
}
|
|
29061
|
+
} else {
|
|
29062
|
+
const all = svc.listAllSecrets();
|
|
29063
|
+
const workers = Object.keys(all);
|
|
29064
|
+
if (workers.length === 0) {
|
|
29065
|
+
formatSuccess("No secrets to sync.", opts);
|
|
29066
|
+
return;
|
|
29067
|
+
}
|
|
29068
|
+
let synced = 0;
|
|
29069
|
+
let failed = 0;
|
|
29070
|
+
const syncSpin = vt();
|
|
29071
|
+
for (const name of workers) {
|
|
29072
|
+
syncSpin.start(`Syncing ${name}...`);
|
|
29073
|
+
const result = await svc.syncToCloudflare(name);
|
|
29074
|
+
if (result.ok) {
|
|
29075
|
+
syncSpin.stop(`${theme.success("synced")} ${result.value?.length ?? 0} for ${name}`);
|
|
29076
|
+
synced++;
|
|
28886
29077
|
} else {
|
|
28887
|
-
|
|
29078
|
+
syncSpin.stop(`${theme.error("failed")} ${name}`);
|
|
29079
|
+
failed++;
|
|
28888
29080
|
}
|
|
28889
29081
|
}
|
|
28890
|
-
|
|
28891
|
-
|
|
28892
|
-
formatError2(err, opts);
|
|
29082
|
+
if (failed === 0) {
|
|
29083
|
+
formatSuccess(`All ${synced} workers synced successfully`, opts);
|
|
28893
29084
|
} else {
|
|
28894
|
-
|
|
28895
|
-
formatError2(new CLIError(`Failed to sync secrets: ${message}`, 1 /* ERROR */), opts);
|
|
29085
|
+
formatError2(new CLIError(`${synced} synced, ${failed} failed`, 1 /* ERROR */), opts);
|
|
28896
29086
|
}
|
|
28897
29087
|
}
|
|
28898
|
-
});
|
|
29088
|
+
}, { service: "config" }));
|
|
28899
29089
|
registerEnvCommand(configCmd);
|
|
28900
29090
|
registerKvCommand(configCmd);
|
|
28901
29091
|
const keysCmd = configCmd.command("keys").summary("Manage internal auth keys").description(`Generate and manage internal auth keys for inter-worker communication.
|
|
@@ -28913,86 +29103,75 @@ Creates the following keys:
|
|
|
28913
29103
|
- WEBHOOK_API_KEY_BINDING (32 char)
|
|
28914
29104
|
- AGENT_INTERNAL_KEY (32 char)
|
|
28915
29105
|
- TG_BOT_TOKEN_BINDING (16 char)
|
|
28916
|
-
- INTERNAL_KEY_BINDING (32 char)
|
|
28917
29106
|
|
|
28918
29107
|
WARNING: Add .keys/ to your .gitignore to avoid committing secrets!
|
|
28919
29108
|
|
|
28920
29109
|
EXAMPLES:
|
|
28921
|
-
hoox config keys generate`).action(async () => {
|
|
28922
|
-
const opts =
|
|
28923
|
-
|
|
28924
|
-
|
|
28925
|
-
|
|
28926
|
-
|
|
28927
|
-
|
|
28928
|
-
|
|
28929
|
-
|
|
28930
|
-
|
|
28931
|
-
|
|
28932
|
-
|
|
28933
|
-
|
|
28934
|
-
|
|
28935
|
-
|
|
28936
|
-
await Bun.write(filePath, `${name}=${value}
|
|
29110
|
+
hoox config keys generate`).action(withErrorHandling(async (_, cmd) => {
|
|
29111
|
+
const opts = getFormatOptions(cmd);
|
|
29112
|
+
const keysDir = ".keys";
|
|
29113
|
+
if (!existsSync3(keysDir)) {
|
|
29114
|
+
mkdirSync2(keysDir, { recursive: true });
|
|
29115
|
+
}
|
|
29116
|
+
const keys = {
|
|
29117
|
+
INTERNAL_KEY_BINDING: generateKey(),
|
|
29118
|
+
WEBHOOK_API_KEY_BINDING: generateKey(),
|
|
29119
|
+
AGENT_INTERNAL_KEY: generateKey(),
|
|
29120
|
+
TG_BOT_TOKEN_BINDING: generateKey(16)
|
|
29121
|
+
};
|
|
29122
|
+
for (const [name, value] of Object.entries(keys)) {
|
|
29123
|
+
const filePath = `${keysDir}/${name.toLowerCase()}.env`;
|
|
29124
|
+
await Bun.write(filePath, `${name}=${value}
|
|
28937
29125
|
`);
|
|
28938
|
-
|
|
28939
|
-
|
|
28940
|
-
|
|
28941
|
-
|
|
29126
|
+
}
|
|
29127
|
+
formatSuccess(`Generated ${Object.keys(keys).length} keys in ${keysDir}/`, opts);
|
|
29128
|
+
if (!opts.quiet && !opts.json) {
|
|
29129
|
+
process.stdout.write(`${theme.warning("!")} Keep these keys secret. Add ${keysDir}/ to .gitignore.
|
|
28942
29130
|
`);
|
|
28943
|
-
|
|
28944
|
-
}
|
|
28945
|
-
} catch (err) {
|
|
28946
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
28947
|
-
formatError2(new CLIError(`Failed to generate keys: ${message}`, 1 /* ERROR */), opts);
|
|
29131
|
+
formatKeyValue(keys, opts);
|
|
28948
29132
|
}
|
|
28949
|
-
});
|
|
29133
|
+
}, { service: "config" }));
|
|
28950
29134
|
keysCmd.command("list").summary("List existing internal auth keys").description(`List existing internal auth keys from the .keys/ directory.
|
|
28951
29135
|
|
|
28952
29136
|
Shows key names (values are hidden for security).
|
|
28953
29137
|
|
|
28954
29138
|
EXAMPLES:
|
|
28955
|
-
hoox config keys list`).action(async () => {
|
|
28956
|
-
const opts =
|
|
28957
|
-
|
|
28958
|
-
|
|
28959
|
-
|
|
28960
|
-
process.stdout.write(`${theme.dim("No .keys/ directory found.")}
|
|
29139
|
+
hoox config keys list`).action(withErrorHandling(async (_, cmd) => {
|
|
29140
|
+
const opts = getFormatOptions(cmd);
|
|
29141
|
+
const keysDir = ".keys";
|
|
29142
|
+
if (!existsSync3(keysDir)) {
|
|
29143
|
+
process.stdout.write(`${theme.dim("No .keys/ directory found.")}
|
|
28961
29144
|
`);
|
|
28962
|
-
|
|
28963
|
-
|
|
28964
|
-
|
|
28965
|
-
|
|
28966
|
-
|
|
28967
|
-
|
|
28968
|
-
|
|
28969
|
-
|
|
28970
|
-
|
|
29145
|
+
return;
|
|
29146
|
+
}
|
|
29147
|
+
const glob = new Bun.Glob("*.env");
|
|
29148
|
+
const entries = [];
|
|
29149
|
+
for await (const f2 of glob.scan({ cwd: keysDir, absolute: false })) {
|
|
29150
|
+
entries.push(f2);
|
|
29151
|
+
}
|
|
29152
|
+
if (entries.length === 0) {
|
|
29153
|
+
process.stdout.write(`${theme.dim("No key files found in .keys/")}
|
|
28971
29154
|
`);
|
|
28972
|
-
|
|
28973
|
-
|
|
28974
|
-
|
|
28975
|
-
|
|
28976
|
-
|
|
28977
|
-
|
|
28978
|
-
|
|
28979
|
-
|
|
28980
|
-
|
|
28981
|
-
}
|
|
29155
|
+
return;
|
|
29156
|
+
}
|
|
29157
|
+
const keyMap = {};
|
|
29158
|
+
for (const entry of entries) {
|
|
29159
|
+
const filePath = `${keysDir}/${entry}`;
|
|
29160
|
+
const content = await (await Bun.file(filePath).text()).trim();
|
|
29161
|
+
const eqIdx = content.indexOf("=");
|
|
29162
|
+
if (eqIdx > 0) {
|
|
29163
|
+
keyMap[content.substring(0, eqIdx)] = "****";
|
|
28982
29164
|
}
|
|
28983
|
-
|
|
28984
|
-
|
|
28985
|
-
}
|
|
28986
|
-
|
|
29165
|
+
}
|
|
29166
|
+
if (opts.json) {
|
|
29167
|
+
formatJson({ keys: entries.length, files: entries }, opts);
|
|
29168
|
+
} else {
|
|
29169
|
+
process.stdout.write(`${theme.heading(`
|
|
28987
29170
|
Key files in ${keysDir}/`)}
|
|
28988
29171
|
`);
|
|
28989
|
-
|
|
28990
|
-
}
|
|
28991
|
-
} catch (err) {
|
|
28992
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
28993
|
-
formatError2(new CLIError(`Failed to list keys: ${message}`, 1 /* ERROR */), opts);
|
|
29172
|
+
formatKeyValue(keyMap, opts);
|
|
28994
29173
|
}
|
|
28995
|
-
});
|
|
29174
|
+
}, { service: "config" }));
|
|
28996
29175
|
}
|
|
28997
29176
|
function parseValue(raw) {
|
|
28998
29177
|
if (raw === "true")
|
|
@@ -29037,6 +29216,7 @@ async function updateDevVars(filePath, key, value) {
|
|
|
29037
29216
|
// src/commands/check/check-command.ts
|
|
29038
29217
|
import { readdir } from "fs/promises";
|
|
29039
29218
|
import path3 from "path";
|
|
29219
|
+
init_main();
|
|
29040
29220
|
init_config2();
|
|
29041
29221
|
|
|
29042
29222
|
// src/commands/check/prerequisites-command.ts
|
|
@@ -29644,18 +29824,33 @@ async function handleFix(opts, dryRun) {
|
|
|
29644
29824
|
};
|
|
29645
29825
|
if (!dryRun) {
|
|
29646
29826
|
try {
|
|
29647
|
-
const
|
|
29648
|
-
|
|
29649
|
-
if (!
|
|
29650
|
-
|
|
29651
|
-
|
|
29652
|
-
|
|
29827
|
+
const parsedConfig = parse6(content);
|
|
29828
|
+
let updated = content;
|
|
29829
|
+
if (!parsedConfig.compatibility_flags) {
|
|
29830
|
+
const edits = modify(content, ["compatibility_flags"], ["nodejs_compat"], {
|
|
29831
|
+
formattingOptions: {
|
|
29832
|
+
tabSize: 2,
|
|
29833
|
+
insertSpaces: true,
|
|
29834
|
+
eol: `
|
|
29835
|
+
`
|
|
29836
|
+
}
|
|
29837
|
+
});
|
|
29838
|
+
updated = applyEdits(content, edits);
|
|
29839
|
+
} else if (Array.isArray(parsedConfig.compatibility_flags)) {
|
|
29840
|
+
const flags = parsedConfig.compatibility_flags;
|
|
29653
29841
|
if (!flags.includes("nodejs_compat")) {
|
|
29654
|
-
flags
|
|
29842
|
+
const newFlags = [...flags, "nodejs_compat"];
|
|
29843
|
+
const edits = modify(content, ["compatibility_flags"], newFlags, {
|
|
29844
|
+
formattingOptions: {
|
|
29845
|
+
tabSize: 2,
|
|
29846
|
+
insertSpaces: true,
|
|
29847
|
+
eol: `
|
|
29848
|
+
`
|
|
29849
|
+
}
|
|
29850
|
+
});
|
|
29851
|
+
updated = applyEdits(content, edits);
|
|
29655
29852
|
}
|
|
29656
29853
|
}
|
|
29657
|
-
const updated = JSON.stringify(config2, null, 2) + `
|
|
29658
|
-
`;
|
|
29659
29854
|
await Bun.write(wranglerPath, updated);
|
|
29660
29855
|
action.applied = true;
|
|
29661
29856
|
} catch (err) {
|
|
@@ -29674,10 +29869,9 @@ async function handleFix(opts, dryRun) {
|
|
|
29674
29869
|
const wranglerFile = Bun.file(wranglerPath);
|
|
29675
29870
|
if (await wranglerFile.exists()) {
|
|
29676
29871
|
const content = await wranglerFile.text();
|
|
29677
|
-
const strippedContent = content.replace(/\/\/.*$/gm, "").replace(/\/\*[\s\S]*?\*\//g, "");
|
|
29678
29872
|
try {
|
|
29679
|
-
const
|
|
29680
|
-
if (!
|
|
29873
|
+
const parsedConfig = parse6(content);
|
|
29874
|
+
if (!parsedConfig.name) {
|
|
29681
29875
|
const action = {
|
|
29682
29876
|
description: `Add missing "name" field to wrangler.jsonc for worker "${workerName}"`,
|
|
29683
29877
|
type: "config",
|
|
@@ -29686,9 +29880,15 @@ async function handleFix(opts, dryRun) {
|
|
|
29686
29880
|
applied: false
|
|
29687
29881
|
};
|
|
29688
29882
|
if (!dryRun) {
|
|
29689
|
-
|
|
29690
|
-
|
|
29691
|
-
|
|
29883
|
+
const edits = modify(content, ["name"], workerName, {
|
|
29884
|
+
formattingOptions: {
|
|
29885
|
+
tabSize: 2,
|
|
29886
|
+
insertSpaces: true,
|
|
29887
|
+
eol: `
|
|
29888
|
+
`
|
|
29889
|
+
}
|
|
29890
|
+
});
|
|
29891
|
+
const updated = applyEdits(content, edits);
|
|
29692
29892
|
await Bun.write(wranglerPath, updated);
|
|
29693
29893
|
action.applied = true;
|
|
29694
29894
|
}
|
|
@@ -29868,10 +30068,10 @@ OUTPUT:
|
|
|
29868
30068
|
|
|
29869
30069
|
EXAMPLES:
|
|
29870
30070
|
hoox check setup
|
|
29871
|
-
hoox check setup --json`).action(async (_, cmd) => {
|
|
30071
|
+
hoox check setup --json`).action(withErrorHandling(async (_, cmd) => {
|
|
29872
30072
|
const opts = getFormatOptions(cmd);
|
|
29873
30073
|
await handleSetup(opts);
|
|
29874
|
-
});
|
|
30074
|
+
}, { service: "check" }));
|
|
29875
30075
|
checkCmd.command("health").summary("Check worker connectivity and responsiveness").description(`Run health checks on all enabled workers to verify they are running and responsive.
|
|
29876
30076
|
|
|
29877
30077
|
Each worker is probed to verify:
|
|
@@ -29888,10 +30088,10 @@ OUTPUT:
|
|
|
29888
30088
|
EXAMPLES:
|
|
29889
30089
|
hoox check health
|
|
29890
30090
|
hoox check health --json
|
|
29891
|
-
hoox check health --fix`).option("--fix", "Attempt automatic repair for detected issues").action(async (options, cmd) => {
|
|
30091
|
+
hoox check health --fix`).option("--fix", "Attempt automatic repair for detected issues").action(withErrorHandling(async (options, cmd) => {
|
|
29892
30092
|
const opts = getFormatOptions(cmd);
|
|
29893
30093
|
await handleHealth(opts, Boolean(options.fix));
|
|
29894
|
-
});
|
|
30094
|
+
}, { service: "check" }));
|
|
29895
30095
|
checkCmd.command("fix").summary("Auto-repair common issues").description(`Automatically repair known common issues in your Hoox setup.
|
|
29896
30096
|
|
|
29897
30097
|
Repairs performed:
|
|
@@ -29904,10 +30104,10 @@ OPTIONS:
|
|
|
29904
30104
|
|
|
29905
30105
|
EXAMPLES:
|
|
29906
30106
|
hoox check fix Apply fixes automatically
|
|
29907
|
-
hoox check fix --dry-run Preview what would be fixed`).option("--dry-run", "Preview changes without applying them").action(async (options, cmd) => {
|
|
30107
|
+
hoox check fix --dry-run Preview what would be fixed`).option("--dry-run", "Preview changes without applying them").action(withErrorHandling(async (options, cmd) => {
|
|
29908
30108
|
const opts = getFormatOptions(cmd);
|
|
29909
30109
|
await handleFix(opts, Boolean(options.dryRun));
|
|
29910
|
-
});
|
|
30110
|
+
}, { service: "check" }));
|
|
29911
30111
|
registerPrerequisitesCommand(checkCmd);
|
|
29912
30112
|
checkCmd.command("submodule-gitignore").alias("sg").summary("Validate and fix worker submodule .gitignore files").description(`Validate and fix .gitignore files in worker submodules.
|
|
29913
30113
|
|
|
@@ -29922,10 +30122,10 @@ Also removes wrangler.jsonc from git tracking if mistakenly tracked.
|
|
|
29922
30122
|
|
|
29923
30123
|
EXAMPLES:
|
|
29924
30124
|
hoox check submodule-gitignore
|
|
29925
|
-
hoox check sg # alias`).action(async (_, cmd) => {
|
|
30125
|
+
hoox check sg # alias`).action(withErrorHandling(async (_, cmd) => {
|
|
29926
30126
|
const opts = getFormatOptions(cmd);
|
|
29927
30127
|
await handleSubmoduleGitignore(opts);
|
|
29928
|
-
});
|
|
30128
|
+
}, { service: "check" }));
|
|
29929
30129
|
}
|
|
29930
30130
|
// src/commands/logs/logs-command.ts
|
|
29931
30131
|
init_config2();
|
|
@@ -30071,9 +30271,8 @@ async function tailAllWorkers(opts, fmt) {
|
|
|
30071
30271
|
});
|
|
30072
30272
|
}
|
|
30073
30273
|
const decoder = new TextDecoder;
|
|
30074
|
-
const buffers = new Map;
|
|
30075
30274
|
const readFromWorker = async (p2) => {
|
|
30076
|
-
let buffer =
|
|
30275
|
+
let buffer = "";
|
|
30077
30276
|
while (true) {
|
|
30078
30277
|
const { done, value } = await p2.reader.read();
|
|
30079
30278
|
if (done)
|
|
@@ -30112,7 +30311,6 @@ async function tailAllWorkers(opts, fmt) {
|
|
|
30112
30311
|
process.stdout.write(`${theme.bold(`[${p2.name}]`)} ${buffer}
|
|
30113
30312
|
`);
|
|
30114
30313
|
}
|
|
30115
|
-
buffers.set(p2.name, buffer);
|
|
30116
30314
|
};
|
|
30117
30315
|
if (opts.follow) {
|
|
30118
30316
|
await Promise.all(processes.map((p2) => readFromWorker(p2)));
|
|
@@ -30263,34 +30461,26 @@ var PIPELINE_STEPS = [
|
|
|
30263
30461
|
];
|
|
30264
30462
|
function registerTestCommand(program2) {
|
|
30265
30463
|
const testCmd = program2.command("test").description("Run tests and CI pipeline");
|
|
30266
|
-
testCmd.command("all").description("Run full CI pipeline: lint \u2192 typecheck \u2192 unit tests \u2192 integration tests").option("--json", "Output results as JSON").action(async (options) => {
|
|
30464
|
+
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) => {
|
|
30267
30465
|
const fmt = getFormatOptions(program2);
|
|
30268
30466
|
const useJson = options.json || fmt.json;
|
|
30269
30467
|
const opts = { json: useJson, quiet: fmt.quiet };
|
|
30270
30468
|
const results = [];
|
|
30271
30469
|
const s = vt();
|
|
30272
30470
|
s.start("Running CI pipeline...");
|
|
30273
|
-
|
|
30274
|
-
|
|
30275
|
-
|
|
30276
|
-
|
|
30277
|
-
|
|
30278
|
-
|
|
30279
|
-
|
|
30280
|
-
}
|
|
30281
|
-
|
|
30282
|
-
|
|
30283
|
-
s.message(theme.dim(result.error.slice(0, 500)));
|
|
30284
|
-
}
|
|
30285
|
-
break;
|
|
30471
|
+
for (const step of PIPELINE_STEPS) {
|
|
30472
|
+
s.message(`${theme.info(icons.info)} ${step.label}...`);
|
|
30473
|
+
const result = await runStep(step.args, process.cwd());
|
|
30474
|
+
results.push({ ...result, step: step.label });
|
|
30475
|
+
if (result.success) {
|
|
30476
|
+
s.message(` ${theme.success(icons.success)} ${step.label} passed (${result.duration}ms)`);
|
|
30477
|
+
} else {
|
|
30478
|
+
s.message(` ${theme.error(icons.error)} ${step.label} failed (${result.duration}ms)`);
|
|
30479
|
+
if (result.error) {
|
|
30480
|
+
s.message(theme.dim(result.error.slice(0, 500)));
|
|
30286
30481
|
}
|
|
30482
|
+
break;
|
|
30287
30483
|
}
|
|
30288
|
-
} catch (err) {
|
|
30289
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
30290
|
-
formatError2(message, opts);
|
|
30291
|
-
process.exitCode = 1 /* ERROR */;
|
|
30292
|
-
s.stop("Pipeline aborted with unexpected error");
|
|
30293
|
-
return;
|
|
30294
30484
|
}
|
|
30295
30485
|
const summary = {
|
|
30296
30486
|
total: results.length,
|
|
@@ -30303,7 +30493,7 @@ function registerTestCommand(program2) {
|
|
|
30303
30493
|
if (summary.failed > 0) {
|
|
30304
30494
|
process.exitCode = 1 /* ERROR */;
|
|
30305
30495
|
}
|
|
30306
|
-
});
|
|
30496
|
+
}, { service: "test" }));
|
|
30307
30497
|
testCmd.command("unit").description("Run unit tests with bun test").option("--coverage", "Run with coverage reporting").action(async (options) => {
|
|
30308
30498
|
const fmt = getFormatOptions(program2);
|
|
30309
30499
|
const args = ["bun", "test"];
|
|
@@ -30330,55 +30520,41 @@ function registerTestCommand(program2) {
|
|
|
30330
30520
|
process.exitCode = 1 /* ERROR */;
|
|
30331
30521
|
}
|
|
30332
30522
|
});
|
|
30333
|
-
testCmd.command("worker <name>").description("Run tests for a specific worker by name").option("--coverage", "Run with coverage reporting").action(async (name, options) => {
|
|
30523
|
+
testCmd.command("worker <name>").description("Run tests for a specific worker by name").option("--coverage", "Run with coverage reporting").action(withErrorHandling(async (name, options) => {
|
|
30334
30524
|
const fmt = getFormatOptions(program2);
|
|
30335
|
-
|
|
30336
|
-
|
|
30337
|
-
|
|
30338
|
-
|
|
30339
|
-
|
|
30340
|
-
|
|
30341
|
-
|
|
30342
|
-
|
|
30343
|
-
|
|
30344
|
-
|
|
30345
|
-
|
|
30346
|
-
|
|
30347
|
-
|
|
30348
|
-
|
|
30349
|
-
|
|
30350
|
-
|
|
30351
|
-
}
|
|
30352
|
-
formatError2(new CLIError(`Worker "${name}" tests failed (exit code ${result.exitCode})`, 1 /* ERROR */), fmt);
|
|
30353
|
-
process.exitCode = 1 /* ERROR */;
|
|
30354
|
-
}
|
|
30355
|
-
} catch (err) {
|
|
30356
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
30357
|
-
formatError2(message, fmt);
|
|
30525
|
+
const configService = new ConfigService;
|
|
30526
|
+
await configService.load();
|
|
30527
|
+
const workerConfig = configService.getWorker(name);
|
|
30528
|
+
if (!workerConfig) {
|
|
30529
|
+
formatError2(new CLIError(`Worker "${name}" not found in wrangler.jsonc`, 2 /* INVALID_USAGE */), fmt);
|
|
30530
|
+
process.exitCode = 2 /* INVALID_USAGE */;
|
|
30531
|
+
return;
|
|
30532
|
+
}
|
|
30533
|
+
const workerDir = `${process.cwd()}/${workerConfig.path}`;
|
|
30534
|
+
const args = ["bun", "test"];
|
|
30535
|
+
if (options.coverage)
|
|
30536
|
+
args.push("--coverage");
|
|
30537
|
+
const result = await runWithInherit(args, workerDir);
|
|
30538
|
+
if (result.success) {
|
|
30539
|
+
formatSuccess(`Worker "${name}" tests passed`, fmt);
|
|
30540
|
+
} else {
|
|
30541
|
+
formatError2(new CLIError(`Worker "${name}" tests failed (exit code ${result.exitCode})`, 1 /* ERROR */), fmt);
|
|
30358
30542
|
process.exitCode = 1 /* ERROR */;
|
|
30359
30543
|
}
|
|
30360
|
-
});
|
|
30361
|
-
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(async (options) => {
|
|
30362
|
-
const fmt = getFormatOptions(program2);
|
|
30544
|
+
}, { service: "test" }));
|
|
30545
|
+
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) => {
|
|
30363
30546
|
const s = vt();
|
|
30364
|
-
|
|
30365
|
-
|
|
30366
|
-
|
|
30367
|
-
|
|
30368
|
-
|
|
30369
|
-
|
|
30370
|
-
|
|
30371
|
-
|
|
30372
|
-
s.stop(`Live tests: some failures (exit code ${result.exitCode})`);
|
|
30373
|
-
process.exitCode = 1 /* ERROR */;
|
|
30374
|
-
}
|
|
30375
|
-
} catch (err) {
|
|
30376
|
-
s.stop("Live tests aborted");
|
|
30377
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
30378
|
-
formatError2(message, fmt);
|
|
30547
|
+
let filePattern = options.service ? `tests/live/${options.service}.test.ts` : "tests/live/";
|
|
30548
|
+
s.start(`Running live tests${options.service ? ` for ${options.service}` : ""}...`);
|
|
30549
|
+
const args = ["bun", "test", filePattern, "--jobs", "1"];
|
|
30550
|
+
const result = await runWithInherit(args, process.cwd());
|
|
30551
|
+
if (result.success) {
|
|
30552
|
+
s.stop("Live tests complete");
|
|
30553
|
+
} else {
|
|
30554
|
+
s.stop(`Live tests: some failures (exit code ${result.exitCode})`);
|
|
30379
30555
|
process.exitCode = 1 /* ERROR */;
|
|
30380
30556
|
}
|
|
30381
|
-
});
|
|
30557
|
+
}, { service: "test" }));
|
|
30382
30558
|
}
|
|
30383
30559
|
// src/commands/waf/waf-command.ts
|
|
30384
30560
|
var CF_API_BASE = "https://api.cloudflare.com/client/v4";
|
|
@@ -30600,29 +30776,29 @@ async function handleMode(mode, opts) {
|
|
|
30600
30776
|
function registerWafCommand(program2) {
|
|
30601
30777
|
const waf = program2.command("waf").description("Manage Cloudflare WAF (Web Application Firewall)");
|
|
30602
30778
|
waf.command("status").description("Show WAF status (enabled/disabled, active rules, recent blocks)").action(withErrorHandling(async (_, cmd) => {
|
|
30603
|
-
const opts = cmd
|
|
30779
|
+
const opts = getFormatOptions(cmd);
|
|
30604
30780
|
await handleStatus(opts);
|
|
30605
30781
|
}, { service: "waf" }));
|
|
30606
30782
|
const rules = waf.command("rules").description("Manage WAF firewall rules");
|
|
30607
30783
|
rules.command("list").description("List all WAF firewall rules").action(withErrorHandling(async (_, cmd) => {
|
|
30608
|
-
const opts = cmd
|
|
30784
|
+
const opts = getFormatOptions(cmd);
|
|
30609
30785
|
await handleRulesList(opts);
|
|
30610
30786
|
}, { service: "waf" }));
|
|
30611
30787
|
rules.command("add <type> <value>").description("Add a WAF rule. Types: ip-allowlist, ip-blocklist, rate-limit, custom").action(withErrorHandling(async (type, value, _, cmd) => {
|
|
30612
|
-
const opts = cmd
|
|
30788
|
+
const opts = getFormatOptions(cmd);
|
|
30613
30789
|
await handleRulesAdd(type, value, opts);
|
|
30614
30790
|
}, { service: "waf" }));
|
|
30615
30791
|
rules.command("remove <ruleId>").description("Remove a WAF rule by ID").action(withErrorHandling(async (ruleId, _, cmd) => {
|
|
30616
|
-
const opts = cmd
|
|
30792
|
+
const opts = getFormatOptions(cmd);
|
|
30617
30793
|
await handleRulesRemove(ruleId, opts);
|
|
30618
30794
|
}, { service: "waf" }));
|
|
30619
30795
|
const mode = waf.command("mode").description("Enable or disable WAF protection");
|
|
30620
30796
|
mode.command("enable").description("Enable WAF protection on the zone").action(withErrorHandling(async (_, cmd) => {
|
|
30621
|
-
const opts = cmd
|
|
30797
|
+
const opts = getFormatOptions(cmd);
|
|
30622
30798
|
await handleMode("on", opts);
|
|
30623
30799
|
}, { service: "waf" }));
|
|
30624
30800
|
mode.command("disable").description("Disable WAF protection on the zone").action(withErrorHandling(async (_, cmd) => {
|
|
30625
|
-
const opts = cmd
|
|
30801
|
+
const opts = getFormatOptions(cmd);
|
|
30626
30802
|
await handleMode("off", opts);
|
|
30627
30803
|
}, { service: "waf" }));
|
|
30628
30804
|
}
|
|
@@ -30726,129 +30902,123 @@ EXAMPLES:
|
|
|
30726
30902
|
hoox clone List all workers' clone status
|
|
30727
30903
|
hoox clone --all Clone all workers
|
|
30728
30904
|
hoox clone trade-worker Clone specific worker
|
|
30729
|
-
hoox clone --org my-org Clone from specific org`).option("--all", "Clone all worker repositories").option("--org <org>", "GitHub organization (derived from git remote by default)").argument("[name]", "Worker name to clone (omit to list status)").action(async (name, options) => {
|
|
30905
|
+
hoox clone --org my-org Clone from specific org`).option("--all", "Clone all worker repositories").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) => {
|
|
30730
30906
|
const fmt = getFormatOptions(program2);
|
|
30731
|
-
|
|
30732
|
-
|
|
30733
|
-
|
|
30734
|
-
|
|
30735
|
-
|
|
30736
|
-
|
|
30737
|
-
|
|
30738
|
-
|
|
30739
|
-
|
|
30740
|
-
|
|
30741
|
-
|
|
30742
|
-
|
|
30743
|
-
|
|
30744
|
-
process.stdout.write(`${s.worker}: ${s.cloned ? "cloned" : "not cloned"}
|
|
30907
|
+
const configService = new ConfigService;
|
|
30908
|
+
await configService.load();
|
|
30909
|
+
const workers = configService.listWorkers();
|
|
30910
|
+
if (!options.all && !name) {
|
|
30911
|
+
const repoBase = options.org ? `https://github.com/${options.org}` : await resolveRepoBase();
|
|
30912
|
+
const statuses = await buildStatusList(configService, repoBase);
|
|
30913
|
+
if (fmt.json) {
|
|
30914
|
+
formatJson(statuses, fmt);
|
|
30915
|
+
return;
|
|
30916
|
+
}
|
|
30917
|
+
if (fmt.quiet) {
|
|
30918
|
+
for (const s of statuses) {
|
|
30919
|
+
process.stdout.write(`${s.worker}: ${s.cloned ? "cloned" : "not cloned"}
|
|
30745
30920
|
`);
|
|
30746
|
-
}
|
|
30747
|
-
return;
|
|
30748
30921
|
}
|
|
30749
|
-
const rows = statuses.map((s) => ({
|
|
30750
|
-
Worker: s.worker,
|
|
30751
|
-
Status: s.cloned ? "cloned" : "not cloned",
|
|
30752
|
-
Repo: s.repo ?? "-"
|
|
30753
|
-
}));
|
|
30754
|
-
formatTable(rows, fmt);
|
|
30755
30922
|
return;
|
|
30756
30923
|
}
|
|
30757
|
-
|
|
30758
|
-
|
|
30759
|
-
|
|
30760
|
-
|
|
30761
|
-
|
|
30762
|
-
|
|
30763
|
-
|
|
30764
|
-
|
|
30765
|
-
|
|
30766
|
-
|
|
30767
|
-
|
|
30768
|
-
const workerConfig = configService.getWorker(name2);
|
|
30769
|
-
if (!workerConfig)
|
|
30770
|
-
continue;
|
|
30771
|
-
const alreadyCloned = await isWorkerCloned(workerConfig.path);
|
|
30772
|
-
if (alreadyCloned) {
|
|
30773
|
-
s.message(`[${i + 1}/${workers.length}] ${icons.info} ${name2} already cloned \u2014 skipping`);
|
|
30774
|
-
results.push({
|
|
30775
|
-
worker: name2,
|
|
30776
|
-
path: workerConfig.path,
|
|
30777
|
-
cloned: true,
|
|
30778
|
-
repo: getRepoUrl(repoBase, name2)
|
|
30779
|
-
});
|
|
30780
|
-
continue;
|
|
30781
|
-
}
|
|
30782
|
-
s.message(`[${i + 1}/${workers.length}] Cloning ${name2}...`);
|
|
30783
|
-
const result = await cloneWorker(getRepoUrl(repoBase, name2), workerConfig.path, name2, process.cwd());
|
|
30784
|
-
results.push(result);
|
|
30785
|
-
if (result.cloned) {
|
|
30786
|
-
s.message(`[${i + 1}/${workers.length}] ${icons.success} ${name2} cloned`);
|
|
30787
|
-
} else {
|
|
30788
|
-
s.message(`[${i + 1}/${workers.length}] ${icons.error} ${name2} failed: ${result.error}`);
|
|
30789
|
-
}
|
|
30790
|
-
}
|
|
30791
|
-
try {
|
|
30792
|
-
s.message("Updating submodules...");
|
|
30793
|
-
await updateSubmodules(process.cwd());
|
|
30794
|
-
} catch (updateErr) {
|
|
30795
|
-
const msg = updateErr instanceof Error ? updateErr.message : String(updateErr);
|
|
30796
|
-
s.message(`Warning: submodule update failed: ${msg}`);
|
|
30797
|
-
}
|
|
30798
|
-
const succeeded = results.filter((r) => r.cloned).length;
|
|
30799
|
-
const failed = results.filter((r) => !r.cloned).length;
|
|
30800
|
-
if (failed > 0) {
|
|
30801
|
-
s.stop(`Clone complete: ${succeeded} succeeded, ${failed} failed`);
|
|
30802
|
-
process.exitCode = 1 /* ERROR */;
|
|
30803
|
-
} else {
|
|
30804
|
-
s.stop(`All ${succeeded} worker(s) cloned successfully`);
|
|
30805
|
-
}
|
|
30806
|
-
if (!fmt.quiet) {
|
|
30807
|
-
const rows = results.map((r) => ({
|
|
30808
|
-
Worker: r.worker,
|
|
30809
|
-
Status: r.cloned ? "cloned" : "failed",
|
|
30810
|
-
Repo: r.repo ?? "-"
|
|
30811
|
-
}));
|
|
30812
|
-
formatTable(rows, { json: fmt.json, quiet: false });
|
|
30813
|
-
}
|
|
30924
|
+
const rows = statuses.map((s) => ({
|
|
30925
|
+
Worker: s.worker,
|
|
30926
|
+
Status: s.cloned ? "cloned" : "not cloned",
|
|
30927
|
+
Repo: s.repo ?? "-"
|
|
30928
|
+
}));
|
|
30929
|
+
formatTable(rows, fmt);
|
|
30930
|
+
return;
|
|
30931
|
+
}
|
|
30932
|
+
if (options.all) {
|
|
30933
|
+
if (workers.length === 0) {
|
|
30934
|
+
formatSuccess("No workers defined in wrangler.jsonc", fmt);
|
|
30814
30935
|
return;
|
|
30815
30936
|
}
|
|
30816
|
-
|
|
30817
|
-
|
|
30818
|
-
|
|
30819
|
-
|
|
30820
|
-
|
|
30821
|
-
|
|
30822
|
-
|
|
30937
|
+
const repoBase = options.org ? `https://github.com/${options.org}` : await resolveRepoBase();
|
|
30938
|
+
const s = vt();
|
|
30939
|
+
s.start(`Cloning ${workers.length} worker(s)...`);
|
|
30940
|
+
const results = [];
|
|
30941
|
+
for (let i = 0;i < workers.length; i++) {
|
|
30942
|
+
const name2 = workers[i];
|
|
30943
|
+
const workerConfig = configService.getWorker(name2);
|
|
30944
|
+
if (!workerConfig)
|
|
30945
|
+
continue;
|
|
30823
30946
|
const alreadyCloned = await isWorkerCloned(workerConfig.path);
|
|
30824
30947
|
if (alreadyCloned) {
|
|
30825
|
-
|
|
30826
|
-
|
|
30948
|
+
s.message(`[${i + 1}/${workers.length}] ${icons.info} ${name2} already cloned \u2014 skipping`);
|
|
30949
|
+
results.push({
|
|
30950
|
+
worker: name2,
|
|
30951
|
+
path: workerConfig.path,
|
|
30952
|
+
cloned: true,
|
|
30953
|
+
repo: getRepoUrl(repoBase, name2)
|
|
30954
|
+
});
|
|
30955
|
+
continue;
|
|
30827
30956
|
}
|
|
30828
|
-
|
|
30829
|
-
const
|
|
30830
|
-
|
|
30831
|
-
const result = await cloneWorker(getRepoUrl(repoBase, name), workerConfig.path, name, process.cwd());
|
|
30957
|
+
s.message(`[${i + 1}/${workers.length}] Cloning ${name2}...`);
|
|
30958
|
+
const result = await cloneWorker(getRepoUrl(repoBase, name2), workerConfig.path, name2, process.cwd());
|
|
30959
|
+
results.push(result);
|
|
30832
30960
|
if (result.cloned) {
|
|
30833
|
-
|
|
30834
|
-
s.message("Updating submodules...");
|
|
30835
|
-
await updateSubmodules(process.cwd());
|
|
30836
|
-
} catch {}
|
|
30837
|
-
s.stop(`Successfully cloned ${name}`);
|
|
30838
|
-
formatSuccess(`Cloned ${name} from ${result.repo}`, fmt);
|
|
30961
|
+
s.message(`[${i + 1}/${workers.length}] ${icons.success} ${name2} cloned`);
|
|
30839
30962
|
} else {
|
|
30840
|
-
s.
|
|
30841
|
-
formatError2(new CLIError(`Failed to clone "${name}": ${result.error}`, 1 /* ERROR */), fmt);
|
|
30842
|
-
process.exitCode = 1 /* ERROR */;
|
|
30963
|
+
s.message(`[${i + 1}/${workers.length}] ${icons.error} ${name2} failed: ${result.error}`);
|
|
30843
30964
|
}
|
|
30965
|
+
}
|
|
30966
|
+
try {
|
|
30967
|
+
s.message("Updating submodules...");
|
|
30968
|
+
await updateSubmodules(process.cwd());
|
|
30969
|
+
} catch (updateErr) {
|
|
30970
|
+
const msg = updateErr instanceof Error ? updateErr.message : String(updateErr);
|
|
30971
|
+
s.message(`Warning: submodule update failed: ${msg}`);
|
|
30972
|
+
}
|
|
30973
|
+
const succeeded = results.filter((r) => r.cloned).length;
|
|
30974
|
+
const failed = results.filter((r) => !r.cloned).length;
|
|
30975
|
+
if (failed > 0) {
|
|
30976
|
+
s.stop(`Clone complete: ${succeeded} succeeded, ${failed} failed`);
|
|
30977
|
+
process.exitCode = 1 /* ERROR */;
|
|
30978
|
+
} else {
|
|
30979
|
+
s.stop(`All ${succeeded} worker(s) cloned successfully`);
|
|
30980
|
+
}
|
|
30981
|
+
if (!fmt.quiet) {
|
|
30982
|
+
const rows = results.map((r) => ({
|
|
30983
|
+
Worker: r.worker,
|
|
30984
|
+
Status: r.cloned ? "cloned" : "failed",
|
|
30985
|
+
Repo: r.repo ?? "-"
|
|
30986
|
+
}));
|
|
30987
|
+
formatTable(rows, { json: fmt.json, quiet: false });
|
|
30988
|
+
}
|
|
30989
|
+
return;
|
|
30990
|
+
}
|
|
30991
|
+
if (name) {
|
|
30992
|
+
const workerConfig = configService.getWorker(name);
|
|
30993
|
+
if (!workerConfig) {
|
|
30994
|
+
formatError2(new CLIError(`Worker "${name}" not found in wrangler.jsonc`, 1 /* ERROR */), fmt);
|
|
30995
|
+
process.exitCode = 1 /* ERROR */;
|
|
30844
30996
|
return;
|
|
30845
30997
|
}
|
|
30846
|
-
|
|
30847
|
-
|
|
30848
|
-
|
|
30849
|
-
|
|
30998
|
+
const alreadyCloned = await isWorkerCloned(workerConfig.path);
|
|
30999
|
+
if (alreadyCloned) {
|
|
31000
|
+
formatSuccess(`Worker "${name}" is already cloned at ${workerConfig.path}`, fmt);
|
|
31001
|
+
return;
|
|
31002
|
+
}
|
|
31003
|
+
const repoBase = options.org ? `https://github.com/${options.org}` : await resolveRepoBase();
|
|
31004
|
+
const s = vt();
|
|
31005
|
+
s.start(`Cloning ${name}...`);
|
|
31006
|
+
const result = await cloneWorker(getRepoUrl(repoBase, name), workerConfig.path, name, process.cwd());
|
|
31007
|
+
if (result.cloned) {
|
|
31008
|
+
try {
|
|
31009
|
+
s.message("Updating submodules...");
|
|
31010
|
+
await updateSubmodules(process.cwd());
|
|
31011
|
+
} catch {}
|
|
31012
|
+
s.stop(`Successfully cloned ${name}`);
|
|
31013
|
+
formatSuccess(`Cloned ${name} from ${result.repo}`, fmt);
|
|
31014
|
+
} else {
|
|
31015
|
+
s.stop(`Failed to clone ${name}`);
|
|
31016
|
+
formatError2(new CLIError(`Failed to clone "${name}": ${result.error}`, 1 /* ERROR */), fmt);
|
|
31017
|
+
process.exitCode = 1 /* ERROR */;
|
|
31018
|
+
}
|
|
31019
|
+
return;
|
|
30850
31020
|
}
|
|
30851
|
-
});
|
|
31021
|
+
}, { service: "clone" }));
|
|
30852
31022
|
}
|
|
30853
31023
|
// src/commands/dashboard/dashboard-command.ts
|
|
30854
31024
|
init_config2();
|
|
@@ -30892,8 +31062,9 @@ function updateWranglerVars(filePath, urls, dryRun, opts) {
|
|
|
30892
31062
|
Old: c.oldValue,
|
|
30893
31063
|
New: theme.success(c.newValue)
|
|
30894
31064
|
}));
|
|
30895
|
-
|
|
30896
|
-
Service URL changes:`)
|
|
31065
|
+
process.stdout.write(theme.heading(`
|
|
31066
|
+
Service URL changes:`) + `
|
|
31067
|
+
`);
|
|
30897
31068
|
formatTable(rows, opts);
|
|
30898
31069
|
}
|
|
30899
31070
|
if (dryRun) {
|
|
@@ -30911,6 +31082,7 @@ Service URL changes:`));
|
|
|
30911
31082
|
function registerDashboardCommand(program2) {
|
|
30912
31083
|
const dashboardCmd = program2.command("dashboard").description("Dashboard-specific operations");
|
|
30913
31084
|
dashboardCmd.command("update-urls").description("Update dashboard wrangler.jsonc with current service URLs").option("--dry-run", "Show changes without applying").action(withErrorHandling(async (options) => {
|
|
31085
|
+
process.stdout.write(theme.warning("! This command is deprecated. Use `hoox deploy update-internal-urls` instead.\n"));
|
|
30914
31086
|
const opts = {
|
|
30915
31087
|
json: program2.opts().json,
|
|
30916
31088
|
quiet: program2.opts().quiet
|
|
@@ -31032,20 +31204,36 @@ async function resolveDb(cmd, svc) {
|
|
|
31032
31204
|
}
|
|
31033
31205
|
async function handleApply(opts, dbName, remote, file2) {
|
|
31034
31206
|
const svc = new DbService;
|
|
31035
|
-
const
|
|
31036
|
-
|
|
31037
|
-
|
|
31038
|
-
|
|
31207
|
+
const s = vt();
|
|
31208
|
+
s.start("Applying schema...");
|
|
31209
|
+
try {
|
|
31210
|
+
const output = await svc.apply(dbName, remote, file2);
|
|
31211
|
+
s.stop("Schema applied");
|
|
31212
|
+
formatSuccess(`Schema applied to ${dbName}${remote ? " (remote)" : " (local)"}`, opts);
|
|
31213
|
+
if (!opts.quiet && output) {
|
|
31214
|
+
process.stdout.write(`${output}
|
|
31039
31215
|
`);
|
|
31216
|
+
}
|
|
31217
|
+
} catch (err) {
|
|
31218
|
+
s.stop("Schema apply failed");
|
|
31219
|
+
throw err;
|
|
31040
31220
|
}
|
|
31041
31221
|
}
|
|
31042
31222
|
async function handleMigrate(opts, dbName, remote) {
|
|
31043
31223
|
const svc = new DbService;
|
|
31044
|
-
const
|
|
31045
|
-
|
|
31046
|
-
|
|
31047
|
-
|
|
31224
|
+
const s = vt();
|
|
31225
|
+
s.start("Running migrations...");
|
|
31226
|
+
try {
|
|
31227
|
+
const output = await svc.migrate(dbName, remote);
|
|
31228
|
+
s.stop("Migrations complete");
|
|
31229
|
+
formatSuccess(`Migrations applied to ${dbName}${remote ? " (remote)" : " (local)"}`, opts);
|
|
31230
|
+
if (!opts.quiet && output) {
|
|
31231
|
+
process.stdout.write(`${output}
|
|
31048
31232
|
`);
|
|
31233
|
+
}
|
|
31234
|
+
} catch (err) {
|
|
31235
|
+
s.stop("Migrations failed");
|
|
31236
|
+
throw err;
|
|
31049
31237
|
}
|
|
31050
31238
|
}
|
|
31051
31239
|
async function handleList2(opts, dbName, remote) {
|
|
@@ -31081,8 +31269,16 @@ async function handleQuery(opts, dbName, sql, remote) {
|
|
|
31081
31269
|
}
|
|
31082
31270
|
async function handleExport(opts, dbName, outputPath) {
|
|
31083
31271
|
const svc = new DbService;
|
|
31084
|
-
const
|
|
31085
|
-
|
|
31272
|
+
const s = vt();
|
|
31273
|
+
s.start("Exporting database...");
|
|
31274
|
+
try {
|
|
31275
|
+
const path4 = await svc.export(dbName, outputPath);
|
|
31276
|
+
s.stop("Database exported");
|
|
31277
|
+
formatSuccess(`Database exported to ${path4}`, opts);
|
|
31278
|
+
} catch (err) {
|
|
31279
|
+
s.stop("Export failed");
|
|
31280
|
+
throw err;
|
|
31281
|
+
}
|
|
31086
31282
|
}
|
|
31087
31283
|
async function handleReset(opts, dbName, confirmed) {
|
|
31088
31284
|
if (!confirmed) {
|
|
@@ -31096,11 +31292,19 @@ async function handleReset(opts, dbName, confirmed) {
|
|
|
31096
31292
|
}
|
|
31097
31293
|
}
|
|
31098
31294
|
const svc = new DbService;
|
|
31099
|
-
const
|
|
31100
|
-
|
|
31101
|
-
|
|
31102
|
-
|
|
31295
|
+
const s = vt();
|
|
31296
|
+
s.start("Resetting database...");
|
|
31297
|
+
try {
|
|
31298
|
+
const output = await svc.reset(dbName);
|
|
31299
|
+
s.stop("Database reset");
|
|
31300
|
+
formatSuccess(`Database "${dbName}" has been recreated`, opts);
|
|
31301
|
+
if (!opts.quiet && output) {
|
|
31302
|
+
process.stdout.write(`${output}
|
|
31103
31303
|
`);
|
|
31304
|
+
}
|
|
31305
|
+
} catch (err) {
|
|
31306
|
+
s.stop("Reset failed");
|
|
31307
|
+
throw err;
|
|
31104
31308
|
}
|
|
31105
31309
|
}
|
|
31106
31310
|
function registerDbCommand(program2) {
|
|
@@ -31266,7 +31470,10 @@ async function doMonitorLogs(workerName, fmt) {
|
|
|
31266
31470
|
const dbName = await db.resolveDbName();
|
|
31267
31471
|
let sql;
|
|
31268
31472
|
if (workerName) {
|
|
31269
|
-
|
|
31473
|
+
if (!/^[a-zA-Z0-9_-]+$/.test(workerName)) {
|
|
31474
|
+
throw new Error(`Invalid worker name: "${workerName}"`);
|
|
31475
|
+
}
|
|
31476
|
+
sql = `SELECT * FROM system_logs WHERE worker = '${workerName}' ORDER BY timestamp DESC LIMIT 20`;
|
|
31270
31477
|
} else {
|
|
31271
31478
|
sql = "SELECT * FROM system_logs ORDER BY timestamp DESC LIMIT 20";
|
|
31272
31479
|
}
|
|
@@ -31317,6 +31524,66 @@ async function doMonitorQueueDepth(fmt) {
|
|
|
31317
31524
|
process.exitCode = 1 /* ERROR */;
|
|
31318
31525
|
}
|
|
31319
31526
|
}
|
|
31527
|
+
async function doMonitorAnalyticsSummary(fmt) {
|
|
31528
|
+
try {
|
|
31529
|
+
const db = new DbService;
|
|
31530
|
+
const dbName = await db.resolveDbName();
|
|
31531
|
+
const sql = "SELECT COUNT(*) as total_events, MIN(timestamp) as earliest, MAX(timestamp) as latest FROM system_logs";
|
|
31532
|
+
const output = await db.query(dbName, sql, true);
|
|
31533
|
+
if (fmt.json) {
|
|
31534
|
+
process.stdout.write(output + `
|
|
31535
|
+
`);
|
|
31536
|
+
return;
|
|
31537
|
+
}
|
|
31538
|
+
const parsed = JSON.parse(output);
|
|
31539
|
+
const results = parsed[0]?.results;
|
|
31540
|
+
if (results && results.length > 0) {
|
|
31541
|
+
const row = results[0];
|
|
31542
|
+
const rows = [
|
|
31543
|
+
{
|
|
31544
|
+
"Total Events": String(row.total_events ?? "-"),
|
|
31545
|
+
Earliest: String(row.earliest ?? "-"),
|
|
31546
|
+
Latest: String(row.latest ?? "-")
|
|
31547
|
+
}
|
|
31548
|
+
];
|
|
31549
|
+
formatTable(rows, fmt);
|
|
31550
|
+
} else {
|
|
31551
|
+
process.stdout.write(`No analytics data found.
|
|
31552
|
+
`);
|
|
31553
|
+
}
|
|
31554
|
+
} catch (err) {
|
|
31555
|
+
formatError2(err instanceof Error ? err.message : String(err), fmt);
|
|
31556
|
+
process.exitCode = 1 /* ERROR */;
|
|
31557
|
+
}
|
|
31558
|
+
}
|
|
31559
|
+
async function doMonitorAnalyticsErrors(hours, fmt) {
|
|
31560
|
+
try {
|
|
31561
|
+
const db = new DbService;
|
|
31562
|
+
const dbName = await db.resolveDbName();
|
|
31563
|
+
const sql = `SELECT level, COUNT(*) as count FROM system_logs WHERE level IN ('error', 'warn') AND timestamp > unixepoch('now', '-${hours} hours') GROUP BY level`;
|
|
31564
|
+
const output = await db.query(dbName, sql, true);
|
|
31565
|
+
if (fmt.json) {
|
|
31566
|
+
process.stdout.write(output + `
|
|
31567
|
+
`);
|
|
31568
|
+
return;
|
|
31569
|
+
}
|
|
31570
|
+
const parsed = JSON.parse(output);
|
|
31571
|
+
const results = parsed[0]?.results;
|
|
31572
|
+
if (results && results.length > 0) {
|
|
31573
|
+
const rows = results.map((r) => ({
|
|
31574
|
+
Level: String(r.level ?? "-"),
|
|
31575
|
+
Count: String(r.count ?? "0")
|
|
31576
|
+
}));
|
|
31577
|
+
formatTable(rows, fmt);
|
|
31578
|
+
} else {
|
|
31579
|
+
process.stdout.write(`No errors or warnings in the last ${hours} hours.
|
|
31580
|
+
`);
|
|
31581
|
+
}
|
|
31582
|
+
} catch (err) {
|
|
31583
|
+
formatError2(err instanceof Error ? err.message : String(err), fmt);
|
|
31584
|
+
process.exitCode = 1 /* ERROR */;
|
|
31585
|
+
}
|
|
31586
|
+
}
|
|
31320
31587
|
async function doMonitorBackup(fmt) {
|
|
31321
31588
|
try {
|
|
31322
31589
|
const db = new DbService;
|
|
@@ -31338,54 +31605,177 @@ SUBCOMMANDS:
|
|
|
31338
31605
|
kill-switch Emergency stop/resume trading
|
|
31339
31606
|
queue-depth List queues and pending messages
|
|
31340
31607
|
backup Export D1 database to timestamped .sql file
|
|
31608
|
+
analytics Query analytics data from D1
|
|
31341
31609
|
|
|
31342
31610
|
EXAMPLES:
|
|
31343
|
-
hoox monitor status
|
|
31344
|
-
hoox monitor trades 20
|
|
31345
|
-
hoox monitor logs hoox
|
|
31346
|
-
hoox monitor kill-switch show
|
|
31347
|
-
hoox monitor kill-switch on
|
|
31348
|
-
hoox monitor kill-switch off
|
|
31349
|
-
hoox monitor queue-depth
|
|
31350
|
-
hoox monitor backup
|
|
31351
|
-
|
|
31352
|
-
|
|
31611
|
+
hoox monitor status Check all workers health
|
|
31612
|
+
hoox monitor trades 20 Show 20 recent trades
|
|
31613
|
+
hoox monitor logs hoox Show logs for hoox worker
|
|
31614
|
+
hoox monitor kill-switch show Check kill switch status
|
|
31615
|
+
hoox monitor kill-switch on Halt all trading
|
|
31616
|
+
hoox monitor kill-switch off Resume trading
|
|
31617
|
+
hoox monitor queue-depth Show queue info
|
|
31618
|
+
hoox monitor backup Export D1 database
|
|
31619
|
+
hoox monitor analytics summary Show event rollup statistics
|
|
31620
|
+
hoox monitor analytics errors Show error/warning counts`);
|
|
31621
|
+
monitorCmd.command("status").summary("Check health of all workers").description("Probe each worker's /health endpoint and report status.").action(withErrorHandling(async (_, cmd) => {
|
|
31622
|
+
const fmt = getFormatOptions(cmd);
|
|
31353
31623
|
await doMonitorStatus(fmt);
|
|
31354
|
-
});
|
|
31355
|
-
monitorCmd.command("trades").summary("Show recent trades from D1").description("Query the trades table for the most recent entries.").argument("[limit]", "Number of trades to show (default: 10, max: 100)", "10").action(async
|
|
31356
|
-
const fmt = getFormatOptions(
|
|
31624
|
+
}, { service: "monitor" }));
|
|
31625
|
+
monitorCmd.command("trades").summary("Show recent trades from D1").description("Query the trades table for the most recent entries.").argument("[limit]", "Number of trades to show (default: 10, max: 100)", "10").action(withErrorHandling(async (limit, _, cmd) => {
|
|
31626
|
+
const fmt = getFormatOptions(cmd);
|
|
31357
31627
|
await doMonitorTrades(parseInt(limit, 10) || 10, fmt);
|
|
31358
|
-
});
|
|
31359
|
-
monitorCmd.command("logs").summary("Show recent system logs from D1").description("Query the system_logs table. Optionally filter by worker name.").argument("[worker]", "Worker name to filter (optional)").action(async
|
|
31360
|
-
const fmt = getFormatOptions(
|
|
31628
|
+
}, { service: "monitor" }));
|
|
31629
|
+
monitorCmd.command("logs").summary("Show recent system logs from D1").description("Query the system_logs table. Optionally filter by worker name.").argument("[worker]", "Worker name to filter (optional)").action(withErrorHandling(async (worker, _, cmd) => {
|
|
31630
|
+
const fmt = getFormatOptions(cmd);
|
|
31361
31631
|
await doMonitorLogs(worker, fmt);
|
|
31362
|
-
});
|
|
31632
|
+
}, { service: "monitor" }));
|
|
31363
31633
|
const ksCmd = monitorCmd.command("kill-switch").summary("Emergency stop or resume trading").description(`Control the trade:kill_switch KV key.
|
|
31364
31634
|
|
|
31365
31635
|
Commands:
|
|
31366
31636
|
show Display current kill switch status
|
|
31367
31637
|
on Halt all trading (set kill_switch=true)
|
|
31368
31638
|
off Resume trading (set kill_switch=false)`);
|
|
31369
|
-
ksCmd.command("show").summary("Show kill switch status").description("Display whether trading is halted or active.").action(async
|
|
31370
|
-
const fmt = getFormatOptions(
|
|
31639
|
+
ksCmd.command("show").summary("Show kill switch status").description("Display whether trading is halted or active.").action(withErrorHandling(async (_, cmd) => {
|
|
31640
|
+
const fmt = getFormatOptions(cmd);
|
|
31371
31641
|
await doMonitorKillSwitch("show", fmt);
|
|
31372
|
-
});
|
|
31373
|
-
ksCmd.command("on").summary("Halt all trading").description("Set trade:kill_switch=true to stop all trading operations. WARNING: This halts ALL trading activity immediately.").action(async
|
|
31374
|
-
const fmt = getFormatOptions(
|
|
31642
|
+
}, { service: "monitor" }));
|
|
31643
|
+
ksCmd.command("on").summary("Halt all trading").description("Set trade:kill_switch=true to stop all trading operations. WARNING: This halts ALL trading activity immediately.").action(withErrorHandling(async (_, cmd) => {
|
|
31644
|
+
const fmt = getFormatOptions(cmd);
|
|
31375
31645
|
await doMonitorKillSwitch("on", fmt);
|
|
31376
|
-
});
|
|
31377
|
-
ksCmd.command("off").summary("Resume trading").description("Set trade:kill_switch=false to resume normal trading operations.").action(async
|
|
31378
|
-
const fmt = getFormatOptions(
|
|
31646
|
+
}, { service: "monitor" }));
|
|
31647
|
+
ksCmd.command("off").summary("Resume trading").description("Set trade:kill_switch=false to resume normal trading operations.").action(withErrorHandling(async (_, cmd) => {
|
|
31648
|
+
const fmt = getFormatOptions(cmd);
|
|
31379
31649
|
await doMonitorKillSwitch("off", fmt);
|
|
31380
|
-
});
|
|
31381
|
-
monitorCmd.command("queue-depth").summary("Show queue details").description("List queues via wrangler queues list to show configured queues.").action(async
|
|
31382
|
-
const fmt = getFormatOptions(
|
|
31650
|
+
}, { service: "monitor" }));
|
|
31651
|
+
monitorCmd.command("queue-depth").summary("Show queue details").description("List queues via wrangler queues list to show configured queues.").action(withErrorHandling(async (_, cmd) => {
|
|
31652
|
+
const fmt = getFormatOptions(cmd);
|
|
31383
31653
|
await doMonitorQueueDepth(fmt);
|
|
31384
|
-
});
|
|
31385
|
-
monitorCmd.command("backup").summary("Export D1 database to .sql file").description("Export the D1 database to a timestamped .sql backup file via wrangler d1 export.").action(async
|
|
31386
|
-
const fmt = getFormatOptions(
|
|
31654
|
+
}, { service: "monitor" }));
|
|
31655
|
+
monitorCmd.command("backup").summary("Export D1 database to .sql file").description("Export the D1 database to a timestamped .sql backup file via wrangler d1 export.").action(withErrorHandling(async (_, cmd) => {
|
|
31656
|
+
const fmt = getFormatOptions(cmd);
|
|
31387
31657
|
await doMonitorBackup(fmt);
|
|
31658
|
+
}, { service: "monitor" }));
|
|
31659
|
+
const analyticsCmd = monitorCmd.command("analytics").summary("Query analytics data from D1").description(`Run analytical queries against the system_logs table.
|
|
31660
|
+
|
|
31661
|
+
SUBCOMMANDS:
|
|
31662
|
+
summary Show rollup statistics of system events
|
|
31663
|
+
errors Show error/warning counts by level`);
|
|
31664
|
+
analyticsCmd.command("summary").summary("Show event rollup statistics").description("Query system_logs for total events, earliest and latest timestamps.").action(withErrorHandling(async (_, cmd) => {
|
|
31665
|
+
const fmt = getFormatOptions(cmd);
|
|
31666
|
+
await doMonitorAnalyticsSummary(fmt);
|
|
31667
|
+
}, { service: "monitor" }));
|
|
31668
|
+
analyticsCmd.command("errors").summary("Show error and warning counts by level").description("Query system_logs for error/warning counts grouped by level, filtered by hours.").option("--hours <n>", "Hours to look back (default: 24)", "24").action(withErrorHandling(async (options, cmd) => {
|
|
31669
|
+
const fmt = getFormatOptions(cmd);
|
|
31670
|
+
const hours = parseInt(options.hours ?? "24", 10) || 24;
|
|
31671
|
+
await doMonitorAnalyticsErrors(hours, fmt);
|
|
31672
|
+
}, { service: "monitor" }));
|
|
31673
|
+
}
|
|
31674
|
+
// src/commands/workers/workers-command.ts
|
|
31675
|
+
init_config2();
|
|
31676
|
+
async function doListWorkers(fmt) {
|
|
31677
|
+
const configService = new ConfigService;
|
|
31678
|
+
await configService.load();
|
|
31679
|
+
const workers = configService.listWorkers();
|
|
31680
|
+
const rows = workers.map((name) => {
|
|
31681
|
+
const worker = configService.getWorker(name);
|
|
31682
|
+
return {
|
|
31683
|
+
Worker: name,
|
|
31684
|
+
Status: worker?.enabled ? "enabled" : "disabled",
|
|
31685
|
+
Path: worker?.path ?? "-",
|
|
31686
|
+
Secrets: String(worker?.secrets?.length ?? 0)
|
|
31687
|
+
};
|
|
31388
31688
|
});
|
|
31689
|
+
if (fmt.json) {
|
|
31690
|
+
formatJson(rows, fmt);
|
|
31691
|
+
return;
|
|
31692
|
+
}
|
|
31693
|
+
formatTable(rows, fmt);
|
|
31694
|
+
}
|
|
31695
|
+
function registerWorkersCommand(program2) {
|
|
31696
|
+
const workersCmd = program2.command("workers").summary("Manage and monitor Cloudflare Workers").description(`Manage and monitor your Hoox Cloudflare Workers.
|
|
31697
|
+
|
|
31698
|
+
SUBCOMMANDS:
|
|
31699
|
+
list List all workers with status, path, and secrets count
|
|
31700
|
+
status Check health status of all workers (delegates to monitor status)
|
|
31701
|
+
dev <name> Start a worker for local development (delegates to dev worker)
|
|
31702
|
+
logs <name> Tail logs for a specific worker (delegates to logs worker)
|
|
31703
|
+
|
|
31704
|
+
EXAMPLES:
|
|
31705
|
+
hoox workers list List all workers
|
|
31706
|
+
hoox workers list --json List workers as JSON (respects global --json)
|
|
31707
|
+
hoox workers status Check health of all workers
|
|
31708
|
+
hoox workers dev trade-worker Start dev server for trade-worker
|
|
31709
|
+
hoox workers logs hoox Tail logs for the hoox gateway`);
|
|
31710
|
+
workersCmd.command("list").summary("List all workers with their enabled/disabled status").description(`List all workers defined in wrangler.jsonc.
|
|
31711
|
+
|
|
31712
|
+
Displays each worker's:
|
|
31713
|
+
- Name
|
|
31714
|
+
- Status (enabled / disabled)
|
|
31715
|
+
- Path (relative from project root)
|
|
31716
|
+
- Secrets (count of Cloudflare secrets configured)
|
|
31717
|
+
|
|
31718
|
+
EXAMPLES:
|
|
31719
|
+
hoox workers list Default table output
|
|
31720
|
+
hoox workers list --json JSON-formatted output
|
|
31721
|
+
hoox workers list --quiet Minimal output`).action(withErrorHandling(async (_opts, cmd) => {
|
|
31722
|
+
const fmt = getFormatOptions(cmd);
|
|
31723
|
+
await doListWorkers(fmt);
|
|
31724
|
+
}, { service: "workers" }));
|
|
31725
|
+
workersCmd.command("status").summary("Check health status of all workers").description(`Check the health of all workers by delegating to \`hoox monitor status\`.
|
|
31726
|
+
|
|
31727
|
+
This command spawns \`hoox monitor status\` with inherited stdio, passing
|
|
31728
|
+
all output directly to the terminal.
|
|
31729
|
+
|
|
31730
|
+
EXAMPLES:
|
|
31731
|
+
hoox workers status Check all worker health
|
|
31732
|
+
hoox workers status --json JSON output (via delegated command)`).action(withErrorHandling(async () => {
|
|
31733
|
+
const proc = Bun.spawn(["hoox", "monitor", "status"], {
|
|
31734
|
+
stdio: ["inherit", "inherit", "inherit"]
|
|
31735
|
+
});
|
|
31736
|
+
const exitCode = await proc.exited;
|
|
31737
|
+
if (exitCode !== 0) {
|
|
31738
|
+
process.exitCode = exitCode;
|
|
31739
|
+
}
|
|
31740
|
+
}, { service: "workers" }));
|
|
31741
|
+
workersCmd.command("dev <name>").summary("Start a single worker for local development").description(`Start a specific worker for local development by delegating to \`hoox dev worker <name>\`.
|
|
31742
|
+
|
|
31743
|
+
This command spawns \`hoox dev worker <name>\` with inherited stdio,
|
|
31744
|
+
so all wrangler dev output and prompts appear directly in the terminal.
|
|
31745
|
+
|
|
31746
|
+
ARGUMENTS:
|
|
31747
|
+
name Worker name (e.g., trade-worker, agent-worker, hoox)
|
|
31748
|
+
|
|
31749
|
+
EXAMPLES:
|
|
31750
|
+
hoox workers dev trade-worker Start dev server for trade-worker
|
|
31751
|
+
hoox workers dev hoox Start dev server for the hoox gateway`).action(withErrorHandling(async (name) => {
|
|
31752
|
+
const proc = Bun.spawn(["hoox", "dev", "worker", name], {
|
|
31753
|
+
stdio: ["inherit", "inherit", "inherit"]
|
|
31754
|
+
});
|
|
31755
|
+
const exitCode = await proc.exited;
|
|
31756
|
+
if (exitCode !== 0) {
|
|
31757
|
+
process.exitCode = exitCode;
|
|
31758
|
+
}
|
|
31759
|
+
}, { service: "workers" }));
|
|
31760
|
+
workersCmd.command("logs <name>").summary("Tail logs for a specific worker").description(`Tail logs for a specific worker by delegating to \`hoox logs worker <name>\`.
|
|
31761
|
+
|
|
31762
|
+
This command spawns \`hoox logs worker <name>\` with inherited stdio,
|
|
31763
|
+
preserving all formatting and log-level filtering from the logs command.
|
|
31764
|
+
|
|
31765
|
+
ARGUMENTS:
|
|
31766
|
+
name Worker name (e.g., trade-worker, agent-worker, hoox)
|
|
31767
|
+
|
|
31768
|
+
EXAMPLES:
|
|
31769
|
+
hoox workers logs hoox Tail logs for the hoox gateway
|
|
31770
|
+
hoox workers logs trade-worker Tail logs for trade-worker`).action(withErrorHandling(async (name) => {
|
|
31771
|
+
const proc = Bun.spawn(["hoox", "logs", "worker", name], {
|
|
31772
|
+
stdio: ["inherit", "inherit", "inherit"]
|
|
31773
|
+
});
|
|
31774
|
+
const exitCode = await proc.exited;
|
|
31775
|
+
if (exitCode !== 0) {
|
|
31776
|
+
process.exitCode = exitCode;
|
|
31777
|
+
}
|
|
31778
|
+
}, { service: "workers" }));
|
|
31389
31779
|
}
|
|
31390
31780
|
// src/commands/repair/repair-service.ts
|
|
31391
31781
|
class RepairService {
|
|
@@ -31623,7 +32013,12 @@ async function handleDb(fmt) {
|
|
|
31623
32013
|
async function handleRebuild(fmt) {
|
|
31624
32014
|
try {
|
|
31625
32015
|
const repair = new RepairService;
|
|
31626
|
-
await repair.runSystemCheck();
|
|
32016
|
+
const result = await repair.runSystemCheck();
|
|
32017
|
+
if (!result.allPassed) {
|
|
32018
|
+
formatError2(new CLIError(`${result.failedCount} check(s) failed \u2014 aborting rebuild`, 1 /* ERROR */), fmt);
|
|
32019
|
+
process.exitCode = 1 /* ERROR */;
|
|
32020
|
+
return;
|
|
32021
|
+
}
|
|
31627
32022
|
const cf = new CloudflareService;
|
|
31628
32023
|
const { ConfigService: ConfigService2 } = await Promise.resolve().then(() => (init_config2(), exports_config));
|
|
31629
32024
|
const config2 = new ConfigService2;
|
|
@@ -31642,34 +32037,34 @@ async function handleRebuild(fmt) {
|
|
|
31642
32037
|
}
|
|
31643
32038
|
function registerRepairCommand(program2) {
|
|
31644
32039
|
const repairCmd = program2.command("repair").summary("Diagnose and repair the Hoox system").description("Run checks, deploy workers, or fix infrastructure, secrets, KV, and DB issues.");
|
|
31645
|
-
repairCmd.command("check").description("Run system diagnostics (workers, deps, types, infra, secrets)").action(async () => {
|
|
32040
|
+
repairCmd.command("check").description("Run system diagnostics (workers, deps, types, infra, secrets)").action(withErrorHandling(async () => {
|
|
31646
32041
|
const fmt = getFormatOptions(repairCmd);
|
|
31647
32042
|
await handleCheck(fmt);
|
|
31648
|
-
});
|
|
31649
|
-
repairCmd.command("worker <name>").description("Deploy a specific worker to fix it").action(async (name) => {
|
|
32043
|
+
}, { service: "repair" }));
|
|
32044
|
+
repairCmd.command("worker <name>").description("Deploy a specific worker to fix it").action(withErrorHandling(async (name) => {
|
|
31650
32045
|
const fmt = getFormatOptions(repairCmd);
|
|
31651
32046
|
await handleWorker(name, fmt);
|
|
31652
|
-
});
|
|
31653
|
-
repairCmd.command("infra").description("Provision missing infrastructure (D1, KV, R2, Queues)").action(async () => {
|
|
32047
|
+
}, { service: "repair" }));
|
|
32048
|
+
repairCmd.command("infra").description("Provision missing infrastructure (D1, KV, R2, Queues)").action(withErrorHandling(async () => {
|
|
31654
32049
|
const fmt = getFormatOptions(repairCmd);
|
|
31655
32050
|
await handleInfra(fmt);
|
|
31656
|
-
});
|
|
31657
|
-
repairCmd.command("secrets").description("Upload missing secrets to Cloudflare").action(async () => {
|
|
32051
|
+
}, { service: "repair" }));
|
|
32052
|
+
repairCmd.command("secrets").description("Upload missing secrets to Cloudflare").action(withErrorHandling(async () => {
|
|
31658
32053
|
const fmt = getFormatOptions(repairCmd);
|
|
31659
32054
|
await handleSecrets(fmt);
|
|
31660
|
-
});
|
|
31661
|
-
repairCmd.command("kv").description("Re-sync KV namespace entries").action(async () => {
|
|
32055
|
+
}, { service: "repair" }));
|
|
32056
|
+
repairCmd.command("kv").description("Re-sync KV namespace entries").action(withErrorHandling(async () => {
|
|
31662
32057
|
const fmt = getFormatOptions(repairCmd);
|
|
31663
32058
|
await handleKv(fmt);
|
|
31664
|
-
});
|
|
31665
|
-
repairCmd.command("db").description("Re-apply database schema").action(async () => {
|
|
32059
|
+
}, { service: "repair" }));
|
|
32060
|
+
repairCmd.command("db").description("Re-apply database schema").action(withErrorHandling(async () => {
|
|
31666
32061
|
const fmt = getFormatOptions(repairCmd);
|
|
31667
32062
|
await handleDb(fmt);
|
|
31668
|
-
});
|
|
31669
|
-
repairCmd.command("rebuild").description("Full rebuild: check, deploy all workers, fix infra/secrets/db").action(async () => {
|
|
32063
|
+
}, { service: "repair" }));
|
|
32064
|
+
repairCmd.command("rebuild").description("Full rebuild: check, deploy all workers, fix infra/secrets/db").action(withErrorHandling(async () => {
|
|
31670
32065
|
const fmt = getFormatOptions(repairCmd);
|
|
31671
32066
|
await handleRebuild(fmt);
|
|
31672
|
-
});
|
|
32067
|
+
}, { service: "repair" }));
|
|
31673
32068
|
}
|
|
31674
32069
|
// src/commands/update/update-command.ts
|
|
31675
32070
|
init_config2();
|
|
@@ -31799,49 +32194,41 @@ With "wrangler", updates wrangler to the latest version.
|
|
|
31799
32194
|
EXAMPLES:
|
|
31800
32195
|
hoox update Pull latest for main repo
|
|
31801
32196
|
hoox update d1-worker Update the d1-worker submodule
|
|
31802
|
-
hoox update wrangler Update wrangler to latest version`).argument("[target]", 'What to update: worker name (e.g. d1-worker) or "wrangler"').action(async (target) => {
|
|
31803
|
-
const fmt = getFormatOptions(program2);
|
|
32197
|
+
hoox update wrangler Update wrangler to latest version`).argument("[target]", 'What to update: worker name (e.g. d1-worker) or "wrangler"').action(withErrorHandling(async (target) => {
|
|
31804
32198
|
const cwd = process.cwd();
|
|
31805
|
-
|
|
31806
|
-
|
|
31807
|
-
|
|
31808
|
-
|
|
31809
|
-
|
|
31810
|
-
formatError2(new CLIError(`Update failed: ${result.error}`, 1 /* ERROR */), fmt);
|
|
31811
|
-
process.exitCode = 1 /* ERROR */;
|
|
31812
|
-
}
|
|
31813
|
-
return;
|
|
32199
|
+
if (target === "wrangler") {
|
|
32200
|
+
const service = new UpdateService;
|
|
32201
|
+
const result = await service.updateWrangler();
|
|
32202
|
+
if (result.error) {
|
|
32203
|
+
throw new CLIError(`Update failed: ${result.error}`, 1 /* ERROR */);
|
|
31814
32204
|
}
|
|
31815
|
-
|
|
31816
|
-
|
|
32205
|
+
return;
|
|
32206
|
+
}
|
|
32207
|
+
if (!await isGitRepo(cwd)) {
|
|
32208
|
+
throw new CLIError("Not a git repository \u2014 run this command from the project root", 2 /* INVALID_USAGE */);
|
|
32209
|
+
}
|
|
32210
|
+
if (target) {
|
|
32211
|
+
const configService = new ConfigService;
|
|
32212
|
+
await configService.load();
|
|
32213
|
+
const worker = configService.getWorker(target);
|
|
32214
|
+
if (!worker) {
|
|
32215
|
+
throw new CLIError(`Unknown worker "${target}" \u2014 not found in wrangler.jsonc`, 2 /* INVALID_USAGE */);
|
|
31817
32216
|
}
|
|
31818
|
-
|
|
31819
|
-
|
|
31820
|
-
|
|
31821
|
-
const worker = configService.getWorker(target);
|
|
31822
|
-
if (!worker) {
|
|
31823
|
-
throw new CLIError(`Unknown worker "${target}" \u2014 not found in wrangler.jsonc`, 2 /* INVALID_USAGE */);
|
|
31824
|
-
}
|
|
31825
|
-
const submodulePath = worker.path;
|
|
31826
|
-
if (!await isSubmodule(cwd, submodulePath)) {
|
|
31827
|
-
throw new CLIError(`"${submodulePath}" is not a git submodule \u2014 nothing to update`, 2 /* INVALID_USAGE */);
|
|
31828
|
-
}
|
|
31829
|
-
const s = vt();
|
|
31830
|
-
s.start(`Updating ${target}...`);
|
|
31831
|
-
const output = await gitSubmoduleUpdate(cwd, submodulePath);
|
|
31832
|
-
s.stop(output ? theme.success(`Updated ${target}`) : theme.muted(`${target} already up to date`));
|
|
31833
|
-
} else {
|
|
31834
|
-
const s = vt();
|
|
31835
|
-
s.start("Pulling latest from remote...");
|
|
31836
|
-
const output = await gitPull(cwd);
|
|
31837
|
-
s.stop(output ? theme.success("Repository up to date") : theme.muted("Already up to date"));
|
|
32217
|
+
const submodulePath = worker.path;
|
|
32218
|
+
if (!await isSubmodule(cwd, submodulePath)) {
|
|
32219
|
+
throw new CLIError(`"${submodulePath}" is not a git submodule \u2014 nothing to update`, 2 /* INVALID_USAGE */);
|
|
31838
32220
|
}
|
|
31839
|
-
|
|
31840
|
-
|
|
31841
|
-
|
|
31842
|
-
|
|
32221
|
+
const s = vt();
|
|
32222
|
+
s.start(`Updating ${target}...`);
|
|
32223
|
+
const output = await gitSubmoduleUpdate(cwd, submodulePath);
|
|
32224
|
+
s.stop(output ? theme.success(`Updated ${target}`) : theme.muted(`${target} already up to date`));
|
|
32225
|
+
} else {
|
|
32226
|
+
const s = vt();
|
|
32227
|
+
s.start("Pulling latest from remote...");
|
|
32228
|
+
const output = await gitPull(cwd);
|
|
32229
|
+
s.stop(output ? theme.success("Repository up to date") : theme.muted("Already up to date"));
|
|
31843
32230
|
}
|
|
31844
|
-
});
|
|
32231
|
+
}, { service: "update" }));
|
|
31845
32232
|
}
|
|
31846
32233
|
// src/commands/schema/schema-command.ts
|
|
31847
32234
|
init_schema_service();
|
|
@@ -32420,8 +32807,9 @@ async function runCommand(program2, commandStr) {
|
|
|
32420
32807
|
}
|
|
32421
32808
|
}
|
|
32422
32809
|
// src/index.ts
|
|
32810
|
+
var pkgVersion = JSON.parse(readFileSync5(new URL("../package.json", import.meta.url), "utf-8")).version;
|
|
32423
32811
|
var program2 = new Command;
|
|
32424
|
-
program2.name("hoox").description("Hoox CLI \u2014 manage Cloudflare Workers, infrastructure, secrets, and deployments").version(
|
|
32812
|
+
program2.name("hoox").description("Hoox CLI \u2014 manage Cloudflare Workers, infrastructure, secrets, and deployments").version(`${pkgVersion}
|
|
32425
32813
|
|
|
32426
32814
|
${COPYRIGHT}`).addHelpText("beforeAll", theme.heading(`
|
|
32427
32815
|
Hoox CLI \u2014 Cloudflare Workers Platform
|
|
@@ -32478,11 +32866,67 @@ registerCloneCommand(program2);
|
|
|
32478
32866
|
registerDashboardCommand(program2);
|
|
32479
32867
|
registerDbCommand(program2);
|
|
32480
32868
|
registerMonitorCommand(program2);
|
|
32869
|
+
registerWorkersCommand(program2);
|
|
32481
32870
|
registerRepairCommand(program2);
|
|
32482
32871
|
registerSchemaCommand(program2);
|
|
32483
32872
|
registerUpdateCommand(program2);
|
|
32484
32873
|
registerTUICommand(program2);
|
|
32485
32874
|
registerDisclaimerCommand(program2);
|
|
32875
|
+
program2.command("completion").description("Generate shell completion script").argument("[shell]", "Shell type (bash, zsh, fish)").action(async (shell) => {
|
|
32876
|
+
if (!shell) {
|
|
32877
|
+
process.stdout.write(`Usage: hoox completion <bash|zsh|fish>
|
|
32878
|
+
`);
|
|
32879
|
+
return;
|
|
32880
|
+
}
|
|
32881
|
+
const validShells = ["bash", "zsh", "fish"];
|
|
32882
|
+
if (!validShells.includes(shell)) {
|
|
32883
|
+
process.stderr.write(`Unsupported shell "${shell}". Use: ${validShells.join(", ")}
|
|
32884
|
+
`);
|
|
32885
|
+
process.exitCode = 1;
|
|
32886
|
+
return;
|
|
32887
|
+
}
|
|
32888
|
+
if (shell === "bash") {
|
|
32889
|
+
process.stdout.write(`_hoox_completion() {
|
|
32890
|
+
local cur prev opts
|
|
32891
|
+
COMPREPLY=()
|
|
32892
|
+
cur="\${COMP_WORDS[COMP_CWORD]}"
|
|
32893
|
+
opts="--help --version --json --quiet --yes init clone dev deploy infra config check db monitor repair logs test waf dashboard schema update tui disclaimer workers"
|
|
32894
|
+
COMPREPLY=( $(compgen -W "\${opts}" -- \${cur}) )
|
|
32895
|
+
return 0
|
|
32896
|
+
}
|
|
32897
|
+
complete -F _hoox_completion hoox
|
|
32898
|
+
`);
|
|
32899
|
+
} else if (shell === "zsh") {
|
|
32900
|
+
process.stdout.write(`#compdef hoox
|
|
32901
|
+
_hoox() {
|
|
32902
|
+
local -a opts
|
|
32903
|
+
opts=(
|
|
32904
|
+
'--help:Show help'
|
|
32905
|
+
'--version:Show version'
|
|
32906
|
+
'--json:JSON output'
|
|
32907
|
+
'--quiet:Minimal output'
|
|
32908
|
+
'init:Interactive setup wizard'
|
|
32909
|
+
'clone:Clone worker repositories'
|
|
32910
|
+
'dev:Local development'
|
|
32911
|
+
'deploy:Deploy to Cloudflare'
|
|
32912
|
+
'infra:Manage infrastructure'
|
|
32913
|
+
'config:Manage configuration'
|
|
32914
|
+
'check:Validate and health-check'
|
|
32915
|
+
'db:Database operations'
|
|
32916
|
+
'monitor:Monitor system'
|
|
32917
|
+
'repair:Repair system'
|
|
32918
|
+
'logs:View worker logs'
|
|
32919
|
+
'test:Run tests'
|
|
32920
|
+
'waf:Manage Web Application Firewall'
|
|
32921
|
+
'dashboard:Dashboard operations'
|
|
32922
|
+
'workers:Worker operations'
|
|
32923
|
+
)
|
|
32924
|
+
_describe 'hoox' opts
|
|
32925
|
+
}
|
|
32926
|
+
compdef _hoox hoox
|
|
32927
|
+
`);
|
|
32928
|
+
}
|
|
32929
|
+
});
|
|
32486
32930
|
var devCmd = program2.commands.find((c) => c.name() === "dev");
|
|
32487
32931
|
if (devCmd) {
|
|
32488
32932
|
const devStartCmd = devCmd.commands.find((c) => c.name() === "start");
|