@larkiny/astro-github-loader 0.11.3 → 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.
Files changed (51) hide show
  1. package/README.md +28 -55
  2. package/dist/github.assets.d.ts +70 -0
  3. package/dist/github.assets.js +253 -0
  4. package/dist/github.auth.js +13 -9
  5. package/dist/github.cleanup.d.ts +3 -2
  6. package/dist/github.cleanup.js +30 -23
  7. package/dist/github.constants.d.ts +0 -16
  8. package/dist/github.constants.js +0 -16
  9. package/dist/github.content.d.ts +5 -131
  10. package/dist/github.content.js +152 -794
  11. package/dist/github.dryrun.d.ts +9 -5
  12. package/dist/github.dryrun.js +46 -25
  13. package/dist/github.link-transform.d.ts +2 -2
  14. package/dist/github.link-transform.js +65 -57
  15. package/dist/github.loader.js +30 -46
  16. package/dist/github.logger.d.ts +2 -2
  17. package/dist/github.logger.js +33 -24
  18. package/dist/github.paths.d.ts +76 -0
  19. package/dist/github.paths.js +190 -0
  20. package/dist/github.storage.d.ts +15 -0
  21. package/dist/github.storage.js +109 -0
  22. package/dist/github.types.d.ts +34 -4
  23. package/dist/index.d.ts +8 -6
  24. package/dist/index.js +3 -6
  25. package/dist/test-helpers.d.ts +130 -0
  26. package/dist/test-helpers.js +194 -0
  27. package/package.json +3 -1
  28. package/src/github.assets.spec.ts +717 -0
  29. package/src/github.assets.ts +365 -0
  30. package/src/github.auth.spec.ts +245 -0
  31. package/src/github.auth.ts +24 -10
  32. package/src/github.cleanup.spec.ts +380 -0
  33. package/src/github.cleanup.ts +91 -47
  34. package/src/github.constants.ts +0 -17
  35. package/src/github.content.spec.ts +305 -454
  36. package/src/github.content.ts +259 -957
  37. package/src/github.dryrun.spec.ts +586 -0
  38. package/src/github.dryrun.ts +105 -54
  39. package/src/github.link-transform.spec.ts +1345 -0
  40. package/src/github.link-transform.ts +174 -95
  41. package/src/github.loader.spec.ts +75 -50
  42. package/src/github.loader.ts +101 -76
  43. package/src/github.logger.spec.ts +795 -0
  44. package/src/github.logger.ts +77 -35
  45. package/src/github.paths.spec.ts +523 -0
  46. package/src/github.paths.ts +259 -0
  47. package/src/github.storage.spec.ts +367 -0
  48. package/src/github.storage.ts +127 -0
  49. package/src/github.types.ts +48 -9
  50. package/src/index.ts +43 -6
  51. package/src/test-helpers.ts +215 -0
@@ -1,7 +1,14 @@
1
- import { slug } from 'github-slugger';
2
- import path from 'node:path';
3
- import type { LinkMapping, LinkTransformContext, MatchedPattern, IncludePattern, PathMappingValue, EnhancedPathMapping } from './github.types.js';
4
- import type { Logger } from './github.logger.js';
1
+ import { slug } from "github-slugger";
2
+ import path from "node:path";
3
+ import type {
4
+ LinkMapping,
5
+ LinkTransformContext,
6
+ MatchedPattern,
7
+ IncludePattern,
8
+ PathMappingValue,
9
+ EnhancedPathMapping,
10
+ } from "./github.types.js";
11
+ import type { Logger } from "./github.logger.js";
5
12
 
6
13
  /**
7
14
  * Represents an imported file with its content and metadata
@@ -66,8 +73,8 @@ interface LinkContext {
66
73
  */
67
74
  function extractAnchor(link: string): { path: string; anchor: string } {
68
75
  const anchorMatch = link.match(/#.*$/);
69
- const anchor = anchorMatch ? anchorMatch[0] : '';
70
- const path = link.replace(/#.*$/, '');
76
+ const anchor = anchorMatch ? anchorMatch[0] : "";
77
+ const path = link.replace(/#.*$/, "");
71
78
  return { path, anchor };
72
79
  }
73
80
 
@@ -83,16 +90,12 @@ function isExternalLink(link: string): boolean {
83
90
  /^tel:/.test(link) ||
84
91
  /^ftp:/.test(link) ||
85
92
  /^ftps:\/\//.test(link) ||
86
-
87
93
  // Any protocol with ://
88
- link.includes('://') ||
89
-
94
+ link.includes("://") ||
90
95
  // Anchor-only links (same page)
91
- link.startsWith('#') ||
92
-
96
+ link.startsWith("#") ||
93
97
  // Data URLs
94
98
  /^data:/.test(link) ||
95
-
96
99
  // File protocol
97
100
  /^file:\/\//.test(link)
98
101
  );
@@ -101,17 +104,30 @@ function isExternalLink(link: string): boolean {
101
104
  /**
102
105
  * Normalize path separators and resolve relative paths
103
106
  */
104
- function normalizePath(linkPath: string, currentFilePath: string, logger?: Logger): string {
105
- logger?.debug(`[normalizePath] BEFORE: linkPath="${linkPath}", currentFilePath="${currentFilePath}"`);
107
+ function normalizePath(
108
+ linkPath: string,
109
+ currentFilePath: string,
110
+ logger?: Logger,
111
+ ): string {
112
+ logger?.debug(
113
+ `[normalizePath] BEFORE: linkPath="${linkPath}", currentFilePath="${currentFilePath}"`,
114
+ );
106
115
 
107
116
  // Handle relative paths (including simple relative paths without ./ prefix)
108
117
  // A link is relative if it doesn't start with / or contain a protocol
109
- const isAbsoluteOrExternal = linkPath.startsWith('/') || linkPath.includes('://') || linkPath.startsWith('#');
118
+ const isAbsoluteOrExternal =
119
+ linkPath.startsWith("/") ||
120
+ linkPath.includes("://") ||
121
+ linkPath.startsWith("#");
110
122
 
111
123
  if (!isAbsoluteOrExternal) {
112
124
  const currentDir = path.dirname(currentFilePath);
113
- const resolved = path.posix.normalize(path.posix.join(currentDir, linkPath));
114
- logger?.debug(`[normalizePath] RELATIVE PATH RESOLVED: "${linkPath}" -> "${resolved}" (currentDir: "${currentDir}")`);
125
+ const resolved = path.posix.normalize(
126
+ path.posix.join(currentDir, linkPath),
127
+ );
128
+ logger?.debug(
129
+ `[normalizePath] RELATIVE PATH RESOLVED: "${linkPath}" -> "${resolved}" (currentDir: "${currentDir}")`,
130
+ );
115
131
  return resolved;
116
132
  }
117
133
 
@@ -125,7 +141,7 @@ function normalizePath(linkPath: string, currentFilePath: string, logger?: Logge
125
141
  function applyLinkMappings(
126
142
  linkUrl: string,
127
143
  linkMappings: LinkMapping[],
128
- context: LinkContext
144
+ context: LinkContext,
129
145
  ): string {
130
146
  const { path: linkPath, anchor } = extractAnchor(linkUrl);
131
147
  let transformedPath = linkPath;
@@ -141,24 +157,27 @@ function applyLinkMappings(
141
157
  // Handle relative links automatically if enabled
142
158
  if (mapping.relativeLinks && context.currentFile.linkContext) {
143
159
  // Check if this is a relative link (doesn't start with /, http, etc.)
144
- if (!linkPath.startsWith('/') && !isExternalLink(linkPath)) {
160
+ if (!linkPath.startsWith("/") && !isExternalLink(linkPath)) {
145
161
  // Check if the link points to a known directory structure
146
- const knownPaths = ['modules/', 'classes/', 'interfaces/', 'enums/'];
147
- const isKnownPath = knownPaths.some(p => linkPath.startsWith(p));
162
+ const knownPaths = ["modules/", "classes/", "interfaces/", "enums/"];
163
+ const isKnownPath = knownPaths.some((p) => linkPath.startsWith(p));
148
164
 
149
165
  if (isKnownPath) {
150
166
  // Strip .md extension from the link path
151
- const cleanLinkPath = linkPath.replace(/\.md$/, '');
167
+ const cleanLinkPath = linkPath.replace(/\.md$/, "");
152
168
 
153
169
  // Convert relative path to absolute path using the target base
154
- const targetBase = generateSiteUrl(context.currentFile.linkContext.basePath, context.global.stripPrefixes);
170
+ const targetBase = generateSiteUrl(
171
+ context.currentFile.linkContext.basePath,
172
+ context.global.stripPrefixes,
173
+ );
155
174
 
156
175
  // Construct final URL with proper Starlight formatting
157
- let finalUrl = targetBase.replace(/\/$/, '') + '/' + cleanLinkPath;
176
+ let finalUrl = targetBase.replace(/\/$/, "") + "/" + cleanLinkPath;
158
177
 
159
178
  // Add trailing slash if it doesn't end with one and isn't empty
160
- if (finalUrl && !finalUrl.endsWith('/')) {
161
- finalUrl += '/';
179
+ if (finalUrl && !finalUrl.endsWith("/")) {
180
+ finalUrl += "/";
162
181
  }
163
182
 
164
183
  transformedPath = finalUrl;
@@ -168,16 +187,25 @@ function applyLinkMappings(
168
187
  }
169
188
 
170
189
  let matched = false;
171
- let replacement = '';
190
+ let replacement = "";
172
191
 
173
- if (typeof mapping.pattern === 'string') {
192
+ if (typeof mapping.pattern === "string") {
174
193
  // String pattern - exact match or contains
175
194
  if (transformedPath.includes(mapping.pattern)) {
176
195
  matched = true;
177
- if (typeof mapping.replacement === 'string') {
178
- replacement = transformedPath.replace(mapping.pattern, mapping.replacement);
196
+ if (typeof mapping.replacement === "string") {
197
+ replacement = transformedPath.replace(
198
+ mapping.pattern,
199
+ mapping.replacement,
200
+ );
179
201
  } else {
180
- replacement = mapping.replacement(transformedPath, anchor, context);
202
+ const linkTransformContext =
203
+ context.currentFile.linkContext ?? ({} as LinkTransformContext);
204
+ replacement = mapping.replacement(
205
+ transformedPath,
206
+ anchor,
207
+ linkTransformContext,
208
+ );
181
209
  }
182
210
  }
183
211
  } else {
@@ -185,10 +213,19 @@ function applyLinkMappings(
185
213
  const match = transformedPath.match(mapping.pattern);
186
214
  if (match) {
187
215
  matched = true;
188
- if (typeof mapping.replacement === 'string') {
189
- replacement = transformedPath.replace(mapping.pattern, mapping.replacement);
216
+ if (typeof mapping.replacement === "string") {
217
+ replacement = transformedPath.replace(
218
+ mapping.pattern,
219
+ mapping.replacement,
220
+ );
190
221
  } else {
191
- replacement = mapping.replacement(transformedPath, anchor, context);
222
+ const linkTransformContext =
223
+ context.currentFile.linkContext ?? ({} as LinkTransformContext);
224
+ replacement = mapping.replacement(
225
+ transformedPath,
226
+ anchor,
227
+ linkTransformContext,
228
+ );
192
229
  }
193
230
  }
194
231
  }
@@ -218,35 +255,37 @@ function generateSiteUrl(targetPath: string, stripPrefixes: string[]): string {
218
255
  }
219
256
 
220
257
  // Remove leading slash if present
221
- url = url.replace(/^\//, '');
258
+ url = url.replace(/^\//, "");
222
259
 
223
260
  // Remove file extension
224
- url = url.replace(/\.(md|mdx)$/i, '');
261
+ url = url.replace(/\.(md|mdx)$/i, "");
225
262
 
226
263
  // Handle index files - they should resolve to parent directory
227
- if (url.endsWith('/index')) {
228
- url = url.replace('/index', '');
229
- } else if (url === 'index') {
230
- url = '';
264
+ if (url.endsWith("/index")) {
265
+ url = url.replace("/index", "");
266
+ } else if (url === "index") {
267
+ url = "";
231
268
  }
232
269
 
233
270
  // Split path into segments and slugify each
234
- const segments = url.split('/').map(segment => segment ? slug(segment) : '');
271
+ const segments = url
272
+ .split("/")
273
+ .map((segment) => (segment ? slug(segment) : ""));
235
274
 
236
275
  // Reconstruct URL
237
- url = segments.filter(s => s).join('/');
276
+ url = segments.filter((s) => s).join("/");
238
277
 
239
278
  // Ensure leading slash
240
- if (url && !url.startsWith('/')) {
241
- url = '/' + url;
279
+ if (url && !url.startsWith("/")) {
280
+ url = "/" + url;
242
281
  }
243
282
 
244
283
  // Add trailing slash for non-empty paths
245
- if (url && !url.endsWith('/')) {
246
- url = url + '/';
284
+ if (url && !url.endsWith("/")) {
285
+ url = url + "/";
247
286
  }
248
287
 
249
- return url || '/';
288
+ return url || "/";
250
289
  }
251
290
 
252
291
  /**
@@ -260,7 +299,11 @@ function generateSiteUrl(targetPath: string, stripPrefixes: string[]): string {
260
299
  * 5. Apply non-global path mappings if unresolved
261
300
  * 6. Check custom handlers
262
301
  */
263
- function transformLink(linkText: string, linkUrl: string, context: LinkContext): string {
302
+ function transformLink(
303
+ linkText: string,
304
+ linkUrl: string,
305
+ context: LinkContext,
306
+ ): string {
264
307
  // Skip external links FIRST - no transformations should ever be applied to them
265
308
  if (isExternalLink(linkUrl)) {
266
309
  return `[${linkText}](${linkUrl})`;
@@ -269,14 +312,22 @@ function transformLink(linkText: string, linkUrl: string, context: LinkContext):
269
312
  const { path: linkPath, anchor } = extractAnchor(linkUrl);
270
313
 
271
314
  // Normalize the link path relative to current file FIRST
272
- const normalizedPath = normalizePath(linkPath, context.currentFile.sourcePath, context.global.logger);
315
+ const normalizedPath = normalizePath(
316
+ linkPath,
317
+ context.currentFile.sourcePath,
318
+ context.global.logger,
319
+ );
273
320
 
274
321
  // Apply global path mappings to the normalized path
275
322
  let processedNormalizedPath = normalizedPath;
276
323
  if (context.global.linkMappings) {
277
- const globalMappings = context.global.linkMappings.filter(m => m.global);
324
+ const globalMappings = context.global.linkMappings.filter((m) => m.global);
278
325
  if (globalMappings.length > 0) {
279
- processedNormalizedPath = applyLinkMappings(normalizedPath + anchor, globalMappings, context);
326
+ processedNormalizedPath = applyLinkMappings(
327
+ normalizedPath + anchor,
328
+ globalMappings,
329
+ context,
330
+ );
280
331
  // Extract path again after global mappings
281
332
  const { path: newPath } = extractAnchor(processedNormalizedPath);
282
333
  processedNormalizedPath = newPath;
@@ -287,8 +338,10 @@ function transformLink(linkText: string, linkUrl: string, context: LinkContext):
287
338
  let targetPath = context.global.sourceToTargetMap.get(normalizedPath);
288
339
 
289
340
  // If not found and path ends with /, try looking for index.md
290
- if (!targetPath && normalizedPath.endsWith('/')) {
291
- targetPath = context.global.sourceToTargetMap.get(normalizedPath + 'index.md');
341
+ if (!targetPath && normalizedPath.endsWith("/")) {
342
+ targetPath = context.global.sourceToTargetMap.get(
343
+ normalizedPath + "index.md",
344
+ );
292
345
  }
293
346
 
294
347
  if (targetPath) {
@@ -299,10 +352,16 @@ function transformLink(linkText: string, linkUrl: string, context: LinkContext):
299
352
 
300
353
  // Apply non-global path mappings to unresolved links
301
354
  if (context.global.linkMappings) {
302
- const nonGlobalMappings = context.global.linkMappings.filter(m => !m.global);
355
+ const nonGlobalMappings = context.global.linkMappings.filter(
356
+ (m) => !m.global,
357
+ );
303
358
  if (nonGlobalMappings.length > 0) {
304
- const mappedUrl = applyLinkMappings(processedNormalizedPath + anchor, nonGlobalMappings, context);
305
- if (mappedUrl !== (processedNormalizedPath + anchor)) {
359
+ const mappedUrl = applyLinkMappings(
360
+ processedNormalizedPath + anchor,
361
+ nonGlobalMappings,
362
+ context,
363
+ );
364
+ if (mappedUrl !== processedNormalizedPath + anchor) {
306
365
  return `[${linkText}](${mappedUrl})`;
307
366
  }
308
367
  }
@@ -321,7 +380,7 @@ function transformLink(linkText: string, linkUrl: string, context: LinkContext):
321
380
 
322
381
  // No transformation matched - strip .md extension from unresolved internal links
323
382
  // This handles links to files that weren't imported but should still use Starlight routing
324
- const cleanPath = processedNormalizedPath.replace(/\.md$/i, '');
383
+ const cleanPath = processedNormalizedPath.replace(/\.md$/i, "");
325
384
  return `[${linkText}](${cleanPath + anchor})`;
326
385
  }
327
386
 
@@ -336,7 +395,7 @@ export function globalLinkTransform(
336
395
  customHandlers?: LinkHandler[];
337
396
  linkMappings?: LinkMapping[];
338
397
  logger?: Logger;
339
- }
398
+ },
340
399
  ): ImportedFile[] {
341
400
  // Build global context
342
401
  const sourceToTargetMap = new Map<string, string>();
@@ -359,31 +418,31 @@ export function globalLinkTransform(
359
418
  // Transform links in all files
360
419
  const markdownLinkRegex = /\[([^\]]*)\]\(([^)]+)\)/g;
361
420
 
362
- return importedFiles.map(file => ({
421
+ return importedFiles.map((file) => ({
363
422
  ...file,
364
- content: file.content.replace(markdownLinkRegex, (match, linkText, linkUrl) => {
365
- const linkContext: LinkContext = {
366
- currentFile: file,
367
- originalLink: linkUrl,
368
- anchor: extractAnchor(linkUrl).anchor,
369
- global: globalContext,
370
- };
371
-
372
- return transformLink(linkText, linkUrl, linkContext);
373
- }),
423
+ content: file.content.replace(
424
+ markdownLinkRegex,
425
+ (match, linkText, linkUrl) => {
426
+ const linkContext: LinkContext = {
427
+ currentFile: file,
428
+ originalLink: linkUrl,
429
+ anchor: extractAnchor(linkUrl).anchor,
430
+ global: globalContext,
431
+ };
432
+
433
+ return transformLink(linkText, linkUrl, linkContext);
434
+ },
435
+ ),
374
436
  }));
375
437
  }
376
438
 
377
-
378
439
  /**
379
440
  * Infer cross-section path from basePath
380
441
  * @param basePath - The base path from include pattern (e.g., 'src/content/docs/reference/api')
381
442
  * @returns Inferred cross-section path (e.g., '/reference/api')
382
443
  */
383
444
  function inferCrossSectionPath(basePath: string): string {
384
- return basePath
385
- .replace(/^src\/content\/docs/, '')
386
- .replace(/\/$/, '') || '/';
445
+ return basePath.replace(/^src\/content\/docs/, "").replace(/\/$/, "") || "/";
387
446
  }
388
447
 
389
448
  /**
@@ -394,7 +453,7 @@ function inferCrossSectionPath(basePath: string): string {
394
453
  */
395
454
  export function generateAutoLinkMappings(
396
455
  includes: IncludePattern[],
397
- stripPrefixes: string[] = []
456
+ stripPrefixes: string[] = [],
398
457
  ): LinkMapping[] {
399
458
  const linkMappings: LinkMapping[] = [];
400
459
 
@@ -403,28 +462,43 @@ export function generateAutoLinkMappings(
403
462
 
404
463
  const inferredCrossSection = inferCrossSectionPath(includePattern.basePath);
405
464
 
406
- for (const [sourcePath, mappingValue] of Object.entries(includePattern.pathMappings)) {
465
+ for (const [sourcePath, mappingValue] of Object.entries(
466
+ includePattern.pathMappings,
467
+ )) {
407
468
  // Handle both string and enhanced object formats
408
- const targetPath = typeof mappingValue === 'string' ? mappingValue : mappingValue.target;
409
- const crossSectionPath = typeof mappingValue === 'object' && mappingValue.crossSectionPath
410
- ? mappingValue.crossSectionPath
411
- : inferredCrossSection;
412
-
413
- if (sourcePath.endsWith('/')) {
469
+ const targetPath =
470
+ typeof mappingValue === "string" ? mappingValue : mappingValue.target;
471
+ const crossSectionPath =
472
+ typeof mappingValue === "object" && mappingValue.crossSectionPath
473
+ ? mappingValue.crossSectionPath
474
+ : inferredCrossSection;
475
+
476
+ if (sourcePath.endsWith("/")) {
414
477
  // Folder mapping - use regex with capture group
415
- const sourcePattern = sourcePath.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
478
+ const sourcePattern = sourcePath.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
416
479
 
417
480
  linkMappings.push({
418
481
  pattern: new RegExp(`^${sourcePattern}(.+)$`),
419
- replacement: (transformedPath: string, anchor: string, context: any) => {
420
- const relativePath = transformedPath.replace(new RegExp(`^${sourcePattern}`), '');
482
+ replacement: (
483
+ transformedPath: string,
484
+ _anchor: string,
485
+ _context: LinkTransformContext,
486
+ ) => {
487
+ const relativePath = transformedPath.replace(
488
+ new RegExp(`^${sourcePattern}`),
489
+ "",
490
+ );
421
491
  let finalPath: string;
422
- if (crossSectionPath && crossSectionPath !== '/') {
423
- finalPath = targetPath === ''
424
- ? `${crossSectionPath}/${relativePath}`
425
- : `${crossSectionPath}/${targetPath}${relativePath}`;
492
+ if (crossSectionPath && crossSectionPath !== "/") {
493
+ finalPath =
494
+ targetPath === ""
495
+ ? `${crossSectionPath}/${relativePath}`
496
+ : `${crossSectionPath}/${targetPath}${relativePath}`;
426
497
  } else {
427
- finalPath = targetPath === '' ? relativePath : `${targetPath}${relativePath}`;
498
+ finalPath =
499
+ targetPath === ""
500
+ ? relativePath
501
+ : `${targetPath}${relativePath}`;
428
502
  }
429
503
  return generateSiteUrl(finalPath, stripPrefixes);
430
504
  },
@@ -432,14 +506,19 @@ export function generateAutoLinkMappings(
432
506
  });
433
507
  } else {
434
508
  // File mapping - exact string match
435
- const sourcePattern = sourcePath.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
509
+ const sourcePattern = sourcePath.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
436
510
 
437
511
  linkMappings.push({
438
512
  pattern: new RegExp(`^${sourcePattern}$`),
439
- replacement: (transformedPath: string, anchor: string, context: any) => {
440
- const finalPath = crossSectionPath && crossSectionPath !== '/'
441
- ? `${crossSectionPath}/${targetPath}`
442
- : targetPath;
513
+ replacement: (
514
+ _transformedPath: string,
515
+ _anchor: string,
516
+ _context: LinkTransformContext,
517
+ ) => {
518
+ const finalPath =
519
+ crossSectionPath && crossSectionPath !== "/"
520
+ ? `${crossSectionPath}/${targetPath}`
521
+ : targetPath;
443
522
  return generateSiteUrl(finalPath, stripPrefixes);
444
523
  },
445
524
  global: true,
@@ -454,4 +533,4 @@ export function generateAutoLinkMappings(
454
533
  /**
455
534
  * Export types for use in configuration
456
535
  */
457
- export type { LinkContext, GlobalLinkContext };
536
+ export type { LinkContext, GlobalLinkContext };
@@ -1,27 +1,47 @@
1
- import { beforeEach, describe, it, expect } from "vitest";
1
+ import { describe, it, expect } from "vitest";
2
2
  import { githubLoader } from "./github.loader.js";
3
- import { globalLinkTransform, type ImportedFile } from "./github.link-transform.js";
3
+ import {
4
+ globalLinkTransform,
5
+ type ImportedFile,
6
+ } from "./github.link-transform.js";
4
7
  import { createLogger, type ImportSummary } from "./github.logger.js";
8
+ import type { ImportOptions, VersionConfig } from "./github.types.js";
5
9
  import { Octokit } from "octokit";
6
10
 
7
- const FIXTURES = [
8
- {
9
- owner: "awesome-algorand",
10
- repo: "algokit-cli",
11
- ref: "docs/starlight-preview",
12
- path: ".devportal/starlight",
13
- },
14
- ];
15
11
  describe("githubLoader", () => {
16
- let octokit: Octokit;
17
- beforeEach(() => {
18
- octokit = new Octokit({ auth: process.env.GITHUB_TOKEN });
12
+ it("should return a loader object", () => {
13
+ const octokit = new Octokit({ auth: "mock-token" });
14
+ const result = githubLoader({ octokit, configs: [] });
15
+ expect(result).toHaveProperty("name", "github-loader");
16
+ expect(result).toHaveProperty("load");
17
+ expect(typeof result.load).toBe("function");
19
18
  });
20
19
 
21
- it("should work", async () => {
22
- const result = githubLoader({ octokit, configs: FIXTURES });
20
+ it("should accept configs with language and versions fields", () => {
21
+ const octokit = new Octokit({ auth: "mock-token" });
22
+ const configs: ImportOptions[] = [
23
+ {
24
+ name: "AlgoKit Utils TS",
25
+ owner: "algorandfoundation",
26
+ repo: "algokit-utils-ts",
27
+ ref: "docs-dist",
28
+ language: "TypeScript",
29
+ versions: [
30
+ { slug: "latest", label: "Latest" },
31
+ { slug: "v8.0.0", label: "v8.0.0" },
32
+ ],
33
+ includes: [
34
+ {
35
+ pattern: "docs/**/*.md",
36
+ basePath: "src/content/docs/docs/algokit-utils/typescript",
37
+ },
38
+ ],
39
+ },
40
+ ];
23
41
 
24
- console.log(result);
42
+ // Should not throw when constructing the loader with new fields
43
+ const result = githubLoader({ octokit, configs });
44
+ expect(result).toHaveProperty("name", "github-loader");
25
45
  });
26
46
 
27
47
  describe("context-aware link transformations", () => {
@@ -30,76 +50,81 @@ describe("githubLoader", () => {
30
50
  {
31
51
  id: "api-readme",
32
52
  sourcePath: "docs/code/README.md",
33
- targetPath: "src/content/docs/reference/algokit-utils-ts/api/README.md",
34
- content: 'Check out the [modules](modules/) for more info.',
53
+ targetPath:
54
+ "src/content/docs/reference/algokit-utils-ts/api/README.md",
55
+ content: "Check out the [modules](modules/) for more info.",
35
56
  linkContext: {
36
57
  sourcePath: "docs/code/README.md",
37
- targetPath: "src/content/docs/reference/algokit-utils-ts/api/README.md",
58
+ targetPath:
59
+ "src/content/docs/reference/algokit-utils-ts/api/README.md",
38
60
  basePath: "src/content/docs/reference/algokit-utils-ts/api",
39
- pathMappings: { "docs/code/": "" }
40
- }
61
+ pathMappings: { "docs/code/": "" },
62
+ },
41
63
  },
42
64
  {
43
65
  id: "modules-index",
44
66
  sourcePath: "docs/code/modules/index.md",
45
- targetPath: "src/content/docs/reference/algokit-utils-ts/api/modules/index.md",
46
- content: 'This is the modules index.',
67
+ targetPath:
68
+ "src/content/docs/reference/algokit-utils-ts/api/modules/index.md",
69
+ content: "This is the modules index.",
47
70
  linkContext: {
48
71
  sourcePath: "docs/code/modules/index.md",
49
- targetPath: "src/content/docs/reference/algokit-utils-ts/api/modules/index.md",
72
+ targetPath:
73
+ "src/content/docs/reference/algokit-utils-ts/api/modules/index.md",
50
74
  basePath: "src/content/docs/reference/algokit-utils-ts/api",
51
- pathMappings: { "docs/code/": "" }
52
- }
53
- }
75
+ pathMappings: { "docs/code/": "" },
76
+ },
77
+ },
54
78
  ];
55
79
 
56
80
  const result = globalLinkTransform(testFiles, {
57
- stripPrefixes: ['src/content/docs'],
81
+ stripPrefixes: ["src/content/docs"],
58
82
  linkMappings: [
59
83
  {
60
- contextFilter: (context) => context.sourcePath.startsWith('docs/code/'),
84
+ contextFilter: (context) =>
85
+ context.sourcePath.startsWith("docs/code/"),
61
86
  relativeLinks: true,
62
87
  pattern: /.*/,
63
- replacement: '',
64
- global: false
65
- }
66
- ]
88
+ replacement: "",
89
+ global: false,
90
+ },
91
+ ],
67
92
  });
68
93
 
69
- // The relative link `modules/` should be transformed to `/reference/algokit-utils-ts/api/modules/`
70
- expect(result[0].content).toContain('[modules](/reference/algokit-utils-ts/api/modules/)');
94
+ expect(result[0].content).toContain(
95
+ "[modules](/reference/algokit-utils-ts/api/modules/)",
96
+ );
71
97
  });
72
98
  });
73
99
 
74
100
  describe("logging system", () => {
75
101
  it("should create logger with different levels", () => {
76
- const silentLogger = createLogger('silent');
77
- const defaultLogger = createLogger('default');
78
- const verboseLogger = createLogger('verbose');
79
- const debugLogger = createLogger('debug');
102
+ const silentLogger = createLogger("silent");
103
+ const defaultLogger = createLogger("default");
104
+ const verboseLogger = createLogger("verbose");
105
+ const debugLogger = createLogger("debug");
80
106
 
81
- expect(silentLogger.getLevel()).toBe('silent');
82
- expect(defaultLogger.getLevel()).toBe('default');
83
- expect(verboseLogger.getLevel()).toBe('verbose');
84
- expect(debugLogger.getLevel()).toBe('debug');
107
+ expect(silentLogger.getLevel()).toBe("silent");
108
+ expect(defaultLogger.getLevel()).toBe("default");
109
+ expect(verboseLogger.getLevel()).toBe("verbose");
110
+ expect(debugLogger.getLevel()).toBe("debug");
85
111
  });
86
112
 
87
- it("should format import summary correctly", () => {
88
- const logger = createLogger('default');
113
+ it("should format import summary without throwing", () => {
114
+ const logger = createLogger("default");
89
115
  const summary: ImportSummary = {
90
- configName: 'Test Config',
91
- repository: 'test/repo',
92
- ref: 'main',
116
+ configName: "Test Config",
117
+ repository: "test/repo",
118
+ ref: "main",
93
119
  filesProcessed: 10,
94
120
  filesUpdated: 5,
95
121
  filesUnchanged: 5,
96
122
  assetsDownloaded: 3,
97
123
  assetsCached: 2,
98
124
  duration: 1500,
99
- status: 'success'
125
+ status: "success",
100
126
  };
101
127
 
102
- // This test mainly verifies the types work correctly
103
128
  expect(() => logger.logImportSummary(summary)).not.toThrow();
104
129
  });
105
130
  });