@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.
- package/README.md +1 -1
- package/package.json +1 -1
- package/src/commands/clone.ts +62 -40
- package/src/commands/community.ts +47 -0
- package/src/commands/init.ts +6 -0
- package/src/commands/services.ts +354 -9
- package/src/commands/sync.ts +9 -0
- package/src/commands/update.ts +10 -0
- package/src/index.ts +7 -0
- package/src/lib/control-plane.ts +62 -0
- package/src/lib/hooks.ts +20 -0
- package/src/lib/managed-deploy.ts +26 -2
- package/src/lib/output.ts +21 -3
- package/src/lib/progress.ts +160 -0
- package/src/lib/project-operations.ts +381 -93
- package/src/lib/services/db-create.ts +6 -3
- package/src/lib/services/db-execute.ts +485 -0
- package/src/lib/services/sql-classifier.test.ts +404 -0
- package/src/lib/services/sql-classifier.ts +346 -0
- package/src/lib/storage/file-filter.ts +4 -0
- package/src/lib/telemetry.ts +3 -0
- package/src/lib/version-check.ts +14 -0
- package/src/lib/wrangler-config.test.ts +322 -0
- package/src/lib/wrangler-config.ts +649 -0
- package/src/lib/zip-packager.ts +38 -0
- package/src/lib/zip-utils.ts +38 -0
- package/src/mcp/tools/index.ts +161 -0
- package/src/templates/index.ts +4 -0
- package/src/templates/types.ts +12 -0
- package/templates/api/AGENTS.md +33 -0
- package/templates/hello/AGENTS.md +33 -0
- package/templates/miniapp/.jack.json +4 -5
- package/templates/miniapp/AGENTS.md +33 -0
- package/templates/nextjs/AGENTS.md +33 -0
|
@@ -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
|
+
});
|