@larkiny/astro-github-loader 0.11.3 → 0.13.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 +35 -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 +49 -25
  13. package/dist/github.link-transform.d.ts +2 -2
  14. package/dist/github.link-transform.js +68 -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 +16 -0
  21. package/dist/github.storage.js +115 -0
  22. package/dist/github.types.d.ts +40 -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 +598 -0
  38. package/src/github.dryrun.ts +108 -54
  39. package/src/github.link-transform.spec.ts +1345 -0
  40. package/src/github.link-transform.ts +177 -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 +377 -0
  48. package/src/github.storage.ts +135 -0
  49. package/src/github.types.ts +54 -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,30 @@ function applyLinkMappings(
168
187
  }
169
188
 
170
189
  let matched = false;
171
- let replacement = '';
190
+ let replacement = "";
191
+
192
+ const getLinkTransformContext = (): LinkTransformContext =>
193
+ context.currentFile.linkContext ?? {
194
+ sourcePath: context.currentFile.sourcePath,
195
+ targetPath: context.currentFile.targetPath,
196
+ basePath: "",
197
+ };
172
198
 
173
- if (typeof mapping.pattern === 'string') {
199
+ if (typeof mapping.pattern === "string") {
174
200
  // String pattern - exact match or contains
175
201
  if (transformedPath.includes(mapping.pattern)) {
176
202
  matched = true;
177
- if (typeof mapping.replacement === 'string') {
178
- replacement = transformedPath.replace(mapping.pattern, mapping.replacement);
203
+ if (typeof mapping.replacement === "string") {
204
+ replacement = transformedPath.replace(
205
+ mapping.pattern,
206
+ mapping.replacement,
207
+ );
179
208
  } else {
180
- replacement = mapping.replacement(transformedPath, anchor, context);
209
+ replacement = mapping.replacement(
210
+ transformedPath,
211
+ anchor,
212
+ getLinkTransformContext(),
213
+ );
181
214
  }
182
215
  }
183
216
  } else {
@@ -185,10 +218,17 @@ function applyLinkMappings(
185
218
  const match = transformedPath.match(mapping.pattern);
186
219
  if (match) {
187
220
  matched = true;
188
- if (typeof mapping.replacement === 'string') {
189
- replacement = transformedPath.replace(mapping.pattern, mapping.replacement);
221
+ if (typeof mapping.replacement === "string") {
222
+ replacement = transformedPath.replace(
223
+ mapping.pattern,
224
+ mapping.replacement,
225
+ );
190
226
  } else {
191
- replacement = mapping.replacement(transformedPath, anchor, context);
227
+ replacement = mapping.replacement(
228
+ transformedPath,
229
+ anchor,
230
+ getLinkTransformContext(),
231
+ );
192
232
  }
193
233
  }
194
234
  }
@@ -218,35 +258,37 @@ function generateSiteUrl(targetPath: string, stripPrefixes: string[]): string {
218
258
  }
219
259
 
220
260
  // Remove leading slash if present
221
- url = url.replace(/^\//, '');
261
+ url = url.replace(/^\//, "");
222
262
 
223
263
  // Remove file extension
224
- url = url.replace(/\.(md|mdx)$/i, '');
264
+ url = url.replace(/\.(md|mdx)$/i, "");
225
265
 
226
266
  // 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 = '';
267
+ if (url.endsWith("/index")) {
268
+ url = url.replace("/index", "");
269
+ } else if (url === "index") {
270
+ url = "";
231
271
  }
232
272
 
233
273
  // Split path into segments and slugify each
234
- const segments = url.split('/').map(segment => segment ? slug(segment) : '');
274
+ const segments = url
275
+ .split("/")
276
+ .map((segment) => (segment ? slug(segment) : ""));
235
277
 
236
278
  // Reconstruct URL
237
- url = segments.filter(s => s).join('/');
279
+ url = segments.filter((s) => s).join("/");
238
280
 
239
281
  // Ensure leading slash
240
- if (url && !url.startsWith('/')) {
241
- url = '/' + url;
282
+ if (url && !url.startsWith("/")) {
283
+ url = "/" + url;
242
284
  }
243
285
 
244
286
  // Add trailing slash for non-empty paths
245
- if (url && !url.endsWith('/')) {
246
- url = url + '/';
287
+ if (url && !url.endsWith("/")) {
288
+ url = url + "/";
247
289
  }
248
290
 
249
- return url || '/';
291
+ return url || "/";
250
292
  }
251
293
 
252
294
  /**
@@ -260,7 +302,11 @@ function generateSiteUrl(targetPath: string, stripPrefixes: string[]): string {
260
302
  * 5. Apply non-global path mappings if unresolved
261
303
  * 6. Check custom handlers
262
304
  */
263
- function transformLink(linkText: string, linkUrl: string, context: LinkContext): string {
305
+ function transformLink(
306
+ linkText: string,
307
+ linkUrl: string,
308
+ context: LinkContext,
309
+ ): string {
264
310
  // Skip external links FIRST - no transformations should ever be applied to them
265
311
  if (isExternalLink(linkUrl)) {
266
312
  return `[${linkText}](${linkUrl})`;
@@ -269,14 +315,22 @@ function transformLink(linkText: string, linkUrl: string, context: LinkContext):
269
315
  const { path: linkPath, anchor } = extractAnchor(linkUrl);
270
316
 
271
317
  // Normalize the link path relative to current file FIRST
272
- const normalizedPath = normalizePath(linkPath, context.currentFile.sourcePath, context.global.logger);
318
+ const normalizedPath = normalizePath(
319
+ linkPath,
320
+ context.currentFile.sourcePath,
321
+ context.global.logger,
322
+ );
273
323
 
274
324
  // Apply global path mappings to the normalized path
275
325
  let processedNormalizedPath = normalizedPath;
276
326
  if (context.global.linkMappings) {
277
- const globalMappings = context.global.linkMappings.filter(m => m.global);
327
+ const globalMappings = context.global.linkMappings.filter((m) => m.global);
278
328
  if (globalMappings.length > 0) {
279
- processedNormalizedPath = applyLinkMappings(normalizedPath + anchor, globalMappings, context);
329
+ processedNormalizedPath = applyLinkMappings(
330
+ normalizedPath + anchor,
331
+ globalMappings,
332
+ context,
333
+ );
280
334
  // Extract path again after global mappings
281
335
  const { path: newPath } = extractAnchor(processedNormalizedPath);
282
336
  processedNormalizedPath = newPath;
@@ -287,8 +341,10 @@ function transformLink(linkText: string, linkUrl: string, context: LinkContext):
287
341
  let targetPath = context.global.sourceToTargetMap.get(normalizedPath);
288
342
 
289
343
  // 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');
344
+ if (!targetPath && normalizedPath.endsWith("/")) {
345
+ targetPath = context.global.sourceToTargetMap.get(
346
+ normalizedPath + "index.md",
347
+ );
292
348
  }
293
349
 
294
350
  if (targetPath) {
@@ -299,10 +355,16 @@ function transformLink(linkText: string, linkUrl: string, context: LinkContext):
299
355
 
300
356
  // Apply non-global path mappings to unresolved links
301
357
  if (context.global.linkMappings) {
302
- const nonGlobalMappings = context.global.linkMappings.filter(m => !m.global);
358
+ const nonGlobalMappings = context.global.linkMappings.filter(
359
+ (m) => !m.global,
360
+ );
303
361
  if (nonGlobalMappings.length > 0) {
304
- const mappedUrl = applyLinkMappings(processedNormalizedPath + anchor, nonGlobalMappings, context);
305
- if (mappedUrl !== (processedNormalizedPath + anchor)) {
362
+ const mappedUrl = applyLinkMappings(
363
+ processedNormalizedPath + anchor,
364
+ nonGlobalMappings,
365
+ context,
366
+ );
367
+ if (mappedUrl !== processedNormalizedPath + anchor) {
306
368
  return `[${linkText}](${mappedUrl})`;
307
369
  }
308
370
  }
@@ -321,7 +383,7 @@ function transformLink(linkText: string, linkUrl: string, context: LinkContext):
321
383
 
322
384
  // No transformation matched - strip .md extension from unresolved internal links
323
385
  // This handles links to files that weren't imported but should still use Starlight routing
324
- const cleanPath = processedNormalizedPath.replace(/\.md$/i, '');
386
+ const cleanPath = processedNormalizedPath.replace(/\.md$/i, "");
325
387
  return `[${linkText}](${cleanPath + anchor})`;
326
388
  }
327
389
 
@@ -336,7 +398,7 @@ export function globalLinkTransform(
336
398
  customHandlers?: LinkHandler[];
337
399
  linkMappings?: LinkMapping[];
338
400
  logger?: Logger;
339
- }
401
+ },
340
402
  ): ImportedFile[] {
341
403
  // Build global context
342
404
  const sourceToTargetMap = new Map<string, string>();
@@ -359,31 +421,31 @@ export function globalLinkTransform(
359
421
  // Transform links in all files
360
422
  const markdownLinkRegex = /\[([^\]]*)\]\(([^)]+)\)/g;
361
423
 
362
- return importedFiles.map(file => ({
424
+ return importedFiles.map((file) => ({
363
425
  ...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
- }),
426
+ content: file.content.replace(
427
+ markdownLinkRegex,
428
+ (match, linkText, linkUrl) => {
429
+ const linkContext: LinkContext = {
430
+ currentFile: file,
431
+ originalLink: linkUrl,
432
+ anchor: extractAnchor(linkUrl).anchor,
433
+ global: globalContext,
434
+ };
435
+
436
+ return transformLink(linkText, linkUrl, linkContext);
437
+ },
438
+ ),
374
439
  }));
375
440
  }
376
441
 
377
-
378
442
  /**
379
443
  * Infer cross-section path from basePath
380
444
  * @param basePath - The base path from include pattern (e.g., 'src/content/docs/reference/api')
381
445
  * @returns Inferred cross-section path (e.g., '/reference/api')
382
446
  */
383
447
  function inferCrossSectionPath(basePath: string): string {
384
- return basePath
385
- .replace(/^src\/content\/docs/, '')
386
- .replace(/\/$/, '') || '/';
448
+ return basePath.replace(/^src\/content\/docs/, "").replace(/\/$/, "") || "/";
387
449
  }
388
450
 
389
451
  /**
@@ -394,7 +456,7 @@ function inferCrossSectionPath(basePath: string): string {
394
456
  */
395
457
  export function generateAutoLinkMappings(
396
458
  includes: IncludePattern[],
397
- stripPrefixes: string[] = []
459
+ stripPrefixes: string[] = [],
398
460
  ): LinkMapping[] {
399
461
  const linkMappings: LinkMapping[] = [];
400
462
 
@@ -403,28 +465,43 @@ export function generateAutoLinkMappings(
403
465
 
404
466
  const inferredCrossSection = inferCrossSectionPath(includePattern.basePath);
405
467
 
406
- for (const [sourcePath, mappingValue] of Object.entries(includePattern.pathMappings)) {
468
+ for (const [sourcePath, mappingValue] of Object.entries(
469
+ includePattern.pathMappings,
470
+ )) {
407
471
  // 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('/')) {
472
+ const targetPath =
473
+ typeof mappingValue === "string" ? mappingValue : mappingValue.target;
474
+ const crossSectionPath =
475
+ typeof mappingValue === "object" && mappingValue.crossSectionPath
476
+ ? mappingValue.crossSectionPath
477
+ : inferredCrossSection;
478
+
479
+ if (sourcePath.endsWith("/")) {
414
480
  // Folder mapping - use regex with capture group
415
- const sourcePattern = sourcePath.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
481
+ const sourcePattern = sourcePath.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
416
482
 
417
483
  linkMappings.push({
418
484
  pattern: new RegExp(`^${sourcePattern}(.+)$`),
419
- replacement: (transformedPath: string, anchor: string, context: any) => {
420
- const relativePath = transformedPath.replace(new RegExp(`^${sourcePattern}`), '');
485
+ replacement: (
486
+ transformedPath: string,
487
+ _anchor: string,
488
+ _context: LinkTransformContext,
489
+ ) => {
490
+ const relativePath = transformedPath.replace(
491
+ new RegExp(`^${sourcePattern}`),
492
+ "",
493
+ );
421
494
  let finalPath: string;
422
- if (crossSectionPath && crossSectionPath !== '/') {
423
- finalPath = targetPath === ''
424
- ? `${crossSectionPath}/${relativePath}`
425
- : `${crossSectionPath}/${targetPath}${relativePath}`;
495
+ if (crossSectionPath && crossSectionPath !== "/") {
496
+ finalPath =
497
+ targetPath === ""
498
+ ? `${crossSectionPath}/${relativePath}`
499
+ : `${crossSectionPath}/${targetPath}${relativePath}`;
426
500
  } else {
427
- finalPath = targetPath === '' ? relativePath : `${targetPath}${relativePath}`;
501
+ finalPath =
502
+ targetPath === ""
503
+ ? relativePath
504
+ : `${targetPath}${relativePath}`;
428
505
  }
429
506
  return generateSiteUrl(finalPath, stripPrefixes);
430
507
  },
@@ -432,14 +509,19 @@ export function generateAutoLinkMappings(
432
509
  });
433
510
  } else {
434
511
  // File mapping - exact string match
435
- const sourcePattern = sourcePath.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
512
+ const sourcePattern = sourcePath.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
436
513
 
437
514
  linkMappings.push({
438
515
  pattern: new RegExp(`^${sourcePattern}$`),
439
- replacement: (transformedPath: string, anchor: string, context: any) => {
440
- const finalPath = crossSectionPath && crossSectionPath !== '/'
441
- ? `${crossSectionPath}/${targetPath}`
442
- : targetPath;
516
+ replacement: (
517
+ _transformedPath: string,
518
+ _anchor: string,
519
+ _context: LinkTransformContext,
520
+ ) => {
521
+ const finalPath =
522
+ crossSectionPath && crossSectionPath !== "/"
523
+ ? `${crossSectionPath}/${targetPath}`
524
+ : targetPath;
443
525
  return generateSiteUrl(finalPath, stripPrefixes);
444
526
  },
445
527
  global: true,
@@ -454,4 +536,4 @@ export function generateAutoLinkMappings(
454
536
  /**
455
537
  * Export types for use in configuration
456
538
  */
457
- export type { LinkContext, GlobalLinkContext };
539
+ 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
  });