@jango-blockchained/hoox-cli 0.5.2 → 0.5.6

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 (114) hide show
  1. package/bin/hoox.js +3 -4
  2. package/dist/index.js +32545 -0
  3. package/package.json +10 -15
  4. package/src/commands/check/check-command.test.ts +0 -476
  5. package/src/commands/check/check-command.ts +0 -1087
  6. package/src/commands/check/index.ts +0 -10
  7. package/src/commands/check/prerequisites-command.test.ts +0 -19
  8. package/src/commands/check/prerequisites-command.ts +0 -97
  9. package/src/commands/check/types.ts +0 -103
  10. package/src/commands/clone/clone-command.test.ts +0 -440
  11. package/src/commands/clone/clone-command.ts +0 -431
  12. package/src/commands/clone/index.ts +0 -1
  13. package/src/commands/config/config-command.test.ts +0 -583
  14. package/src/commands/config/config-command.ts +0 -896
  15. package/src/commands/config/env-command.test.ts +0 -43
  16. package/src/commands/config/env-command.ts +0 -292
  17. package/src/commands/config/index.ts +0 -3
  18. package/src/commands/config/kv-command.test.ts +0 -14
  19. package/src/commands/config/kv-command.ts +0 -290
  20. package/src/commands/dashboard/dashboard-command.test.ts +0 -47
  21. package/src/commands/dashboard/dashboard-command.ts +0 -130
  22. package/src/commands/dashboard/index.ts +0 -1
  23. package/src/commands/db/db-command.test.ts +0 -21
  24. package/src/commands/db/db-command.ts +0 -311
  25. package/src/commands/db/index.ts +0 -1
  26. package/src/commands/deploy/deploy-command.test.ts +0 -324
  27. package/src/commands/deploy/deploy-command.ts +0 -1054
  28. package/src/commands/deploy/index.ts +0 -2
  29. package/src/commands/deploy/telegram-service.ts +0 -76
  30. package/src/commands/deploy/types.ts +0 -34
  31. package/src/commands/dev/dev-command.test.ts +0 -405
  32. package/src/commands/dev/dev-command.ts +0 -398
  33. package/src/commands/dev/index.ts +0 -1
  34. package/src/commands/disclaimer/disclaimer-command.ts +0 -21
  35. package/src/commands/disclaimer/index.ts +0 -1
  36. package/src/commands/infra/index.ts +0 -5
  37. package/src/commands/infra/infra-command.test.ts +0 -719
  38. package/src/commands/infra/infra-command.ts +0 -974
  39. package/src/commands/infra/types.ts +0 -23
  40. package/src/commands/init/__tests__/cli-provisioner.test.ts +0 -17
  41. package/src/commands/init/cli-provisioner.ts +0 -71
  42. package/src/commands/init/index.ts +0 -1
  43. package/src/commands/init/init-command.test.ts +0 -854
  44. package/src/commands/init/init-command.ts +0 -597
  45. package/src/commands/init/types.ts +0 -23
  46. package/src/commands/logs/index.ts +0 -1
  47. package/src/commands/logs/logs-command.test.ts +0 -293
  48. package/src/commands/logs/logs-command.ts +0 -452
  49. package/src/commands/monitor/index.ts +0 -2
  50. package/src/commands/monitor/monitor-command.test.ts +0 -271
  51. package/src/commands/monitor/monitor-command.ts +0 -269
  52. package/src/commands/monitor/monitor-service.ts +0 -65
  53. package/src/commands/monitor/types.ts +0 -13
  54. package/src/commands/repair/index.ts +0 -2
  55. package/src/commands/repair/repair-command.test.ts +0 -243
  56. package/src/commands/repair/repair-command.ts +0 -220
  57. package/src/commands/repair/repair-service.ts +0 -164
  58. package/src/commands/repair/types.ts +0 -13
  59. package/src/commands/schema/index.ts +0 -1
  60. package/src/commands/schema/schema-command.ts +0 -137
  61. package/src/commands/test/index.ts +0 -2
  62. package/src/commands/test/test-command.test.ts +0 -318
  63. package/src/commands/test/test-command.ts +0 -406
  64. package/src/commands/tui/index.ts +0 -1
  65. package/src/commands/tui/tui-command.ts +0 -87
  66. package/src/commands/update/index.ts +0 -1
  67. package/src/commands/update/update-command.ts +0 -109
  68. package/src/commands/waf/index.ts +0 -2
  69. package/src/commands/waf/types.ts +0 -48
  70. package/src/commands/waf/waf-command.test.ts +0 -506
  71. package/src/commands/waf/waf-command.ts +0 -545
  72. package/src/index.ts +0 -261
  73. package/src/services/cloudflare/cloudflare-service.test.ts +0 -658
  74. package/src/services/cloudflare/cloudflare-service.ts +0 -433
  75. package/src/services/cloudflare/index.ts +0 -2
  76. package/src/services/cloudflare/types.ts +0 -30
  77. package/src/services/config/config-service.test.ts +0 -395
  78. package/src/services/config/config-service.ts +0 -202
  79. package/src/services/config/index.ts +0 -9
  80. package/src/services/config/types.ts +0 -66
  81. package/src/services/db/db-service.test.ts +0 -63
  82. package/src/services/db/db-service.ts +0 -131
  83. package/src/services/db/index.ts +0 -1
  84. package/src/services/docker/docker-service.ts +0 -155
  85. package/src/services/docker/index.ts +0 -1
  86. package/src/services/env/env-service.test.ts +0 -259
  87. package/src/services/env/env-service.ts +0 -372
  88. package/src/services/env/index.ts +0 -1
  89. package/src/services/kv/index.ts +0 -1
  90. package/src/services/kv/kv-sync-service.test.ts +0 -38
  91. package/src/services/kv/kv-sync-service.ts +0 -243
  92. package/src/services/prerequisites/index.ts +0 -1
  93. package/src/services/prerequisites/prerequisites-service.test.ts +0 -89
  94. package/src/services/prerequisites/prerequisites-service.ts +0 -377
  95. package/src/services/prerequisites/types.ts +0 -48
  96. package/src/services/schema/index.ts +0 -1
  97. package/src/services/schema/schema-service.ts +0 -99
  98. package/src/services/secrets/index.ts +0 -11
  99. package/src/services/secrets/secrets-service.test.ts +0 -502
  100. package/src/services/secrets/secrets-service.ts +0 -287
  101. package/src/services/secrets/types.ts +0 -50
  102. package/src/services/update/index.ts +0 -2
  103. package/src/services/update/update-service.test.ts +0 -76
  104. package/src/services/update/update-service.ts +0 -193
  105. package/src/ui/banner.ts +0 -141
  106. package/src/ui/index.ts +0 -8
  107. package/src/ui/menu.ts +0 -490
  108. package/src/utils/error-handler.ts +0 -62
  109. package/src/utils/errors.test.ts +0 -69
  110. package/src/utils/errors.ts +0 -24
  111. package/src/utils/formatters.test.ts +0 -180
  112. package/src/utils/formatters.ts +0 -269
  113. package/src/utils/git.ts +0 -134
  114. package/src/utils/theme.ts +0 -92
@@ -1,287 +0,0 @@
1
- import { parse as parseJsonc } from "jsonc-parser";
2
- import type {
3
- Result,
4
- SecretCheckResult,
5
- SecretStatus,
6
- WorkersJsonc,
7
- } from "./types.js";
8
-
9
- /**
10
- * Manages Cloudflare Worker secrets defined in `wrangler.jsonc`.
11
- *
12
- * Reads worker-level `secrets` arrays, checks local `.dev.vars` files,
13
- * syncs secrets to Cloudflare via `wrangler secret put`, and generates
14
- * `.dev.vars` templates.
15
- *
16
- * Use the static `create()` factory to instantiate — the constructor
17
- * is private so the config is parsed once and the sync methods remain
18
- * synchronous.
19
- *
20
- * @example
21
- * ```ts
22
- * const svc = await SecretsService.create("wrangler.jsonc");
23
- * const names = svc.listSecrets("trade-worker");
24
- * const check = await svc.checkLocalSecrets("trade-worker");
25
- * ```
26
- */
27
- export class SecretsService {
28
- private config: WorkersJsonc;
29
- private configPath: string;
30
-
31
- // -----------------------------------------------------------------------
32
- // Construction
33
- // -----------------------------------------------------------------------
34
-
35
- /** Use {@link create} instead — private so config is always loaded. */
36
- private constructor(config: WorkersJsonc, configPath: string) {
37
- this.config = config;
38
- this.configPath = configPath;
39
- }
40
-
41
- /**
42
- * Factory that reads and parses the wrangler.jsonc config file.
43
- * Throws if the file doesn't exist or can't be parsed.
44
- */
45
- static async create(configPath?: string): Promise<SecretsService> {
46
- const path = configPath ?? "wrangler.jsonc";
47
- const file = Bun.file(path);
48
- if (!(await file.exists())) {
49
- throw new Error(`Config file not found: ${path}`);
50
- }
51
- const text = await file.text();
52
- const config = parseJsonc(text) as WorkersJsonc;
53
- return new SecretsService(config, path);
54
- }
55
-
56
- // -----------------------------------------------------------------------
57
- // Public API
58
- // -----------------------------------------------------------------------
59
-
60
- /**
61
- * Returns secret names declared for a worker in `wrangler.jsonc`.
62
- * Synchronous because the config was loaded during construction.
63
- */
64
- listSecrets(workerName: string): string[] {
65
- const worker = this.config.workers[workerName];
66
- return worker?.secrets ?? [];
67
- }
68
-
69
- /**
70
- * Returns a map of every worker → its declared secret names.
71
- * Workers that declare no secrets are omitted from the result.
72
- */
73
- listAllSecrets(): Record<string, string[]> {
74
- const result: Record<string, string[]> = {};
75
- for (const [name, worker] of Object.entries(this.config.workers)) {
76
- if (worker.secrets && worker.secrets.length > 0) {
77
- result[name] = worker.secrets;
78
- }
79
- }
80
- return result;
81
- }
82
-
83
- /**
84
- * Checks a worker's local `.dev.vars` file and reports which secrets
85
- * are set (with real, non-placeholder values) and which are missing.
86
- */
87
- async checkLocalSecrets(workerName: string): Promise<SecretCheckResult> {
88
- const worker = this.config.workers[workerName];
89
- if (!worker) {
90
- return {
91
- worker: workerName,
92
- secrets: [],
93
- allSet: false,
94
- missing: [],
95
- };
96
- }
97
-
98
- const requiredSecrets = worker.secrets ?? [];
99
- const devVarsPath = `${worker.path}/.dev.vars`;
100
- const file = Bun.file(devVarsPath);
101
-
102
- if (!(await file.exists())) {
103
- return {
104
- worker: workerName,
105
- secrets: requiredSecrets.map((s) => ({ name: s, set: false })),
106
- allSet: requiredSecrets.length === 0,
107
- missing: [...requiredSecrets],
108
- };
109
- }
110
-
111
- const content = await file.text();
112
- const secretMap = this.parseDotEnv(content);
113
-
114
- const secrets: SecretStatus[] = [];
115
- const missing: string[] = [];
116
-
117
- for (const name of requiredSecrets) {
118
- const value = secretMap.get(name);
119
- if (value !== undefined && !this.isPlaceholder(value)) {
120
- secrets.push({ name, set: true, source: devVarsPath });
121
- } else {
122
- secrets.push({
123
- name,
124
- set: false,
125
- source: value !== undefined ? devVarsPath : undefined,
126
- });
127
- missing.push(name);
128
- }
129
- }
130
-
131
- return {
132
- worker: workerName,
133
- secrets,
134
- allSet: missing.length === 0,
135
- missing,
136
- };
137
- }
138
-
139
- /**
140
- * Creates (or overwrites) a `.dev.vars` template file for a worker
141
- * with placeholder values for every secret declared in `wrangler.jsonc`.
142
- */
143
- async generateDevVars(workerName: string): Promise<Result<string>> {
144
- const worker = this.config.workers[workerName];
145
- if (!worker) {
146
- return {
147
- ok: false,
148
- error: `Worker "${workerName}" not found in config`,
149
- };
150
- }
151
-
152
- const secrets = worker.secrets ?? [];
153
- const content =
154
- secrets.length > 0
155
- ? secrets.map((s) => `${s}=placeholder_${s.toLowerCase()}`).join("\n") +
156
- "\n"
157
- : "";
158
- const devVarsPath = `${worker.path}/.dev.vars`;
159
-
160
- try {
161
- await Bun.write(devVarsPath, content);
162
- return { ok: true, value: devVarsPath };
163
- } catch (err: unknown) {
164
- const message = err instanceof Error ? err.message : String(err);
165
- return { ok: false, error: `Failed to write .dev.vars: ${message}` };
166
- }
167
- }
168
-
169
- /**
170
- * Syncs a worker's secrets to Cloudflare via `wrangler secret put`.
171
- *
172
- * For each required secret the service first looks for a real value in
173
- * the worker's `.dev.vars` file. Secrets with placeholder values are
174
- * skipped and reported as errors.
175
- */
176
- async syncToCloudflare(workerName: string): Promise<Result<string[]>> {
177
- const worker = this.config.workers[workerName];
178
- if (!worker) {
179
- return {
180
- ok: false,
181
- error: `Worker "${workerName}" not found in config`,
182
- };
183
- }
184
-
185
- const secrets = worker.secrets ?? [];
186
- const synced: string[] = [];
187
- const errors: string[] = [];
188
-
189
- // Pre-load existing .dev.vars to avoid prompting in a service.
190
- const devVarsPath = `${worker.path}/.dev.vars`;
191
- const devVarsFile = Bun.file(devVarsPath);
192
- let existingValues: Map<string, string> = new Map();
193
-
194
- if (await devVarsFile.exists()) {
195
- const content = await devVarsFile.text();
196
- existingValues = this.parseDotEnv(content);
197
- }
198
-
199
- for (const secret of secrets) {
200
- try {
201
- const value = existingValues.get(secret);
202
- if (value !== undefined && !this.isPlaceholder(value)) {
203
- await this.execWranglerSecretPut(worker.path, secret, value);
204
- synced.push(secret);
205
- } else {
206
- errors.push(
207
- `Secret "${secret}": no valid value in .dev.vars (run generateDevVars then edit)`
208
- );
209
- }
210
- } catch (err: unknown) {
211
- const message = err instanceof Error ? err.message : String(err);
212
- errors.push(`Secret "${secret}": ${message}`);
213
- }
214
- }
215
-
216
- if (errors.length > 0) {
217
- return { ok: false, error: errors.join("; ") };
218
- }
219
-
220
- return { ok: true, value: synced };
221
- }
222
-
223
- // -----------------------------------------------------------------------
224
- // Private helpers
225
- // -----------------------------------------------------------------------
226
-
227
- /**
228
- * Parses a `.env` / `.dev.vars` style file into a `Map<key, value>`.
229
- * Skips empty lines and comments (lines starting with `#`).
230
- */
231
- private parseDotEnv(content: string): Map<string, string> {
232
- const map = new Map<string, string>();
233
- for (const line of content.split("\n")) {
234
- const trimmed = line.trim();
235
- if (trimmed === "" || trimmed.startsWith("#")) continue;
236
-
237
- const eqIdx = trimmed.indexOf("=");
238
- if (eqIdx < 1) continue;
239
-
240
- const key = trimmed.substring(0, eqIdx).trim();
241
- const value = trimmed.substring(eqIdx + 1).trim();
242
- map.set(key, value);
243
- }
244
- return map;
245
- }
246
-
247
- /** Returns `true` when a value looks like an unfilled template. */
248
- private isPlaceholder(value: string): boolean {
249
- return (
250
- value.startsWith("placeholder_") ||
251
- value.startsWith("your_") ||
252
- value.startsWith("generate_") ||
253
- value === ""
254
- );
255
- }
256
-
257
- /**
258
- * Runs `wrangler secret put <name>` inside the worker's directory.
259
- * Marked `protected` so unit tests can stub it without touching the
260
- * real `Bun.spawn`.
261
- */
262
- protected async execWranglerSecretPut(
263
- workerPath: string,
264
- name: string,
265
- value: string
266
- ): Promise<void> {
267
- const proc = Bun.spawn(["wrangler", "secret", "put", name], {
268
- cwd: workerPath,
269
- stdin: "pipe",
270
- stdout: "pipe",
271
- stderr: "pipe",
272
- });
273
-
274
- proc.stdin.write(value + "\n");
275
- proc.stdin.end();
276
-
277
- const exitCode = await proc.exited;
278
- if (exitCode !== 0) {
279
- const stderrText = await new Response(proc.stderr).text();
280
- throw new Error(
281
- `wrangler exited with code ${exitCode}: ${stderrText.trim()}`
282
- );
283
- }
284
- }
285
- }
286
-
287
- export default SecretsService;
@@ -1,50 +0,0 @@
1
- /**
2
- * SecretsService types — secret management for Cloudflare Workers.
3
- */
4
-
5
- // ---------------------------------------------------------------------------
6
- // Result
7
- // ---------------------------------------------------------------------------
8
-
9
- import type { Result } from "@jango-blockchained/hoox-shared";
10
-
11
- // Re-export the shared Result<T> for convenience
12
- export type { Result };
13
-
14
- // ---------------------------------------------------------------------------
15
- // Domain types
16
- // ---------------------------------------------------------------------------
17
-
18
- export interface SecretStatus {
19
- /** Name of the secret (e.g. "TG_BOT_TOKEN_BINDING"). */
20
- name: string;
21
- /** Whether the secret has a real (non-placeholder) value set. */
22
- set: boolean;
23
- /** Where the value was found ("workers/<name>/.dev.vars" or undefined). */
24
- source?: string;
25
- }
26
-
27
- export interface SecretCheckResult {
28
- /** Worker name as it appears in wrangler.jsonc. */
29
- worker: string;
30
- /** Per-secret status entries. */
31
- secrets: SecretStatus[];
32
- /** True when *every* required secret is present with a real value. */
33
- allSet: boolean;
34
- /** Secret names that are either missing or still placeholder. */
35
- missing: string[];
36
- }
37
-
38
- // ---------------------------------------------------------------------------
39
- // Internal config shape (subset of wrangler.jsonc used by SecretsService)
40
- // ---------------------------------------------------------------------------
41
-
42
- export interface WorkerSecretConfig {
43
- enabled: boolean;
44
- path: string;
45
- secrets?: string[];
46
- }
47
-
48
- export interface WorkersJsonc {
49
- workers: Record<string, WorkerSecretConfig>;
50
- }
@@ -1,2 +0,0 @@
1
- export { UpdateService } from "./update-service.js";
2
- export type { UpdateResult } from "./update-service.js";
@@ -1,76 +0,0 @@
1
- import { describe, expect, it, mock } from "bun:test";
2
- import { UpdateService } from "./update-service.js";
3
-
4
- function createMockPrereqs(
5
- overrides?: Partial<{
6
- outdated: boolean;
7
- current: string;
8
- minimum: string;
9
- }>
10
- ) {
11
- const defaults = { outdated: false, current: "4.0.0", minimum: "3.88.0" };
12
- const config = { ...defaults, ...overrides };
13
-
14
- return {
15
- checkWranglerVersion: mock(() => Promise.resolve(config)),
16
- checkBun: mock(() => Promise.resolve({})),
17
- checkGit: mock(() => Promise.resolve({})),
18
- checkNode: mock(() => Promise.resolve({})),
19
- checkWrangler: mock(() => Promise.resolve({})),
20
- checkCloudflareAuth: mock(() => Promise.resolve({})),
21
- checkDocker: mock(() => Promise.resolve({})),
22
- checkRepository: mock(() => Promise.resolve({})),
23
- runAll: mock(() => Promise.resolve({ checks: [], allPassed: true })),
24
- };
25
- }
26
-
27
- describe("UpdateService", () => {
28
- describe("checkAndPromptUpdate", () => {
29
- it("returns updated=false when wrangler is up to date", async () => {
30
- const mockPrereqs = createMockPrereqs({ outdated: false });
31
- const svc = new UpdateService(undefined, mockPrereqs as any);
32
-
33
- const result = await svc.checkAndPromptUpdate({ yes: true });
34
-
35
- expect(result.updated).toBe(false);
36
- expect(result.error).toBeUndefined();
37
- });
38
-
39
- it("auto-updates when wrangler is outdated and --yes is set", async () => {
40
- const mockPrereqs = createMockPrereqs({
41
- outdated: true,
42
- current: "3.87.0",
43
- });
44
- const svc = new UpdateService(undefined, mockPrereqs as any);
45
-
46
- const result = await svc.checkAndPromptUpdate({ yes: true });
47
-
48
- expect(typeof result.updated).toBe("boolean");
49
- }, 30000);
50
- });
51
-
52
- describe("updateWrangler", () => {
53
- it("returns result when called (does not throw)", async () => {
54
- const mockPrereqs = createMockPrereqs({
55
- outdated: false,
56
- current: "4.0.0",
57
- });
58
- const svc = new UpdateService(undefined, mockPrereqs as any);
59
-
60
- const result = await svc.updateWrangler();
61
-
62
- expect(typeof result.updated).toBe("boolean");
63
- }, 30000);
64
- });
65
-
66
- describe("checkLatestVersion", () => {
67
- it("returns a version string or null", async () => {
68
- const svc = new UpdateService();
69
- const version = await svc.checkLatestVersion();
70
- expect(version === null || typeof version === "string").toBe(true);
71
- if (typeof version === "string") {
72
- expect(version).toMatch(/^\d+\.\d+\.\d+$/);
73
- }
74
- }, 30000);
75
- });
76
- });
@@ -1,193 +0,0 @@
1
- /**
2
- * UpdateService — manages wrangler version updates.
3
- *
4
- * Checks current wrangler version, compares against minimum, and updates
5
- * via `bun update wrangler` when needed. Integrates with PrerequisitesService
6
- * for version checking.
7
- */
8
-
9
- import { PrerequisitesService } from "../prerequisites/index.js";
10
- import { theme } from "../../utils/theme.js";
11
- import { confirm } from "@clack/prompts";
12
-
13
- export interface UpdateResult {
14
- updated: boolean;
15
- previousVersion?: string;
16
- newVersion?: string;
17
- error?: string;
18
- }
19
-
20
- export class UpdateService {
21
- private readonly prereqs: PrerequisitesService;
22
- private readonly cwd: string;
23
-
24
- constructor(cwd?: string, prereqs?: PrerequisitesService) {
25
- this.prereqs = prereqs ?? new PrerequisitesService();
26
- this.cwd = cwd ?? process.cwd();
27
- }
28
-
29
- /**
30
- * Check if wrangler is outdated. If yes, prompt user (TTY) or
31
- * auto-update (non-TTY / --yes flag). Never throws — errors are
32
- * returned in UpdateResult.
33
- */
34
- async checkAndPromptUpdate(options?: {
35
- yes?: boolean;
36
- }): Promise<UpdateResult> {
37
- try {
38
- const versionCheck = await this.prereqs.checkWranglerVersion();
39
-
40
- if (!versionCheck.outdated) {
41
- process.stdout.write(
42
- ` ${theme.success("✓")} Wrangler ${versionCheck.current} is up to date\n`
43
- );
44
- return { updated: false };
45
- }
46
-
47
- const current = versionCheck.current ?? "unknown";
48
- const minimum = versionCheck.minimum ?? "unknown";
49
-
50
- // Fetch latest available version for the prompt
51
- const latest = await this.checkLatestVersion();
52
-
53
- // Determine if we should prompt or auto-update
54
- let shouldUpdate: boolean;
55
- if (options?.yes ?? !process.stdout.isTTY) {
56
- shouldUpdate = true;
57
- } else {
58
- shouldUpdate = await this.promptUpdate(current, minimum, latest);
59
- }
60
-
61
- if (!shouldUpdate) {
62
- process.stdout.write(
63
- ` ${theme.warning("!")} Skipping wrangler update (current: ${current})\n`
64
- );
65
- return { updated: false };
66
- }
67
-
68
- return await this.runUpdate();
69
- } catch (err) {
70
- const message = err instanceof Error ? err.message : String(err);
71
- process.stdout.write(
72
- ` ${theme.error("!")} Wrangler update check failed: ${message}\n`
73
- );
74
- return { updated: false, error: message };
75
- }
76
- }
77
-
78
- /**
79
- * Force-update wrangler regardless of current version.
80
- * Used by the standalone `hoox update` command.
81
- */
82
- async updateWrangler(): Promise<UpdateResult> {
83
- process.stdout.write(` ${theme.info("i")} Checking wrangler version...\n`);
84
-
85
- const before = await this.prereqs.checkWranglerVersion();
86
- const previousVersion = before.current;
87
-
88
- if (!previousVersion) {
89
- process.stdout.write(
90
- ` ${theme.warning("!")} Wrangler is not installed. Install with: bun add -g wrangler\n`
91
- );
92
- return { updated: false, error: "Wrangler not installed" };
93
- }
94
-
95
- const result = await this.runUpdate(previousVersion);
96
- return { ...result, previousVersion };
97
- }
98
-
99
- /**
100
- * Check the latest available wrangler version from the npm registry.
101
- * Uses the npm registry JSON API directly — no dependency on npm CLI.
102
- * Returns null if the check fails (network error, etc.).
103
- */
104
- async checkLatestVersion(): Promise<string | null> {
105
- try {
106
- const res = await fetch("https://registry.npmjs.org/wrangler/latest");
107
- if (!res.ok) return null;
108
- const data = (await res.json()) as { version?: string };
109
- return data.version ?? null;
110
- } catch {
111
- return null;
112
- }
113
- }
114
-
115
- // ── Private helpers ──────────────────────────────────────────────
116
-
117
- /**
118
- * Prompt the user whether to update wrangler.
119
- * Returns true if the user wants to update.
120
- */
121
- private async promptUpdate(
122
- current: string,
123
- minimum: string,
124
- latest: string | null
125
- ): Promise<boolean> {
126
- const latestStr = latest ? `, latest: ${latest}` : "";
127
- process.stdout.write(
128
- `\n ${theme.warning("!")} Wrangler ${current} is outdated (minimum: ${minimum}${latestStr})\n`
129
- );
130
-
131
- const result = await confirm({
132
- message: "Update wrangler?",
133
- initialValue: true,
134
- });
135
-
136
- // Confirm returns boolean | symbol (CLACK_CANCEL)
137
- return result === true;
138
- }
139
-
140
- /**
141
- * Run `bun update wrangler` in the project root and verify the result.
142
- */
143
- private async runUpdate(previousVersion?: string): Promise<UpdateResult> {
144
- process.stdout.write(` ${theme.info("i")} Updating wrangler...\n`);
145
-
146
- try {
147
- const proc = Bun.spawn(["bun", "update", "wrangler"], {
148
- cwd: this.cwd,
149
- stdout: "ignore",
150
- stderr: "pipe",
151
- });
152
-
153
- const stderr = await new Response(proc.stderr).text();
154
- const exitCode = await proc.exited;
155
-
156
- if (exitCode !== 0) {
157
- const errorMsg = stderr.split("\n")[0] || "bun update wrangler failed";
158
- process.stdout.write(
159
- ` ${theme.error("!")} Update failed: ${errorMsg}\n`
160
- );
161
- return { updated: false, error: errorMsg };
162
- }
163
-
164
- // Verify the new version
165
- const verify = await this.prereqs.checkWranglerVersion();
166
- const newVersion = verify.current;
167
-
168
- const versionChanged = previousVersion
169
- ? previousVersion !== newVersion
170
- : true;
171
-
172
- if (versionChanged) {
173
- process.stdout.write(
174
- ` ${theme.success("✓")} Wrangler updated from ${previousVersion ?? "?"} to ${newVersion}\n`
175
- );
176
- } else {
177
- process.stdout.write(
178
- ` ${theme.warning("!")} Wrangler version unchanged (${newVersion})\n`
179
- );
180
- }
181
-
182
- return {
183
- updated: versionChanged,
184
- previousVersion,
185
- newVersion,
186
- };
187
- } catch (err) {
188
- const message = err instanceof Error ? err.message : String(err);
189
- process.stdout.write(` ${theme.error("!")} Update failed: ${message}\n`);
190
- return { updated: false, error: message };
191
- }
192
- }
193
- }