@jango-blockchained/hoox-cli 0.4.0 → 0.5.0
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/package.json +1 -1
- package/src/commands/dashboard/dashboard-command.ts +3 -2
- package/src/commands/deploy/deploy-command.ts +3 -2
- package/src/commands/init/__tests__/cli-provisioner.test.ts +17 -0
- package/src/commands/init/cli-provisioner.ts +71 -0
- package/src/commands/init/init-command.test.ts +109 -60
- package/src/commands/init/init-command.ts +330 -364
- package/src/commands/init/types.ts +6 -168
- package/src/commands/logs/index.ts +1 -0
- package/src/commands/logs/logs-command.test.ts +294 -0
- package/src/commands/logs/logs-command.ts +452 -0
- package/src/commands/update/update-command.ts +143 -26
- package/src/index.ts +20 -0
- package/src/ui/banner.ts +117 -28
- package/src/ui/menu.ts +15 -3
package/package.json
CHANGED
|
@@ -79,10 +79,11 @@ function updateWranglerVars(
|
|
|
79
79
|
return;
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
-
const
|
|
82
|
+
const fresh = readFileSync(filePath, "utf-8");
|
|
83
|
+
const edits = jsonc.modify(fresh, ["vars"], vars, {
|
|
83
84
|
formattingOptions: { tabSize: 2, insertSpaces: true },
|
|
84
85
|
});
|
|
85
|
-
const updated = jsonc.applyEdits(
|
|
86
|
+
const updated = jsonc.applyEdits(fresh, edits);
|
|
86
87
|
writeFileSync(filePath, updated, "utf-8");
|
|
87
88
|
|
|
88
89
|
formatSuccess(
|
|
@@ -697,10 +697,11 @@ async function doUpdateInternalUrls(fmt: FormatOptions): Promise<void> {
|
|
|
697
697
|
return;
|
|
698
698
|
}
|
|
699
699
|
|
|
700
|
-
const
|
|
700
|
+
const fresh = readFileSync(filePath, "utf-8");
|
|
701
|
+
const edits = jsonc.modify(fresh, ["vars"], vars, {
|
|
701
702
|
formattingOptions: { tabSize: 2, insertSpaces: true },
|
|
702
703
|
});
|
|
703
|
-
writeFileSync(filePath, jsonc.applyEdits(
|
|
704
|
+
writeFileSync(filePath, jsonc.applyEdits(fresh, edits), "utf-8");
|
|
704
705
|
formatSuccess(
|
|
705
706
|
`Updated ${changesCount} service URL(s) in dashboard wrangler.jsonc`,
|
|
706
707
|
fmt
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { describe, it, expect } from "bun:test";
|
|
2
|
+
import { CLIProvisioner } from "../cli-provisioner";
|
|
3
|
+
|
|
4
|
+
describe("CLIProvisioner", () => {
|
|
5
|
+
it("check returns expected resources", async () => {
|
|
6
|
+
const provisioner = new CLIProvisioner();
|
|
7
|
+
const result = await provisioner.check({
|
|
8
|
+
d1Databases: ["hoox-db"],
|
|
9
|
+
kvNamespaces: ["CONFIG_KV"],
|
|
10
|
+
r2Buckets: [],
|
|
11
|
+
queues: [],
|
|
12
|
+
});
|
|
13
|
+
expect(result.success).toBe(true);
|
|
14
|
+
expect(result.created).toContain("D1:hoox-db");
|
|
15
|
+
expect(result.created).toContain("KV:CONFIG_KV");
|
|
16
|
+
});
|
|
17
|
+
});
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI implementation of the Provisioner interface.
|
|
3
|
+
* Uses `wrangler` CLI via Bun.spawn to create Cloudflare resources.
|
|
4
|
+
*/
|
|
5
|
+
import type {
|
|
6
|
+
Provisioner,
|
|
7
|
+
ProvisioningPlan,
|
|
8
|
+
ProvisionResult,
|
|
9
|
+
} from "@jango-blockchained/hoox-shared";
|
|
10
|
+
|
|
11
|
+
export class CLIProvisioner implements Provisioner {
|
|
12
|
+
async provision(plan: ProvisioningPlan): Promise<ProvisionResult> {
|
|
13
|
+
const created: string[] = [];
|
|
14
|
+
const errors: string[] = [];
|
|
15
|
+
|
|
16
|
+
for (const db of plan.d1Databases) {
|
|
17
|
+
try {
|
|
18
|
+
const proc = Bun.spawn(["wrangler", "d1", "create", db], {
|
|
19
|
+
stdout: "pipe",
|
|
20
|
+
stderr: "pipe",
|
|
21
|
+
});
|
|
22
|
+
await new Response(proc.stdout).text();
|
|
23
|
+
const exitCode = await proc.exited;
|
|
24
|
+
if (exitCode === 0) {
|
|
25
|
+
created.push(`D1:${db}`);
|
|
26
|
+
} else {
|
|
27
|
+
const err = await new Response(proc.stderr).text();
|
|
28
|
+
errors.push(`D1:${db} — ${err.trim() || `exit code ${exitCode}`}`);
|
|
29
|
+
}
|
|
30
|
+
} catch (e) {
|
|
31
|
+
errors.push(`D1:${db} — ${(e as Error).message}`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
for (const ns of plan.kvNamespaces) {
|
|
36
|
+
try {
|
|
37
|
+
const proc = Bun.spawn(["wrangler", "kv", "namespace", "create", ns], {
|
|
38
|
+
stdout: "pipe",
|
|
39
|
+
stderr: "pipe",
|
|
40
|
+
});
|
|
41
|
+
await new Response(proc.stdout).text();
|
|
42
|
+
const exitCode = await proc.exited;
|
|
43
|
+
if (exitCode === 0) {
|
|
44
|
+
created.push(`KV:${ns}`);
|
|
45
|
+
} else {
|
|
46
|
+
const err = await new Response(proc.stderr).text();
|
|
47
|
+
errors.push(`KV:${ns} — ${err.trim() || `exit code ${exitCode}`}`);
|
|
48
|
+
}
|
|
49
|
+
} catch (e) {
|
|
50
|
+
errors.push(`KV:${ns} — ${(e as Error).message}`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return {
|
|
55
|
+
success: errors.length === 0,
|
|
56
|
+
created,
|
|
57
|
+
errors,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async check(plan: ProvisioningPlan): Promise<ProvisionResult> {
|
|
62
|
+
// Dry-run: just report what would be created
|
|
63
|
+
const created: string[] = [
|
|
64
|
+
...plan.d1Databases.map((d) => `D1:${d}`),
|
|
65
|
+
...plan.kvNamespaces.map((k) => `KV:${k}`),
|
|
66
|
+
...plan.r2Buckets.map((b) => `R2:${b}`),
|
|
67
|
+
...plan.queues.map((q) => `Queue:${q}`),
|
|
68
|
+
];
|
|
69
|
+
return { success: true, created, errors: [] };
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -35,6 +35,7 @@ interface CapturedCalls {
|
|
|
35
35
|
passwordMessages: string[];
|
|
36
36
|
textMessages: string[];
|
|
37
37
|
multiselectMessages: string[];
|
|
38
|
+
selectMessages: string[];
|
|
38
39
|
confirmMessages: string[];
|
|
39
40
|
cancelMessages: string[];
|
|
40
41
|
logInfo: string[];
|
|
@@ -54,6 +55,7 @@ function makeCapture(): CapturedCalls {
|
|
|
54
55
|
passwordMessages: [],
|
|
55
56
|
textMessages: [],
|
|
56
57
|
multiselectMessages: [],
|
|
58
|
+
selectMessages: [],
|
|
57
59
|
confirmMessages: [],
|
|
58
60
|
cancelMessages: [],
|
|
59
61
|
logInfo: [],
|
|
@@ -72,7 +74,9 @@ interface PromptResponses {
|
|
|
72
74
|
password?: string | symbol;
|
|
73
75
|
text?: string | symbol;
|
|
74
76
|
multiselect?: string[] | symbol;
|
|
75
|
-
|
|
77
|
+
select?: string | symbol;
|
|
78
|
+
/** Confirm responses returned in order (call-by-call). Defaults are `[true, false, false]`. */
|
|
79
|
+
confirmSequence?: boolean[];
|
|
76
80
|
/** Group responses keyed by field name */
|
|
77
81
|
group?: Record<string, string>;
|
|
78
82
|
}
|
|
@@ -81,7 +85,8 @@ const defaultResponses: Required<PromptResponses> = {
|
|
|
81
85
|
password: "test-token",
|
|
82
86
|
text: "test-account",
|
|
83
87
|
multiselect: [],
|
|
84
|
-
|
|
88
|
+
select: "minimal",
|
|
89
|
+
confirmSequence: [true, false, false], // risk → provisioning → deploy
|
|
85
90
|
group: {},
|
|
86
91
|
};
|
|
87
92
|
|
|
@@ -92,6 +97,7 @@ interface CallCounters {
|
|
|
92
97
|
password: number;
|
|
93
98
|
text: number;
|
|
94
99
|
multiselect: number;
|
|
100
|
+
select: number;
|
|
95
101
|
confirm: number;
|
|
96
102
|
}
|
|
97
103
|
|
|
@@ -99,11 +105,12 @@ let counters: CallCounters = {
|
|
|
99
105
|
password: 0,
|
|
100
106
|
text: 0,
|
|
101
107
|
multiselect: 0,
|
|
108
|
+
select: 0,
|
|
102
109
|
confirm: 0,
|
|
103
110
|
};
|
|
104
111
|
|
|
105
112
|
function resetCounters(): void {
|
|
106
|
-
counters = { password: 0, text: 0, multiselect: 0, confirm: 0 };
|
|
113
|
+
counters = { password: 0, text: 0, multiselect: 0, select: 0, confirm: 0 };
|
|
107
114
|
}
|
|
108
115
|
|
|
109
116
|
/** Whether to simulate cancellation on next check. */
|
|
@@ -182,13 +189,26 @@ function installClackSpies(): void {
|
|
|
182
189
|
: (responses.multiselect ?? defaultResponses.multiselect);
|
|
183
190
|
}
|
|
184
191
|
);
|
|
192
|
+
spyOn(clack, "select").mockImplementation(
|
|
193
|
+
async (opts: {
|
|
194
|
+
message: string;
|
|
195
|
+
options: { value: string; label: string; hint?: string }[];
|
|
196
|
+
}) => {
|
|
197
|
+
captured.selectMessages.push(opts.message);
|
|
198
|
+
counters.select++;
|
|
199
|
+
return simulateCancel
|
|
200
|
+
? Symbol.for("clack.cancel")
|
|
201
|
+
: (responses.select ?? defaultResponses.select);
|
|
202
|
+
}
|
|
203
|
+
);
|
|
185
204
|
spyOn(clack, "confirm").mockImplementation(
|
|
186
205
|
async (opts: { message: string; initialValue?: boolean }) => {
|
|
187
206
|
captured.confirmMessages.push(opts.message);
|
|
188
|
-
counters.confirm++;
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
207
|
+
const idx = counters.confirm++;
|
|
208
|
+
const seq = responses.confirmSequence ?? defaultResponses.confirmSequence;
|
|
209
|
+
const val = idx < seq.length ? seq[idx] : seq[seq.length - 1];
|
|
210
|
+
if (simulateCancel) return Symbol.for("clack.cancel");
|
|
211
|
+
return val;
|
|
192
212
|
}
|
|
193
213
|
);
|
|
194
214
|
spyOn(clack, "group").mockImplementation(
|
|
@@ -244,8 +264,7 @@ function installClackSpies(): void {
|
|
|
244
264
|
// Mock: Bun.write & Bun.file
|
|
245
265
|
// ---------------------------------------------------------------------------
|
|
246
266
|
|
|
247
|
-
const
|
|
248
|
-
const originalFile = Bun.file;
|
|
267
|
+
const origWhoamiPrototype = CloudflareService.prototype.whoami;
|
|
249
268
|
|
|
250
269
|
beforeEach(() => {
|
|
251
270
|
captured = makeCapture();
|
|
@@ -302,7 +321,7 @@ beforeEach(() => {
|
|
|
302
321
|
|
|
303
322
|
afterEach(() => {
|
|
304
323
|
mock.restore();
|
|
305
|
-
CloudflareService.prototype.whoami =
|
|
324
|
+
CloudflareService.prototype.whoami = origWhoamiPrototype;
|
|
306
325
|
});
|
|
307
326
|
|
|
308
327
|
// ---------------------------------------------------------------------------
|
|
@@ -320,18 +339,19 @@ describe("init command", () => {
|
|
|
320
339
|
password: "valid-token",
|
|
321
340
|
text: "test-account-id",
|
|
322
341
|
multiselect: [],
|
|
323
|
-
|
|
342
|
+
select: "minimal",
|
|
343
|
+
confirmSequence: [true, false, false],
|
|
324
344
|
};
|
|
325
345
|
|
|
326
|
-
// Prevent collectBaseSecrets confirm from breaking
|
|
327
346
|
await runInitCommand({}, { json: false, quiet: true }, false);
|
|
328
347
|
|
|
329
348
|
expect(captured.intro.length).toBeGreaterThan(0);
|
|
330
349
|
expect(captured.intro.some((m) => m.includes("Hoox Setup Wizard"))).toBe(
|
|
331
350
|
true
|
|
332
351
|
);
|
|
333
|
-
|
|
334
|
-
|
|
352
|
+
expect(captured.outro.some((m) => m.includes("Setup complete"))).toBe(
|
|
353
|
+
true
|
|
354
|
+
);
|
|
335
355
|
});
|
|
336
356
|
|
|
337
357
|
it("collects Cloudflare API token with validation", async () => {
|
|
@@ -339,7 +359,8 @@ describe("init command", () => {
|
|
|
339
359
|
password: "valid-token",
|
|
340
360
|
text: "test-account-id",
|
|
341
361
|
multiselect: [],
|
|
342
|
-
|
|
362
|
+
select: "minimal",
|
|
363
|
+
confirmSequence: [true, false, false],
|
|
343
364
|
};
|
|
344
365
|
|
|
345
366
|
await runInitCommand({}, { json: false, quiet: true }, false);
|
|
@@ -369,7 +390,8 @@ describe("init command", () => {
|
|
|
369
390
|
password: "",
|
|
370
391
|
text: "test-account-id",
|
|
371
392
|
multiselect: [],
|
|
372
|
-
|
|
393
|
+
select: "minimal",
|
|
394
|
+
confirmSequence: [true, false, false],
|
|
373
395
|
};
|
|
374
396
|
|
|
375
397
|
await runInitCommand({}, { json: false, quiet: true }, false);
|
|
@@ -417,7 +439,8 @@ describe("init command", () => {
|
|
|
417
439
|
password: "valid-token",
|
|
418
440
|
text: "existing-account-id-123",
|
|
419
441
|
multiselect: [],
|
|
420
|
-
|
|
442
|
+
select: "minimal",
|
|
443
|
+
confirmSequence: [true, false, false],
|
|
421
444
|
};
|
|
422
445
|
|
|
423
446
|
await runInitCommand({}, { json: false, quiet: true }, false);
|
|
@@ -434,7 +457,8 @@ describe("init command", () => {
|
|
|
434
457
|
password: "valid-token",
|
|
435
458
|
text: "test-account-id",
|
|
436
459
|
multiselect: ["binance", "telegram"],
|
|
437
|
-
|
|
460
|
+
select: "full", // full preset includes integrations
|
|
461
|
+
confirmSequence: [true, false, false],
|
|
438
462
|
group: {
|
|
439
463
|
BINANCE_API_KEY: "binance-key-123",
|
|
440
464
|
BINANCE_API_SECRET: "binance-secret-123",
|
|
@@ -445,8 +469,8 @@ describe("init command", () => {
|
|
|
445
469
|
await runInitCommand({}, { json: false, quiet: true }, false);
|
|
446
470
|
|
|
447
471
|
expect(
|
|
448
|
-
captured.
|
|
449
|
-
m.includes("Select
|
|
472
|
+
captured.selectMessages.some((m) =>
|
|
473
|
+
m.includes("Select a worker preset")
|
|
450
474
|
)
|
|
451
475
|
).toBe(true);
|
|
452
476
|
});
|
|
@@ -456,7 +480,8 @@ describe("init command", () => {
|
|
|
456
480
|
password: "valid-token",
|
|
457
481
|
text: "test-account-id",
|
|
458
482
|
multiselect: ["telegram"],
|
|
459
|
-
|
|
483
|
+
select: "full",
|
|
484
|
+
confirmSequence: [true, false, false],
|
|
460
485
|
group: {
|
|
461
486
|
TELEGRAM_BOT_TOKEN: "tg-token-abc",
|
|
462
487
|
},
|
|
@@ -510,7 +535,7 @@ describe("init command", () => {
|
|
|
510
535
|
|
|
511
536
|
const options: InitOptions = {
|
|
512
537
|
token: "bad-token",
|
|
513
|
-
account: "
|
|
538
|
+
account: "abc123def456abc123def456abc123de",
|
|
514
539
|
};
|
|
515
540
|
|
|
516
541
|
const promise = runInitCommand(
|
|
@@ -569,12 +594,14 @@ describe("init command", () => {
|
|
|
569
594
|
password: "valid-token",
|
|
570
595
|
text: "test-account",
|
|
571
596
|
multiselect: [],
|
|
572
|
-
|
|
597
|
+
select: "minimal",
|
|
598
|
+
confirmSequence: [true, false, false],
|
|
573
599
|
};
|
|
574
600
|
|
|
575
601
|
await runInitCommand({}, { json: false, quiet: true }, false);
|
|
576
602
|
|
|
577
603
|
const workersJsonc = captured.writes["wrangler.jsonc"];
|
|
604
|
+
expect(workersJsonc).toBeDefined();
|
|
578
605
|
expect(workersJsonc).toContain('"d1-worker"');
|
|
579
606
|
expect(workersJsonc).toContain('"hoox"');
|
|
580
607
|
expect(workersJsonc).toContain('"agent-worker"');
|
|
@@ -585,8 +612,9 @@ describe("init command", () => {
|
|
|
585
612
|
responses = {
|
|
586
613
|
password: "valid-token",
|
|
587
614
|
text: "test-account",
|
|
588
|
-
multiselect: [
|
|
589
|
-
|
|
615
|
+
multiselect: [],
|
|
616
|
+
select: "full",
|
|
617
|
+
confirmSequence: [true, false, false],
|
|
590
618
|
group: {
|
|
591
619
|
BINANCE_API_KEY: "bk",
|
|
592
620
|
BINANCE_API_SECRET: "bs",
|
|
@@ -597,6 +625,7 @@ describe("init command", () => {
|
|
|
597
625
|
await runInitCommand({}, { json: false, quiet: true }, false);
|
|
598
626
|
|
|
599
627
|
const workersJsonc = captured.writes["wrangler.jsonc"];
|
|
628
|
+
expect(workersJsonc).toBeDefined();
|
|
600
629
|
expect(workersJsonc).toContain('"trade-worker"');
|
|
601
630
|
expect(workersJsonc).toContain('"BINANCE_API_KEY"');
|
|
602
631
|
expect(workersJsonc).toContain('"BINANCE_API_SECRET"');
|
|
@@ -604,52 +633,53 @@ describe("init command", () => {
|
|
|
604
633
|
expect(workersJsonc).toContain('"TELEGRAM_BOT_TOKEN"');
|
|
605
634
|
});
|
|
606
635
|
|
|
607
|
-
it("includes AI provider
|
|
636
|
+
it("includes AI provider (OpenAI) integration from full preset", async () => {
|
|
608
637
|
responses = {
|
|
609
638
|
password: "valid-token",
|
|
610
639
|
text: "test-account",
|
|
611
|
-
multiselect: [
|
|
612
|
-
|
|
640
|
+
multiselect: [],
|
|
641
|
+
select: "full",
|
|
642
|
+
confirmSequence: [true, false, false],
|
|
613
643
|
group: {
|
|
614
644
|
AGENT_OPENAI_KEY: "sk-openai-123",
|
|
615
|
-
AGENT_ANTHROPIC_KEY: "sk-ant-123",
|
|
616
|
-
AGENT_GOOGLE_KEY: "google-ai-key-456",
|
|
617
645
|
},
|
|
618
646
|
};
|
|
619
647
|
|
|
620
648
|
await runInitCommand({}, { json: false, quiet: true }, false);
|
|
621
649
|
|
|
622
650
|
const workersJsonc = captured.writes["wrangler.jsonc"];
|
|
651
|
+
expect(workersJsonc).toBeDefined();
|
|
623
652
|
expect(workersJsonc).toContain('"agent-worker"');
|
|
624
653
|
expect(workersJsonc).toContain('"AGENT_OPENAI_KEY"');
|
|
625
|
-
expect(workersJsonc).toContain('"AGENT_ANTHROPIC_KEY"');
|
|
626
|
-
expect(workersJsonc).toContain('"AGENT_GOOGLE_KEY"');
|
|
627
654
|
});
|
|
628
655
|
|
|
629
|
-
it("includes
|
|
656
|
+
it("includes wallet integration from full preset", async () => {
|
|
630
657
|
responses = {
|
|
631
658
|
password: "valid-token",
|
|
632
659
|
text: "test-account",
|
|
633
|
-
multiselect: [
|
|
634
|
-
|
|
660
|
+
multiselect: [],
|
|
661
|
+
select: "full",
|
|
662
|
+
confirmSequence: [true, false, false],
|
|
635
663
|
group: {
|
|
636
|
-
|
|
664
|
+
WALLET_MNEMONIC_SECRET: "seed-phrase-789",
|
|
637
665
|
},
|
|
638
666
|
};
|
|
639
667
|
|
|
640
668
|
await runInitCommand({}, { json: false, quiet: true }, false);
|
|
641
669
|
|
|
642
670
|
const workersJsonc = captured.writes["wrangler.jsonc"];
|
|
643
|
-
expect(workersJsonc).
|
|
644
|
-
expect(workersJsonc).toContain('"
|
|
671
|
+
expect(workersJsonc).toBeDefined();
|
|
672
|
+
expect(workersJsonc).toContain('"web3-wallet-worker"');
|
|
673
|
+
expect(workersJsonc).toContain('"WALLET_MNEMONIC_SECRET"');
|
|
645
674
|
});
|
|
646
675
|
|
|
647
676
|
it("merges exchange secrets into single trade-worker", async () => {
|
|
648
677
|
responses = {
|
|
649
678
|
password: "valid-token",
|
|
650
679
|
text: "test-account",
|
|
651
|
-
multiselect: [
|
|
652
|
-
|
|
680
|
+
multiselect: [],
|
|
681
|
+
select: "full",
|
|
682
|
+
confirmSequence: [true, false, false],
|
|
653
683
|
group: {
|
|
654
684
|
BINANCE_API_KEY: "bk",
|
|
655
685
|
BINANCE_API_SECRET: "bs",
|
|
@@ -661,6 +691,7 @@ describe("init command", () => {
|
|
|
661
691
|
await runInitCommand({}, { json: false, quiet: true }, false);
|
|
662
692
|
|
|
663
693
|
const workersJsonc = captured.writes["wrangler.jsonc"];
|
|
694
|
+
expect(workersJsonc).toBeDefined();
|
|
664
695
|
expect(workersJsonc).toContain('"trade-worker"');
|
|
665
696
|
expect(workersJsonc).toContain('"BINANCE_API_KEY"');
|
|
666
697
|
expect(workersJsonc).toContain('"MEXC_API_KEY"');
|
|
@@ -675,12 +706,14 @@ describe("init command", () => {
|
|
|
675
706
|
password: "my-cf-token",
|
|
676
707
|
text: "my-account-id",
|
|
677
708
|
multiselect: [],
|
|
678
|
-
|
|
709
|
+
select: "minimal",
|
|
710
|
+
confirmSequence: [true, false, false],
|
|
679
711
|
};
|
|
680
712
|
|
|
681
713
|
await runInitCommand({}, { json: false, quiet: true }, false);
|
|
682
714
|
|
|
683
715
|
const workersJsonc = captured.writes["wrangler.jsonc"];
|
|
716
|
+
expect(workersJsonc).toBeDefined();
|
|
684
717
|
expect(workersJsonc).toContain('"global"');
|
|
685
718
|
expect(workersJsonc).toContain('"cloudflare_api_token"');
|
|
686
719
|
expect(workersJsonc).toContain('"cloudflare_account_id"');
|
|
@@ -692,12 +725,14 @@ describe("init command", () => {
|
|
|
692
725
|
password: "valid-token",
|
|
693
726
|
text: "test-account",
|
|
694
727
|
multiselect: [],
|
|
695
|
-
|
|
728
|
+
select: "minimal",
|
|
729
|
+
confirmSequence: [true, false, false],
|
|
696
730
|
};
|
|
697
731
|
|
|
698
732
|
await runInitCommand({}, { json: false, quiet: true }, false);
|
|
699
733
|
|
|
700
734
|
const workersJsonc = captured.writes["wrangler.jsonc"];
|
|
735
|
+
expect(workersJsonc).toBeDefined();
|
|
701
736
|
expect(workersJsonc).toContain("// Hoox Workspace Configuration");
|
|
702
737
|
expect(workersJsonc).toContain("// Generated by `hoox init`");
|
|
703
738
|
});
|
|
@@ -712,8 +747,9 @@ describe("init command", () => {
|
|
|
712
747
|
responses = {
|
|
713
748
|
password: "valid-token",
|
|
714
749
|
text: "test-account",
|
|
715
|
-
multiselect: [
|
|
716
|
-
|
|
750
|
+
multiselect: [],
|
|
751
|
+
select: "full",
|
|
752
|
+
confirmSequence: [true, false, false],
|
|
717
753
|
group: {
|
|
718
754
|
TELEGRAM_BOT_TOKEN: "tg-secret-value",
|
|
719
755
|
},
|
|
@@ -723,6 +759,7 @@ describe("init command", () => {
|
|
|
723
759
|
|
|
724
760
|
const devVarsPath = "workers/telegram-worker/.dev.vars";
|
|
725
761
|
const devVars = captured.writes[devVarsPath];
|
|
762
|
+
expect(devVars).toBeDefined();
|
|
726
763
|
expect(devVars).toContain("TELEGRAM_BOT_TOKEN=tg-secret-value");
|
|
727
764
|
expect(devVars).toContain("NEVER commit this file");
|
|
728
765
|
});
|
|
@@ -732,12 +769,13 @@ describe("init command", () => {
|
|
|
732
769
|
password: "valid-token",
|
|
733
770
|
text: "test-account",
|
|
734
771
|
multiselect: [],
|
|
735
|
-
|
|
772
|
+
select: "minimal",
|
|
773
|
+
confirmSequence: [true, false, false],
|
|
736
774
|
};
|
|
737
775
|
|
|
738
776
|
await runInitCommand({}, { json: false, quiet: true }, false);
|
|
739
777
|
|
|
740
|
-
// d1-worker has no user-collected secrets
|
|
778
|
+
// d1-worker has no user-collected secrets
|
|
741
779
|
const d1DevVars = captured.writes["workers/d1-worker/.dev.vars"];
|
|
742
780
|
expect(d1DevVars).toBeUndefined();
|
|
743
781
|
});
|
|
@@ -746,8 +784,9 @@ describe("init command", () => {
|
|
|
746
784
|
responses = {
|
|
747
785
|
password: "valid-token",
|
|
748
786
|
text: "test-account",
|
|
749
|
-
multiselect: [
|
|
750
|
-
|
|
787
|
+
multiselect: [],
|
|
788
|
+
select: "full",
|
|
789
|
+
confirmSequence: [true, false, false],
|
|
751
790
|
group: {
|
|
752
791
|
AGENT_OPENAI_KEY: "sk-openai-real-value",
|
|
753
792
|
},
|
|
@@ -757,26 +796,29 @@ describe("init command", () => {
|
|
|
757
796
|
|
|
758
797
|
const devVarsPath = "workers/agent-worker/.dev.vars";
|
|
759
798
|
const devVars = captured.writes[devVarsPath];
|
|
799
|
+
expect(devVars).toBeDefined();
|
|
760
800
|
expect(devVars).toContain("AGENT_OPENAI_KEY=sk-openai-real-value");
|
|
761
801
|
expect(devVars).toContain("NEVER commit this file");
|
|
762
802
|
});
|
|
763
803
|
|
|
764
|
-
it("creates .dev.vars for
|
|
804
|
+
it("creates .dev.vars for web3-wallet-worker when wallet integration selected", async () => {
|
|
765
805
|
responses = {
|
|
766
806
|
password: "valid-token",
|
|
767
807
|
text: "test-account",
|
|
768
|
-
multiselect: [
|
|
769
|
-
|
|
808
|
+
multiselect: [],
|
|
809
|
+
select: "full",
|
|
810
|
+
confirmSequence: [true, false, false],
|
|
770
811
|
group: {
|
|
771
|
-
|
|
812
|
+
WALLET_MNEMONIC_SECRET: "mnemonic-real-value",
|
|
772
813
|
},
|
|
773
814
|
};
|
|
774
815
|
|
|
775
816
|
await runInitCommand({}, { json: false, quiet: true }, false);
|
|
776
817
|
|
|
777
|
-
const devVarsPath = "workers/
|
|
818
|
+
const devVarsPath = "workers/web3-wallet-worker/.dev.vars";
|
|
778
819
|
const devVars = captured.writes[devVarsPath];
|
|
779
|
-
expect(devVars).
|
|
820
|
+
expect(devVars).toBeDefined();
|
|
821
|
+
expect(devVars).toContain("WALLET_MNEMONIC_SECRET=mnemonic-real-value");
|
|
780
822
|
expect(devVars).toContain("NEVER commit this file");
|
|
781
823
|
});
|
|
782
824
|
});
|
|
@@ -786,19 +828,24 @@ describe("init command", () => {
|
|
|
786
828
|
// ------------------------------------------------------------------
|
|
787
829
|
|
|
788
830
|
describe("cancellation handling", () => {
|
|
789
|
-
it("exits on cancel during
|
|
790
|
-
// Override the process.exit mock to track calls
|
|
831
|
+
it("exits on cancel during risk acknowledgment", async () => {
|
|
791
832
|
let exitCalled = false;
|
|
792
833
|
let exitCode = -1;
|
|
793
834
|
const exitMock = mock((code?: number) => {
|
|
794
835
|
exitCalled = true;
|
|
795
836
|
exitCode = code ?? -1;
|
|
837
|
+
throw new Error("EXIT"); // Must throw to stop execution flow
|
|
796
838
|
});
|
|
797
839
|
process.exit = exitMock as unknown as typeof process.exit;
|
|
798
840
|
|
|
841
|
+
// First confirm (risk) is canceled
|
|
799
842
|
simulateCancel = true;
|
|
800
843
|
|
|
801
|
-
|
|
844
|
+
try {
|
|
845
|
+
await runInitCommand({}, { json: false, quiet: true }, false);
|
|
846
|
+
} catch {
|
|
847
|
+
// expected — process.exit throws
|
|
848
|
+
}
|
|
802
849
|
|
|
803
850
|
expect(exitCalled).toBe(true);
|
|
804
851
|
expect(exitCode).toBe(0);
|
|
@@ -815,13 +862,15 @@ describe("init command", () => {
|
|
|
815
862
|
password: "valid-token",
|
|
816
863
|
text: "test-account",
|
|
817
864
|
multiselect: [],
|
|
818
|
-
|
|
865
|
+
select: "minimal",
|
|
866
|
+
confirmSequence: [true, false, false],
|
|
819
867
|
};
|
|
820
868
|
|
|
821
869
|
await runInitCommand({}, { json: false, quiet: true }, false);
|
|
822
870
|
|
|
823
|
-
// In quiet mode,
|
|
824
|
-
//
|
|
871
|
+
// In quiet mode, formatSuccess/formatter functions respect quiet mode.
|
|
872
|
+
// The flow should complete without throwing.
|
|
873
|
+
expect(captured.writes["wrangler.jsonc"]).toBeDefined();
|
|
825
874
|
});
|
|
826
875
|
});
|
|
827
876
|
});
|