@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.
- package/README.md +28 -55
- 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 +5 -131
- package/dist/github.content.js +152 -794
- 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 +30 -46
- 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 +34 -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 +259 -957
- 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 +101 -76
- 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 +48 -9
- package/src/index.ts +43 -6
- package/src/test-helpers.ts +215 -0
|
@@ -1,7 +1,14 @@
|
|
|
1
|
-
import { slug } from
|
|
2
|
-
import path from
|
|
3
|
-
import type {
|
|
4
|
-
|
|
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(
|
|
105
|
-
|
|
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 =
|
|
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(
|
|
114
|
-
|
|
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(
|
|
160
|
+
if (!linkPath.startsWith("/") && !isExternalLink(linkPath)) {
|
|
145
161
|
// Check if the link points to a known directory structure
|
|
146
|
-
const knownPaths = [
|
|
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(
|
|
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(/\/$/,
|
|
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 ===
|
|
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 ===
|
|
178
|
-
replacement = transformedPath.replace(
|
|
196
|
+
if (typeof mapping.replacement === "string") {
|
|
197
|
+
replacement = transformedPath.replace(
|
|
198
|
+
mapping.pattern,
|
|
199
|
+
mapping.replacement,
|
|
200
|
+
);
|
|
179
201
|
} else {
|
|
180
|
-
|
|
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 ===
|
|
189
|
-
replacement = transformedPath.replace(
|
|
216
|
+
if (typeof mapping.replacement === "string") {
|
|
217
|
+
replacement = transformedPath.replace(
|
|
218
|
+
mapping.pattern,
|
|
219
|
+
mapping.replacement,
|
|
220
|
+
);
|
|
190
221
|
} else {
|
|
191
|
-
|
|
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(
|
|
228
|
-
url = url.replace(
|
|
229
|
-
} else if (url ===
|
|
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
|
|
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 =
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
355
|
+
const nonGlobalMappings = context.global.linkMappings.filter(
|
|
356
|
+
(m) => !m.global,
|
|
357
|
+
);
|
|
303
358
|
if (nonGlobalMappings.length > 0) {
|
|
304
|
-
const mappedUrl = applyLinkMappings(
|
|
305
|
-
|
|
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(
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
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(
|
|
465
|
+
for (const [sourcePath, mappingValue] of Object.entries(
|
|
466
|
+
includePattern.pathMappings,
|
|
467
|
+
)) {
|
|
407
468
|
// Handle both string and enhanced object formats
|
|
408
|
-
const targetPath =
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
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: (
|
|
420
|
-
|
|
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 =
|
|
424
|
-
|
|
425
|
-
|
|
492
|
+
if (crossSectionPath && crossSectionPath !== "/") {
|
|
493
|
+
finalPath =
|
|
494
|
+
targetPath === ""
|
|
495
|
+
? `${crossSectionPath}/${relativePath}`
|
|
496
|
+
: `${crossSectionPath}/${targetPath}${relativePath}`;
|
|
426
497
|
} else {
|
|
427
|
-
finalPath =
|
|
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: (
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
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 {
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
2
|
import { githubLoader } from "./github.loader.js";
|
|
3
|
-
import {
|
|
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
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
|
22
|
-
const
|
|
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
|
-
|
|
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:
|
|
34
|
-
|
|
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:
|
|
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:
|
|
46
|
-
|
|
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:
|
|
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: [
|
|
81
|
+
stripPrefixes: ["src/content/docs"],
|
|
58
82
|
linkMappings: [
|
|
59
83
|
{
|
|
60
|
-
contextFilter: (context) =>
|
|
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
|
-
|
|
70
|
-
|
|
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(
|
|
77
|
-
const defaultLogger = createLogger(
|
|
78
|
-
const verboseLogger = createLogger(
|
|
79
|
-
const debugLogger = createLogger(
|
|
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(
|
|
82
|
-
expect(defaultLogger.getLevel()).toBe(
|
|
83
|
-
expect(verboseLogger.getLevel()).toBe(
|
|
84
|
-
expect(debugLogger.getLevel()).toBe(
|
|
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
|
|
88
|
-
const logger = createLogger(
|
|
113
|
+
it("should format import summary without throwing", () => {
|
|
114
|
+
const logger = createLogger("default");
|
|
89
115
|
const summary: ImportSummary = {
|
|
90
|
-
configName:
|
|
91
|
-
repository:
|
|
92
|
-
ref:
|
|
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:
|
|
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
|
});
|