@jango-blockchained/hoox-cli 0.5.1 → 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 (106) 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 -1144
  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 -96
  9. package/src/commands/check/types.ts +0 -103
  10. package/src/commands/clone/clone-command.test.ts +0 -442
  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 -897
  15. package/src/commands/config/env-command.test.ts +0 -43
  16. package/src/commands/config/env-command.ts +0 -295
  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 -128
  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 -312
  25. package/src/commands/db/index.ts +0 -1
  26. package/src/commands/deploy/deploy-command.test.ts +0 -304
  27. package/src/commands/deploy/deploy-command.ts +0 -1135
  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 -383
  32. package/src/commands/dev/dev-command.ts +0 -398
  33. package/src/commands/dev/index.ts +0 -1
  34. package/src/commands/infra/index.ts +0 -5
  35. package/src/commands/infra/infra-command.test.ts +0 -719
  36. package/src/commands/infra/infra-command.ts +0 -974
  37. package/src/commands/infra/types.ts +0 -23
  38. package/src/commands/init/__tests__/cli-provisioner.test.ts +0 -17
  39. package/src/commands/init/cli-provisioner.ts +0 -71
  40. package/src/commands/init/index.ts +0 -1
  41. package/src/commands/init/init-command.test.ts +0 -876
  42. package/src/commands/init/init-command.ts +0 -593
  43. package/src/commands/init/types.ts +0 -23
  44. package/src/commands/logs/index.ts +0 -1
  45. package/src/commands/logs/logs-command.test.ts +0 -294
  46. package/src/commands/logs/logs-command.ts +0 -452
  47. package/src/commands/monitor/index.ts +0 -2
  48. package/src/commands/monitor/monitor-command.test.ts +0 -263
  49. package/src/commands/monitor/monitor-command.ts +0 -269
  50. package/src/commands/monitor/monitor-service.ts +0 -65
  51. package/src/commands/monitor/types.ts +0 -13
  52. package/src/commands/repair/index.ts +0 -2
  53. package/src/commands/repair/repair-command.test.ts +0 -224
  54. package/src/commands/repair/repair-command.ts +0 -220
  55. package/src/commands/repair/repair-service.ts +0 -138
  56. package/src/commands/repair/types.ts +0 -13
  57. package/src/commands/test/index.ts +0 -2
  58. package/src/commands/test/test-command.test.ts +0 -319
  59. package/src/commands/test/test-command.ts +0 -406
  60. package/src/commands/tui/index.ts +0 -1
  61. package/src/commands/tui/tui-command.ts +0 -87
  62. package/src/commands/update/index.ts +0 -1
  63. package/src/commands/update/update-command.ts +0 -168
  64. package/src/commands/waf/index.ts +0 -2
  65. package/src/commands/waf/types.ts +0 -48
  66. package/src/commands/waf/waf-command.test.ts +0 -506
  67. package/src/commands/waf/waf-command.ts +0 -548
  68. package/src/index.ts +0 -269
  69. package/src/services/cloudflare/cloudflare-service.test.ts +0 -658
  70. package/src/services/cloudflare/cloudflare-service.ts +0 -433
  71. package/src/services/cloudflare/index.ts +0 -2
  72. package/src/services/cloudflare/types.ts +0 -30
  73. package/src/services/config/config-service.test.ts +0 -395
  74. package/src/services/config/config-service.ts +0 -207
  75. package/src/services/config/index.ts +0 -9
  76. package/src/services/config/types.ts +0 -66
  77. package/src/services/db/db-service.test.ts +0 -51
  78. package/src/services/db/db-service.ts +0 -131
  79. package/src/services/db/index.ts +0 -1
  80. package/src/services/docker/docker-service.ts +0 -155
  81. package/src/services/docker/index.ts +0 -1
  82. package/src/services/env/env-service.test.ts +0 -236
  83. package/src/services/env/env-service.ts +0 -386
  84. package/src/services/env/index.ts +0 -1
  85. package/src/services/kv/index.ts +0 -1
  86. package/src/services/kv/kv-sync-service.test.ts +0 -38
  87. package/src/services/kv/kv-sync-service.ts +0 -234
  88. package/src/services/prerequisites/index.ts +0 -1
  89. package/src/services/prerequisites/prerequisites-service.test.ts +0 -89
  90. package/src/services/prerequisites/prerequisites-service.ts +0 -377
  91. package/src/services/prerequisites/types.ts +0 -48
  92. package/src/services/secrets/index.ts +0 -11
  93. package/src/services/secrets/secrets-service.test.ts +0 -495
  94. package/src/services/secrets/secrets-service.ts +0 -287
  95. package/src/services/secrets/types.ts +0 -50
  96. package/src/services/update/index.ts +0 -2
  97. package/src/services/update/update-service.test.ts +0 -76
  98. package/src/services/update/update-service.ts +0 -193
  99. package/src/ui/banner.ts +0 -141
  100. package/src/ui/index.ts +0 -8
  101. package/src/ui/menu.ts +0 -490
  102. package/src/utils/errors.test.ts +0 -69
  103. package/src/utils/errors.ts +0 -23
  104. package/src/utils/formatters.test.ts +0 -180
  105. package/src/utils/formatters.ts +0 -269
  106. package/src/utils/theme.ts +0 -94
@@ -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. "TELEGRAM_BOT_TOKEN"). */
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
- }