@jango-blockchained/hoox-cli 0.5.1 → 0.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/package.json +1 -1
  2. package/src/commands/check/check-command.ts +8 -65
  3. package/src/commands/check/prerequisites-command.ts +7 -6
  4. package/src/commands/clone/clone-command.test.ts +8 -10
  5. package/src/commands/config/config-command.test.ts +4 -4
  6. package/src/commands/config/config-command.ts +10 -11
  7. package/src/commands/config/env-command.test.ts +2 -2
  8. package/src/commands/config/env-command.ts +40 -43
  9. package/src/commands/config/kv-command.ts +60 -60
  10. package/src/commands/dashboard/dashboard-command.ts +32 -30
  11. package/src/commands/db/db-command.ts +79 -80
  12. package/src/commands/deploy/deploy-command.test.ts +54 -34
  13. package/src/commands/deploy/deploy-command.ts +3 -84
  14. package/src/commands/dev/dev-command.test.ts +84 -62
  15. package/src/commands/disclaimer/disclaimer-command.ts +21 -0
  16. package/src/commands/disclaimer/index.ts +1 -0
  17. package/src/commands/init/init-command.test.ts +69 -91
  18. package/src/commands/init/init-command.ts +19 -15
  19. package/src/commands/logs/logs-command.test.ts +0 -1
  20. package/src/commands/monitor/monitor-command.test.ts +37 -29
  21. package/src/commands/repair/repair-command.test.ts +60 -41
  22. package/src/commands/repair/repair-service.ts +26 -0
  23. package/src/commands/schema/index.ts +1 -0
  24. package/src/commands/schema/schema-command.ts +137 -0
  25. package/src/commands/test/test-command.test.ts +2 -3
  26. package/src/commands/update/update-command.ts +6 -65
  27. package/src/commands/waf/waf-command.ts +56 -59
  28. package/src/index.ts +5 -13
  29. package/src/services/config/config-service.ts +1 -6
  30. package/src/services/db/db-service.test.ts +13 -1
  31. package/src/services/env/env-service.test.ts +41 -18
  32. package/src/services/env/env-service.ts +45 -59
  33. package/src/services/kv/kv-sync-service.ts +14 -5
  34. package/src/services/schema/index.ts +1 -0
  35. package/src/services/schema/schema-service.ts +99 -0
  36. package/src/services/secrets/secrets-service.test.ts +42 -35
  37. package/src/services/secrets/types.ts +1 -1
  38. package/src/ui/menu.ts +1 -1
  39. package/src/utils/error-handler.ts +62 -0
  40. package/src/utils/errors.ts +1 -0
  41. package/src/utils/git.ts +134 -0
  42. package/src/utils/theme.ts +0 -2
@@ -1,13 +1,10 @@
1
1
  import type { Command } from "commander";
2
2
  import { ConfigService } from "../../services/config/index.js";
3
3
  import { CLIError, ExitCode } from "../../utils/errors.js";
4
- import {
5
- formatSuccess,
6
- formatError,
7
- formatTable,
8
- } from "../../utils/formatters.js";
4
+ import { formatSuccess, formatTable } from "../../utils/formatters.js";
5
+ import { withErrorHandling } from "../../utils/error-handler.js";
9
6
  import type { FormatOptions } from "../../utils/formatters.js";
10
- import { theme, icons } from "../../utils/theme.js";
7
+ import { theme } from "../../utils/theme.js";
11
8
  import * as jsonc from "jsonc-parser";
12
9
  import { readFileSync, writeFileSync, existsSync } from "node:fs";
13
10
  import { resolve } from "node:path";
@@ -101,28 +98,33 @@ export function registerDashboardCommand(program: Command): void {
101
98
  .command("update-urls")
102
99
  .description("Update dashboard wrangler.jsonc with current service URLs")
103
100
  .option("--dry-run", "Show changes without applying")
104
- .action(async (options: { dryRun?: boolean }) => {
105
- const opts: FormatOptions = {
106
- json: program.opts<{ json?: boolean }>().json,
107
- quiet: program.opts<{ quiet?: boolean }>().quiet,
108
- };
109
-
110
- try {
111
- const config = new ConfigService();
112
- await config.load();
113
-
114
- const urls = getServiceUrls(config);
115
- const dashboardPath = resolve(
116
- process.cwd(),
117
- "pages",
118
- "dashboard",
119
- "wrangler.jsonc"
120
- );
121
-
122
- updateWranglerVars(dashboardPath, urls, options.dryRun === true, opts);
123
- } catch (err) {
124
- formatError(err instanceof Error ? err : String(err), opts);
125
- process.exit(err instanceof CLIError ? err.code : ExitCode.ERROR);
126
- }
127
- });
101
+ .action(
102
+ withErrorHandling(
103
+ async (options: { dryRun?: boolean }) => {
104
+ const opts: FormatOptions = {
105
+ json: program.opts<{ json?: boolean }>().json,
106
+ quiet: program.opts<{ quiet?: boolean }>().quiet,
107
+ };
108
+
109
+ const config = new ConfigService();
110
+ await config.load();
111
+
112
+ const urls = getServiceUrls(config);
113
+ const dashboardPath = resolve(
114
+ process.cwd(),
115
+ "pages",
116
+ "dashboard",
117
+ "wrangler.jsonc"
118
+ );
119
+
120
+ updateWranglerVars(
121
+ dashboardPath,
122
+ urls,
123
+ options.dryRun === true,
124
+ opts
125
+ );
126
+ },
127
+ { service: "dashboard" }
128
+ )
129
+ );
128
130
  }
@@ -15,12 +15,11 @@ import * as p from "@clack/prompts";
15
15
  import { DbService } from "../../services/db/index.js";
16
16
  import {
17
17
  formatSuccess,
18
- formatError,
19
18
  formatTable,
20
19
  formatJson,
21
20
  getFormatOptions,
22
21
  } from "../../utils/formatters.js";
23
- import { CLIError, ExitCode } from "../../utils/errors.js";
22
+ import { withErrorHandling } from "../../utils/error-handler.js";
24
23
  import type { FormatOptions } from "../../utils/formatters.js";
25
24
 
26
25
  /**
@@ -204,109 +203,109 @@ EXAMPLES:
204
203
  .command("apply")
205
204
  .description("Apply schema.sql to the database")
206
205
  .option("--file <path>", "Path to schema.sql file")
207
- .action(async (options: { file?: string }, cmd: Command) => {
208
- const opts = getFormatOptions(cmd);
209
- try {
210
- const svc = new DbService();
211
- const dbName = await resolveDb(cmd, svc);
212
- const remote = Boolean(
213
- cmd.optsWithGlobals<{ remote?: boolean }>().remote
214
- );
215
- await handleApply(opts, dbName, remote, options.file);
216
- } catch (err) {
217
- formatError(err instanceof Error ? err : String(err), opts);
218
- process.exit(err instanceof CLIError ? err.code : ExitCode.ERROR);
219
- }
220
- });
206
+ .action(
207
+ withErrorHandling(
208
+ async (options: { file?: string }, cmd: Command) => {
209
+ const opts = getFormatOptions(cmd);
210
+ const svc = new DbService();
211
+ const dbName = await resolveDb(cmd, svc);
212
+ const remote = Boolean(
213
+ cmd.optsWithGlobals<{ remote?: boolean }>().remote
214
+ );
215
+ await handleApply(opts, dbName, remote, options.file);
216
+ },
217
+ { service: "db" }
218
+ )
219
+ );
221
220
 
222
221
  // -- migrate
223
222
  dbCmd
224
223
  .command("migrate")
225
224
  .description("Run tracking migrations")
226
- .action(async (_, cmd: Command) => {
227
- const opts = getFormatOptions(cmd);
228
- try {
229
- const svc = new DbService();
230
- const dbName = await resolveDb(cmd, svc);
231
- const remote = Boolean(
232
- cmd.optsWithGlobals<{ remote?: boolean }>().remote
233
- );
234
- await handleMigrate(opts, dbName, remote);
235
- } catch (err) {
236
- formatError(err instanceof Error ? err : String(err), opts);
237
- process.exit(err instanceof CLIError ? err.code : ExitCode.ERROR);
238
- }
239
- });
225
+ .action(
226
+ withErrorHandling(
227
+ async (_, cmd: Command) => {
228
+ const opts = getFormatOptions(cmd);
229
+ const svc = new DbService();
230
+ const dbName = await resolveDb(cmd, svc);
231
+ const remote = Boolean(
232
+ cmd.optsWithGlobals<{ remote?: boolean }>().remote
233
+ );
234
+ await handleMigrate(opts, dbName, remote);
235
+ },
236
+ { service: "db" }
237
+ )
238
+ );
240
239
 
241
240
  // -- list
242
241
  dbCmd
243
242
  .command("list")
244
243
  .description("List database tables")
245
- .action(async (_, cmd: Command) => {
246
- const opts = getFormatOptions(cmd);
247
- try {
248
- const svc = new DbService();
249
- const dbName = await resolveDb(cmd, svc);
250
- const remote = Boolean(
251
- cmd.optsWithGlobals<{ remote?: boolean }>().remote
252
- );
253
- await handleList(opts, dbName, remote);
254
- } catch (err) {
255
- formatError(err instanceof Error ? err : String(err), opts);
256
- process.exit(err instanceof CLIError ? err.code : ExitCode.ERROR);
257
- }
258
- });
244
+ .action(
245
+ withErrorHandling(
246
+ async (_, cmd: Command) => {
247
+ const opts = getFormatOptions(cmd);
248
+ const svc = new DbService();
249
+ const dbName = await resolveDb(cmd, svc);
250
+ const remote = Boolean(
251
+ cmd.optsWithGlobals<{ remote?: boolean }>().remote
252
+ );
253
+ await handleList(opts, dbName, remote);
254
+ },
255
+ { service: "db" }
256
+ )
257
+ );
259
258
 
260
259
  // -- query
261
260
  dbCmd
262
261
  .command("query <sql>")
263
262
  .description("Execute a SQL query")
264
- .action(async (sql: string, _, cmd: Command) => {
265
- const opts = getFormatOptions(cmd);
266
- try {
267
- const svc = new DbService();
268
- const dbName = await resolveDb(cmd, svc);
269
- const remote = Boolean(
270
- cmd.optsWithGlobals<{ remote?: boolean }>().remote
271
- );
272
- await handleQuery(opts, dbName, sql, remote);
273
- } catch (err) {
274
- formatError(err instanceof Error ? err : String(err), opts);
275
- process.exit(err instanceof CLIError ? err.code : ExitCode.ERROR);
276
- }
277
- });
263
+ .action(
264
+ withErrorHandling(
265
+ async (sql: string, _, cmd: Command) => {
266
+ const opts = getFormatOptions(cmd);
267
+ const svc = new DbService();
268
+ const dbName = await resolveDb(cmd, svc);
269
+ const remote = Boolean(
270
+ cmd.optsWithGlobals<{ remote?: boolean }>().remote
271
+ );
272
+ await handleQuery(opts, dbName, sql, remote);
273
+ },
274
+ { service: "db" }
275
+ )
276
+ );
278
277
 
279
278
  // -- export
280
279
  dbCmd
281
280
  .command("export")
282
281
  .description("Export database to .sql file")
283
282
  .option("--output <path>", "Output file path")
284
- .action(async (options: { output?: string }, cmd: Command) => {
285
- const opts = getFormatOptions(cmd);
286
- try {
287
- const svc = new DbService();
288
- const dbName = await resolveDb(cmd, svc);
289
- await handleExport(opts, dbName, options.output);
290
- } catch (err) {
291
- formatError(err instanceof Error ? err : String(err), opts);
292
- process.exit(err instanceof CLIError ? err.code : ExitCode.ERROR);
293
- }
294
- });
283
+ .action(
284
+ withErrorHandling(
285
+ async (options: { output?: string }, cmd: Command) => {
286
+ const opts = getFormatOptions(cmd);
287
+ const svc = new DbService();
288
+ const dbName = await resolveDb(cmd, svc);
289
+ await handleExport(opts, dbName, options.output);
290
+ },
291
+ { service: "db" }
292
+ )
293
+ );
295
294
 
296
295
  // -- reset
297
296
  dbCmd
298
297
  .command("reset")
299
298
  .description("Drop and recreate the database (DESTRUCTIVE)")
300
299
  .option("--confirm", "Skip confirmation prompt")
301
- .action(async (options: { confirm?: boolean }, cmd: Command) => {
302
- const opts = getFormatOptions(cmd);
303
- try {
304
- const svc = new DbService();
305
- const dbName = await resolveDb(cmd, svc);
306
- await handleReset(opts, dbName, Boolean(options.confirm));
307
- } catch (err) {
308
- formatError(err instanceof Error ? err : String(err), opts);
309
- process.exit(err instanceof CLIError ? err.code : ExitCode.ERROR);
310
- }
311
- });
300
+ .action(
301
+ withErrorHandling(
302
+ async (options: { confirm?: boolean }, cmd: Command) => {
303
+ const opts = getFormatOptions(cmd);
304
+ const svc = new DbService();
305
+ const dbName = await resolveDb(cmd, svc);
306
+ await handleReset(opts, dbName, Boolean(options.confirm));
307
+ },
308
+ { service: "db" }
309
+ )
310
+ );
312
311
  }
@@ -1,4 +1,3 @@
1
- // @ts-nocheck
2
1
  /**
3
2
  * Unit tests for the deploy command.
4
3
  *
@@ -43,17 +42,20 @@ beforeEach(() => {
43
42
  process.exitCode = undefined;
44
43
 
45
44
  // Reset prototypes to originals (in case a previous test didn't restore)
46
- (ConfigService.prototype as Record<string, unknown>).load = origLoad;
47
- (ConfigService.prototype as Record<string, unknown>).listEnabledWorkers =
48
- origListEnabled;
49
- (ConfigService.prototype as Record<string, unknown>).getWorker =
45
+ (ConfigService.prototype as unknown as Record<string, unknown>).load =
46
+ origLoad;
47
+ (
48
+ ConfigService.prototype as unknown as Record<string, unknown>
49
+ ).listEnabledWorkers = origListEnabled;
50
+ (ConfigService.prototype as unknown as Record<string, unknown>).getWorker =
50
51
  origGetWorker;
51
- (CloudflareService.prototype as Record<string, unknown>).deploy = origDeploy;
52
+ (CloudflareService.prototype as unknown as Record<string, unknown>).deploy =
53
+ origDeploy;
52
54
 
53
55
  // Fresh mocks
54
56
  deployMock = mock(async (_path: string, _env?: string) => ({
55
57
  ok: true as const,
56
- data: { url: "https://test-worker.cryptolinx.workers.dev" },
58
+ value: { url: "https://test-worker.cryptolinx.workers.dev", rawOutput: "" },
57
59
  }));
58
60
 
59
61
  loadMock = mock(async () => ({}));
@@ -64,24 +66,30 @@ beforeEach(() => {
64
66
  }));
65
67
 
66
68
  // Install mocks on prototypes
67
- (ConfigService.prototype as Record<string, unknown>).load = loadMock;
68
- (ConfigService.prototype as Record<string, unknown>).listEnabledWorkers =
69
- listEnabledWorkersMock;
70
- (ConfigService.prototype as Record<string, unknown>).getWorker =
69
+ (ConfigService.prototype as unknown as Record<string, unknown>).load =
70
+ loadMock;
71
+ (
72
+ ConfigService.prototype as unknown as Record<string, unknown>
73
+ ).listEnabledWorkers = listEnabledWorkersMock;
74
+ (ConfigService.prototype as unknown as Record<string, unknown>).getWorker =
71
75
  getWorkerMock;
72
- (CloudflareService.prototype as Record<string, unknown>).deploy = deployMock;
76
+ (CloudflareService.prototype as unknown as Record<string, unknown>).deploy =
77
+ deployMock;
73
78
  });
74
79
 
75
80
  afterEach(() => {
76
81
  mock.restore();
77
82
 
78
83
  // Restore originals
79
- (ConfigService.prototype as Record<string, unknown>).load = origLoad;
80
- (ConfigService.prototype as Record<string, unknown>).listEnabledWorkers =
81
- origListEnabled;
82
- (ConfigService.prototype as Record<string, unknown>).getWorker =
84
+ (ConfigService.prototype as unknown as Record<string, unknown>).load =
85
+ origLoad;
86
+ (
87
+ ConfigService.prototype as unknown as Record<string, unknown>
88
+ ).listEnabledWorkers = origListEnabled;
89
+ (ConfigService.prototype as unknown as Record<string, unknown>).getWorker =
83
90
  origGetWorker;
84
- (CloudflareService.prototype as Record<string, unknown>).deploy = origDeploy;
91
+ (CloudflareService.prototype as unknown as Record<string, unknown>).deploy =
92
+ origDeploy;
85
93
  });
86
94
 
87
95
  // ---------------------------------------------------------------------------
@@ -109,7 +117,8 @@ async function createProgram(): Promise<Command> {
109
117
  /** Make deployMock return a failure for all subsequent calls. */
110
118
  function makeDeployFail(error: string): void {
111
119
  deployMock = mock(async () => ({ ok: false as const, error }));
112
- (CloudflareService.prototype as Record<string, unknown>).deploy = deployMock;
120
+ (CloudflareService.prototype as unknown as Record<string, unknown>).deploy =
121
+ deployMock;
113
122
  }
114
123
 
115
124
  // ---------------------------------------------------------------------------
@@ -169,8 +178,9 @@ describe("registerDeployCommand", () => {
169
178
  "hoox",
170
179
  "trade-worker",
171
180
  ]);
172
- (ConfigService.prototype as Record<string, unknown>).listEnabledWorkers =
173
- listEnabledWorkersMock;
181
+ (
182
+ ConfigService.prototype as unknown as Record<string, unknown>
183
+ ).listEnabledWorkers = listEnabledWorkersMock;
174
184
 
175
185
  const program = await createProgram();
176
186
  await program.parseAsync(["deploy", "workers"], { from: "user" });
@@ -181,8 +191,9 @@ describe("registerDeployCommand", () => {
181
191
 
182
192
  it("handles no enabled workers gracefully", async () => {
183
193
  listEnabledWorkersMock = mock(() => []);
184
- (ConfigService.prototype as Record<string, unknown>).listEnabledWorkers =
185
- listEnabledWorkersMock;
194
+ (
195
+ ConfigService.prototype as unknown as Record<string, unknown>
196
+ ).listEnabledWorkers = listEnabledWorkersMock;
186
197
 
187
198
  const program = await createProgram();
188
199
  await program.parseAsync(["deploy", "workers"], { from: "user" });
@@ -192,8 +203,9 @@ describe("registerDeployCommand", () => {
192
203
 
193
204
  it("continues deploying remaining workers on partial failure", async () => {
194
205
  listEnabledWorkersMock = mock(() => ["a", "b", "c"]);
195
- (ConfigService.prototype as Record<string, unknown>).listEnabledWorkers =
196
- listEnabledWorkersMock;
206
+ (
207
+ ConfigService.prototype as unknown as Record<string, unknown>
208
+ ).listEnabledWorkers = listEnabledWorkersMock;
197
209
 
198
210
  let calls = 0;
199
211
  deployMock = mock(async () => {
@@ -201,10 +213,14 @@ describe("registerDeployCommand", () => {
201
213
  if (calls === 2) {
202
214
  return { ok: false as const, error: "deploy error" };
203
215
  }
204
- return { ok: true as const, data: { url: "https://x.workers.dev" } };
216
+ return {
217
+ ok: true as const,
218
+ value: { url: "https://x.workers.dev", rawOutput: "" },
219
+ };
205
220
  });
206
- (CloudflareService.prototype as Record<string, unknown>).deploy =
207
- deployMock;
221
+ (
222
+ CloudflareService.prototype as unknown as Record<string, unknown>
223
+ ).deploy = deployMock;
208
224
 
209
225
  const program = await createProgram();
210
226
  await program.parseAsync(["deploy", "workers"], { from: "user" });
@@ -215,8 +231,9 @@ describe("registerDeployCommand", () => {
215
231
 
216
232
  it("passes --env to deploy", async () => {
217
233
  listEnabledWorkersMock = mock(() => ["single-worker"]);
218
- (ConfigService.prototype as Record<string, unknown>).listEnabledWorkers =
219
- listEnabledWorkersMock;
234
+ (
235
+ ConfigService.prototype as unknown as Record<string, unknown>
236
+ ).listEnabledWorkers = listEnabledWorkersMock;
220
237
 
221
238
  const program = await createProgram();
222
239
  await program.parseAsync(["deploy", "workers", "--env", "staging"], {
@@ -264,8 +281,9 @@ describe("registerDeployCommand", () => {
264
281
  enabled: true,
265
282
  path: "workers/hoox",
266
283
  }));
267
- (ConfigService.prototype as Record<string, unknown>).getWorker =
268
- getWorkerMock;
284
+ (
285
+ ConfigService.prototype as unknown as Record<string, unknown>
286
+ ).getWorker = getWorkerMock;
269
287
 
270
288
  const program = await createProgram();
271
289
  await program.parseAsync(["deploy", "worker", "hoox"], { from: "user" });
@@ -275,8 +293,9 @@ describe("registerDeployCommand", () => {
275
293
 
276
294
  it("handles unknown worker name without calling deploy", async () => {
277
295
  getWorkerMock = mock(() => undefined);
278
- (ConfigService.prototype as Record<string, unknown>).getWorker =
279
- getWorkerMock;
296
+ (
297
+ ConfigService.prototype as unknown as Record<string, unknown>
298
+ ).getWorker = getWorkerMock;
280
299
 
281
300
  const program = await createProgram();
282
301
  await program.parseAsync(["deploy", "worker", "nonexistent"], {
@@ -294,7 +313,8 @@ describe("registerDeployCommand", () => {
294
313
  loadMock = mock(async () => {
295
314
  throw new Error("Config file not found");
296
315
  });
297
- (ConfigService.prototype as Record<string, unknown>).load = loadMock;
316
+ (ConfigService.prototype as unknown as Record<string, unknown>).load =
317
+ loadMock;
298
318
 
299
319
  const program = await createProgram();
300
320
  await program.parseAsync(["deploy", "workers"], { from: "user" });
@@ -324,7 +324,7 @@ async function deployDashboard(
324
324
 
325
325
  if (action === "rebuild") {
326
326
  // Build + Deploy: bun run deploy (runs opennext:build && opennext:deploy)
327
- const buildProc = Bun.spawn(["bun", "run", "opennext:deploy"], {
327
+ const buildProc = Bun.spawn(["bun", "run", "deploy"], {
328
328
  cwd: dashboardPath,
329
329
  stdout: "pipe",
330
330
  stderr: "pipe",
@@ -440,87 +440,6 @@ async function deployDashboard(
440
440
  }
441
441
  }
442
442
 
443
- /**
444
- * Print a summary table of deploy results.
445
- * Excluded in quiet mode; uses JSON or box-drawn table depending on --json.
446
- */
447
- function printSummary(
448
- results: DeployResult[],
449
- opts: { json?: boolean; quiet?: boolean }
450
- ): void {
451
- if (opts.quiet) return;
452
-
453
- // Separate workers from dashboard for different formatting
454
- const workerResults = results.filter((r) => r.worker !== "dashboard");
455
- const dashboardResult = results.find((r) => r.worker === "dashboard");
456
-
457
- // Print worker deployments with verbose info
458
- if (workerResults.length > 0) {
459
- process.stdout.write(`\n${theme.heading("Workers Deployed")}\n`);
460
- for (const r of workerResults) {
461
- const icon = r.success
462
- ? theme.success(icons.success)
463
- : theme.error(icons.error);
464
- const status = r.success ? "deployed" : "failed";
465
- process.stdout.write(`${icon} ${r.worker}: ${status}\n`);
466
-
467
- if (r.success && r.url) {
468
- process.stdout.write(` ${theme.dim("URL:")} ${r.url}\n`);
469
- }
470
- if (r.success && r.size) {
471
- process.stdout.write(` ${theme.dim("Size:")} ${r.size}\n`);
472
- }
473
- if (r.success && r.startupTime) {
474
- process.stdout.write(` ${theme.dim("Startup:")} ${r.startupTime}\n`);
475
- }
476
- if (r.success && r.versionId) {
477
- process.stdout.write(
478
- ` ${theme.dim("Version:")} ${r.versionId.slice(0, 8)}...\n`
479
- );
480
- }
481
- if (!r.success && r.error) {
482
- process.stdout.write(` ${theme.error("Error:")} ${r.error}\n`);
483
- }
484
- }
485
- }
486
-
487
- // Print dashboard deployment
488
- if (dashboardResult) {
489
- process.stdout.write(`\n${theme.heading("Dashboard Deployed")}\n`);
490
- const icon = dashboardResult.success
491
- ? theme.success(icons.success)
492
- : theme.error(icons.error);
493
- const status = dashboardResult.success ? "deployed" : "failed";
494
- process.stdout.write(`${icon} dashboard: ${status}\n`);
495
-
496
- if (dashboardResult.success && dashboardResult.url) {
497
- process.stdout.write(` ${theme.dim("URL:")} ${dashboardResult.url}\n`);
498
- }
499
- if (dashboardResult.success && dashboardResult.size) {
500
- process.stdout.write(
501
- ` ${theme.dim("Size:")} ${dashboardResult.size}\n`
502
- );
503
- }
504
- if (dashboardResult.success && dashboardResult.startupTime) {
505
- process.stdout.write(
506
- ` ${theme.dim("Startup:")} ${dashboardResult.startupTime}\n`
507
- );
508
- }
509
- if (!dashboardResult.success && dashboardResult.error) {
510
- process.stdout.write(
511
- ` ${theme.error("Error:")} ${dashboardResult.error}\n`
512
- );
513
- }
514
- }
515
-
516
- // Summary line
517
- const succeeded = results.filter((r) => r.success).length;
518
- const failed = results.filter((r) => !r.success).length;
519
- process.stdout.write(
520
- `\n${theme.heading("Summary:")} ${succeeded} succeeded, ${failed} failed\n`
521
- );
522
- }
523
-
524
443
  // ---------------------------------------------------------------------------
525
444
  // Telegram webhook handler
526
445
  // ---------------------------------------------------------------------------
@@ -545,12 +464,12 @@ async function doTelegramWebhook(
545
464
  let botToken = token;
546
465
  if (!botToken) {
547
466
  const envVars = await EnvService.loadDotEnvAsync(".env.local");
548
- botToken = envVars["TELEGRAM_BOT_TOKEN"];
467
+ botToken = envVars["TG_BOT_TOKEN_BINDING"];
549
468
  }
550
469
  if (!botToken) {
551
470
  formatError(
552
471
  new CLIError(
553
- "Telegram bot token not found. Provide --token or set TELEGRAM_BOT_TOKEN in .env.local",
472
+ "Telegram bot token not found. Provide --token or set TG_BOT_TOKEN_BINDING in .env.local",
554
473
  ExitCode.ERROR
555
474
  ),
556
475
  fmt