@jango-blockchained/hoox-cli 0.5.0 → 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.
- package/package.json +1 -1
- package/src/commands/check/check-command.ts +8 -65
- package/src/commands/check/prerequisites-command.ts +7 -6
- package/src/commands/clone/clone-command.test.ts +8 -10
- package/src/commands/config/config-command.test.ts +4 -4
- package/src/commands/config/config-command.ts +10 -11
- package/src/commands/config/env-command.test.ts +2 -2
- package/src/commands/config/env-command.ts +40 -43
- package/src/commands/config/kv-command.ts +60 -60
- package/src/commands/dashboard/dashboard-command.ts +32 -30
- package/src/commands/db/db-command.ts +79 -80
- package/src/commands/deploy/deploy-command.test.ts +54 -34
- package/src/commands/deploy/deploy-command.ts +3 -84
- package/src/commands/dev/dev-command.test.ts +84 -62
- package/src/commands/disclaimer/disclaimer-command.ts +21 -0
- package/src/commands/disclaimer/index.ts +1 -0
- package/src/commands/init/init-command.test.ts +69 -91
- package/src/commands/init/init-command.ts +19 -15
- package/src/commands/logs/logs-command.test.ts +0 -1
- package/src/commands/monitor/monitor-command.test.ts +37 -29
- package/src/commands/repair/repair-command.test.ts +60 -41
- package/src/commands/repair/repair-service.ts +26 -0
- package/src/commands/schema/index.ts +1 -0
- package/src/commands/schema/schema-command.ts +137 -0
- package/src/commands/test/test-command.test.ts +2 -3
- package/src/commands/update/update-command.ts +6 -65
- package/src/commands/waf/waf-command.ts +56 -59
- package/src/index.ts +5 -13
- package/src/services/config/config-service.ts +1 -6
- package/src/services/db/db-service.test.ts +13 -1
- package/src/services/env/env-service.test.ts +41 -18
- package/src/services/env/env-service.ts +45 -59
- package/src/services/kv/kv-sync-service.ts +14 -5
- package/src/services/schema/index.ts +1 -0
- package/src/services/schema/schema-service.ts +99 -0
- package/src/services/secrets/secrets-service.test.ts +42 -35
- package/src/services/secrets/types.ts +1 -1
- package/src/ui/menu.ts +1 -1
- package/src/utils/error-handler.ts +62 -0
- package/src/utils/errors.ts +1 -0
- package/src/utils/git.ts +134 -0
- package/src/utils/theme.ts +0 -2
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
// @ts-nocheck
|
|
2
1
|
/**
|
|
3
2
|
* Unit tests for the dev command.
|
|
4
3
|
*
|
|
@@ -64,39 +63,39 @@ beforeEach(() => {
|
|
|
64
63
|
process.exitCode = undefined;
|
|
65
64
|
|
|
66
65
|
// Restore prototypes to originals
|
|
67
|
-
(ConfigService.prototype as Record<string, unknown>).load =
|
|
68
|
-
|
|
66
|
+
(ConfigService.prototype as unknown as Record<string, unknown>).load =
|
|
67
|
+
origLoad;
|
|
68
|
+
(ConfigService.prototype as unknown as Record<string, unknown>).listWorkers =
|
|
69
69
|
origListWorkers;
|
|
70
|
-
(
|
|
71
|
-
|
|
72
|
-
|
|
70
|
+
(
|
|
71
|
+
ConfigService.prototype as unknown as Record<string, unknown>
|
|
72
|
+
).listEnabledWorkers = origListEnabled;
|
|
73
|
+
(ConfigService.prototype as unknown as Record<string, unknown>).getWorker =
|
|
73
74
|
origGetWorker;
|
|
74
|
-
(ConfigService.prototype as Record<string, unknown>).validate =
|
|
75
|
-
|
|
75
|
+
(ConfigService.prototype as unknown as Record<string, unknown>).validate =
|
|
76
|
+
origValidate;
|
|
77
|
+
(CloudflareService.prototype as unknown as Record<string, unknown>).dev =
|
|
78
|
+
origDev;
|
|
76
79
|
|
|
77
80
|
// Stub Bun.spawn to prevent actual process spawning during tests.
|
|
78
81
|
// Uses individual property assignment (not globalThis.Bun = {...}) to
|
|
79
82
|
// avoid "readonly property" errors in Bun 1.3.x.
|
|
80
|
-
(Bun as unknown as Record<string, unknown>).spawn = mock(() => ({
|
|
83
|
+
(Bun as unknown as unknown as Record<string, unknown>).spawn = mock(() => ({
|
|
81
84
|
stdout: new Blob([]),
|
|
82
85
|
stderr: new Blob([]),
|
|
83
86
|
exited: Promise.resolve(0),
|
|
84
87
|
stdin: { write: mock(() => {}), end: mock(() => {}) },
|
|
85
88
|
kill: mock(() => {}),
|
|
86
89
|
}));
|
|
87
|
-
(Bun as unknown as Record<string, unknown>).file = mock((_path: string) => ({
|
|
88
|
-
exists: mock(async () => true),
|
|
89
|
-
}));
|
|
90
|
-
|
|
91
90
|
// Fresh mocks
|
|
92
91
|
devMock = mock(async (_path: string, _port?: number) => ({
|
|
93
92
|
ok: true as const,
|
|
94
|
-
|
|
93
|
+
value: { port: _port ?? 8787 },
|
|
95
94
|
}));
|
|
96
95
|
|
|
97
|
-
loadMock = mock(async function () {
|
|
96
|
+
loadMock = mock(async function (this: ConfigService) {
|
|
98
97
|
// Use a regular function so `this` refers to the ConfigService instance
|
|
99
|
-
(this as Record<string, unknown>).config = {};
|
|
98
|
+
(this as unknown as Record<string, unknown>).config = {};
|
|
100
99
|
return {} as Record<string, unknown>;
|
|
101
100
|
});
|
|
102
101
|
listWorkersMock = mock(() => ["hoox", "trade-worker", "d1-worker"]);
|
|
@@ -114,52 +113,66 @@ beforeEach(() => {
|
|
|
114
113
|
composeExistsMock = mock(async () => false);
|
|
115
114
|
|
|
116
115
|
// Install mocks on prototypes
|
|
117
|
-
(ConfigService.prototype as Record<string, unknown>).load =
|
|
118
|
-
|
|
116
|
+
(ConfigService.prototype as unknown as Record<string, unknown>).load =
|
|
117
|
+
loadMock;
|
|
118
|
+
(ConfigService.prototype as unknown as Record<string, unknown>).listWorkers =
|
|
119
119
|
listWorkersMock;
|
|
120
|
-
(
|
|
121
|
-
|
|
122
|
-
|
|
120
|
+
(
|
|
121
|
+
ConfigService.prototype as unknown as Record<string, unknown>
|
|
122
|
+
).listEnabledWorkers = listEnabledWorkersMock;
|
|
123
|
+
(ConfigService.prototype as unknown as Record<string, unknown>).getWorker =
|
|
123
124
|
getWorkerMock;
|
|
124
|
-
(ConfigService.prototype as Record<string, unknown>).validate =
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
125
|
+
(ConfigService.prototype as unknown as Record<string, unknown>).validate =
|
|
126
|
+
validateMock;
|
|
127
|
+
(
|
|
128
|
+
ConfigService.prototype as unknown as Record<string, unknown>
|
|
129
|
+
).getDevRuntime = getDevRuntimeMock;
|
|
130
|
+
(CloudflareService.prototype as unknown as Record<string, unknown>).dev =
|
|
131
|
+
devMock;
|
|
128
132
|
(
|
|
129
|
-
PrerequisitesService.prototype as Record<string, unknown>
|
|
133
|
+
PrerequisitesService.prototype as unknown as Record<string, unknown>
|
|
130
134
|
).checkWranglerVersion = checkWranglerMock;
|
|
131
|
-
(
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
+
(
|
|
136
|
+
DockerService.prototype as unknown as Record<string, unknown>
|
|
137
|
+
).checkAvailability = dockerCheckMock;
|
|
138
|
+
(
|
|
139
|
+
DockerService.prototype as unknown as Record<string, unknown>
|
|
140
|
+
).composeFileExists = composeExistsMock;
|
|
135
141
|
});
|
|
136
142
|
|
|
137
143
|
afterEach(() => {
|
|
138
144
|
mock.restore();
|
|
139
145
|
|
|
140
146
|
// Restore originals
|
|
141
|
-
(ConfigService.prototype as Record<string, unknown>).load =
|
|
142
|
-
|
|
147
|
+
(ConfigService.prototype as unknown as Record<string, unknown>).load =
|
|
148
|
+
origLoad;
|
|
149
|
+
(ConfigService.prototype as unknown as Record<string, unknown>).listWorkers =
|
|
143
150
|
origListWorkers;
|
|
144
|
-
(
|
|
145
|
-
|
|
146
|
-
|
|
151
|
+
(
|
|
152
|
+
ConfigService.prototype as unknown as Record<string, unknown>
|
|
153
|
+
).listEnabledWorkers = origListEnabled;
|
|
154
|
+
(ConfigService.prototype as unknown as Record<string, unknown>).getWorker =
|
|
147
155
|
origGetWorker;
|
|
148
|
-
(ConfigService.prototype as Record<string, unknown>).validate =
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
156
|
+
(ConfigService.prototype as unknown as Record<string, unknown>).validate =
|
|
157
|
+
origValidate;
|
|
158
|
+
(
|
|
159
|
+
ConfigService.prototype as unknown as Record<string, unknown>
|
|
160
|
+
).getDevRuntime = origGetDevRuntime;
|
|
161
|
+
(CloudflareService.prototype as unknown as Record<string, unknown>).dev =
|
|
162
|
+
origDev;
|
|
152
163
|
(
|
|
153
|
-
PrerequisitesService.prototype as Record<string, unknown>
|
|
164
|
+
PrerequisitesService.prototype as unknown as Record<string, unknown>
|
|
154
165
|
).checkWranglerVersion = origCheckWrangler;
|
|
155
|
-
(
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
166
|
+
(
|
|
167
|
+
DockerService.prototype as unknown as Record<string, unknown>
|
|
168
|
+
).checkAvailability = origDockerCheck;
|
|
169
|
+
(
|
|
170
|
+
DockerService.prototype as unknown as Record<string, unknown>
|
|
171
|
+
).composeFileExists = origComposeExists;
|
|
159
172
|
|
|
160
173
|
// Restore Bun globals (individual property restore)
|
|
161
|
-
(Bun as unknown as Record<string, unknown>).spawn = origBunSpawn;
|
|
162
|
-
(Bun as unknown as Record<string, unknown>).file = origBunFile;
|
|
174
|
+
(Bun as unknown as unknown as Record<string, unknown>).spawn = origBunSpawn;
|
|
175
|
+
(Bun as unknown as unknown as Record<string, unknown>).file = origBunFile;
|
|
163
176
|
});
|
|
164
177
|
|
|
165
178
|
// ---------------------------------------------------------------------------
|
|
@@ -187,7 +200,8 @@ async function createProgram(): Promise<Command> {
|
|
|
187
200
|
/** Make devMock return a failure for all subsequent calls. */
|
|
188
201
|
function makeDevFail(error: string): void {
|
|
189
202
|
devMock = mock(async () => ({ ok: false as const, error }));
|
|
190
|
-
(CloudflareService.prototype as Record<string, unknown>).dev =
|
|
203
|
+
(CloudflareService.prototype as unknown as Record<string, unknown>).dev =
|
|
204
|
+
devMock;
|
|
191
205
|
}
|
|
192
206
|
|
|
193
207
|
// ---------------------------------------------------------------------------
|
|
@@ -237,7 +251,7 @@ describe("registerDevCommand", () => {
|
|
|
237
251
|
valid: false,
|
|
238
252
|
errors: ["global.cloudflare_account_id is required"],
|
|
239
253
|
}));
|
|
240
|
-
(ConfigService.prototype as Record<string, unknown>).validate =
|
|
254
|
+
(ConfigService.prototype as unknown as Record<string, unknown>).validate =
|
|
241
255
|
validateMock;
|
|
242
256
|
|
|
243
257
|
const program = await createProgram();
|
|
@@ -248,8 +262,9 @@ describe("registerDevCommand", () => {
|
|
|
248
262
|
|
|
249
263
|
it("handles no enabled workers gracefully", async () => {
|
|
250
264
|
listEnabledWorkersMock = mock(() => []);
|
|
251
|
-
(
|
|
252
|
-
|
|
265
|
+
(
|
|
266
|
+
ConfigService.prototype as unknown as Record<string, unknown>
|
|
267
|
+
).listEnabledWorkers = listEnabledWorkersMock;
|
|
253
268
|
|
|
254
269
|
const program = await createProgram();
|
|
255
270
|
await program.parseAsync(["dev", "start"], { from: "user" });
|
|
@@ -259,8 +274,9 @@ describe("registerDevCommand", () => {
|
|
|
259
274
|
|
|
260
275
|
it("starts all enabled workers on assigned ports", async () => {
|
|
261
276
|
listEnabledWorkersMock = mock(() => ["hoox", "trade-worker"]);
|
|
262
|
-
(
|
|
263
|
-
|
|
277
|
+
(
|
|
278
|
+
ConfigService.prototype as unknown as Record<string, unknown>
|
|
279
|
+
).listEnabledWorkers = listEnabledWorkersMock;
|
|
264
280
|
|
|
265
281
|
const program = await createProgram();
|
|
266
282
|
await program.parseAsync(["dev", "start"], { from: "user" });
|
|
@@ -306,8 +322,9 @@ describe("registerDevCommand", () => {
|
|
|
306
322
|
|
|
307
323
|
it("handles unknown worker name", async () => {
|
|
308
324
|
getWorkerMock = mock(() => undefined);
|
|
309
|
-
(
|
|
310
|
-
|
|
325
|
+
(
|
|
326
|
+
ConfigService.prototype as unknown as Record<string, unknown>
|
|
327
|
+
).getWorker = getWorkerMock;
|
|
311
328
|
|
|
312
329
|
const program = await createProgram();
|
|
313
330
|
await program.parseAsync(["dev", "worker", "nonexistent"], {
|
|
@@ -323,8 +340,9 @@ describe("registerDevCommand", () => {
|
|
|
323
340
|
enabled: false,
|
|
324
341
|
path: "workers/disabled-worker",
|
|
325
342
|
}));
|
|
326
|
-
(
|
|
327
|
-
|
|
343
|
+
(
|
|
344
|
+
ConfigService.prototype as unknown as Record<string, unknown>
|
|
345
|
+
).getWorker = getWorkerMock;
|
|
328
346
|
|
|
329
347
|
const program = await createProgram();
|
|
330
348
|
await program.parseAsync(["dev", "worker", "disabled-worker"], {
|
|
@@ -341,8 +359,9 @@ describe("registerDevCommand", () => {
|
|
|
341
359
|
enabled: true,
|
|
342
360
|
path: "workers/hoox",
|
|
343
361
|
}));
|
|
344
|
-
(
|
|
345
|
-
|
|
362
|
+
(
|
|
363
|
+
ConfigService.prototype as unknown as Record<string, unknown>
|
|
364
|
+
).getWorker = getWorkerMock;
|
|
346
365
|
|
|
347
366
|
const program = await createProgram();
|
|
348
367
|
await program.parseAsync(["dev", "worker", "hoox"], { from: "user" });
|
|
@@ -356,9 +375,11 @@ describe("registerDevCommand", () => {
|
|
|
356
375
|
describe("dev dashboard", () => {
|
|
357
376
|
it("handles missing dashboard directory", async () => {
|
|
358
377
|
// Override Bun.file for this test to say dashboard doesn't exist
|
|
359
|
-
(Bun as unknown as Record<string, unknown>).file = mock(
|
|
360
|
-
|
|
361
|
-
|
|
378
|
+
(Bun as unknown as unknown as Record<string, unknown>).file = mock(
|
|
379
|
+
(_p: string) => ({
|
|
380
|
+
exists: mock(async () => false),
|
|
381
|
+
})
|
|
382
|
+
);
|
|
362
383
|
|
|
363
384
|
const program = await createProgram();
|
|
364
385
|
await program.parseAsync(["dev", "dashboard"], { from: "user" });
|
|
@@ -373,7 +394,8 @@ describe("registerDevCommand", () => {
|
|
|
373
394
|
loadMock = mock(async () => {
|
|
374
395
|
throw new Error("Config file not found");
|
|
375
396
|
});
|
|
376
|
-
(ConfigService.prototype as Record<string, unknown>).load =
|
|
397
|
+
(ConfigService.prototype as unknown as Record<string, unknown>).load =
|
|
398
|
+
loadMock;
|
|
377
399
|
|
|
378
400
|
const program = await createProgram();
|
|
379
401
|
await program.parseAsync(["dev", "start"], { from: "user" });
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `hoox disclaimer` command — displays legal disclaimers and trademark information.
|
|
3
|
+
*
|
|
4
|
+
* Uses the centralized legal notices from @jango-blockchained/hoox-shared/legal
|
|
5
|
+
* so all surfaces (CLI, HTTP, Dashboard) stay consistent.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { Command } from "commander";
|
|
9
|
+
import { FULL_LEGAL_NOTICE } from "@jango-blockchained/hoox-shared/legal";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Registers the `hoox disclaimer` command on the given Commander.js program instance.
|
|
13
|
+
*/
|
|
14
|
+
export function registerDisclaimerCommand(program: Command): void {
|
|
15
|
+
program
|
|
16
|
+
.command("disclaimer")
|
|
17
|
+
.description("Display legal disclaimers and trademark information")
|
|
18
|
+
.action(() => {
|
|
19
|
+
console.log(FULL_LEGAL_NOTICE);
|
|
20
|
+
});
|
|
21
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { registerDisclaimerCommand } from "./disclaimer-command.js";
|
|
@@ -8,7 +8,6 @@
|
|
|
8
8
|
* - process.exit → captured exit codes
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
// @ts-nocheck — spyOn on module namespace requires type bypasses for strict signatures
|
|
12
11
|
import {
|
|
13
12
|
afterEach,
|
|
14
13
|
beforeEach,
|
|
@@ -123,8 +122,6 @@ let customPasswordResponder: ((msg: string) => string | symbol) | null = null;
|
|
|
123
122
|
// Mock: CloudflareService (prototype based — no mock.module, no leakage)
|
|
124
123
|
// ---------------------------------------------------------------------------
|
|
125
124
|
|
|
126
|
-
const origWhoami = CloudflareService.prototype.whoami;
|
|
127
|
-
|
|
128
125
|
const mockWhoami = mock(
|
|
129
126
|
async (): Promise<
|
|
130
127
|
{ ok: true; value: string } | { ok: false; error: string }
|
|
@@ -141,16 +138,19 @@ const mockWhoami = mock(
|
|
|
141
138
|
import * as clack from "@clack/prompts";
|
|
142
139
|
|
|
143
140
|
function installClackSpies(): void {
|
|
144
|
-
spyOn(clack, "intro").mockImplementation((msg
|
|
145
|
-
captured.intro.push(msg);
|
|
146
|
-
});
|
|
147
|
-
spyOn(clack, "outro").mockImplementation((msg: string) => {
|
|
148
|
-
captured.outro.push(msg);
|
|
141
|
+
spyOn(clack, "intro").mockImplementation((msg?: string) => {
|
|
142
|
+
captured.intro.push(msg ?? "");
|
|
149
143
|
});
|
|
150
|
-
spyOn(clack, "
|
|
151
|
-
captured.
|
|
144
|
+
spyOn(clack, "outro").mockImplementation((msg?: string) => {
|
|
145
|
+
captured.outro.push(msg ?? "");
|
|
152
146
|
});
|
|
147
|
+
spyOn(clack, "note").mockImplementation(
|
|
148
|
+
(content?: string, title?: string) => {
|
|
149
|
+
captured.note.push({ title: title ?? "", content: content ?? "" });
|
|
150
|
+
}
|
|
151
|
+
);
|
|
153
152
|
spyOn(clack, "password").mockImplementation(
|
|
153
|
+
// @ts-expect-error — mock validate fn signature simplified vs clack's PasswordOptions
|
|
154
154
|
async (opts: {
|
|
155
155
|
message: string;
|
|
156
156
|
validate?: (v: string) => string | void;
|
|
@@ -163,6 +163,7 @@ function installClackSpies(): void {
|
|
|
163
163
|
}
|
|
164
164
|
);
|
|
165
165
|
spyOn(clack, "text").mockImplementation(
|
|
166
|
+
// @ts-expect-error — mock validate fn signature simplified vs clack's TextOptions
|
|
166
167
|
async (opts: {
|
|
167
168
|
message: string;
|
|
168
169
|
placeholder?: string;
|
|
@@ -177,6 +178,7 @@ function installClackSpies(): void {
|
|
|
177
178
|
}
|
|
178
179
|
);
|
|
179
180
|
spyOn(clack, "multiselect").mockImplementation(
|
|
181
|
+
// @ts-expect-error — clack's multiselect is generic, mock uses concrete types
|
|
180
182
|
async (opts: {
|
|
181
183
|
message: string;
|
|
182
184
|
options: { value: string; label: string; hint?: string }[];
|
|
@@ -190,6 +192,7 @@ function installClackSpies(): void {
|
|
|
190
192
|
}
|
|
191
193
|
);
|
|
192
194
|
spyOn(clack, "select").mockImplementation(
|
|
195
|
+
// @ts-expect-error — clack's select is generic, mock uses concrete types
|
|
193
196
|
async (opts: {
|
|
194
197
|
message: string;
|
|
195
198
|
options: { value: string; label: string; hint?: string }[];
|
|
@@ -212,6 +215,7 @@ function installClackSpies(): void {
|
|
|
212
215
|
}
|
|
213
216
|
);
|
|
214
217
|
spyOn(clack, "group").mockImplementation(
|
|
218
|
+
// @ts-expect-error — clack's group uses PromptGroup<T>, not Record<string, () => Promise<...>>
|
|
215
219
|
async (
|
|
216
220
|
fields: Record<string, () => Promise<string | symbol>>,
|
|
217
221
|
groupOpts?: { onCancel?: () => void }
|
|
@@ -233,31 +237,33 @@ function installClackSpies(): void {
|
|
|
233
237
|
return results;
|
|
234
238
|
}
|
|
235
239
|
);
|
|
236
|
-
spyOn(clack, "isCancel").mockImplementation(
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
240
|
+
spyOn(clack, "isCancel").mockImplementation(
|
|
241
|
+
(value: unknown): value is symbol => {
|
|
242
|
+
return (
|
|
243
|
+
simulateCancel ||
|
|
244
|
+
(typeof value === "symbol" &&
|
|
245
|
+
Symbol.keyFor(value as symbol) === "clack.cancel")
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
);
|
|
249
|
+
spyOn(clack, "cancel").mockImplementation((msg?: string) => {
|
|
250
|
+
captured.cancelMessages.push(msg ?? "");
|
|
242
251
|
});
|
|
243
|
-
spyOn(clack, "
|
|
244
|
-
captured.
|
|
252
|
+
spyOn(clack.log, "info").mockImplementation((msg?: string) => {
|
|
253
|
+
captured.logInfo.push(msg ?? "");
|
|
254
|
+
});
|
|
255
|
+
spyOn(clack.log, "step").mockImplementation((msg?: string) => {
|
|
256
|
+
captured.logStep.push(msg ?? "");
|
|
257
|
+
});
|
|
258
|
+
spyOn(clack.log, "success").mockImplementation((msg?: string) => {
|
|
259
|
+
captured.logSuccess.push(msg ?? "");
|
|
260
|
+
});
|
|
261
|
+
spyOn(clack.log, "warn").mockImplementation((msg?: string) => {
|
|
262
|
+
captured.logWarn.push(msg ?? "");
|
|
263
|
+
});
|
|
264
|
+
spyOn(clack.log, "error").mockImplementation((msg?: string) => {
|
|
265
|
+
captured.logError.push(msg ?? "");
|
|
245
266
|
});
|
|
246
|
-
spyOn(clack.log, "info").mockImplementation((msg: string) =>
|
|
247
|
-
captured.logInfo.push(msg)
|
|
248
|
-
);
|
|
249
|
-
spyOn(clack.log, "step").mockImplementation((msg: string) =>
|
|
250
|
-
captured.logStep.push(msg)
|
|
251
|
-
);
|
|
252
|
-
spyOn(clack.log, "success").mockImplementation((msg: string) =>
|
|
253
|
-
captured.logSuccess.push(msg)
|
|
254
|
-
);
|
|
255
|
-
spyOn(clack.log, "warn").mockImplementation((msg: string) =>
|
|
256
|
-
captured.logWarn.push(msg)
|
|
257
|
-
);
|
|
258
|
-
spyOn(clack.log, "error").mockImplementation((msg: string) =>
|
|
259
|
-
captured.logError.push(msg)
|
|
260
|
-
);
|
|
261
267
|
}
|
|
262
268
|
|
|
263
269
|
// ---------------------------------------------------------------------------
|
|
@@ -274,7 +280,7 @@ beforeEach(() => {
|
|
|
274
280
|
customPasswordResponder = null;
|
|
275
281
|
mockWhoami.mockImplementation(async () => ({
|
|
276
282
|
ok: true,
|
|
277
|
-
|
|
283
|
+
value: "user@example.com",
|
|
278
284
|
}));
|
|
279
285
|
|
|
280
286
|
// Mock: CloudflareService prototype
|
|
@@ -376,7 +382,7 @@ describe("init command", () => {
|
|
|
376
382
|
if (callCount === 1) {
|
|
377
383
|
return { ok: false as const, error: "Invalid credentials" };
|
|
378
384
|
}
|
|
379
|
-
return { ok: true as const,
|
|
385
|
+
return { ok: true as const, value: "user@example.com" };
|
|
380
386
|
});
|
|
381
387
|
|
|
382
388
|
// First password call returns bad token, second returns good token
|
|
@@ -460,9 +466,9 @@ describe("init command", () => {
|
|
|
460
466
|
select: "full", // full preset includes integrations
|
|
461
467
|
confirmSequence: [true, false, false],
|
|
462
468
|
group: {
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
469
|
+
BINANCE_KEY_BINDING: "binance-key-123",
|
|
470
|
+
BINANCE_SECRET_BINDING: "binance-secret-123",
|
|
471
|
+
TG_BOT_TOKEN_BINDING: "tg-bot-token-123",
|
|
466
472
|
},
|
|
467
473
|
};
|
|
468
474
|
|
|
@@ -483,7 +489,7 @@ describe("init command", () => {
|
|
|
483
489
|
select: "full",
|
|
484
490
|
confirmSequence: [true, false, false],
|
|
485
491
|
group: {
|
|
486
|
-
|
|
492
|
+
TG_BOT_TOKEN_BINDING: "tg-token-abc",
|
|
487
493
|
},
|
|
488
494
|
};
|
|
489
495
|
|
|
@@ -526,28 +532,14 @@ describe("init command", () => {
|
|
|
526
532
|
error: "Bad token",
|
|
527
533
|
}));
|
|
528
534
|
|
|
529
|
-
// Override process.exit to capture and throw (halts execution)
|
|
530
|
-
let exitCode = -1;
|
|
531
|
-
process.exit = mock((code?: number) => {
|
|
532
|
-
exitCode = code ?? -1;
|
|
533
|
-
throw new Error("EXIT");
|
|
534
|
-
}) as unknown as typeof process.exit;
|
|
535
|
-
|
|
536
535
|
const options: InitOptions = {
|
|
537
536
|
token: "bad-token",
|
|
538
537
|
account: "abc123def456abc123def456abc123de",
|
|
539
538
|
};
|
|
540
539
|
|
|
541
|
-
|
|
542
|
-
options,
|
|
543
|
-
{ json: false, quiet: true },
|
|
544
|
-
true
|
|
545
|
-
);
|
|
546
|
-
await promise.catch(() => {
|
|
547
|
-
/* expected */
|
|
548
|
-
});
|
|
540
|
+
await runInitCommand(options, { json: false, quiet: true }, true);
|
|
549
541
|
|
|
550
|
-
expect(exitCode).toBe(ExitCode.ERROR);
|
|
542
|
+
expect(process.exitCode).toBe(ExitCode.ERROR);
|
|
551
543
|
});
|
|
552
544
|
|
|
553
545
|
it("writes wrangler.jsonc in non-interactive mode", async () => {
|
|
@@ -616,9 +608,9 @@ describe("init command", () => {
|
|
|
616
608
|
select: "full",
|
|
617
609
|
confirmSequence: [true, false, false],
|
|
618
610
|
group: {
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
611
|
+
BINANCE_KEY_BINDING: "bk",
|
|
612
|
+
BINANCE_SECRET_BINDING: "bs",
|
|
613
|
+
TG_BOT_TOKEN_BINDING: "tgt",
|
|
622
614
|
},
|
|
623
615
|
};
|
|
624
616
|
|
|
@@ -627,10 +619,10 @@ describe("init command", () => {
|
|
|
627
619
|
const workersJsonc = captured.writes["wrangler.jsonc"];
|
|
628
620
|
expect(workersJsonc).toBeDefined();
|
|
629
621
|
expect(workersJsonc).toContain('"trade-worker"');
|
|
630
|
-
expect(workersJsonc).toContain('"
|
|
631
|
-
expect(workersJsonc).toContain('"
|
|
622
|
+
expect(workersJsonc).toContain('"BINANCE_KEY_BINDING"');
|
|
623
|
+
expect(workersJsonc).toContain('"BINANCE_SECRET_BINDING"');
|
|
632
624
|
expect(workersJsonc).toContain('"telegram-worker"');
|
|
633
|
-
expect(workersJsonc).toContain('"
|
|
625
|
+
expect(workersJsonc).toContain('"TG_BOT_TOKEN_BINDING"');
|
|
634
626
|
});
|
|
635
627
|
|
|
636
628
|
it("includes AI provider (OpenAI) integration from full preset", async () => {
|
|
@@ -641,7 +633,7 @@ describe("init command", () => {
|
|
|
641
633
|
select: "full",
|
|
642
634
|
confirmSequence: [true, false, false],
|
|
643
635
|
group: {
|
|
644
|
-
|
|
636
|
+
AGENT_INTERNAL_KEY: "sk-openai-123",
|
|
645
637
|
},
|
|
646
638
|
};
|
|
647
639
|
|
|
@@ -650,7 +642,7 @@ describe("init command", () => {
|
|
|
650
642
|
const workersJsonc = captured.writes["wrangler.jsonc"];
|
|
651
643
|
expect(workersJsonc).toBeDefined();
|
|
652
644
|
expect(workersJsonc).toContain('"agent-worker"');
|
|
653
|
-
expect(workersJsonc).toContain('"
|
|
645
|
+
expect(workersJsonc).toContain('"AGENT_INTERNAL_KEY"');
|
|
654
646
|
});
|
|
655
647
|
|
|
656
648
|
it("includes wallet integration from full preset", async () => {
|
|
@@ -681,10 +673,10 @@ describe("init command", () => {
|
|
|
681
673
|
select: "full",
|
|
682
674
|
confirmSequence: [true, false, false],
|
|
683
675
|
group: {
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
676
|
+
BINANCE_KEY_BINDING: "bk",
|
|
677
|
+
BINANCE_SECRET_BINDING: "bs",
|
|
678
|
+
MEXC_KEY_BINDING: "mk",
|
|
679
|
+
MEXC_SECRET_BINDING: "ms",
|
|
688
680
|
},
|
|
689
681
|
};
|
|
690
682
|
|
|
@@ -693,8 +685,8 @@ describe("init command", () => {
|
|
|
693
685
|
const workersJsonc = captured.writes["wrangler.jsonc"];
|
|
694
686
|
expect(workersJsonc).toBeDefined();
|
|
695
687
|
expect(workersJsonc).toContain('"trade-worker"');
|
|
696
|
-
expect(workersJsonc).toContain('"
|
|
697
|
-
expect(workersJsonc).toContain('"
|
|
688
|
+
expect(workersJsonc).toContain('"BINANCE_KEY_BINDING"');
|
|
689
|
+
expect(workersJsonc).toContain('"MEXC_KEY_BINDING"');
|
|
698
690
|
// Should only appear once
|
|
699
691
|
const tradeWorkerCount =
|
|
700
692
|
workersJsonc.match(/"trade-worker"/g)?.length ?? 0;
|
|
@@ -751,7 +743,7 @@ describe("init command", () => {
|
|
|
751
743
|
select: "full",
|
|
752
744
|
confirmSequence: [true, false, false],
|
|
753
745
|
group: {
|
|
754
|
-
|
|
746
|
+
TG_BOT_TOKEN_BINDING: "tg-secret-value",
|
|
755
747
|
},
|
|
756
748
|
};
|
|
757
749
|
|
|
@@ -760,7 +752,7 @@ describe("init command", () => {
|
|
|
760
752
|
const devVarsPath = "workers/telegram-worker/.dev.vars";
|
|
761
753
|
const devVars = captured.writes[devVarsPath];
|
|
762
754
|
expect(devVars).toBeDefined();
|
|
763
|
-
expect(devVars).toContain("
|
|
755
|
+
expect(devVars).toContain("TG_BOT_TOKEN_BINDING=tg-secret-value");
|
|
764
756
|
expect(devVars).toContain("NEVER commit this file");
|
|
765
757
|
});
|
|
766
758
|
|
|
@@ -788,7 +780,7 @@ describe("init command", () => {
|
|
|
788
780
|
select: "full",
|
|
789
781
|
confirmSequence: [true, false, false],
|
|
790
782
|
group: {
|
|
791
|
-
|
|
783
|
+
AGENT_INTERNAL_KEY: "sk-openai-real-value",
|
|
792
784
|
},
|
|
793
785
|
};
|
|
794
786
|
|
|
@@ -797,7 +789,7 @@ describe("init command", () => {
|
|
|
797
789
|
const devVarsPath = "workers/agent-worker/.dev.vars";
|
|
798
790
|
const devVars = captured.writes[devVarsPath];
|
|
799
791
|
expect(devVars).toBeDefined();
|
|
800
|
-
expect(devVars).toContain("
|
|
792
|
+
expect(devVars).toContain("AGENT_INTERNAL_KEY=sk-openai-real-value");
|
|
801
793
|
expect(devVars).toContain("NEVER commit this file");
|
|
802
794
|
});
|
|
803
795
|
|
|
@@ -829,26 +821,12 @@ describe("init command", () => {
|
|
|
829
821
|
|
|
830
822
|
describe("cancellation handling", () => {
|
|
831
823
|
it("exits on cancel during risk acknowledgment", async () => {
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
const exitMock = mock((code?: number) => {
|
|
835
|
-
exitCalled = true;
|
|
836
|
-
exitCode = code ?? -1;
|
|
837
|
-
throw new Error("EXIT"); // Must throw to stop execution flow
|
|
838
|
-
});
|
|
839
|
-
process.exit = exitMock as unknown as typeof process.exit;
|
|
824
|
+
// Make the first confirm return false (user declines the risk terms)
|
|
825
|
+
responses.confirmSequence = [false];
|
|
840
826
|
|
|
841
|
-
|
|
842
|
-
simulateCancel = true;
|
|
843
|
-
|
|
844
|
-
try {
|
|
845
|
-
await runInitCommand({}, { json: false, quiet: true }, false);
|
|
846
|
-
} catch {
|
|
847
|
-
// expected — process.exit throws
|
|
848
|
-
}
|
|
827
|
+
await runInitCommand({}, { json: false, quiet: true }, false);
|
|
849
828
|
|
|
850
|
-
expect(
|
|
851
|
-
expect(exitCode).toBe(0);
|
|
829
|
+
expect(process.exitCode).toBe(0);
|
|
852
830
|
});
|
|
853
831
|
});
|
|
854
832
|
|