@getjack/jack 0.1.16 → 0.1.19

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.
@@ -0,0 +1,322 @@
1
+ /**
2
+ * Unit tests for wrangler-config.ts
3
+ *
4
+ * Tests adding D1 bindings to wrangler.jsonc while preserving comments.
5
+ */
6
+
7
+ import { afterEach, beforeEach, describe, expect, it } from "bun:test";
8
+ import { existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
9
+ import { join } from "node:path";
10
+ import { tmpdir } from "node:os";
11
+ import { addD1Binding, getExistingD1Bindings, type D1BindingConfig } from "./wrangler-config.ts";
12
+
13
+ // ============================================================================
14
+ // Test Helpers
15
+ // ============================================================================
16
+
17
+ let testDir: string;
18
+
19
+ function createTestConfig(content: string): string {
20
+ const configPath = join(testDir, "wrangler.jsonc");
21
+ writeFileSync(configPath, content);
22
+ return configPath;
23
+ }
24
+
25
+ async function readTestConfig(configPath: string): Promise<string> {
26
+ return await Bun.file(configPath).text();
27
+ }
28
+
29
+ // ============================================================================
30
+ // Tests
31
+ // ============================================================================
32
+
33
+ describe("wrangler-config", () => {
34
+ beforeEach(() => {
35
+ testDir = join(tmpdir(), `wrangler-config-test-${Date.now()}`);
36
+ mkdirSync(testDir, { recursive: true });
37
+ });
38
+
39
+ afterEach(() => {
40
+ if (existsSync(testDir)) {
41
+ rmSync(testDir, { recursive: true });
42
+ }
43
+ });
44
+
45
+ describe("getExistingD1Bindings", () => {
46
+ it("returns empty array when no d1_databases", async () => {
47
+ const configPath = createTestConfig(`{
48
+ "name": "test-app",
49
+ "main": "src/index.ts"
50
+ }`);
51
+
52
+ const bindings = await getExistingD1Bindings(configPath);
53
+
54
+ expect(bindings).toHaveLength(0);
55
+ });
56
+
57
+ it("returns existing D1 bindings", async () => {
58
+ const configPath = createTestConfig(`{
59
+ "name": "test-app",
60
+ "d1_databases": [
61
+ {
62
+ "binding": "DB",
63
+ "database_name": "my-db",
64
+ "database_id": "abc-123"
65
+ }
66
+ ]
67
+ }`);
68
+
69
+ const bindings = await getExistingD1Bindings(configPath);
70
+
71
+ expect(bindings).toHaveLength(1);
72
+ expect(bindings[0]).toEqual({
73
+ binding: "DB",
74
+ database_name: "my-db",
75
+ database_id: "abc-123",
76
+ });
77
+ });
78
+
79
+ it("returns multiple D1 bindings", async () => {
80
+ const configPath = createTestConfig(`{
81
+ "name": "test-app",
82
+ "d1_databases": [
83
+ {
84
+ "binding": "DB",
85
+ "database_name": "main-db",
86
+ "database_id": "abc-123"
87
+ },
88
+ {
89
+ "binding": "ANALYTICS_DB",
90
+ "database_name": "analytics-db",
91
+ "database_id": "def-456"
92
+ }
93
+ ]
94
+ }`);
95
+
96
+ const bindings = await getExistingD1Bindings(configPath);
97
+
98
+ expect(bindings).toHaveLength(2);
99
+ expect(bindings[0]?.binding).toBe("DB");
100
+ expect(bindings[1]?.binding).toBe("ANALYTICS_DB");
101
+ });
102
+
103
+ it("filters out incomplete bindings", async () => {
104
+ const configPath = createTestConfig(`{
105
+ "name": "test-app",
106
+ "d1_databases": [
107
+ {
108
+ "binding": "DB",
109
+ "database_name": "my-db"
110
+ }
111
+ ]
112
+ }`);
113
+
114
+ const bindings = await getExistingD1Bindings(configPath);
115
+
116
+ expect(bindings).toHaveLength(0);
117
+ });
118
+
119
+ it("throws error when config file does not exist", async () => {
120
+ const configPath = join(testDir, "nonexistent.jsonc");
121
+
122
+ expect(getExistingD1Bindings(configPath)).rejects.toThrow("wrangler.jsonc not found");
123
+ });
124
+
125
+ it("handles JSONC with comments", async () => {
126
+ const configPath = createTestConfig(`{
127
+ "name": "test-app",
128
+ // Database configuration
129
+ "d1_databases": [
130
+ {
131
+ "binding": "DB",
132
+ "database_name": "my-db", // main database
133
+ "database_id": "abc-123"
134
+ }
135
+ ]
136
+ }`);
137
+
138
+ const bindings = await getExistingD1Bindings(configPath);
139
+
140
+ expect(bindings).toHaveLength(1);
141
+ expect(bindings[0]?.binding).toBe("DB");
142
+ });
143
+ });
144
+
145
+ describe("addD1Binding", () => {
146
+ const testBinding: D1BindingConfig = {
147
+ binding: "DB",
148
+ database_name: "test-db",
149
+ database_id: "abc-123-def-456",
150
+ };
151
+
152
+ it("throws error when config file does not exist", async () => {
153
+ const configPath = join(testDir, "nonexistent.jsonc");
154
+
155
+ expect(addD1Binding(configPath, testBinding)).rejects.toThrow("wrangler.jsonc not found");
156
+ });
157
+
158
+ it("adds d1_databases section when it does not exist", async () => {
159
+ const configPath = createTestConfig(`{
160
+ "name": "test-app",
161
+ "main": "src/index.ts"
162
+ }`);
163
+
164
+ await addD1Binding(configPath, testBinding);
165
+
166
+ const content = await readTestConfig(configPath);
167
+ expect(content).toContain('"d1_databases"');
168
+ expect(content).toContain('"binding": "DB"');
169
+ expect(content).toContain('"database_name": "test-db"');
170
+ expect(content).toContain('"database_id": "abc-123-def-456"');
171
+ });
172
+
173
+ it("appends to existing d1_databases array", async () => {
174
+ const configPath = createTestConfig(`{
175
+ "name": "test-app",
176
+ "d1_databases": [
177
+ {
178
+ "binding": "MAIN_DB",
179
+ "database_name": "main-db",
180
+ "database_id": "existing-id"
181
+ }
182
+ ]
183
+ }`);
184
+
185
+ await addD1Binding(configPath, {
186
+ binding: "SECONDARY_DB",
187
+ database_name: "secondary-db",
188
+ database_id: "new-id",
189
+ });
190
+
191
+ const content = await readTestConfig(configPath);
192
+ expect(content).toContain('"binding": "MAIN_DB"');
193
+ expect(content).toContain('"binding": "SECONDARY_DB"');
194
+ });
195
+
196
+ it("preserves comments when adding d1_databases section", async () => {
197
+ const configPath = createTestConfig(`{
198
+ "name": "test-app",
199
+ // This is the main entry point
200
+ "main": "src/index.ts"
201
+ }`);
202
+
203
+ await addD1Binding(configPath, testBinding);
204
+
205
+ const content = await readTestConfig(configPath);
206
+ expect(content).toContain("// This is the main entry point");
207
+ expect(content).toContain('"d1_databases"');
208
+ });
209
+
210
+ it("preserves comments when appending to existing array", async () => {
211
+ const configPath = createTestConfig(`{
212
+ "name": "test-app",
213
+ // Database configuration
214
+ "d1_databases": [
215
+ {
216
+ "binding": "MAIN_DB",
217
+ "database_name": "main-db", // Primary database
218
+ "database_id": "existing-id"
219
+ }
220
+ ]
221
+ }`);
222
+
223
+ await addD1Binding(configPath, {
224
+ binding: "SECONDARY_DB",
225
+ database_name: "secondary-db",
226
+ database_id: "new-id",
227
+ });
228
+
229
+ const content = await readTestConfig(configPath);
230
+ expect(content).toContain("// Database configuration");
231
+ expect(content).toContain("// Primary database");
232
+ expect(content).toContain('"binding": "SECONDARY_DB"');
233
+ });
234
+
235
+ it("handles empty d1_databases array", async () => {
236
+ const configPath = createTestConfig(`{
237
+ "name": "test-app",
238
+ "d1_databases": []
239
+ }`);
240
+
241
+ await addD1Binding(configPath, testBinding);
242
+
243
+ const content = await readTestConfig(configPath);
244
+ expect(content).toContain('"binding": "DB"');
245
+ });
246
+
247
+ it("handles real-world miniapp template format", async () => {
248
+ const configPath = createTestConfig(`{
249
+ "name": "jack-template",
250
+ "main": "src/worker.ts",
251
+ "compatibility_date": "2024-12-01",
252
+ "assets": {
253
+ "binding": "ASSETS",
254
+ "directory": "dist/client",
255
+ "not_found_handling": "single-page-application",
256
+ // Required for dynamic routes (/share, /api/og) to work alongside static assets
257
+ // Without this, Cloudflare serves static files directly, bypassing the worker
258
+ "run_worker_first": true
259
+ },
260
+ "d1_databases": [
261
+ {
262
+ "binding": "DB",
263
+ "database_name": "jack-template-db"
264
+ }
265
+ ],
266
+ "ai": {
267
+ "binding": "AI"
268
+ },
269
+ "vars": {
270
+ // Set this after first deploy - required for share embeds
271
+ // Get your URL from: jack projects or wrangler deployments list
272
+ // Example: "APP_URL": "https://my-app.username.workers.dev"
273
+ "APP_URL": ""
274
+ }
275
+ }`);
276
+
277
+ await addD1Binding(configPath, {
278
+ binding: "ANALYTICS_DB",
279
+ database_name: "analytics-db",
280
+ database_id: "analytics-uuid",
281
+ });
282
+
283
+ const content = await readTestConfig(configPath);
284
+ // Verify original comments preserved
285
+ expect(content).toContain("// Required for dynamic routes");
286
+ expect(content).toContain("// Set this after first deploy");
287
+ // Verify new binding added
288
+ expect(content).toContain('"binding": "ANALYTICS_DB"');
289
+ expect(content).toContain('"database_name": "analytics-db"');
290
+ });
291
+
292
+ it("produces valid JSON output", async () => {
293
+ const configPath = createTestConfig(`{
294
+ "name": "test-app",
295
+ "main": "src/index.ts"
296
+ }`);
297
+
298
+ await addD1Binding(configPath, testBinding);
299
+
300
+ const content = await readTestConfig(configPath);
301
+ // Strip comments and parse
302
+ const { parseJsonc } = await import("./jsonc.ts");
303
+ const parsed = parseJsonc<{ d1_databases: D1BindingConfig[] }>(content);
304
+ expect(parsed.d1_databases).toBeDefined();
305
+ expect(parsed.d1_databases[0]?.binding).toBe("DB");
306
+ });
307
+
308
+ it("handles config with trailing comma", async () => {
309
+ const configPath = createTestConfig(`{
310
+ "name": "test-app",
311
+ "main": "src/index.ts",
312
+ }`);
313
+
314
+ await addD1Binding(configPath, testBinding);
315
+
316
+ const content = await readTestConfig(configPath);
317
+ const { parseJsonc } = await import("./jsonc.ts");
318
+ const parsed = parseJsonc<{ d1_databases: D1BindingConfig[] }>(content);
319
+ expect(parsed.d1_databases).toBeDefined();
320
+ });
321
+ });
322
+ });