@getjack/jack 0.1.4 → 0.1.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.
- package/package.json +2 -6
- package/src/commands/agents.ts +9 -24
- package/src/commands/clone.ts +27 -0
- package/src/commands/down.ts +31 -57
- package/src/commands/feedback.ts +4 -5
- package/src/commands/link.ts +147 -0
- package/src/commands/login.ts +124 -1
- package/src/commands/logs.ts +8 -18
- package/src/commands/new.ts +7 -1
- package/src/commands/projects.ts +166 -105
- package/src/commands/secrets.ts +7 -6
- package/src/commands/services.ts +5 -4
- package/src/commands/tag.ts +282 -0
- package/src/commands/unlink.ts +30 -0
- package/src/index.ts +46 -1
- package/src/lib/auth/index.ts +2 -0
- package/src/lib/auth/store.ts +26 -2
- package/src/lib/binding-validator.ts +4 -13
- package/src/lib/build-helper.ts +93 -5
- package/src/lib/control-plane.ts +137 -0
- package/src/lib/deploy-mode.ts +1 -1
- package/src/lib/managed-deploy.ts +11 -1
- package/src/lib/managed-down.ts +7 -20
- package/src/lib/paths-index.test.ts +546 -0
- package/src/lib/paths-index.ts +310 -0
- package/src/lib/project-link.test.ts +459 -0
- package/src/lib/project-link.ts +279 -0
- package/src/lib/project-list.test.ts +581 -0
- package/src/lib/project-list.ts +449 -0
- package/src/lib/project-operations.ts +304 -183
- package/src/lib/project-resolver.ts +191 -211
- package/src/lib/tags.ts +389 -0
- package/src/lib/telemetry.ts +86 -157
- package/src/lib/zip-packager.ts +9 -0
- package/src/templates/index.ts +5 -3
- package/templates/api/.jack/template.json +4 -0
- package/templates/hello/.jack/template.json +4 -0
- package/templates/miniapp/.jack/template.json +4 -0
- package/templates/nextjs/.jack.json +28 -0
- package/templates/nextjs/app/globals.css +9 -0
- package/templates/nextjs/app/layout.tsx +19 -0
- package/templates/nextjs/app/page.tsx +8 -0
- package/templates/nextjs/bun.lock +2232 -0
- package/templates/nextjs/cloudflare-env.d.ts +3 -0
- package/templates/nextjs/next-env.d.ts +6 -0
- package/templates/nextjs/next.config.ts +8 -0
- package/templates/nextjs/open-next.config.ts +6 -0
- package/templates/nextjs/package.json +24 -0
- package/templates/nextjs/public/_headers +2 -0
- package/templates/nextjs/tsconfig.json +44 -0
- package/templates/nextjs/wrangler.jsonc +17 -0
- package/src/lib/local-paths.test.ts +0 -902
- package/src/lib/local-paths.ts +0 -258
- package/src/lib/registry.ts +0 -181
|
@@ -1,902 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Unit tests for local-paths.ts
|
|
3
|
-
*
|
|
4
|
-
* These tests use a temporary directory for isolation and mock the CONFIG_DIR
|
|
5
|
-
* to avoid touching the real ~/.config/jack directory.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { afterEach, beforeEach, describe, expect, it, mock, spyOn } from "bun:test";
|
|
9
|
-
import { existsSync } from "node:fs";
|
|
10
|
-
import { mkdir, rm, writeFile } from "node:fs/promises";
|
|
11
|
-
import { tmpdir } from "node:os";
|
|
12
|
-
import { join } from "node:path";
|
|
13
|
-
|
|
14
|
-
// Test helpers
|
|
15
|
-
let testDir: string;
|
|
16
|
-
let testConfigDir: string;
|
|
17
|
-
let testIndexPath: string;
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Create a unique temp directory for each test
|
|
21
|
-
*/
|
|
22
|
-
async function createTestDir(): Promise<string> {
|
|
23
|
-
const timestamp = Date.now();
|
|
24
|
-
const random = Math.random().toString(36).substring(7);
|
|
25
|
-
const dir = join(tmpdir(), `jack-test-${timestamp}-${random}`);
|
|
26
|
-
await mkdir(dir, { recursive: true });
|
|
27
|
-
return dir;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Create a mock project with wrangler config
|
|
32
|
-
*/
|
|
33
|
-
async function createMockProject(
|
|
34
|
-
parentDir: string,
|
|
35
|
-
name: string,
|
|
36
|
-
configType: "jsonc" | "toml" | "json" = "jsonc",
|
|
37
|
-
): Promise<string> {
|
|
38
|
-
const projectDir = join(parentDir, name);
|
|
39
|
-
await mkdir(projectDir, { recursive: true });
|
|
40
|
-
|
|
41
|
-
if (configType === "jsonc") {
|
|
42
|
-
await writeFile(join(projectDir, "wrangler.jsonc"), JSON.stringify({ name }));
|
|
43
|
-
} else if (configType === "toml") {
|
|
44
|
-
await writeFile(join(projectDir, "wrangler.toml"), `name = "${name}"`);
|
|
45
|
-
} else {
|
|
46
|
-
await writeFile(join(projectDir, "wrangler.json"), JSON.stringify({ name }));
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
return projectDir;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Write a test index file directly
|
|
54
|
-
*/
|
|
55
|
-
async function writeTestIndex(index: object): Promise<void> {
|
|
56
|
-
await mkdir(testConfigDir, { recursive: true });
|
|
57
|
-
await writeFile(testIndexPath, JSON.stringify(index, null, 2));
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Read the test index file directly
|
|
62
|
-
*/
|
|
63
|
-
async function readTestIndex(): Promise<object | null> {
|
|
64
|
-
if (!existsSync(testIndexPath)) {
|
|
65
|
-
return null;
|
|
66
|
-
}
|
|
67
|
-
const content = await Bun.file(testIndexPath).text();
|
|
68
|
-
return JSON.parse(content);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
describe("local-paths", () => {
|
|
72
|
-
beforeEach(async () => {
|
|
73
|
-
// Create fresh temp directory for each test
|
|
74
|
-
testDir = await createTestDir();
|
|
75
|
-
testConfigDir = join(testDir, "config");
|
|
76
|
-
testIndexPath = join(testConfigDir, "local-paths.json");
|
|
77
|
-
await mkdir(testConfigDir, { recursive: true });
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
afterEach(async () => {
|
|
81
|
-
// Clean up temp directory
|
|
82
|
-
await rm(testDir, { recursive: true, force: true });
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
describe("readLocalPaths", () => {
|
|
86
|
-
it("returns empty index when file does not exist", async () => {
|
|
87
|
-
// Import fresh module for each test to avoid state pollution
|
|
88
|
-
// We'll use a mock approach for the CONFIG_DIR
|
|
89
|
-
|
|
90
|
-
// Create a mock module that uses our test paths
|
|
91
|
-
const mockConfigDir = testConfigDir;
|
|
92
|
-
const mockIndexPath = testIndexPath;
|
|
93
|
-
|
|
94
|
-
// Since we can't easily mock the constant, we test the behavior
|
|
95
|
-
// by directly testing the file I/O pattern
|
|
96
|
-
|
|
97
|
-
// The file doesn't exist yet
|
|
98
|
-
expect(existsSync(testIndexPath)).toBe(false);
|
|
99
|
-
|
|
100
|
-
// Reading a non-existent file should return empty index
|
|
101
|
-
// We simulate this behavior pattern
|
|
102
|
-
const emptyIndex = { version: 1, paths: {}, updatedAt: expect.any(String) };
|
|
103
|
-
|
|
104
|
-
// Test that our helper returns null for non-existent file
|
|
105
|
-
const result = await readTestIndex();
|
|
106
|
-
expect(result).toBe(null);
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
it("handles corrupted index file gracefully", async () => {
|
|
110
|
-
// Write invalid JSON to the index file
|
|
111
|
-
await writeFile(testIndexPath, "{ invalid json content");
|
|
112
|
-
|
|
113
|
-
// Reading should fail, so we verify the pattern
|
|
114
|
-
try {
|
|
115
|
-
const content = await Bun.file(testIndexPath).json();
|
|
116
|
-
// If we get here, parsing succeeded unexpectedly
|
|
117
|
-
expect(true).toBe(false);
|
|
118
|
-
} catch {
|
|
119
|
-
// Expected - corrupted file should throw
|
|
120
|
-
expect(true).toBe(true);
|
|
121
|
-
}
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
it("reads valid index file", async () => {
|
|
125
|
-
const testIndex = {
|
|
126
|
-
version: 1,
|
|
127
|
-
paths: {
|
|
128
|
-
"my-project": ["/path/to/my-project"],
|
|
129
|
-
},
|
|
130
|
-
updatedAt: "2024-12-28T00:00:00.000Z",
|
|
131
|
-
};
|
|
132
|
-
|
|
133
|
-
await writeTestIndex(testIndex);
|
|
134
|
-
|
|
135
|
-
const result = await readTestIndex();
|
|
136
|
-
expect(result).toEqual(testIndex);
|
|
137
|
-
});
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
describe("registerLocalPath", () => {
|
|
141
|
-
it("registers a new path for a new project", async () => {
|
|
142
|
-
// Create a mock project
|
|
143
|
-
const projectDir = await createMockProject(testDir, "test-project");
|
|
144
|
-
|
|
145
|
-
// Test the registration pattern
|
|
146
|
-
const index = { version: 1, paths: {} as Record<string, string[]>, updatedAt: "" };
|
|
147
|
-
|
|
148
|
-
// Simulate registerLocalPath behavior
|
|
149
|
-
const projectName = "test-project";
|
|
150
|
-
const absolutePath = projectDir;
|
|
151
|
-
|
|
152
|
-
if (!index.paths[projectName]) {
|
|
153
|
-
index.paths[projectName] = [];
|
|
154
|
-
}
|
|
155
|
-
if (!index.paths[projectName].includes(absolutePath)) {
|
|
156
|
-
index.paths[projectName].push(absolutePath);
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
expect(index.paths["test-project"]).toContain(projectDir);
|
|
160
|
-
expect(index.paths["test-project"]).toHaveLength(1);
|
|
161
|
-
});
|
|
162
|
-
|
|
163
|
-
it("handles duplicate paths (idempotent)", async () => {
|
|
164
|
-
const projectDir = await createMockProject(testDir, "test-project");
|
|
165
|
-
|
|
166
|
-
// Simulate registering the same path twice
|
|
167
|
-
const index = { version: 1, paths: {} as Record<string, string[]>, updatedAt: "" };
|
|
168
|
-
const projectName = "test-project";
|
|
169
|
-
const absolutePath = projectDir;
|
|
170
|
-
|
|
171
|
-
// First registration
|
|
172
|
-
if (!index.paths[projectName]) {
|
|
173
|
-
index.paths[projectName] = [];
|
|
174
|
-
}
|
|
175
|
-
if (!index.paths[projectName].includes(absolutePath)) {
|
|
176
|
-
index.paths[projectName].push(absolutePath);
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
// Second registration (should be idempotent)
|
|
180
|
-
if (!index.paths[projectName].includes(absolutePath)) {
|
|
181
|
-
index.paths[projectName].push(absolutePath);
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
expect(index.paths["test-project"]).toHaveLength(1);
|
|
185
|
-
expect(index.paths["test-project"]).toContain(projectDir);
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
it("allows multiple paths for the same project", async () => {
|
|
189
|
-
const projectDir1 = await createMockProject(testDir, "test-project");
|
|
190
|
-
const projectDir2 = await createMockProject(join(testDir, "fork"), "test-project");
|
|
191
|
-
|
|
192
|
-
const index = { version: 1, paths: {} as Record<string, string[]>, updatedAt: "" };
|
|
193
|
-
const projectName = "test-project";
|
|
194
|
-
|
|
195
|
-
// Register first path
|
|
196
|
-
if (!index.paths[projectName]) {
|
|
197
|
-
index.paths[projectName] = [];
|
|
198
|
-
}
|
|
199
|
-
if (!index.paths[projectName].includes(projectDir1)) {
|
|
200
|
-
index.paths[projectName].push(projectDir1);
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
// Register second path
|
|
204
|
-
if (!index.paths[projectName].includes(projectDir2)) {
|
|
205
|
-
index.paths[projectName].push(projectDir2);
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
expect(index.paths["test-project"]).toHaveLength(2);
|
|
209
|
-
expect(index.paths["test-project"]).toContain(projectDir1);
|
|
210
|
-
expect(index.paths["test-project"]).toContain(projectDir2);
|
|
211
|
-
});
|
|
212
|
-
});
|
|
213
|
-
|
|
214
|
-
describe("removeLocalPath", () => {
|
|
215
|
-
it("removes an existing path", async () => {
|
|
216
|
-
const projectDir = await createMockProject(testDir, "test-project");
|
|
217
|
-
|
|
218
|
-
const index = {
|
|
219
|
-
version: 1,
|
|
220
|
-
paths: { "test-project": [projectDir] } as Record<string, string[]>,
|
|
221
|
-
updatedAt: "",
|
|
222
|
-
};
|
|
223
|
-
|
|
224
|
-
// Simulate removeLocalPath behavior
|
|
225
|
-
const projectName = "test-project";
|
|
226
|
-
const absolutePath = projectDir;
|
|
227
|
-
|
|
228
|
-
if (index.paths[projectName]) {
|
|
229
|
-
index.paths[projectName] = index.paths[projectName].filter((p) => p !== absolutePath);
|
|
230
|
-
if (index.paths[projectName].length === 0) {
|
|
231
|
-
delete index.paths[projectName];
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
expect(index.paths["test-project"]).toBeUndefined();
|
|
236
|
-
});
|
|
237
|
-
|
|
238
|
-
it("handles removing non-existent path gracefully", async () => {
|
|
239
|
-
const index = {
|
|
240
|
-
version: 1,
|
|
241
|
-
paths: {} as Record<string, string[]>,
|
|
242
|
-
updatedAt: "",
|
|
243
|
-
};
|
|
244
|
-
|
|
245
|
-
// Simulate removeLocalPath for non-existent project
|
|
246
|
-
const projectName = "non-existent";
|
|
247
|
-
const absolutePath = "/some/path";
|
|
248
|
-
|
|
249
|
-
if (index.paths[projectName]) {
|
|
250
|
-
index.paths[projectName] = index.paths[projectName].filter((p) => p !== absolutePath);
|
|
251
|
-
if (index.paths[projectName].length === 0) {
|
|
252
|
-
delete index.paths[projectName];
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
// Should not throw and paths should remain empty
|
|
257
|
-
expect(index.paths).toEqual({});
|
|
258
|
-
});
|
|
259
|
-
});
|
|
260
|
-
|
|
261
|
-
describe("getLocalPaths", () => {
|
|
262
|
-
it("returns empty array for unknown project", async () => {
|
|
263
|
-
const index = { version: 1, paths: {}, updatedAt: "" };
|
|
264
|
-
const paths = index.paths["unknown-project"] || [];
|
|
265
|
-
expect(paths).toHaveLength(0);
|
|
266
|
-
});
|
|
267
|
-
|
|
268
|
-
it("prunes non-existent paths automatically", async () => {
|
|
269
|
-
// Create a project, add it to index, then delete the project
|
|
270
|
-
const projectDir = await createMockProject(testDir, "ghost-project");
|
|
271
|
-
|
|
272
|
-
const index = {
|
|
273
|
-
version: 1,
|
|
274
|
-
paths: {
|
|
275
|
-
"ghost-project": [projectDir, "/non/existent/path"],
|
|
276
|
-
} as Record<string, string[]>,
|
|
277
|
-
updatedAt: "",
|
|
278
|
-
};
|
|
279
|
-
|
|
280
|
-
// Simulate getLocalPaths pruning behavior
|
|
281
|
-
const projectName = "ghost-project";
|
|
282
|
-
const paths = index.paths[projectName] || [];
|
|
283
|
-
const validPaths: string[] = [];
|
|
284
|
-
const invalidPaths: string[] = [];
|
|
285
|
-
|
|
286
|
-
for (const path of paths) {
|
|
287
|
-
const hasConfig =
|
|
288
|
-
existsSync(join(path, "wrangler.jsonc")) ||
|
|
289
|
-
existsSync(join(path, "wrangler.toml")) ||
|
|
290
|
-
existsSync(join(path, "wrangler.json"));
|
|
291
|
-
|
|
292
|
-
if (hasConfig) {
|
|
293
|
-
validPaths.push(path);
|
|
294
|
-
} else {
|
|
295
|
-
invalidPaths.push(path);
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
expect(validPaths).toContain(projectDir);
|
|
300
|
-
expect(validPaths).not.toContain("/non/existent/path");
|
|
301
|
-
expect(invalidPaths).toContain("/non/existent/path");
|
|
302
|
-
});
|
|
303
|
-
|
|
304
|
-
it("removes project entry when all paths are invalid", async () => {
|
|
305
|
-
const index = {
|
|
306
|
-
version: 1,
|
|
307
|
-
paths: {
|
|
308
|
-
"ghost-project": ["/non/existent/path1", "/non/existent/path2"],
|
|
309
|
-
} as Record<string, string[]>,
|
|
310
|
-
updatedAt: "",
|
|
311
|
-
};
|
|
312
|
-
|
|
313
|
-
// Simulate pruning
|
|
314
|
-
const projectName = "ghost-project";
|
|
315
|
-
const paths = index.paths[projectName] || [];
|
|
316
|
-
const validPaths: string[] = [];
|
|
317
|
-
|
|
318
|
-
for (const path of paths) {
|
|
319
|
-
const hasConfig =
|
|
320
|
-
existsSync(join(path, "wrangler.jsonc")) ||
|
|
321
|
-
existsSync(join(path, "wrangler.toml")) ||
|
|
322
|
-
existsSync(join(path, "wrangler.json"));
|
|
323
|
-
|
|
324
|
-
if (hasConfig) {
|
|
325
|
-
validPaths.push(path);
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
// Update index
|
|
330
|
-
if (validPaths.length > 0) {
|
|
331
|
-
index.paths[projectName] = validPaths;
|
|
332
|
-
} else {
|
|
333
|
-
delete index.paths[projectName];
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
expect(index.paths["ghost-project"]).toBeUndefined();
|
|
337
|
-
});
|
|
338
|
-
});
|
|
339
|
-
|
|
340
|
-
describe("getAllLocalPaths", () => {
|
|
341
|
-
it("returns empty object when no projects registered", async () => {
|
|
342
|
-
const index = { version: 1, paths: {}, updatedAt: "" };
|
|
343
|
-
expect(Object.keys(index.paths)).toHaveLength(0);
|
|
344
|
-
});
|
|
345
|
-
|
|
346
|
-
it("returns all valid paths for all projects", async () => {
|
|
347
|
-
const project1 = await createMockProject(testDir, "project1");
|
|
348
|
-
const project2 = await createMockProject(testDir, "project2");
|
|
349
|
-
|
|
350
|
-
const index = {
|
|
351
|
-
version: 1,
|
|
352
|
-
paths: {
|
|
353
|
-
project1: [project1],
|
|
354
|
-
project2: [project2],
|
|
355
|
-
} as Record<string, string[]>,
|
|
356
|
-
updatedAt: "",
|
|
357
|
-
};
|
|
358
|
-
|
|
359
|
-
// Simulate getAllLocalPaths
|
|
360
|
-
const result: Record<string, string[]> = {};
|
|
361
|
-
|
|
362
|
-
for (const [projectName, paths] of Object.entries(index.paths)) {
|
|
363
|
-
const validPaths: string[] = [];
|
|
364
|
-
|
|
365
|
-
for (const path of paths) {
|
|
366
|
-
const hasConfig =
|
|
367
|
-
existsSync(join(path, "wrangler.jsonc")) ||
|
|
368
|
-
existsSync(join(path, "wrangler.toml")) ||
|
|
369
|
-
existsSync(join(path, "wrangler.json"));
|
|
370
|
-
|
|
371
|
-
if (hasConfig) {
|
|
372
|
-
validPaths.push(path);
|
|
373
|
-
}
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
if (validPaths.length > 0) {
|
|
377
|
-
result[projectName] = validPaths;
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
expect(result).toEqual({
|
|
382
|
-
project1: [project1],
|
|
383
|
-
project2: [project2],
|
|
384
|
-
});
|
|
385
|
-
});
|
|
386
|
-
|
|
387
|
-
it("prunes invalid paths across all projects", async () => {
|
|
388
|
-
const validProject = await createMockProject(testDir, "valid-project");
|
|
389
|
-
|
|
390
|
-
const index = {
|
|
391
|
-
version: 1,
|
|
392
|
-
paths: {
|
|
393
|
-
"valid-project": [validProject],
|
|
394
|
-
"invalid-project": ["/non/existent/path"],
|
|
395
|
-
} as Record<string, string[]>,
|
|
396
|
-
updatedAt: "",
|
|
397
|
-
};
|
|
398
|
-
|
|
399
|
-
// Simulate getAllLocalPaths with pruning
|
|
400
|
-
const result: Record<string, string[]> = {};
|
|
401
|
-
|
|
402
|
-
for (const [projectName, paths] of Object.entries(index.paths)) {
|
|
403
|
-
const validPaths: string[] = [];
|
|
404
|
-
|
|
405
|
-
for (const path of paths) {
|
|
406
|
-
const hasConfig =
|
|
407
|
-
existsSync(join(path, "wrangler.jsonc")) ||
|
|
408
|
-
existsSync(join(path, "wrangler.toml")) ||
|
|
409
|
-
existsSync(join(path, "wrangler.json"));
|
|
410
|
-
|
|
411
|
-
if (hasConfig) {
|
|
412
|
-
validPaths.push(path);
|
|
413
|
-
}
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
if (validPaths.length > 0) {
|
|
417
|
-
result[projectName] = validPaths;
|
|
418
|
-
}
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
expect(result).toEqual({
|
|
422
|
-
"valid-project": [validProject],
|
|
423
|
-
});
|
|
424
|
-
expect(result["invalid-project"]).toBeUndefined();
|
|
425
|
-
});
|
|
426
|
-
});
|
|
427
|
-
|
|
428
|
-
describe("scanDirectoryForProjects", () => {
|
|
429
|
-
it("finds projects in directory", async () => {
|
|
430
|
-
// Create nested structure with projects
|
|
431
|
-
const project1 = await createMockProject(join(testDir, "apps"), "project1", "jsonc");
|
|
432
|
-
const project2 = await createMockProject(join(testDir, "libs"), "project2", "toml");
|
|
433
|
-
|
|
434
|
-
// Manually scan like the function does
|
|
435
|
-
const discovered: Array<{ name: string; path: string }> = [];
|
|
436
|
-
|
|
437
|
-
// Check apps/project1
|
|
438
|
-
const project1ConfigPath = join(project1, "wrangler.jsonc");
|
|
439
|
-
if (existsSync(project1ConfigPath)) {
|
|
440
|
-
const content = await Bun.file(project1ConfigPath).text();
|
|
441
|
-
const config = JSON.parse(content);
|
|
442
|
-
discovered.push({ name: config.name, path: project1 });
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
// Check libs/project2
|
|
446
|
-
const project2ConfigPath = join(project2, "wrangler.toml");
|
|
447
|
-
if (existsSync(project2ConfigPath)) {
|
|
448
|
-
const content = await Bun.file(project2ConfigPath).text();
|
|
449
|
-
const match = content.match(/^name\s*=\s*["']([^"']+)["']/m);
|
|
450
|
-
if (match?.[1]) {
|
|
451
|
-
discovered.push({ name: match[1], path: project2 });
|
|
452
|
-
}
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
expect(discovered).toHaveLength(2);
|
|
456
|
-
expect(discovered.map((p) => p.name).sort()).toEqual(["project1", "project2"]);
|
|
457
|
-
});
|
|
458
|
-
|
|
459
|
-
it("respects maxDepth limit", async () => {
|
|
460
|
-
// Create deeply nested project
|
|
461
|
-
const deepPath = join(testDir, "a", "b", "c", "d", "e");
|
|
462
|
-
await mkdir(deepPath, { recursive: true });
|
|
463
|
-
await writeFile(join(deepPath, "wrangler.jsonc"), JSON.stringify({ name: "deep-project" }));
|
|
464
|
-
|
|
465
|
-
// At maxDepth=3, depth 0=testDir, 1=a, 2=b, 3=c - should not find d/e
|
|
466
|
-
const maxDepth = 3;
|
|
467
|
-
|
|
468
|
-
// Track depth during scan
|
|
469
|
-
function getDepth(basePath: string, fullPath: string): number {
|
|
470
|
-
const relative = fullPath.slice(basePath.length);
|
|
471
|
-
const parts = relative.split("/").filter(Boolean);
|
|
472
|
-
return parts.length;
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
const depth = getDepth(testDir, deepPath);
|
|
476
|
-
expect(depth).toBe(5); // a/b/c/d/e = 5 levels deep
|
|
477
|
-
|
|
478
|
-
// At maxDepth=3, we would not scan into d or e
|
|
479
|
-
expect(depth).toBeGreaterThan(maxDepth);
|
|
480
|
-
});
|
|
481
|
-
|
|
482
|
-
it("skips node_modules and .git directories", async () => {
|
|
483
|
-
// Create projects in directories that should be skipped
|
|
484
|
-
await mkdir(join(testDir, "node_modules", "some-package"), { recursive: true });
|
|
485
|
-
await writeFile(
|
|
486
|
-
join(testDir, "node_modules", "some-package", "wrangler.jsonc"),
|
|
487
|
-
JSON.stringify({ name: "should-skip" }),
|
|
488
|
-
);
|
|
489
|
-
|
|
490
|
-
await mkdir(join(testDir, ".git", "hooks"), { recursive: true });
|
|
491
|
-
await writeFile(
|
|
492
|
-
join(testDir, ".git", "hooks", "wrangler.jsonc"),
|
|
493
|
-
JSON.stringify({ name: "also-skip" }),
|
|
494
|
-
);
|
|
495
|
-
|
|
496
|
-
// Create a valid project
|
|
497
|
-
await createMockProject(testDir, "valid-project");
|
|
498
|
-
|
|
499
|
-
// Directories to skip
|
|
500
|
-
const SKIP_DIRS = new Set([
|
|
501
|
-
"node_modules",
|
|
502
|
-
".git",
|
|
503
|
-
"dist",
|
|
504
|
-
"build",
|
|
505
|
-
".next",
|
|
506
|
-
".nuxt",
|
|
507
|
-
".output",
|
|
508
|
-
"coverage",
|
|
509
|
-
".turbo",
|
|
510
|
-
".cache",
|
|
511
|
-
]);
|
|
512
|
-
|
|
513
|
-
// Simulate scanning with skip logic
|
|
514
|
-
const discovered: string[] = [];
|
|
515
|
-
|
|
516
|
-
const entries = ["node_modules", ".git", "valid-project"];
|
|
517
|
-
for (const entry of entries) {
|
|
518
|
-
if (entry.startsWith(".") || SKIP_DIRS.has(entry)) {
|
|
519
|
-
continue;
|
|
520
|
-
}
|
|
521
|
-
// Would add to discovered if it's a project
|
|
522
|
-
discovered.push(entry);
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
expect(discovered).toEqual(["valid-project"]);
|
|
526
|
-
expect(discovered).not.toContain("node_modules");
|
|
527
|
-
expect(discovered).not.toContain(".git");
|
|
528
|
-
});
|
|
529
|
-
|
|
530
|
-
it("does not scan subdirectories of found projects", async () => {
|
|
531
|
-
// Create a project with nested directories
|
|
532
|
-
const projectDir = await createMockProject(testDir, "parent-project");
|
|
533
|
-
await mkdir(join(projectDir, "src", "nested"), { recursive: true });
|
|
534
|
-
|
|
535
|
-
// This nested wrangler should not be found because parent is a project
|
|
536
|
-
await writeFile(
|
|
537
|
-
join(projectDir, "src", "nested", "wrangler.jsonc"),
|
|
538
|
-
JSON.stringify({ name: "nested-project" }),
|
|
539
|
-
);
|
|
540
|
-
|
|
541
|
-
// The scan should stop at parent-project
|
|
542
|
-
// Simulating the behavior: when a project is found, return early
|
|
543
|
-
let foundProjects = 0;
|
|
544
|
-
|
|
545
|
-
// Check if testDir/parent-project is a project
|
|
546
|
-
if (existsSync(join(projectDir, "wrangler.jsonc"))) {
|
|
547
|
-
foundProjects++;
|
|
548
|
-
// Return early - don't scan subdirectories
|
|
549
|
-
}
|
|
550
|
-
|
|
551
|
-
// If we continued scanning, we'd find the nested one
|
|
552
|
-
// But the algorithm stops, so we only find 1
|
|
553
|
-
|
|
554
|
-
expect(foundProjects).toBe(1);
|
|
555
|
-
});
|
|
556
|
-
|
|
557
|
-
it("returns empty array for directory with no projects", async () => {
|
|
558
|
-
// Create some regular files, no projects
|
|
559
|
-
await writeFile(join(testDir, "README.md"), "# Hello");
|
|
560
|
-
await mkdir(join(testDir, "src"));
|
|
561
|
-
await writeFile(join(testDir, "src", "index.ts"), "console.log('hi')");
|
|
562
|
-
|
|
563
|
-
// Simulate scan - no wrangler configs found
|
|
564
|
-
const discovered: Array<{ name: string; path: string }> = [];
|
|
565
|
-
|
|
566
|
-
const hasConfig =
|
|
567
|
-
existsSync(join(testDir, "wrangler.jsonc")) ||
|
|
568
|
-
existsSync(join(testDir, "wrangler.toml")) ||
|
|
569
|
-
existsSync(join(testDir, "wrangler.json"));
|
|
570
|
-
|
|
571
|
-
expect(hasConfig).toBe(false);
|
|
572
|
-
expect(discovered).toHaveLength(0);
|
|
573
|
-
});
|
|
574
|
-
});
|
|
575
|
-
|
|
576
|
-
describe("registerDiscoveredProjects", () => {
|
|
577
|
-
it("registers multiple projects at once", async () => {
|
|
578
|
-
const project1 = await createMockProject(testDir, "project1");
|
|
579
|
-
const project2 = await createMockProject(testDir, "project2");
|
|
580
|
-
|
|
581
|
-
const projects = [
|
|
582
|
-
{ name: "project1", path: project1 },
|
|
583
|
-
{ name: "project2", path: project2 },
|
|
584
|
-
];
|
|
585
|
-
|
|
586
|
-
const index = { version: 1, paths: {} as Record<string, string[]>, updatedAt: "" };
|
|
587
|
-
|
|
588
|
-
// Simulate registerDiscoveredProjects
|
|
589
|
-
for (const { name, path } of projects) {
|
|
590
|
-
if (!index.paths[name]) {
|
|
591
|
-
index.paths[name] = [];
|
|
592
|
-
}
|
|
593
|
-
if (!index.paths[name].includes(path)) {
|
|
594
|
-
index.paths[name].push(path);
|
|
595
|
-
}
|
|
596
|
-
}
|
|
597
|
-
|
|
598
|
-
expect(Object.keys(index.paths)).toHaveLength(2);
|
|
599
|
-
expect(index.paths.project1).toContain(project1);
|
|
600
|
-
expect(index.paths.project2).toContain(project2);
|
|
601
|
-
});
|
|
602
|
-
|
|
603
|
-
it("merges with existing paths", async () => {
|
|
604
|
-
const existingProject = await createMockProject(join(testDir, "existing"), "project1");
|
|
605
|
-
const newProject = await createMockProject(join(testDir, "new"), "project1");
|
|
606
|
-
|
|
607
|
-
// Start with existing path
|
|
608
|
-
const index = {
|
|
609
|
-
version: 1,
|
|
610
|
-
paths: { project1: [existingProject] } as Record<string, string[]>,
|
|
611
|
-
updatedAt: "",
|
|
612
|
-
};
|
|
613
|
-
|
|
614
|
-
// Register new discovery
|
|
615
|
-
const projects = [{ name: "project1", path: newProject }];
|
|
616
|
-
|
|
617
|
-
for (const { name, path } of projects) {
|
|
618
|
-
if (!index.paths[name]) {
|
|
619
|
-
index.paths[name] = [];
|
|
620
|
-
}
|
|
621
|
-
if (!index.paths[name].includes(path)) {
|
|
622
|
-
index.paths[name].push(path);
|
|
623
|
-
}
|
|
624
|
-
}
|
|
625
|
-
|
|
626
|
-
expect(index.paths.project1).toHaveLength(2);
|
|
627
|
-
expect(index.paths.project1).toContain(existingProject);
|
|
628
|
-
expect(index.paths.project1).toContain(newProject);
|
|
629
|
-
});
|
|
630
|
-
});
|
|
631
|
-
|
|
632
|
-
describe("hasWranglerConfig helper", () => {
|
|
633
|
-
it("detects wrangler.jsonc", async () => {
|
|
634
|
-
const projectDir = await createMockProject(testDir, "jsonc-project", "jsonc");
|
|
635
|
-
expect(existsSync(join(projectDir, "wrangler.jsonc"))).toBe(true);
|
|
636
|
-
});
|
|
637
|
-
|
|
638
|
-
it("detects wrangler.toml", async () => {
|
|
639
|
-
const projectDir = await createMockProject(testDir, "toml-project", "toml");
|
|
640
|
-
expect(existsSync(join(projectDir, "wrangler.toml"))).toBe(true);
|
|
641
|
-
});
|
|
642
|
-
|
|
643
|
-
it("detects wrangler.json", async () => {
|
|
644
|
-
const projectDir = await createMockProject(testDir, "json-project", "json");
|
|
645
|
-
expect(existsSync(join(projectDir, "wrangler.json"))).toBe(true);
|
|
646
|
-
});
|
|
647
|
-
|
|
648
|
-
it("returns false for directory without config", async () => {
|
|
649
|
-
const emptyDir = join(testDir, "empty");
|
|
650
|
-
await mkdir(emptyDir);
|
|
651
|
-
|
|
652
|
-
const hasConfig =
|
|
653
|
-
existsSync(join(emptyDir, "wrangler.jsonc")) ||
|
|
654
|
-
existsSync(join(emptyDir, "wrangler.toml")) ||
|
|
655
|
-
existsSync(join(emptyDir, "wrangler.json"));
|
|
656
|
-
|
|
657
|
-
expect(hasConfig).toBe(false);
|
|
658
|
-
});
|
|
659
|
-
});
|
|
660
|
-
|
|
661
|
-
describe("edge cases", () => {
|
|
662
|
-
it("handles empty index file gracefully", async () => {
|
|
663
|
-
await writeFile(testIndexPath, "");
|
|
664
|
-
|
|
665
|
-
try {
|
|
666
|
-
await Bun.file(testIndexPath).json();
|
|
667
|
-
expect(true).toBe(false); // Should not reach here
|
|
668
|
-
} catch {
|
|
669
|
-
// Empty file is not valid JSON
|
|
670
|
-
expect(true).toBe(true);
|
|
671
|
-
}
|
|
672
|
-
});
|
|
673
|
-
|
|
674
|
-
it("handles index with empty paths object", async () => {
|
|
675
|
-
await writeTestIndex({ version: 1, paths: {}, updatedAt: "2024-12-28T00:00:00.000Z" });
|
|
676
|
-
|
|
677
|
-
const result = await readTestIndex();
|
|
678
|
-
expect(result).toEqual({
|
|
679
|
-
version: 1,
|
|
680
|
-
paths: {},
|
|
681
|
-
updatedAt: "2024-12-28T00:00:00.000Z",
|
|
682
|
-
});
|
|
683
|
-
});
|
|
684
|
-
|
|
685
|
-
it("converts relative paths to absolute", async () => {
|
|
686
|
-
// The resolve() function converts relative to absolute
|
|
687
|
-
const { resolve } = await import("node:path");
|
|
688
|
-
|
|
689
|
-
const relativePath = "./my-project";
|
|
690
|
-
const absolutePath = resolve(relativePath);
|
|
691
|
-
|
|
692
|
-
expect(absolutePath).not.toBe(relativePath);
|
|
693
|
-
expect(absolutePath.startsWith("/")).toBe(true);
|
|
694
|
-
});
|
|
695
|
-
|
|
696
|
-
it("stores absolute paths in index", async () => {
|
|
697
|
-
const projectDir = await createMockProject(testDir, "test-project");
|
|
698
|
-
|
|
699
|
-
const index = { version: 1, paths: {} as Record<string, string[]>, updatedAt: "" };
|
|
700
|
-
|
|
701
|
-
// Use resolve to ensure absolute path
|
|
702
|
-
const { resolve } = await import("node:path");
|
|
703
|
-
const absolutePath = resolve(projectDir);
|
|
704
|
-
|
|
705
|
-
if (!index.paths["test-project"]) {
|
|
706
|
-
index.paths["test-project"] = [];
|
|
707
|
-
}
|
|
708
|
-
index.paths["test-project"].push(absolutePath);
|
|
709
|
-
|
|
710
|
-
// Verify it's an absolute path
|
|
711
|
-
expect(index.paths["test-project"][0].startsWith("/")).toBe(true);
|
|
712
|
-
});
|
|
713
|
-
});
|
|
714
|
-
});
|
|
715
|
-
|
|
716
|
-
/**
|
|
717
|
-
* Integration tests that use the actual module functions
|
|
718
|
-
* Note: scanDirectoryForProjects is safe to test as it doesn't depend on INDEX_PATH
|
|
719
|
-
*/
|
|
720
|
-
describe("local-paths integration", () => {
|
|
721
|
-
let testDir: string;
|
|
722
|
-
|
|
723
|
-
beforeEach(async () => {
|
|
724
|
-
// Create fresh temp directory for each test
|
|
725
|
-
const timestamp = Date.now();
|
|
726
|
-
const random = Math.random().toString(36).substring(7);
|
|
727
|
-
testDir = join(tmpdir(), `jack-integration-${timestamp}-${random}`);
|
|
728
|
-
await mkdir(testDir, { recursive: true });
|
|
729
|
-
});
|
|
730
|
-
|
|
731
|
-
afterEach(async () => {
|
|
732
|
-
await rm(testDir, { recursive: true, force: true });
|
|
733
|
-
});
|
|
734
|
-
|
|
735
|
-
describe("scanDirectoryForProjects - real function", () => {
|
|
736
|
-
it("discovers projects with wrangler.jsonc", async () => {
|
|
737
|
-
// Create a real project structure
|
|
738
|
-
const projectDir = join(testDir, "my-api");
|
|
739
|
-
await mkdir(projectDir, { recursive: true });
|
|
740
|
-
await writeFile(
|
|
741
|
-
join(projectDir, "wrangler.jsonc"),
|
|
742
|
-
JSON.stringify({ name: "my-api", main: "src/index.ts" }),
|
|
743
|
-
);
|
|
744
|
-
|
|
745
|
-
// Import and call the real function
|
|
746
|
-
const { scanDirectoryForProjects } = await import("./local-paths.ts");
|
|
747
|
-
const discovered = await scanDirectoryForProjects(testDir);
|
|
748
|
-
|
|
749
|
-
expect(discovered).toHaveLength(1);
|
|
750
|
-
expect(discovered[0].name).toBe("my-api");
|
|
751
|
-
expect(discovered[0].path).toBe(projectDir);
|
|
752
|
-
});
|
|
753
|
-
|
|
754
|
-
it("discovers projects with wrangler.toml", async () => {
|
|
755
|
-
const projectDir = join(testDir, "toml-project");
|
|
756
|
-
await mkdir(projectDir, { recursive: true });
|
|
757
|
-
await writeFile(
|
|
758
|
-
join(projectDir, "wrangler.toml"),
|
|
759
|
-
'name = "toml-project"\nmain = "src/index.ts"',
|
|
760
|
-
);
|
|
761
|
-
|
|
762
|
-
const { scanDirectoryForProjects } = await import("./local-paths.ts");
|
|
763
|
-
const discovered = await scanDirectoryForProjects(testDir);
|
|
764
|
-
|
|
765
|
-
expect(discovered).toHaveLength(1);
|
|
766
|
-
expect(discovered[0].name).toBe("toml-project");
|
|
767
|
-
});
|
|
768
|
-
|
|
769
|
-
it("discovers multiple nested projects", async () => {
|
|
770
|
-
// Create multiple projects in subdirectories
|
|
771
|
-
const project1 = join(testDir, "apps", "api");
|
|
772
|
-
const project2 = join(testDir, "apps", "web");
|
|
773
|
-
const project3 = join(testDir, "services", "worker");
|
|
774
|
-
|
|
775
|
-
await mkdir(project1, { recursive: true });
|
|
776
|
-
await mkdir(project2, { recursive: true });
|
|
777
|
-
await mkdir(project3, { recursive: true });
|
|
778
|
-
|
|
779
|
-
await writeFile(join(project1, "wrangler.jsonc"), JSON.stringify({ name: "api" }));
|
|
780
|
-
await writeFile(join(project2, "wrangler.jsonc"), JSON.stringify({ name: "web" }));
|
|
781
|
-
await writeFile(join(project3, "wrangler.toml"), 'name = "worker"');
|
|
782
|
-
|
|
783
|
-
const { scanDirectoryForProjects } = await import("./local-paths.ts");
|
|
784
|
-
const discovered = await scanDirectoryForProjects(testDir);
|
|
785
|
-
|
|
786
|
-
expect(discovered).toHaveLength(3);
|
|
787
|
-
const names = discovered.map((p) => p.name).sort();
|
|
788
|
-
expect(names).toEqual(["api", "web", "worker"]);
|
|
789
|
-
});
|
|
790
|
-
|
|
791
|
-
it("respects maxDepth parameter", async () => {
|
|
792
|
-
// Create a deeply nested project
|
|
793
|
-
const deepProject = join(testDir, "level1", "level2", "level3", "level4", "deep-project");
|
|
794
|
-
await mkdir(deepProject, { recursive: true });
|
|
795
|
-
await writeFile(
|
|
796
|
-
join(deepProject, "wrangler.jsonc"),
|
|
797
|
-
JSON.stringify({ name: "deep-project" }),
|
|
798
|
-
);
|
|
799
|
-
|
|
800
|
-
// Also create a shallow project
|
|
801
|
-
const shallowProject = join(testDir, "shallow");
|
|
802
|
-
await mkdir(shallowProject, { recursive: true });
|
|
803
|
-
await writeFile(join(shallowProject, "wrangler.jsonc"), JSON.stringify({ name: "shallow" }));
|
|
804
|
-
|
|
805
|
-
const { scanDirectoryForProjects } = await import("./local-paths.ts");
|
|
806
|
-
|
|
807
|
-
// With maxDepth=2, should only find shallow project
|
|
808
|
-
const discovered = await scanDirectoryForProjects(testDir, 2);
|
|
809
|
-
|
|
810
|
-
expect(discovered).toHaveLength(1);
|
|
811
|
-
expect(discovered[0].name).toBe("shallow");
|
|
812
|
-
});
|
|
813
|
-
|
|
814
|
-
it("skips node_modules directory", async () => {
|
|
815
|
-
// Create a project in node_modules (should be skipped)
|
|
816
|
-
const nodeModulesProject = join(testDir, "node_modules", "some-package");
|
|
817
|
-
await mkdir(nodeModulesProject, { recursive: true });
|
|
818
|
-
await writeFile(
|
|
819
|
-
join(nodeModulesProject, "wrangler.jsonc"),
|
|
820
|
-
JSON.stringify({ name: "should-skip" }),
|
|
821
|
-
);
|
|
822
|
-
|
|
823
|
-
// Create a regular project
|
|
824
|
-
const realProject = join(testDir, "real-project");
|
|
825
|
-
await mkdir(realProject, { recursive: true });
|
|
826
|
-
await writeFile(
|
|
827
|
-
join(realProject, "wrangler.jsonc"),
|
|
828
|
-
JSON.stringify({ name: "real-project" }),
|
|
829
|
-
);
|
|
830
|
-
|
|
831
|
-
const { scanDirectoryForProjects } = await import("./local-paths.ts");
|
|
832
|
-
const discovered = await scanDirectoryForProjects(testDir);
|
|
833
|
-
|
|
834
|
-
expect(discovered).toHaveLength(1);
|
|
835
|
-
expect(discovered[0].name).toBe("real-project");
|
|
836
|
-
});
|
|
837
|
-
|
|
838
|
-
it("does not recurse into found projects", async () => {
|
|
839
|
-
// Create a project with a nested sub-project
|
|
840
|
-
const parentProject = join(testDir, "parent");
|
|
841
|
-
await mkdir(parentProject, { recursive: true });
|
|
842
|
-
await writeFile(join(parentProject, "wrangler.jsonc"), JSON.stringify({ name: "parent" }));
|
|
843
|
-
|
|
844
|
-
// Create a nested project inside the parent
|
|
845
|
-
const nestedProject = join(parentProject, "packages", "child");
|
|
846
|
-
await mkdir(nestedProject, { recursive: true });
|
|
847
|
-
await writeFile(join(nestedProject, "wrangler.jsonc"), JSON.stringify({ name: "child" }));
|
|
848
|
-
|
|
849
|
-
const { scanDirectoryForProjects } = await import("./local-paths.ts");
|
|
850
|
-
const discovered = await scanDirectoryForProjects(testDir);
|
|
851
|
-
|
|
852
|
-
// Should only find parent, not child
|
|
853
|
-
expect(discovered).toHaveLength(1);
|
|
854
|
-
expect(discovered[0].name).toBe("parent");
|
|
855
|
-
});
|
|
856
|
-
|
|
857
|
-
it("returns empty array for directory with no projects", async () => {
|
|
858
|
-
// Create some files but no wrangler configs
|
|
859
|
-
await writeFile(join(testDir, "README.md"), "# Hello");
|
|
860
|
-
await mkdir(join(testDir, "src"));
|
|
861
|
-
await writeFile(join(testDir, "src", "index.ts"), "export {}");
|
|
862
|
-
|
|
863
|
-
const { scanDirectoryForProjects } = await import("./local-paths.ts");
|
|
864
|
-
const discovered = await scanDirectoryForProjects(testDir);
|
|
865
|
-
|
|
866
|
-
expect(discovered).toHaveLength(0);
|
|
867
|
-
});
|
|
868
|
-
|
|
869
|
-
it("handles permission errors gracefully", async () => {
|
|
870
|
-
// Create a normal project
|
|
871
|
-
const project = join(testDir, "accessible");
|
|
872
|
-
await mkdir(project, { recursive: true });
|
|
873
|
-
await writeFile(join(project, "wrangler.jsonc"), JSON.stringify({ name: "accessible" }));
|
|
874
|
-
|
|
875
|
-
const { scanDirectoryForProjects } = await import("./local-paths.ts");
|
|
876
|
-
const discovered = await scanDirectoryForProjects(testDir);
|
|
877
|
-
|
|
878
|
-
// Should find the accessible project
|
|
879
|
-
expect(discovered.length).toBeGreaterThanOrEqual(1);
|
|
880
|
-
expect(discovered.some((p) => p.name === "accessible")).toBe(true);
|
|
881
|
-
});
|
|
882
|
-
});
|
|
883
|
-
|
|
884
|
-
describe("LocalPathsIndex structure", () => {
|
|
885
|
-
it("validates index structure", () => {
|
|
886
|
-
// Test the expected structure
|
|
887
|
-
const validIndex = {
|
|
888
|
-
version: 1 as const,
|
|
889
|
-
paths: {
|
|
890
|
-
"project-a": ["/path/to/a"],
|
|
891
|
-
"project-b": ["/path/to/b1", "/path/to/b2"],
|
|
892
|
-
},
|
|
893
|
-
updatedAt: "2024-12-28T00:00:00.000Z",
|
|
894
|
-
};
|
|
895
|
-
|
|
896
|
-
expect(validIndex.version).toBe(1);
|
|
897
|
-
expect(typeof validIndex.paths).toBe("object");
|
|
898
|
-
expect(typeof validIndex.updatedAt).toBe("string");
|
|
899
|
-
expect(Array.isArray(validIndex.paths["project-a"])).toBe(true);
|
|
900
|
-
});
|
|
901
|
-
});
|
|
902
|
-
});
|