@larkiny/astro-github-loader 0.10.1 ā 0.11.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 +134 -13
- package/dist/github.auth.d.ts +83 -0
- package/dist/github.auth.js +119 -0
- package/dist/github.content.js +63 -70
- package/dist/github.content.spec.d.ts +1 -0
- package/dist/github.content.spec.js +537 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/package.json +4 -1
- package/src/github.auth.ts +151 -0
- package/src/github.content.spec.ts +599 -0
- package/src/github.content.ts +73 -77
- package/src/index.ts +1 -0
|
@@ -0,0 +1,537 @@
|
|
|
1
|
+
import { beforeEach, describe, it, expect, vi } from "vitest";
|
|
2
|
+
import { toCollectionEntry } from "./github.content.js";
|
|
3
|
+
import { Octokit } from "octokit";
|
|
4
|
+
/**
|
|
5
|
+
* Test suite for Git Trees API optimization
|
|
6
|
+
*
|
|
7
|
+
* These tests verify that the new Git Trees API approach:
|
|
8
|
+
* 1. Correctly discovers files matching include patterns
|
|
9
|
+
* 2. Reduces API calls compared to recursive approach
|
|
10
|
+
* 3. Works with the expected repository configuration
|
|
11
|
+
*
|
|
12
|
+
* These are unit tests with mocked API responses to test the optimization
|
|
13
|
+
* logic without requiring network access.
|
|
14
|
+
*/
|
|
15
|
+
describe("Git Trees API Optimization", () => {
|
|
16
|
+
let octokit;
|
|
17
|
+
// Mock commit data
|
|
18
|
+
const mockCommit = {
|
|
19
|
+
sha: "abc123def456",
|
|
20
|
+
commit: {
|
|
21
|
+
tree: {
|
|
22
|
+
sha: "tree123abc456"
|
|
23
|
+
},
|
|
24
|
+
message: "Test commit",
|
|
25
|
+
author: {
|
|
26
|
+
name: "Test Author",
|
|
27
|
+
email: "test@example.com",
|
|
28
|
+
date: "2024-01-01T00:00:00Z"
|
|
29
|
+
},
|
|
30
|
+
committer: {
|
|
31
|
+
name: "Test Committer",
|
|
32
|
+
email: "test@example.com",
|
|
33
|
+
date: "2024-01-01T00:00:00Z"
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
// Mock tree data representing a repository structure similar to algokit-cli
|
|
38
|
+
const mockTreeData = {
|
|
39
|
+
sha: "tree123abc456",
|
|
40
|
+
url: "https://api.github.com/repos/test/repo/git/trees/tree123abc456",
|
|
41
|
+
tree: [
|
|
42
|
+
{
|
|
43
|
+
path: "docs/algokit.md",
|
|
44
|
+
mode: "100644",
|
|
45
|
+
type: "blob",
|
|
46
|
+
sha: "file1sha",
|
|
47
|
+
size: 1234,
|
|
48
|
+
url: "https://api.github.com/repos/test/repo/git/blobs/file1sha"
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
path: "docs/features",
|
|
52
|
+
mode: "040000",
|
|
53
|
+
type: "tree",
|
|
54
|
+
sha: "dir1sha",
|
|
55
|
+
url: "https://api.github.com/repos/test/repo/git/trees/dir1sha"
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
path: "docs/features/accounts.md",
|
|
59
|
+
mode: "100644",
|
|
60
|
+
type: "blob",
|
|
61
|
+
sha: "file2sha",
|
|
62
|
+
size: 2345,
|
|
63
|
+
url: "https://api.github.com/repos/test/repo/git/blobs/file2sha"
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
path: "docs/features/tasks.md",
|
|
67
|
+
mode: "100644",
|
|
68
|
+
type: "blob",
|
|
69
|
+
sha: "file3sha",
|
|
70
|
+
size: 3456,
|
|
71
|
+
url: "https://api.github.com/repos/test/repo/git/blobs/file3sha"
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
path: "docs/features/generate.md",
|
|
75
|
+
mode: "100644",
|
|
76
|
+
type: "blob",
|
|
77
|
+
sha: "file4sha",
|
|
78
|
+
size: 4567,
|
|
79
|
+
url: "https://api.github.com/repos/test/repo/git/blobs/file4sha"
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
path: "docs/cli/index.md",
|
|
83
|
+
mode: "100644",
|
|
84
|
+
type: "blob",
|
|
85
|
+
sha: "file5sha",
|
|
86
|
+
size: 5678,
|
|
87
|
+
url: "https://api.github.com/repos/test/repo/git/blobs/file5sha"
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
path: "README.md",
|
|
91
|
+
mode: "100644",
|
|
92
|
+
type: "blob",
|
|
93
|
+
sha: "file6sha",
|
|
94
|
+
size: 678,
|
|
95
|
+
url: "https://api.github.com/repos/test/repo/git/blobs/file6sha"
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
path: "package.json",
|
|
99
|
+
mode: "100644",
|
|
100
|
+
type: "blob",
|
|
101
|
+
sha: "file7sha",
|
|
102
|
+
size: 789,
|
|
103
|
+
url: "https://api.github.com/repos/test/repo/git/blobs/file7sha"
|
|
104
|
+
}
|
|
105
|
+
],
|
|
106
|
+
truncated: false
|
|
107
|
+
};
|
|
108
|
+
beforeEach(() => {
|
|
109
|
+
// Create Octokit instance
|
|
110
|
+
octokit = new Octokit({ auth: "mock-token" });
|
|
111
|
+
// Reset all mocks
|
|
112
|
+
vi.restoreAllMocks();
|
|
113
|
+
});
|
|
114
|
+
describe("API call efficiency", () => {
|
|
115
|
+
it("should use Git Trees API (2 calls) instead of recursive getContent (N calls)", async () => {
|
|
116
|
+
// Mock the API calls
|
|
117
|
+
const listCommitsMock = vi.spyOn(octokit.rest.repos, 'listCommits')
|
|
118
|
+
.mockResolvedValue({
|
|
119
|
+
data: [mockCommit],
|
|
120
|
+
status: 200,
|
|
121
|
+
url: '',
|
|
122
|
+
headers: {}
|
|
123
|
+
});
|
|
124
|
+
const getTreeMock = vi.spyOn(octokit.rest.git, 'getTree')
|
|
125
|
+
.mockResolvedValue({
|
|
126
|
+
data: mockTreeData,
|
|
127
|
+
status: 200,
|
|
128
|
+
url: '',
|
|
129
|
+
headers: {}
|
|
130
|
+
});
|
|
131
|
+
const getContentMock = vi.spyOn(octokit.rest.repos, 'getContent')
|
|
132
|
+
.mockResolvedValue({
|
|
133
|
+
data: [],
|
|
134
|
+
status: 200,
|
|
135
|
+
url: '',
|
|
136
|
+
headers: {}
|
|
137
|
+
});
|
|
138
|
+
// Mock fetch for file downloads
|
|
139
|
+
global.fetch = vi.fn().mockResolvedValue({
|
|
140
|
+
ok: true,
|
|
141
|
+
status: 200,
|
|
142
|
+
headers: new Headers(),
|
|
143
|
+
text: async () => "# Test Content\n\nThis is test markdown content."
|
|
144
|
+
});
|
|
145
|
+
const testConfig = {
|
|
146
|
+
name: "Test Repo",
|
|
147
|
+
owner: "algorandfoundation",
|
|
148
|
+
repo: "algokit-cli",
|
|
149
|
+
ref: "chore/content-fix",
|
|
150
|
+
includes: [
|
|
151
|
+
{
|
|
152
|
+
pattern: "docs/{features/**/*.md,algokit.md}",
|
|
153
|
+
basePath: "test-output",
|
|
154
|
+
},
|
|
155
|
+
],
|
|
156
|
+
};
|
|
157
|
+
// Create minimal mock context with Astro-specific components
|
|
158
|
+
const mockStore = new Map();
|
|
159
|
+
const mockContext = {
|
|
160
|
+
store: {
|
|
161
|
+
set: (entry) => mockStore.set(entry.id, entry),
|
|
162
|
+
get: (id) => mockStore.get(id),
|
|
163
|
+
clear: () => mockStore.clear(),
|
|
164
|
+
entries: () => mockStore.entries(),
|
|
165
|
+
keys: () => mockStore.keys(),
|
|
166
|
+
values: () => mockStore.values(),
|
|
167
|
+
},
|
|
168
|
+
meta: new Map(),
|
|
169
|
+
logger: {
|
|
170
|
+
info: vi.fn(),
|
|
171
|
+
warn: vi.fn(),
|
|
172
|
+
error: vi.fn(),
|
|
173
|
+
debug: vi.fn(),
|
|
174
|
+
verbose: vi.fn(),
|
|
175
|
+
logFileProcessing: vi.fn(),
|
|
176
|
+
logImportSummary: vi.fn(),
|
|
177
|
+
withSpinner: async (msg, fn) => await fn(),
|
|
178
|
+
getLevel: () => 'default',
|
|
179
|
+
},
|
|
180
|
+
config: {},
|
|
181
|
+
entryTypes: new Map([
|
|
182
|
+
['.md', {
|
|
183
|
+
getEntryInfo: async ({ contents, fileUrl }) => ({
|
|
184
|
+
body: contents,
|
|
185
|
+
data: {}
|
|
186
|
+
})
|
|
187
|
+
}]
|
|
188
|
+
]),
|
|
189
|
+
generateDigest: (content) => {
|
|
190
|
+
// Simple hash function for testing
|
|
191
|
+
return content.length.toString();
|
|
192
|
+
},
|
|
193
|
+
parseData: async (data) => data,
|
|
194
|
+
};
|
|
195
|
+
await toCollectionEntry({
|
|
196
|
+
context: mockContext,
|
|
197
|
+
octokit,
|
|
198
|
+
options: testConfig,
|
|
199
|
+
});
|
|
200
|
+
// Verify Git Trees API is used
|
|
201
|
+
expect(listCommitsMock).toHaveBeenCalledTimes(1);
|
|
202
|
+
expect(listCommitsMock).toHaveBeenCalledWith(expect.objectContaining({
|
|
203
|
+
owner: "algorandfoundation",
|
|
204
|
+
repo: "algokit-cli",
|
|
205
|
+
sha: "chore/content-fix",
|
|
206
|
+
per_page: 1,
|
|
207
|
+
}));
|
|
208
|
+
expect(getTreeMock).toHaveBeenCalledTimes(1);
|
|
209
|
+
expect(getTreeMock).toHaveBeenCalledWith(expect.objectContaining({
|
|
210
|
+
owner: "algorandfoundation",
|
|
211
|
+
repo: "algokit-cli",
|
|
212
|
+
tree_sha: "tree123abc456",
|
|
213
|
+
recursive: "true",
|
|
214
|
+
}));
|
|
215
|
+
// Verify getContent is NOT called (old recursive approach)
|
|
216
|
+
expect(getContentMock).not.toHaveBeenCalled();
|
|
217
|
+
console.log('ā
API Efficiency Test Results:');
|
|
218
|
+
console.log(` - listCommits calls: ${listCommitsMock.mock.calls.length} (expected: 1)`);
|
|
219
|
+
console.log(` - getTree calls: ${getTreeMock.mock.calls.length} (expected: 1)`);
|
|
220
|
+
console.log(` - getContent calls: ${getContentMock.mock.calls.length} (expected: 0)`);
|
|
221
|
+
console.log(` - Total API calls for discovery: ${listCommitsMock.mock.calls.length + getTreeMock.mock.calls.length}`);
|
|
222
|
+
console.log(` - š Optimization achieved: 2 calls instead of potentially 10+ recursive calls`);
|
|
223
|
+
});
|
|
224
|
+
});
|
|
225
|
+
describe("file filtering", () => {
|
|
226
|
+
it("should correctly filter files matching the glob pattern", async () => {
|
|
227
|
+
const listCommitsMock = vi.spyOn(octokit.rest.repos, 'listCommits')
|
|
228
|
+
.mockResolvedValue({
|
|
229
|
+
data: [mockCommit],
|
|
230
|
+
status: 200,
|
|
231
|
+
url: '',
|
|
232
|
+
headers: {}
|
|
233
|
+
});
|
|
234
|
+
const getTreeMock = vi.spyOn(octokit.rest.git, 'getTree')
|
|
235
|
+
.mockResolvedValue({
|
|
236
|
+
data: mockTreeData,
|
|
237
|
+
status: 200,
|
|
238
|
+
url: '',
|
|
239
|
+
headers: {}
|
|
240
|
+
});
|
|
241
|
+
global.fetch = vi.fn().mockResolvedValue({
|
|
242
|
+
ok: true,
|
|
243
|
+
status: 200,
|
|
244
|
+
headers: new Headers(),
|
|
245
|
+
text: async () => "# Test Content\n\nMockfile content."
|
|
246
|
+
});
|
|
247
|
+
const testConfig = {
|
|
248
|
+
name: "Test filtering",
|
|
249
|
+
owner: "algorandfoundation",
|
|
250
|
+
repo: "algokit-cli",
|
|
251
|
+
ref: "chore/content-fix",
|
|
252
|
+
includes: [
|
|
253
|
+
{
|
|
254
|
+
pattern: "docs/{features/**/*.md,algokit.md}",
|
|
255
|
+
basePath: "test-output",
|
|
256
|
+
},
|
|
257
|
+
],
|
|
258
|
+
};
|
|
259
|
+
const mockStore = new Map();
|
|
260
|
+
const mockContext = {
|
|
261
|
+
store: {
|
|
262
|
+
set: (entry) => {
|
|
263
|
+
mockStore.set(entry.id, entry);
|
|
264
|
+
return entry;
|
|
265
|
+
},
|
|
266
|
+
get: (id) => mockStore.get(id),
|
|
267
|
+
clear: () => mockStore.clear(),
|
|
268
|
+
entries: () => mockStore.entries(),
|
|
269
|
+
keys: () => mockStore.keys(),
|
|
270
|
+
values: () => mockStore.values(),
|
|
271
|
+
},
|
|
272
|
+
meta: new Map(),
|
|
273
|
+
logger: {
|
|
274
|
+
info: vi.fn(),
|
|
275
|
+
warn: vi.fn(),
|
|
276
|
+
error: vi.fn(),
|
|
277
|
+
debug: vi.fn(),
|
|
278
|
+
verbose: vi.fn(),
|
|
279
|
+
logFileProcessing: vi.fn(),
|
|
280
|
+
logImportSummary: vi.fn(),
|
|
281
|
+
withSpinner: async (msg, fn) => await fn(),
|
|
282
|
+
getLevel: () => 'default',
|
|
283
|
+
},
|
|
284
|
+
config: {},
|
|
285
|
+
entryTypes: new Map([['.md', { getEntryInfo: async ({ contents }) => ({ body: contents, data: {} }) }]]),
|
|
286
|
+
generateDigest: (content) => content.length.toString(),
|
|
287
|
+
parseData: async (data) => data,
|
|
288
|
+
};
|
|
289
|
+
const stats = await toCollectionEntry({
|
|
290
|
+
context: mockContext,
|
|
291
|
+
octokit,
|
|
292
|
+
options: testConfig,
|
|
293
|
+
});
|
|
294
|
+
console.log('\nš File Filtering Test Results:');
|
|
295
|
+
console.log(` - Pattern: docs/{features/**/*.md,algokit.md}`);
|
|
296
|
+
console.log(` - Files in tree: ${mockTreeData.tree.length}`);
|
|
297
|
+
console.log(` - Files processed: ${stats.processed}`);
|
|
298
|
+
console.log(` - Files matched: ${mockStore.size}`);
|
|
299
|
+
// Based on our mock data, we should match:
|
|
300
|
+
// - docs/algokit.md (explicit match)
|
|
301
|
+
// - docs/features/accounts.md (matches features/**/*.md)
|
|
302
|
+
// - docs/features/tasks.md (matches features/**/*.md)
|
|
303
|
+
// - docs/features/generate.md (matches features/**/*.md)
|
|
304
|
+
// Should NOT match:
|
|
305
|
+
// - docs/cli/index.md (not in pattern)
|
|
306
|
+
// - README.md (not in pattern)
|
|
307
|
+
// - package.json (not in pattern)
|
|
308
|
+
expect(stats.processed).toBe(4); // algokit.md + 3 features/*.md files
|
|
309
|
+
expect(mockStore.size).toBe(4);
|
|
310
|
+
// Verify correct files were stored
|
|
311
|
+
const storedIds = Array.from(mockStore.keys());
|
|
312
|
+
expect(storedIds).toContain('docs/algokit');
|
|
313
|
+
expect(storedIds.some(id => id.includes('features'))).toBe(true);
|
|
314
|
+
expect(storedIds).not.toContain('package');
|
|
315
|
+
expect(storedIds).not.toContain('README');
|
|
316
|
+
});
|
|
317
|
+
it("should filter to match only specific file when pattern is exact", async () => {
|
|
318
|
+
vi.spyOn(octokit.rest.repos, 'listCommits')
|
|
319
|
+
.mockResolvedValue({ data: [mockCommit], status: 200, url: '', headers: {} });
|
|
320
|
+
vi.spyOn(octokit.rest.git, 'getTree')
|
|
321
|
+
.mockResolvedValue({ data: mockTreeData, status: 200, url: '', headers: {} });
|
|
322
|
+
global.fetch = vi.fn().mockResolvedValue({
|
|
323
|
+
ok: true,
|
|
324
|
+
status: 200,
|
|
325
|
+
headers: new Headers(),
|
|
326
|
+
text: async () => "# Single File Content"
|
|
327
|
+
});
|
|
328
|
+
const testConfig = {
|
|
329
|
+
name: "Exact match test",
|
|
330
|
+
owner: "test",
|
|
331
|
+
repo: "repo",
|
|
332
|
+
ref: "main",
|
|
333
|
+
includes: [{
|
|
334
|
+
pattern: "docs/algokit.md", // Exact file
|
|
335
|
+
basePath: "test-output",
|
|
336
|
+
}],
|
|
337
|
+
};
|
|
338
|
+
const mockStore = new Map();
|
|
339
|
+
const mockContext = {
|
|
340
|
+
store: {
|
|
341
|
+
set: (entry) => mockStore.set(entry.id, entry),
|
|
342
|
+
get: (id) => mockStore.get(id),
|
|
343
|
+
clear: () => mockStore.clear(),
|
|
344
|
+
entries: () => mockStore.entries(),
|
|
345
|
+
keys: () => mockStore.keys(),
|
|
346
|
+
values: () => mockStore.values(),
|
|
347
|
+
},
|
|
348
|
+
meta: new Map(),
|
|
349
|
+
logger: {
|
|
350
|
+
info: vi.fn(),
|
|
351
|
+
warn: vi.fn(),
|
|
352
|
+
error: vi.fn(),
|
|
353
|
+
debug: vi.fn(),
|
|
354
|
+
verbose: vi.fn(),
|
|
355
|
+
logFileProcessing: vi.fn(),
|
|
356
|
+
logImportSummary: vi.fn(),
|
|
357
|
+
withSpinner: async (msg, fn) => await fn(),
|
|
358
|
+
getLevel: () => 'default',
|
|
359
|
+
},
|
|
360
|
+
config: {},
|
|
361
|
+
entryTypes: new Map([['.md', { getEntryInfo: async ({ contents }) => ({ body: contents, data: {} }) }]]),
|
|
362
|
+
generateDigest: (content) => content.length.toString(),
|
|
363
|
+
parseData: async (data) => data,
|
|
364
|
+
};
|
|
365
|
+
const stats = await toCollectionEntry({
|
|
366
|
+
context: mockContext,
|
|
367
|
+
octokit,
|
|
368
|
+
options: testConfig,
|
|
369
|
+
});
|
|
370
|
+
console.log('\nšÆ Exact Pattern Match Test:');
|
|
371
|
+
console.log(` - Pattern: docs/algokit.md`);
|
|
372
|
+
console.log(` - Files processed: ${stats.processed}`);
|
|
373
|
+
console.log(` - Expected: 1 file`);
|
|
374
|
+
expect(stats.processed).toBe(1);
|
|
375
|
+
expect(mockStore.size).toBe(1);
|
|
376
|
+
expect(Array.from(mockStore.keys())[0]).toContain('algokit');
|
|
377
|
+
});
|
|
378
|
+
});
|
|
379
|
+
describe("download URL construction", () => {
|
|
380
|
+
it("should construct valid raw.githubusercontent.com URLs from tree data", async () => {
|
|
381
|
+
vi.spyOn(octokit.rest.repos, 'listCommits')
|
|
382
|
+
.mockResolvedValue({ data: [mockCommit], status: 200, url: '', headers: {} });
|
|
383
|
+
vi.spyOn(octokit.rest.git, 'getTree')
|
|
384
|
+
.mockResolvedValue({ data: mockTreeData, status: 200, url: '', headers: {} });
|
|
385
|
+
const fetchMock = vi.fn().mockResolvedValue({
|
|
386
|
+
ok: true,
|
|
387
|
+
status: 200,
|
|
388
|
+
headers: new Headers(),
|
|
389
|
+
text: async () => "# Content"
|
|
390
|
+
});
|
|
391
|
+
global.fetch = fetchMock;
|
|
392
|
+
const testConfig = {
|
|
393
|
+
name: "URL test",
|
|
394
|
+
owner: "algorandfoundation",
|
|
395
|
+
repo: "algokit-cli",
|
|
396
|
+
ref: "chore/content-fix",
|
|
397
|
+
includes: [{
|
|
398
|
+
pattern: "docs/algokit.md",
|
|
399
|
+
basePath: "test-output",
|
|
400
|
+
}],
|
|
401
|
+
};
|
|
402
|
+
const mockStore = new Map();
|
|
403
|
+
const mockContext = {
|
|
404
|
+
store: {
|
|
405
|
+
set: (entry) => mockStore.set(entry.id, entry),
|
|
406
|
+
get: (id) => mockStore.get(id),
|
|
407
|
+
clear: () => mockStore.clear(),
|
|
408
|
+
entries: () => mockStore.entries(),
|
|
409
|
+
keys: () => mockStore.keys(),
|
|
410
|
+
values: () => mockStore.values(),
|
|
411
|
+
},
|
|
412
|
+
meta: new Map(),
|
|
413
|
+
logger: {
|
|
414
|
+
info: vi.fn(),
|
|
415
|
+
warn: vi.fn(),
|
|
416
|
+
error: vi.fn(),
|
|
417
|
+
debug: vi.fn(),
|
|
418
|
+
verbose: vi.fn(),
|
|
419
|
+
logFileProcessing: vi.fn(),
|
|
420
|
+
logImportSummary: vi.fn(),
|
|
421
|
+
withSpinner: async (msg, fn) => await fn(),
|
|
422
|
+
getLevel: () => 'default',
|
|
423
|
+
},
|
|
424
|
+
config: {},
|
|
425
|
+
entryTypes: new Map([['.md', { getEntryInfo: async ({ contents }) => ({ body: contents, data: {} }) }]]),
|
|
426
|
+
generateDigest: (content) => content.length.toString(),
|
|
427
|
+
parseData: async (data) => data,
|
|
428
|
+
};
|
|
429
|
+
await toCollectionEntry({
|
|
430
|
+
context: mockContext,
|
|
431
|
+
octokit,
|
|
432
|
+
options: testConfig,
|
|
433
|
+
});
|
|
434
|
+
// Find fetch calls to raw.githubusercontent.com
|
|
435
|
+
const rawGithubCalls = fetchMock.mock.calls.filter(call => {
|
|
436
|
+
const url = call[0]?.toString() || '';
|
|
437
|
+
return url.includes('raw.githubusercontent.com');
|
|
438
|
+
});
|
|
439
|
+
console.log('\nš URL Construction Test:');
|
|
440
|
+
console.log(` - Total fetch calls: ${fetchMock.mock.calls.length}`);
|
|
441
|
+
console.log(` - Calls to raw.githubusercontent.com: ${rawGithubCalls.length}`);
|
|
442
|
+
expect(rawGithubCalls.length).toBeGreaterThan(0);
|
|
443
|
+
const exampleUrl = rawGithubCalls[0][0]?.toString();
|
|
444
|
+
console.log(` - Example URL: ${exampleUrl}`);
|
|
445
|
+
// Verify URL format: https://raw.githubusercontent.com/{owner}/{repo}/{commit_sha}/{file_path}
|
|
446
|
+
expect(exampleUrl).toMatch(/^https:\/\/raw\.githubusercontent\.com\/algorandfoundation\/algokit-cli\/abc123def456\/docs\/algokit\.md$/);
|
|
447
|
+
});
|
|
448
|
+
});
|
|
449
|
+
describe("real-world config simulation", () => {
|
|
450
|
+
it("should handle the production algokit-cli config pattern correctly", async () => {
|
|
451
|
+
vi.spyOn(octokit.rest.repos, 'listCommits')
|
|
452
|
+
.mockResolvedValue({ data: [mockCommit], status: 200, url: '', headers: {} });
|
|
453
|
+
vi.spyOn(octokit.rest.git, 'getTree')
|
|
454
|
+
.mockResolvedValue({ data: mockTreeData, status: 200, url: '', headers: {} });
|
|
455
|
+
global.fetch = vi.fn().mockResolvedValue({
|
|
456
|
+
ok: true,
|
|
457
|
+
status: 200,
|
|
458
|
+
headers: new Headers(),
|
|
459
|
+
text: async () => "# Content"
|
|
460
|
+
});
|
|
461
|
+
// This is the actual production config from content.config.ts
|
|
462
|
+
const productionConfig = {
|
|
463
|
+
name: "AlgoKit CLI Docs",
|
|
464
|
+
owner: "algorandfoundation",
|
|
465
|
+
repo: "algokit-cli",
|
|
466
|
+
ref: "chore/content-fix",
|
|
467
|
+
includes: [
|
|
468
|
+
{
|
|
469
|
+
pattern: "docs/{features/**/*.md,algokit.md}",
|
|
470
|
+
basePath: "src/content/docs/algokit/cli",
|
|
471
|
+
pathMappings: {
|
|
472
|
+
"docs/features/": "",
|
|
473
|
+
"docs/algokit.md": "overview.md",
|
|
474
|
+
},
|
|
475
|
+
},
|
|
476
|
+
{
|
|
477
|
+
pattern: "docs/cli/index.md",
|
|
478
|
+
basePath: "src/content/docs/reference/algokit-cli/",
|
|
479
|
+
pathMappings: {
|
|
480
|
+
"docs/cli/index.md": "index.md",
|
|
481
|
+
},
|
|
482
|
+
},
|
|
483
|
+
],
|
|
484
|
+
};
|
|
485
|
+
const mockStore = new Map();
|
|
486
|
+
const mockContext = {
|
|
487
|
+
store: {
|
|
488
|
+
set: (entry) => mockStore.set(entry.id, entry),
|
|
489
|
+
get: (id) => mockStore.get(id),
|
|
490
|
+
clear: () => mockStore.clear(),
|
|
491
|
+
entries: () => mockStore.entries(),
|
|
492
|
+
keys: () => mockStore.keys(),
|
|
493
|
+
values: () => mockStore.values(),
|
|
494
|
+
},
|
|
495
|
+
meta: new Map(),
|
|
496
|
+
logger: {
|
|
497
|
+
info: vi.fn(),
|
|
498
|
+
warn: vi.fn(),
|
|
499
|
+
error: vi.fn(),
|
|
500
|
+
debug: vi.fn(),
|
|
501
|
+
verbose: vi.fn(),
|
|
502
|
+
logFileProcessing: vi.fn(),
|
|
503
|
+
logImportSummary: vi.fn(),
|
|
504
|
+
withSpinner: async (msg, fn) => await fn(),
|
|
505
|
+
getLevel: () => 'default',
|
|
506
|
+
},
|
|
507
|
+
config: {},
|
|
508
|
+
entryTypes: new Map([['.md', { getEntryInfo: async ({ contents }) => ({ body: contents, data: {} }) }]]),
|
|
509
|
+
generateDigest: (content) => content.length.toString(),
|
|
510
|
+
parseData: async (data) => data,
|
|
511
|
+
};
|
|
512
|
+
const stats = await toCollectionEntry({
|
|
513
|
+
context: mockContext,
|
|
514
|
+
octokit,
|
|
515
|
+
options: productionConfig,
|
|
516
|
+
});
|
|
517
|
+
console.log('\nš Production Config Test:');
|
|
518
|
+
console.log(` - Pattern 1: docs/{features/**/*.md,algokit.md}`);
|
|
519
|
+
console.log(` - Pattern 2: docs/cli/index.md`);
|
|
520
|
+
console.log(` - Files processed: ${stats.processed}`);
|
|
521
|
+
console.log(` - Expected files:`);
|
|
522
|
+
console.log(` ⢠docs/algokit.md ā overview.md (from pattern 1)`);
|
|
523
|
+
console.log(` ⢠docs/features/accounts.md ā accounts.md (from pattern 1)`);
|
|
524
|
+
console.log(` ⢠docs/features/tasks.md ā tasks.md (from pattern 1)`);
|
|
525
|
+
console.log(` ⢠docs/features/generate.md ā generate.md (from pattern 1)`);
|
|
526
|
+
console.log(` ⢠docs/cli/index.md ā index.md (from pattern 2)`);
|
|
527
|
+
// Should match 4 files from pattern 1 + 1 file from pattern 2
|
|
528
|
+
expect(stats.processed).toBe(5);
|
|
529
|
+
const storedIds = Array.from(mockStore.keys());
|
|
530
|
+
console.log(` - Stored IDs:`, storedIds);
|
|
531
|
+
// Verify expected files are stored
|
|
532
|
+
expect(storedIds.some(id => id.includes('overview'))).toBe(true); // algokit.md mapped to overview
|
|
533
|
+
expect(storedIds.filter(id => id.includes('features')).length).toBe(3); // 3 features files
|
|
534
|
+
expect(storedIds.some(id => id.includes('cli') && id.includes('index'))).toBe(true); // cli/index.md
|
|
535
|
+
});
|
|
536
|
+
});
|
|
537
|
+
});
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@larkiny/astro-github-loader",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.11.0",
|
|
5
5
|
"description": "Load content from GitHub repositories into Astro content collections with asset management and content transformations",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"astro",
|
|
@@ -36,12 +36,15 @@
|
|
|
36
36
|
},
|
|
37
37
|
"scripts": {
|
|
38
38
|
"build": "tsc",
|
|
39
|
+
"test": "vitest run",
|
|
40
|
+
"test:watch": "vitest",
|
|
39
41
|
"lint": "eslint .",
|
|
40
42
|
"prettier": "prettier --check .",
|
|
41
43
|
"preview": "astro preview",
|
|
42
44
|
"astro": "astro"
|
|
43
45
|
},
|
|
44
46
|
"dependencies": {
|
|
47
|
+
"@octokit/auth-app": "^8.1.1",
|
|
45
48
|
"github-slugger": "^2.0.0",
|
|
46
49
|
"octokit": "^5.0.4",
|
|
47
50
|
"picomatch": "^4.0.2"
|