@larkiny/astro-github-loader 0.11.2 → 0.12.0
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 +69 -61
- package/dist/github.assets.d.ts +70 -0
- package/dist/github.assets.js +253 -0
- package/dist/github.auth.js +13 -9
- package/dist/github.cleanup.d.ts +3 -2
- package/dist/github.cleanup.js +30 -23
- package/dist/github.constants.d.ts +0 -16
- package/dist/github.constants.js +0 -16
- package/dist/github.content.d.ts +6 -132
- package/dist/github.content.js +154 -789
- package/dist/github.dryrun.d.ts +9 -5
- package/dist/github.dryrun.js +46 -25
- package/dist/github.link-transform.d.ts +2 -2
- package/dist/github.link-transform.js +65 -57
- package/dist/github.loader.js +45 -51
- package/dist/github.logger.d.ts +2 -2
- package/dist/github.logger.js +33 -24
- package/dist/github.paths.d.ts +76 -0
- package/dist/github.paths.js +190 -0
- package/dist/github.storage.d.ts +15 -0
- package/dist/github.storage.js +109 -0
- package/dist/github.types.d.ts +41 -4
- package/dist/index.d.ts +8 -6
- package/dist/index.js +3 -6
- package/dist/test-helpers.d.ts +130 -0
- package/dist/test-helpers.js +194 -0
- package/package.json +3 -1
- package/src/github.assets.spec.ts +717 -0
- package/src/github.assets.ts +365 -0
- package/src/github.auth.spec.ts +245 -0
- package/src/github.auth.ts +24 -10
- package/src/github.cleanup.spec.ts +380 -0
- package/src/github.cleanup.ts +91 -47
- package/src/github.constants.ts +0 -17
- package/src/github.content.spec.ts +305 -454
- package/src/github.content.ts +261 -950
- package/src/github.dryrun.spec.ts +586 -0
- package/src/github.dryrun.ts +105 -54
- package/src/github.link-transform.spec.ts +1345 -0
- package/src/github.link-transform.ts +174 -95
- package/src/github.loader.spec.ts +75 -50
- package/src/github.loader.ts +113 -78
- package/src/github.logger.spec.ts +795 -0
- package/src/github.logger.ts +77 -35
- package/src/github.paths.spec.ts +523 -0
- package/src/github.paths.ts +259 -0
- package/src/github.storage.spec.ts +367 -0
- package/src/github.storage.ts +127 -0
- package/src/github.types.ts +55 -9
- package/src/index.ts +43 -6
- package/src/test-helpers.ts +215 -0
|
@@ -1,157 +1,22 @@
|
|
|
1
1
|
import { beforeEach, describe, it, expect, vi } from "vitest";
|
|
2
|
-
import { toCollectionEntry } from "./github.content.js";
|
|
3
|
-
import {
|
|
4
|
-
import
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
* These tests verify that the new Git Trees API approach:
|
|
10
|
-
* 1. Correctly discovers files matching include patterns
|
|
11
|
-
* 2. Reduces API calls compared to recursive approach
|
|
12
|
-
* 3. Works with the expected repository configuration
|
|
13
|
-
*
|
|
14
|
-
* These are unit tests with mocked API responses to test the optimization
|
|
15
|
-
* logic without requiring network access.
|
|
16
|
-
*/
|
|
17
|
-
describe("Git Trees API Optimization", () => {
|
|
18
|
-
let octokit: Octokit;
|
|
19
|
-
|
|
20
|
-
// Mock commit data
|
|
21
|
-
const mockCommit = {
|
|
22
|
-
sha: "abc123def456",
|
|
23
|
-
commit: {
|
|
24
|
-
tree: {
|
|
25
|
-
sha: "tree123abc456"
|
|
26
|
-
},
|
|
27
|
-
message: "Test commit",
|
|
28
|
-
author: {
|
|
29
|
-
name: "Test Author",
|
|
30
|
-
email: "test@example.com",
|
|
31
|
-
date: "2024-01-01T00:00:00Z"
|
|
32
|
-
},
|
|
33
|
-
committer: {
|
|
34
|
-
name: "Test Committer",
|
|
35
|
-
email: "test@example.com",
|
|
36
|
-
date: "2024-01-01T00:00:00Z"
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
// Mock tree data representing a repository structure similar to algokit-cli
|
|
42
|
-
const mockTreeData = {
|
|
43
|
-
sha: "tree123abc456",
|
|
44
|
-
url: "https://api.github.com/repos/test/repo/git/trees/tree123abc456",
|
|
45
|
-
tree: [
|
|
46
|
-
{
|
|
47
|
-
path: "docs/algokit.md",
|
|
48
|
-
mode: "100644",
|
|
49
|
-
type: "blob",
|
|
50
|
-
sha: "file1sha",
|
|
51
|
-
size: 1234,
|
|
52
|
-
url: "https://api.github.com/repos/test/repo/git/blobs/file1sha"
|
|
53
|
-
},
|
|
54
|
-
{
|
|
55
|
-
path: "docs/features",
|
|
56
|
-
mode: "040000",
|
|
57
|
-
type: "tree",
|
|
58
|
-
sha: "dir1sha",
|
|
59
|
-
url: "https://api.github.com/repos/test/repo/git/trees/dir1sha"
|
|
60
|
-
},
|
|
61
|
-
{
|
|
62
|
-
path: "docs/features/accounts.md",
|
|
63
|
-
mode: "100644",
|
|
64
|
-
type: "blob",
|
|
65
|
-
sha: "file2sha",
|
|
66
|
-
size: 2345,
|
|
67
|
-
url: "https://api.github.com/repos/test/repo/git/blobs/file2sha"
|
|
68
|
-
},
|
|
69
|
-
{
|
|
70
|
-
path: "docs/features/tasks.md",
|
|
71
|
-
mode: "100644",
|
|
72
|
-
type: "blob",
|
|
73
|
-
sha: "file3sha",
|
|
74
|
-
size: 3456,
|
|
75
|
-
url: "https://api.github.com/repos/test/repo/git/blobs/file3sha"
|
|
76
|
-
},
|
|
77
|
-
{
|
|
78
|
-
path: "docs/features/generate.md",
|
|
79
|
-
mode: "100644",
|
|
80
|
-
type: "blob",
|
|
81
|
-
sha: "file4sha",
|
|
82
|
-
size: 4567,
|
|
83
|
-
url: "https://api.github.com/repos/test/repo/git/blobs/file4sha"
|
|
84
|
-
},
|
|
85
|
-
{
|
|
86
|
-
path: "docs/cli/index.md",
|
|
87
|
-
mode: "100644",
|
|
88
|
-
type: "blob",
|
|
89
|
-
sha: "file5sha",
|
|
90
|
-
size: 5678,
|
|
91
|
-
url: "https://api.github.com/repos/test/repo/git/blobs/file5sha"
|
|
92
|
-
},
|
|
93
|
-
{
|
|
94
|
-
path: "README.md",
|
|
95
|
-
mode: "100644",
|
|
96
|
-
type: "blob",
|
|
97
|
-
sha: "file6sha",
|
|
98
|
-
size: 678,
|
|
99
|
-
url: "https://api.github.com/repos/test/repo/git/blobs/file6sha"
|
|
100
|
-
},
|
|
101
|
-
{
|
|
102
|
-
path: "package.json",
|
|
103
|
-
mode: "100644",
|
|
104
|
-
type: "blob",
|
|
105
|
-
sha: "file7sha",
|
|
106
|
-
size: 789,
|
|
107
|
-
url: "https://api.github.com/repos/test/repo/git/blobs/file7sha"
|
|
108
|
-
}
|
|
109
|
-
],
|
|
110
|
-
truncated: false
|
|
111
|
-
};
|
|
2
|
+
import { toCollectionEntry, resolveAssetConfig } from "./github.content.js";
|
|
3
|
+
import type { ImportOptions, VersionConfig } from "./github.types.js";
|
|
4
|
+
import {
|
|
5
|
+
createMockContext,
|
|
6
|
+
createMockOctokit,
|
|
7
|
+
mockFetch,
|
|
8
|
+
} from "./test-helpers.js";
|
|
112
9
|
|
|
10
|
+
describe("Git Trees API Optimization", () => {
|
|
113
11
|
beforeEach(() => {
|
|
114
|
-
// Create Octokit instance
|
|
115
|
-
octokit = new Octokit({ auth: "mock-token" });
|
|
116
|
-
|
|
117
|
-
// Reset all mocks
|
|
118
12
|
vi.restoreAllMocks();
|
|
119
13
|
});
|
|
120
14
|
|
|
121
15
|
describe("API call efficiency", () => {
|
|
122
16
|
it("should use Git Trees API (2 calls) instead of recursive getContent (N calls)", async () => {
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
data: [mockCommit],
|
|
127
|
-
status: 200,
|
|
128
|
-
url: '',
|
|
129
|
-
headers: {}
|
|
130
|
-
} as any);
|
|
131
|
-
|
|
132
|
-
const getTreeMock = vi.spyOn(octokit.rest.git, 'getTree')
|
|
133
|
-
.mockResolvedValue({
|
|
134
|
-
data: mockTreeData,
|
|
135
|
-
status: 200,
|
|
136
|
-
url: '',
|
|
137
|
-
headers: {}
|
|
138
|
-
} as any);
|
|
139
|
-
|
|
140
|
-
const getContentMock = vi.spyOn(octokit.rest.repos, 'getContent')
|
|
141
|
-
.mockResolvedValue({
|
|
142
|
-
data: [],
|
|
143
|
-
status: 200,
|
|
144
|
-
url: '',
|
|
145
|
-
headers: {}
|
|
146
|
-
} as any);
|
|
147
|
-
|
|
148
|
-
// Mock fetch for file downloads
|
|
149
|
-
global.fetch = vi.fn().mockResolvedValue({
|
|
150
|
-
ok: true,
|
|
151
|
-
status: 200,
|
|
152
|
-
headers: new Headers(),
|
|
153
|
-
text: async () => "# Test Content\n\nThis is test markdown content."
|
|
154
|
-
} as any);
|
|
17
|
+
const { octokit, spies } = createMockOctokit();
|
|
18
|
+
mockFetch();
|
|
19
|
+
const ctx = createMockContext();
|
|
155
20
|
|
|
156
21
|
const testConfig: ImportOptions = {
|
|
157
22
|
name: "Test Repo",
|
|
@@ -166,108 +31,42 @@ describe("Git Trees API Optimization", () => {
|
|
|
166
31
|
],
|
|
167
32
|
};
|
|
168
33
|
|
|
169
|
-
// Create minimal mock context with Astro-specific components
|
|
170
|
-
const mockStore = new Map();
|
|
171
|
-
const mockContext = {
|
|
172
|
-
store: {
|
|
173
|
-
set: (entry: any) => mockStore.set(entry.id, entry),
|
|
174
|
-
get: (id: string) => mockStore.get(id),
|
|
175
|
-
clear: () => mockStore.clear(),
|
|
176
|
-
entries: () => mockStore.entries(),
|
|
177
|
-
keys: () => mockStore.keys(),
|
|
178
|
-
values: () => mockStore.values(),
|
|
179
|
-
},
|
|
180
|
-
meta: new Map(),
|
|
181
|
-
logger: {
|
|
182
|
-
info: vi.fn(),
|
|
183
|
-
warn: vi.fn(),
|
|
184
|
-
error: vi.fn(),
|
|
185
|
-
debug: vi.fn(),
|
|
186
|
-
verbose: vi.fn(),
|
|
187
|
-
logFileProcessing: vi.fn(),
|
|
188
|
-
logImportSummary: vi.fn(),
|
|
189
|
-
withSpinner: async (msg: string, fn: () => Promise<any>) => await fn(),
|
|
190
|
-
getLevel: () => 'default',
|
|
191
|
-
},
|
|
192
|
-
config: {},
|
|
193
|
-
entryTypes: new Map([
|
|
194
|
-
['.md', {
|
|
195
|
-
getEntryInfo: async ({ contents, fileUrl }: any) => ({
|
|
196
|
-
body: contents,
|
|
197
|
-
data: {}
|
|
198
|
-
})
|
|
199
|
-
}]
|
|
200
|
-
]),
|
|
201
|
-
generateDigest: (content: string) => {
|
|
202
|
-
// Simple hash function for testing
|
|
203
|
-
return content.length.toString();
|
|
204
|
-
},
|
|
205
|
-
parseData: async (data: any) => data,
|
|
206
|
-
};
|
|
207
|
-
|
|
208
34
|
await toCollectionEntry({
|
|
209
|
-
context:
|
|
35
|
+
context: ctx as any,
|
|
210
36
|
octokit,
|
|
211
37
|
options: testConfig,
|
|
212
38
|
});
|
|
213
39
|
|
|
214
|
-
|
|
215
|
-
expect(
|
|
216
|
-
expect(listCommitsMock).toHaveBeenCalledWith(
|
|
40
|
+
expect(spies.listCommitsSpy).toHaveBeenCalledTimes(1);
|
|
41
|
+
expect(spies.listCommitsSpy).toHaveBeenCalledWith(
|
|
217
42
|
expect.objectContaining({
|
|
218
43
|
owner: "algorandfoundation",
|
|
219
44
|
repo: "algokit-cli",
|
|
220
45
|
sha: "chore/content-fix",
|
|
221
46
|
per_page: 1,
|
|
222
|
-
})
|
|
47
|
+
}),
|
|
223
48
|
);
|
|
224
49
|
|
|
225
|
-
expect(
|
|
226
|
-
expect(
|
|
50
|
+
expect(spies.getTreeSpy).toHaveBeenCalledTimes(1);
|
|
51
|
+
expect(spies.getTreeSpy).toHaveBeenCalledWith(
|
|
227
52
|
expect.objectContaining({
|
|
228
53
|
owner: "algorandfoundation",
|
|
229
54
|
repo: "algokit-cli",
|
|
230
55
|
tree_sha: "tree123abc456",
|
|
231
56
|
recursive: "true",
|
|
232
|
-
})
|
|
57
|
+
}),
|
|
233
58
|
);
|
|
234
59
|
|
|
235
|
-
//
|
|
236
|
-
expect(
|
|
237
|
-
|
|
238
|
-
console.log('✅ API Efficiency Test Results:');
|
|
239
|
-
console.log(` - listCommits calls: ${listCommitsMock.mock.calls.length} (expected: 1)`);
|
|
240
|
-
console.log(` - getTree calls: ${getTreeMock.mock.calls.length} (expected: 1)`);
|
|
241
|
-
console.log(` - getContent calls: ${getContentMock.mock.calls.length} (expected: 0)`);
|
|
242
|
-
console.log(` - Total API calls for discovery: ${listCommitsMock.mock.calls.length + getTreeMock.mock.calls.length}`);
|
|
243
|
-
console.log(` - 🎉 Optimization achieved: 2 calls instead of potentially 10+ recursive calls`);
|
|
60
|
+
// getContent should NOT be called (old recursive approach)
|
|
61
|
+
expect(spies.getContentSpy).not.toHaveBeenCalled();
|
|
244
62
|
});
|
|
245
63
|
});
|
|
246
64
|
|
|
247
65
|
describe("file filtering", () => {
|
|
248
66
|
it("should correctly filter files matching the glob pattern", async () => {
|
|
249
|
-
const
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
status: 200,
|
|
253
|
-
url: '',
|
|
254
|
-
headers: {}
|
|
255
|
-
} as any);
|
|
256
|
-
|
|
257
|
-
const getTreeMock = vi.spyOn(octokit.rest.git, 'getTree')
|
|
258
|
-
.mockResolvedValue({
|
|
259
|
-
data: mockTreeData,
|
|
260
|
-
status: 200,
|
|
261
|
-
url: '',
|
|
262
|
-
headers: {}
|
|
263
|
-
} as any);
|
|
264
|
-
|
|
265
|
-
global.fetch = vi.fn().mockResolvedValue({
|
|
266
|
-
ok: true,
|
|
267
|
-
status: 200,
|
|
268
|
-
headers: new Headers(),
|
|
269
|
-
text: async () => "# Test Content\n\nMockfile content."
|
|
270
|
-
} as any);
|
|
67
|
+
const { octokit } = createMockOctokit();
|
|
68
|
+
mockFetch("# Test Content\n\nMockfile content.");
|
|
69
|
+
const ctx = createMockContext();
|
|
271
70
|
|
|
272
71
|
const testConfig: ImportOptions = {
|
|
273
72
|
name: "Test filtering",
|
|
@@ -282,239 +81,96 @@ describe("Git Trees API Optimization", () => {
|
|
|
282
81
|
],
|
|
283
82
|
};
|
|
284
83
|
|
|
285
|
-
const mockStore = new Map();
|
|
286
|
-
const mockContext = {
|
|
287
|
-
store: {
|
|
288
|
-
set: (entry: any) => {
|
|
289
|
-
mockStore.set(entry.id, entry);
|
|
290
|
-
return entry;
|
|
291
|
-
},
|
|
292
|
-
get: (id: string) => mockStore.get(id),
|
|
293
|
-
clear: () => mockStore.clear(),
|
|
294
|
-
entries: () => mockStore.entries(),
|
|
295
|
-
keys: () => mockStore.keys(),
|
|
296
|
-
values: () => mockStore.values(),
|
|
297
|
-
},
|
|
298
|
-
meta: new Map(),
|
|
299
|
-
logger: {
|
|
300
|
-
info: vi.fn(),
|
|
301
|
-
warn: vi.fn(),
|
|
302
|
-
error: vi.fn(),
|
|
303
|
-
debug: vi.fn(),
|
|
304
|
-
verbose: vi.fn(),
|
|
305
|
-
logFileProcessing: vi.fn(),
|
|
306
|
-
logImportSummary: vi.fn(),
|
|
307
|
-
withSpinner: async (msg: string, fn: () => Promise<any>) => await fn(),
|
|
308
|
-
getLevel: () => 'default',
|
|
309
|
-
},
|
|
310
|
-
config: {},
|
|
311
|
-
entryTypes: new Map([['.md', { getEntryInfo: async ({ contents }: any) => ({ body: contents, data: {} }) }]]),
|
|
312
|
-
generateDigest: (content: string) => content.length.toString(),
|
|
313
|
-
parseData: async (data: any) => data,
|
|
314
|
-
};
|
|
315
|
-
|
|
316
84
|
const stats = await toCollectionEntry({
|
|
317
|
-
context:
|
|
85
|
+
context: ctx as any,
|
|
318
86
|
octokit,
|
|
319
87
|
options: testConfig,
|
|
320
88
|
});
|
|
321
89
|
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
// - docs/features/tasks.md (matches features/**/*.md)
|
|
332
|
-
// - docs/features/generate.md (matches features/**/*.md)
|
|
333
|
-
// Should NOT match:
|
|
334
|
-
// - docs/cli/index.md (not in pattern)
|
|
335
|
-
// - README.md (not in pattern)
|
|
336
|
-
// - package.json (not in pattern)
|
|
337
|
-
|
|
338
|
-
expect(stats.processed).toBe(4); // algokit.md + 3 features/*.md files
|
|
339
|
-
expect(mockStore.size).toBe(4);
|
|
340
|
-
|
|
341
|
-
// Verify correct files were stored
|
|
342
|
-
const storedIds = Array.from(mockStore.keys());
|
|
343
|
-
expect(storedIds).toContain('docs/algokit');
|
|
344
|
-
expect(storedIds.some(id => id.includes('features'))).toBe(true);
|
|
345
|
-
expect(storedIds).not.toContain('package');
|
|
346
|
-
expect(storedIds).not.toContain('README');
|
|
90
|
+
// Should match: docs/algokit.md + 3 features/*.md files
|
|
91
|
+
expect(stats.processed).toBe(4);
|
|
92
|
+
expect(ctx._store.size).toBe(4);
|
|
93
|
+
|
|
94
|
+
const storedIds = Array.from(ctx._store.keys());
|
|
95
|
+
expect(storedIds).toContain("docs/algokit");
|
|
96
|
+
expect(storedIds.some((id) => id.includes("features"))).toBe(true);
|
|
97
|
+
expect(storedIds).not.toContain("package");
|
|
98
|
+
expect(storedIds).not.toContain("README");
|
|
347
99
|
});
|
|
348
100
|
|
|
349
101
|
it("should filter to match only specific file when pattern is exact", async () => {
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
vi.spyOn(octokit.rest.git, 'getTree')
|
|
354
|
-
.mockResolvedValue({ data: mockTreeData, status: 200, url: '', headers: {} } as any);
|
|
355
|
-
|
|
356
|
-
global.fetch = vi.fn().mockResolvedValue({
|
|
357
|
-
ok: true,
|
|
358
|
-
status: 200,
|
|
359
|
-
headers: new Headers(),
|
|
360
|
-
text: async () => "# Single File Content"
|
|
361
|
-
} as any);
|
|
102
|
+
const { octokit } = createMockOctokit();
|
|
103
|
+
mockFetch("# Single File Content");
|
|
104
|
+
const ctx = createMockContext();
|
|
362
105
|
|
|
363
106
|
const testConfig: ImportOptions = {
|
|
364
107
|
name: "Exact match test",
|
|
365
108
|
owner: "test",
|
|
366
109
|
repo: "repo",
|
|
367
110
|
ref: "main",
|
|
368
|
-
includes: [
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
const mockStore = new Map();
|
|
375
|
-
const mockContext = {
|
|
376
|
-
store: {
|
|
377
|
-
set: (entry: any) => mockStore.set(entry.id, entry),
|
|
378
|
-
get: (id: string) => mockStore.get(id),
|
|
379
|
-
clear: () => mockStore.clear(),
|
|
380
|
-
entries: () => mockStore.entries(),
|
|
381
|
-
keys: () => mockStore.keys(),
|
|
382
|
-
values: () => mockStore.values(),
|
|
383
|
-
},
|
|
384
|
-
meta: new Map(),
|
|
385
|
-
logger: {
|
|
386
|
-
info: vi.fn(),
|
|
387
|
-
warn: vi.fn(),
|
|
388
|
-
error: vi.fn(),
|
|
389
|
-
debug: vi.fn(),
|
|
390
|
-
verbose: vi.fn(),
|
|
391
|
-
logFileProcessing: vi.fn(),
|
|
392
|
-
logImportSummary: vi.fn(),
|
|
393
|
-
withSpinner: async (msg: string, fn: () => Promise<any>) => await fn(),
|
|
394
|
-
getLevel: () => 'default',
|
|
395
|
-
},
|
|
396
|
-
config: {},
|
|
397
|
-
entryTypes: new Map([['.md', { getEntryInfo: async ({ contents }: any) => ({ body: contents, data: {} }) }]]),
|
|
398
|
-
generateDigest: (content: string) => content.length.toString(),
|
|
399
|
-
parseData: async (data: any) => data,
|
|
111
|
+
includes: [
|
|
112
|
+
{
|
|
113
|
+
pattern: "docs/algokit.md",
|
|
114
|
+
basePath: "test-output",
|
|
115
|
+
},
|
|
116
|
+
],
|
|
400
117
|
};
|
|
401
118
|
|
|
402
119
|
const stats = await toCollectionEntry({
|
|
403
|
-
context:
|
|
120
|
+
context: ctx as any,
|
|
404
121
|
octokit,
|
|
405
122
|
options: testConfig,
|
|
406
123
|
});
|
|
407
124
|
|
|
408
|
-
console.log('\n🎯 Exact Pattern Match Test:');
|
|
409
|
-
console.log(` - Pattern: docs/algokit.md`);
|
|
410
|
-
console.log(` - Files processed: ${stats.processed}`);
|
|
411
|
-
console.log(` - Expected: 1 file`);
|
|
412
|
-
|
|
413
125
|
expect(stats.processed).toBe(1);
|
|
414
|
-
expect(
|
|
415
|
-
expect(Array.from(
|
|
126
|
+
expect(ctx._store.size).toBe(1);
|
|
127
|
+
expect(Array.from(ctx._store.keys())[0]).toContain("algokit");
|
|
416
128
|
});
|
|
417
129
|
});
|
|
418
130
|
|
|
419
131
|
describe("download URL construction", () => {
|
|
420
132
|
it("should construct valid raw.githubusercontent.com URLs from tree data", async () => {
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
vi.spyOn(octokit.rest.git, 'getTree')
|
|
425
|
-
.mockResolvedValue({ data: mockTreeData, status: 200, url: '', headers: {} } as any);
|
|
426
|
-
|
|
427
|
-
const fetchMock = vi.fn().mockResolvedValue({
|
|
428
|
-
ok: true,
|
|
429
|
-
status: 200,
|
|
430
|
-
headers: new Headers(),
|
|
431
|
-
text: async () => "# Content"
|
|
432
|
-
} as any);
|
|
433
|
-
global.fetch = fetchMock;
|
|
133
|
+
const { octokit } = createMockOctokit();
|
|
134
|
+
const fetchMock = mockFetch("# Content");
|
|
135
|
+
const ctx = createMockContext();
|
|
434
136
|
|
|
435
137
|
const testConfig: ImportOptions = {
|
|
436
138
|
name: "URL test",
|
|
437
139
|
owner: "algorandfoundation",
|
|
438
140
|
repo: "algokit-cli",
|
|
439
141
|
ref: "chore/content-fix",
|
|
440
|
-
includes: [
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
const mockStore = new Map();
|
|
447
|
-
const mockContext = {
|
|
448
|
-
store: {
|
|
449
|
-
set: (entry: any) => mockStore.set(entry.id, entry),
|
|
450
|
-
get: (id: string) => mockStore.get(id),
|
|
451
|
-
clear: () => mockStore.clear(),
|
|
452
|
-
entries: () => mockStore.entries(),
|
|
453
|
-
keys: () => mockStore.keys(),
|
|
454
|
-
values: () => mockStore.values(),
|
|
455
|
-
},
|
|
456
|
-
meta: new Map(),
|
|
457
|
-
logger: {
|
|
458
|
-
info: vi.fn(),
|
|
459
|
-
warn: vi.fn(),
|
|
460
|
-
error: vi.fn(),
|
|
461
|
-
debug: vi.fn(),
|
|
462
|
-
verbose: vi.fn(),
|
|
463
|
-
logFileProcessing: vi.fn(),
|
|
464
|
-
logImportSummary: vi.fn(),
|
|
465
|
-
withSpinner: async (msg: string, fn: () => Promise<any>) => await fn(),
|
|
466
|
-
getLevel: () => 'default',
|
|
467
|
-
},
|
|
468
|
-
config: {},
|
|
469
|
-
entryTypes: new Map([['.md', { getEntryInfo: async ({ contents }: any) => ({ body: contents, data: {} }) }]]),
|
|
470
|
-
generateDigest: (content: string) => content.length.toString(),
|
|
471
|
-
parseData: async (data: any) => data,
|
|
142
|
+
includes: [
|
|
143
|
+
{
|
|
144
|
+
pattern: "docs/algokit.md",
|
|
145
|
+
basePath: "test-output",
|
|
146
|
+
},
|
|
147
|
+
],
|
|
472
148
|
};
|
|
473
149
|
|
|
474
150
|
await toCollectionEntry({
|
|
475
|
-
context:
|
|
151
|
+
context: ctx as any,
|
|
476
152
|
octokit,
|
|
477
153
|
options: testConfig,
|
|
478
154
|
});
|
|
479
155
|
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
return url.includes('raw.githubusercontent.com');
|
|
156
|
+
const rawGithubCalls = fetchMock.mock.calls.filter((call) => {
|
|
157
|
+
const url = call[0]?.toString() || "";
|
|
158
|
+
return url.includes("raw.githubusercontent.com");
|
|
484
159
|
});
|
|
485
160
|
|
|
486
|
-
console.log('\n🔗 URL Construction Test:');
|
|
487
|
-
console.log(` - Total fetch calls: ${fetchMock.mock.calls.length}`);
|
|
488
|
-
console.log(` - Calls to raw.githubusercontent.com: ${rawGithubCalls.length}`);
|
|
489
|
-
|
|
490
161
|
expect(rawGithubCalls.length).toBeGreaterThan(0);
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
console.log(` - Example URL: ${exampleUrl}`);
|
|
494
|
-
|
|
495
|
-
// Verify URL format: https://raw.githubusercontent.com/{owner}/{repo}/{commit_sha}/{file_path}
|
|
496
|
-
expect(exampleUrl).toMatch(
|
|
497
|
-
/^https:\/\/raw\.githubusercontent\.com\/algorandfoundation\/algokit-cli\/abc123def456\/docs\/algokit\.md$/
|
|
162
|
+
expect(rawGithubCalls[0][0]?.toString()).toMatch(
|
|
163
|
+
/^https:\/\/raw\.githubusercontent\.com\/algorandfoundation\/algokit-cli\/abc123def456\/docs\/algokit\.md$/,
|
|
498
164
|
);
|
|
499
165
|
});
|
|
500
166
|
});
|
|
501
167
|
|
|
502
168
|
describe("real-world config simulation", () => {
|
|
503
169
|
it("should handle the production algokit-cli config pattern correctly", async () => {
|
|
504
|
-
|
|
505
|
-
|
|
170
|
+
const { octokit } = createMockOctokit();
|
|
171
|
+
mockFetch("# Content");
|
|
172
|
+
const ctx = createMockContext();
|
|
506
173
|
|
|
507
|
-
vi.spyOn(octokit.rest.git, 'getTree')
|
|
508
|
-
.mockResolvedValue({ data: mockTreeData, status: 200, url: '', headers: {} } as any);
|
|
509
|
-
|
|
510
|
-
global.fetch = vi.fn().mockResolvedValue({
|
|
511
|
-
ok: true,
|
|
512
|
-
status: 200,
|
|
513
|
-
headers: new Headers(),
|
|
514
|
-
text: async () => "# Content"
|
|
515
|
-
} as any);
|
|
516
|
-
|
|
517
|
-
// This is the actual production config from content.config.ts
|
|
518
174
|
const productionConfig: ImportOptions = {
|
|
519
175
|
name: "AlgoKit CLI Docs",
|
|
520
176
|
owner: "algorandfoundation",
|
|
@@ -539,61 +195,256 @@ describe("Git Trees API Optimization", () => {
|
|
|
539
195
|
],
|
|
540
196
|
};
|
|
541
197
|
|
|
542
|
-
const mockStore = new Map();
|
|
543
|
-
const mockContext = {
|
|
544
|
-
store: {
|
|
545
|
-
set: (entry: any) => mockStore.set(entry.id, entry),
|
|
546
|
-
get: (id: string) => mockStore.get(id),
|
|
547
|
-
clear: () => mockStore.clear(),
|
|
548
|
-
entries: () => mockStore.entries(),
|
|
549
|
-
keys: () => mockStore.keys(),
|
|
550
|
-
values: () => mockStore.values(),
|
|
551
|
-
},
|
|
552
|
-
meta: new Map(),
|
|
553
|
-
logger: {
|
|
554
|
-
info: vi.fn(),
|
|
555
|
-
warn: vi.fn(),
|
|
556
|
-
error: vi.fn(),
|
|
557
|
-
debug: vi.fn(),
|
|
558
|
-
verbose: vi.fn(),
|
|
559
|
-
logFileProcessing: vi.fn(),
|
|
560
|
-
logImportSummary: vi.fn(),
|
|
561
|
-
withSpinner: async (msg: string, fn: () => Promise<any>) => await fn(),
|
|
562
|
-
getLevel: () => 'default',
|
|
563
|
-
},
|
|
564
|
-
config: {},
|
|
565
|
-
entryTypes: new Map([['.md', { getEntryInfo: async ({ contents }: any) => ({ body: contents, data: {} }) }]]),
|
|
566
|
-
generateDigest: (content: string) => content.length.toString(),
|
|
567
|
-
parseData: async (data: any) => data,
|
|
568
|
-
};
|
|
569
|
-
|
|
570
198
|
const stats = await toCollectionEntry({
|
|
571
|
-
context:
|
|
199
|
+
context: ctx as any,
|
|
572
200
|
octokit,
|
|
573
201
|
options: productionConfig,
|
|
574
202
|
});
|
|
575
203
|
|
|
576
|
-
|
|
577
|
-
console.log(` - Pattern 1: docs/{features/**/*.md,algokit.md}`);
|
|
578
|
-
console.log(` - Pattern 2: docs/cli/index.md`);
|
|
579
|
-
console.log(` - Files processed: ${stats.processed}`);
|
|
580
|
-
console.log(` - Expected files:`);
|
|
581
|
-
console.log(` • docs/algokit.md → overview.md (from pattern 1)`);
|
|
582
|
-
console.log(` • docs/features/accounts.md → accounts.md (from pattern 1)`);
|
|
583
|
-
console.log(` • docs/features/tasks.md → tasks.md (from pattern 1)`);
|
|
584
|
-
console.log(` • docs/features/generate.md → generate.md (from pattern 1)`);
|
|
585
|
-
console.log(` • docs/cli/index.md → index.md (from pattern 2)`);
|
|
586
|
-
|
|
587
|
-
// Should match 4 files from pattern 1 + 1 file from pattern 2
|
|
204
|
+
// 4 files from pattern 1 + 1 file from pattern 2
|
|
588
205
|
expect(stats.processed).toBe(5);
|
|
589
206
|
|
|
590
|
-
const storedIds = Array.from(
|
|
591
|
-
|
|
207
|
+
const storedIds = Array.from(ctx._store.keys());
|
|
208
|
+
expect(storedIds.some((id) => id.includes("overview"))).toBe(true);
|
|
209
|
+
expect(storedIds.filter((id) => id.includes("features")).length).toBe(3);
|
|
210
|
+
expect(
|
|
211
|
+
storedIds.some((id) => id.includes("cli") && id.includes("index")),
|
|
212
|
+
).toBe(true);
|
|
213
|
+
});
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
describe("ImportOptions new fields (language, versions)", () => {
|
|
217
|
+
it("should accept language and versions fields without errors", async () => {
|
|
218
|
+
const { octokit } = createMockOctokit();
|
|
219
|
+
mockFetch("# Content with versioned config");
|
|
220
|
+
const ctx = createMockContext();
|
|
221
|
+
|
|
222
|
+
const testConfig: ImportOptions = {
|
|
223
|
+
name: "AlgoKit Utils TS",
|
|
224
|
+
owner: "algorandfoundation",
|
|
225
|
+
repo: "algokit-utils-ts",
|
|
226
|
+
ref: "docs-dist",
|
|
227
|
+
language: "TypeScript",
|
|
228
|
+
versions: [
|
|
229
|
+
{ slug: "latest", label: "Latest" },
|
|
230
|
+
{ slug: "v8.0.0", label: "v8.0.0" },
|
|
231
|
+
],
|
|
232
|
+
includes: [
|
|
233
|
+
{
|
|
234
|
+
pattern: "docs/algokit.md",
|
|
235
|
+
basePath: "test-output",
|
|
236
|
+
},
|
|
237
|
+
],
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
const stats = await toCollectionEntry({
|
|
241
|
+
context: ctx as any,
|
|
242
|
+
octokit,
|
|
243
|
+
options: testConfig,
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
expect(stats.processed).toBe(1);
|
|
247
|
+
expect(ctx._store.size).toBe(1);
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
it("should make language and versions accessible in transform context", async () => {
|
|
251
|
+
const { octokit } = createMockOctokit();
|
|
252
|
+
mockFetch("# Content to transform");
|
|
253
|
+
const ctx = createMockContext();
|
|
254
|
+
|
|
255
|
+
let capturedOptions: ImportOptions | undefined;
|
|
256
|
+
|
|
257
|
+
const testConfig: ImportOptions = {
|
|
258
|
+
name: "Transform context test",
|
|
259
|
+
owner: "test",
|
|
260
|
+
repo: "repo",
|
|
261
|
+
ref: "main",
|
|
262
|
+
language: "Python",
|
|
263
|
+
versions: [{ slug: "latest", label: "Latest" }],
|
|
264
|
+
includes: [
|
|
265
|
+
{
|
|
266
|
+
pattern: "docs/algokit.md",
|
|
267
|
+
basePath: "test-output",
|
|
268
|
+
},
|
|
269
|
+
],
|
|
270
|
+
transforms: [
|
|
271
|
+
(content, context) => {
|
|
272
|
+
capturedOptions = context.options;
|
|
273
|
+
return content;
|
|
274
|
+
},
|
|
275
|
+
],
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
await toCollectionEntry({
|
|
279
|
+
context: ctx as any,
|
|
280
|
+
octokit,
|
|
281
|
+
options: testConfig,
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
expect(capturedOptions).toBeDefined();
|
|
285
|
+
expect(capturedOptions!.language).toBe("Python");
|
|
286
|
+
expect(capturedOptions!.versions).toEqual([
|
|
287
|
+
{ slug: "latest", label: "Latest" },
|
|
288
|
+
]);
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
it("should work without language and versions (backward compatible)", async () => {
|
|
292
|
+
const { octokit } = createMockOctokit();
|
|
293
|
+
mockFetch("# Content without new fields");
|
|
294
|
+
const ctx = createMockContext();
|
|
295
|
+
|
|
296
|
+
const testConfig: ImportOptions = {
|
|
297
|
+
name: "No new fields",
|
|
298
|
+
owner: "test",
|
|
299
|
+
repo: "repo",
|
|
300
|
+
ref: "main",
|
|
301
|
+
includes: [
|
|
302
|
+
{
|
|
303
|
+
pattern: "docs/algokit.md",
|
|
304
|
+
basePath: "test-output",
|
|
305
|
+
},
|
|
306
|
+
],
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
const stats = await toCollectionEntry({
|
|
310
|
+
context: ctx as any,
|
|
311
|
+
octokit,
|
|
312
|
+
options: testConfig,
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
expect(stats.processed).toBe(1);
|
|
316
|
+
});
|
|
317
|
+
});
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
describe("resolveAssetConfig", () => {
|
|
321
|
+
it("should return explicit assetsPath and assetsBaseUrl when both are provided", () => {
|
|
322
|
+
const options: ImportOptions = {
|
|
323
|
+
owner: "test",
|
|
324
|
+
repo: "repo",
|
|
325
|
+
assetsPath: "src/assets/custom",
|
|
326
|
+
assetsBaseUrl: "/assets/custom",
|
|
327
|
+
includes: [
|
|
328
|
+
{
|
|
329
|
+
pattern: "docs/**/*.md",
|
|
330
|
+
basePath: "src/content/docs/lib",
|
|
331
|
+
},
|
|
332
|
+
],
|
|
333
|
+
};
|
|
334
|
+
|
|
335
|
+
const result = resolveAssetConfig(options, "docs/guide.md");
|
|
336
|
+
|
|
337
|
+
expect(result).toEqual({
|
|
338
|
+
assetsPath: "src/assets/custom",
|
|
339
|
+
assetsBaseUrl: "/assets/custom",
|
|
340
|
+
});
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
it("should derive co-located defaults from basePath when assetsPath/assetsBaseUrl are omitted", () => {
|
|
344
|
+
const options: ImportOptions = {
|
|
345
|
+
owner: "test",
|
|
346
|
+
repo: "repo",
|
|
347
|
+
includes: [
|
|
348
|
+
{
|
|
349
|
+
pattern: "docs/**/*.md",
|
|
350
|
+
basePath: "src/content/docs/algokit-utils/typescript/v8.0.0",
|
|
351
|
+
},
|
|
352
|
+
],
|
|
353
|
+
};
|
|
354
|
+
|
|
355
|
+
const result = resolveAssetConfig(options, "docs/guide.md");
|
|
356
|
+
|
|
357
|
+
expect(result).toEqual({
|
|
358
|
+
assetsPath: "src/content/docs/algokit-utils/typescript/v8.0.0/assets",
|
|
359
|
+
assetsBaseUrl: "./assets",
|
|
360
|
+
});
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
it("should return null when only assetsPath is set (misconfiguration)", () => {
|
|
364
|
+
const options: ImportOptions = {
|
|
365
|
+
owner: "test",
|
|
366
|
+
repo: "repo",
|
|
367
|
+
assetsPath: "src/assets/custom",
|
|
368
|
+
// assetsBaseUrl intentionally omitted
|
|
369
|
+
includes: [
|
|
370
|
+
{
|
|
371
|
+
pattern: "docs/**/*.md",
|
|
372
|
+
basePath: "src/content/docs/lib",
|
|
373
|
+
},
|
|
374
|
+
],
|
|
375
|
+
};
|
|
376
|
+
|
|
377
|
+
const result = resolveAssetConfig(options, "docs/guide.md");
|
|
378
|
+
expect(result).toBeNull();
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
it("should return null when only assetsBaseUrl is set (misconfiguration)", () => {
|
|
382
|
+
const options: ImportOptions = {
|
|
383
|
+
owner: "test",
|
|
384
|
+
repo: "repo",
|
|
385
|
+
// assetsPath intentionally omitted
|
|
386
|
+
assetsBaseUrl: "/assets/custom",
|
|
387
|
+
includes: [
|
|
388
|
+
{
|
|
389
|
+
pattern: "docs/**/*.md",
|
|
390
|
+
basePath: "src/content/docs/lib",
|
|
391
|
+
},
|
|
392
|
+
],
|
|
393
|
+
};
|
|
394
|
+
|
|
395
|
+
const result = resolveAssetConfig(options, "docs/guide.md");
|
|
396
|
+
expect(result).toBeNull();
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
it("should return null when file does not match any include pattern", () => {
|
|
400
|
+
const options: ImportOptions = {
|
|
401
|
+
owner: "test",
|
|
402
|
+
repo: "repo",
|
|
403
|
+
includes: [
|
|
404
|
+
{
|
|
405
|
+
pattern: "docs/**/*.md",
|
|
406
|
+
basePath: "src/content/docs/lib",
|
|
407
|
+
},
|
|
408
|
+
],
|
|
409
|
+
};
|
|
410
|
+
|
|
411
|
+
const result = resolveAssetConfig(options, "src/main.ts");
|
|
412
|
+
expect(result).toBeNull();
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
it("should return null when no includes are defined and no explicit config", () => {
|
|
416
|
+
const options: ImportOptions = {
|
|
417
|
+
owner: "test",
|
|
418
|
+
repo: "repo",
|
|
419
|
+
};
|
|
420
|
+
|
|
421
|
+
const result = resolveAssetConfig(options, "docs/guide.md");
|
|
422
|
+
// No includes means shouldIncludeFile returns matchedPattern: null
|
|
423
|
+
expect(result).toBeNull();
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
it("should use the correct basePath when multiple patterns exist", () => {
|
|
427
|
+
const options: ImportOptions = {
|
|
428
|
+
owner: "test",
|
|
429
|
+
repo: "repo",
|
|
430
|
+
includes: [
|
|
431
|
+
{
|
|
432
|
+
pattern: "docs/guides/**/*.md",
|
|
433
|
+
basePath: "src/content/docs/guides",
|
|
434
|
+
},
|
|
435
|
+
{
|
|
436
|
+
pattern: "docs/api/**/*.md",
|
|
437
|
+
basePath: "src/content/docs/reference/api",
|
|
438
|
+
},
|
|
439
|
+
],
|
|
440
|
+
};
|
|
441
|
+
|
|
442
|
+
// File matches the second pattern
|
|
443
|
+
const result = resolveAssetConfig(options, "docs/api/endpoints.md");
|
|
592
444
|
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
expect(storedIds.some(id => id.includes('cli') && id.includes('index'))).toBe(true); // cli/index.md
|
|
445
|
+
expect(result).toEqual({
|
|
446
|
+
assetsPath: "src/content/docs/reference/api/assets",
|
|
447
|
+
assetsBaseUrl: "./assets",
|
|
597
448
|
});
|
|
598
449
|
});
|
|
599
450
|
});
|