@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,3 +1,5 @@
1
+ import { Octokit } from "octokit";
2
+ import type { Logger } from "./github.logger.js";
1
3
  import type { ImportOptions, LoaderContext } from "./github.types.js";
2
4
  /**
3
5
  * Represents the state of a single import configuration
@@ -49,11 +51,11 @@ export declare function createConfigId(config: ImportOptions): string;
49
51
  /**
50
52
  * Loads the import state from the state file
51
53
  */
52
- export declare function loadImportState(workingDir: string): Promise<StateFile>;
54
+ export declare function loadImportState(workingDir: string, logger?: Logger): Promise<StateFile>;
53
55
  /**
54
56
  * Gets the latest commit information for a repository path
55
57
  */
56
- export declare function getLatestCommitInfo(octokit: any, config: ImportOptions, signal?: AbortSignal): Promise<{
58
+ export declare function getLatestCommitInfo(octokit: Octokit, config: ImportOptions, signal?: AbortSignal): Promise<{
57
59
  sha: string;
58
60
  message: string;
59
61
  date: string;
@@ -61,12 +63,14 @@ export declare function getLatestCommitInfo(octokit: any, config: ImportOptions,
61
63
  /**
62
64
  * Updates the import state after a successful import
63
65
  */
64
- export declare function updateImportState(workingDir: string, config: ImportOptions, commitSha?: string): Promise<void>;
66
+ export declare function updateImportState(workingDir: string, config: ImportOptions, commitSha?: string, logger?: Logger): Promise<void>;
65
67
  /**
66
68
  * Performs a dry run check on all configured repositories
67
69
  */
68
- export declare function performDryRun(configs: ImportOptions[], context: LoaderContext, octokit: any, workingDir?: string, signal?: AbortSignal): Promise<RepositoryChangeInfo[]>;
70
+ export declare function performDryRun(configs: ImportOptions[], context: LoaderContext, octokit: Octokit, workingDir?: string, signal?: AbortSignal): Promise<RepositoryChangeInfo[]>;
69
71
  /**
70
72
  * Formats and displays the dry run results
71
73
  */
72
- export declare function displayDryRunResults(results: RepositoryChangeInfo[], logger: any): void;
74
+ export declare function displayDryRunResults(results: RepositoryChangeInfo[], logger: {
75
+ info: (msg: string) => void;
76
+ }): void;
@@ -1,46 +1,64 @@
1
1
  import { promises as fs } from "node:fs";
2
2
  import { existsSync } from "node:fs";
3
3
  import { join } from "node:path";
4
- const STATE_FILENAME = '.github-import-state.json';
4
+ const STATE_FILENAME = ".github-import-state.json";
5
5
  /**
6
6
  * Creates a unique identifier for an import configuration
7
7
  */
8
8
  export function createConfigId(config) {
9
- return `${config.owner}/${config.repo}@${config.ref || 'main'}`;
9
+ if (config.stateKey) {
10
+ return config.stateKey;
11
+ }
12
+ return `${config.owner}/${config.repo}@${config.ref || "main"}`;
10
13
  }
11
14
  /**
12
15
  * Loads the import state from the state file
13
16
  */
14
- export async function loadImportState(workingDir) {
17
+ export async function loadImportState(workingDir, logger) {
15
18
  const statePath = join(workingDir, STATE_FILENAME);
16
19
  if (!existsSync(statePath)) {
17
20
  return {
18
21
  imports: {},
19
- lastChecked: new Date().toISOString()
22
+ lastChecked: new Date().toISOString(),
20
23
  };
21
24
  }
22
25
  try {
23
- const content = await fs.readFile(statePath, 'utf-8');
24
- return JSON.parse(content);
26
+ const content = await fs.readFile(statePath, "utf-8");
27
+ const parsed = JSON.parse(content);
28
+ // Validate the parsed state has the expected shape
29
+ if (!parsed ||
30
+ typeof parsed !== "object" ||
31
+ !("imports" in parsed) ||
32
+ typeof parsed.imports !== "object") {
33
+ const msg = `Malformed state file at ${statePath}, starting fresh`;
34
+ // eslint-disable-next-line no-console -- fallback when no logger provided
35
+ logger ? logger.warn(msg) : console.warn(msg);
36
+ return { imports: {}, lastChecked: new Date().toISOString() };
37
+ }
38
+ return parsed;
25
39
  }
26
40
  catch (error) {
27
- console.warn(`Failed to load import state from ${statePath}, starting fresh:`, error);
41
+ const msg = `Failed to load import state from ${statePath}, starting fresh: ${error}`;
42
+ // eslint-disable-next-line no-console -- fallback when no logger provided
43
+ logger ? logger.warn(msg) : console.warn(msg);
28
44
  return {
29
45
  imports: {},
30
- lastChecked: new Date().toISOString()
46
+ lastChecked: new Date().toISOString(),
31
47
  };
32
48
  }
33
49
  }
34
50
  /**
35
51
  * Saves the import state to the state file
36
52
  */
37
- async function saveImportState(workingDir, state) {
53
+ async function saveImportState(workingDir, state, logger) {
38
54
  const statePath = join(workingDir, STATE_FILENAME);
39
55
  try {
40
- await fs.writeFile(statePath, JSON.stringify(state, null, 2), 'utf-8');
56
+ await fs.writeFile(statePath, JSON.stringify(state, null, 2), "utf-8");
41
57
  }
42
58
  catch (error) {
43
- console.warn(`Failed to save import state to ${statePath}:`, error);
59
+ const msg = `Failed to save import state to ${statePath}: ${error}`;
60
+ // eslint-disable-next-line no-console -- fallback when no logger provided
61
+ logger ? logger.warn(msg) : console.warn(msg);
44
62
  }
45
63
  }
46
64
  /**
@@ -55,7 +73,7 @@ export async function getLatestCommitInfo(octokit, config, signal) {
55
73
  repo,
56
74
  sha: ref,
57
75
  per_page: 1,
58
- request: { signal }
76
+ request: { signal },
59
77
  });
60
78
  if (data.length === 0) {
61
79
  return null;
@@ -63,12 +81,17 @@ export async function getLatestCommitInfo(octokit, config, signal) {
63
81
  const latestCommit = data[0];
64
82
  return {
65
83
  sha: latestCommit.sha,
66
- message: latestCommit.commit.message.split('\n')[0], // First line only
67
- date: latestCommit.commit.committer?.date || latestCommit.commit.author?.date || new Date().toISOString()
84
+ message: latestCommit.commit.message.split("\n")[0], // First line only
85
+ date: latestCommit.commit.committer?.date ||
86
+ latestCommit.commit.author?.date ||
87
+ new Date().toISOString(),
68
88
  };
69
89
  }
70
90
  catch (error) {
71
- if (error.status === 404) {
91
+ if (typeof error === "object" &&
92
+ error !== null &&
93
+ "status" in error &&
94
+ error.status === 404) {
72
95
  throw new Error(`Repository not found: ${owner}/${repo}`);
73
96
  }
74
97
  throw error;
@@ -86,17 +109,18 @@ async function checkRepositoryForChanges(octokit, config, currentState, signal)
86
109
  config,
87
110
  state: currentState,
88
111
  needsReimport: false,
89
- error: "No commits found in repository"
112
+ error: "No commits found in repository",
90
113
  };
91
114
  }
92
- const needsReimport = !currentState.lastCommitSha || currentState.lastCommitSha !== latestCommit.sha;
115
+ const needsReimport = !currentState.lastCommitSha ||
116
+ currentState.lastCommitSha !== latestCommit.sha;
93
117
  return {
94
118
  config,
95
119
  state: currentState,
96
120
  needsReimport,
97
121
  latestCommitSha: latestCommit.sha,
98
122
  latestCommitMessage: latestCommit.message,
99
- latestCommitDate: latestCommit.date
123
+ latestCommitDate: latestCommit.date,
100
124
  };
101
125
  }
102
126
  catch (error) {
@@ -104,15 +128,15 @@ async function checkRepositoryForChanges(octokit, config, currentState, signal)
104
128
  config,
105
129
  state: currentState,
106
130
  needsReimport: false,
107
- error: error.message
131
+ error: error instanceof Error ? error.message : String(error),
108
132
  };
109
133
  }
110
134
  }
111
135
  /**
112
136
  * Updates the import state after a successful import
113
137
  */
114
- export async function updateImportState(workingDir, config, commitSha) {
115
- const state = await loadImportState(workingDir);
138
+ export async function updateImportState(workingDir, config, commitSha, logger) {
139
+ const state = await loadImportState(workingDir, logger);
116
140
  const configId = createConfigId(config);
117
141
  const configName = config.name || `${config.owner}/${config.repo}`;
118
142
  state.imports[configId] = {
@@ -120,9 +144,9 @@ export async function updateImportState(workingDir, config, commitSha) {
120
144
  repoId: configId,
121
145
  lastCommitSha: commitSha,
122
146
  lastImported: new Date().toISOString(),
123
- ref: config.ref || 'main'
147
+ ref: config.ref || "main",
124
148
  };
125
- await saveImportState(workingDir, state);
149
+ await saveImportState(workingDir, state, logger);
126
150
  }
127
151
  /**
128
152
  * Performs a dry run check on all configured repositories
@@ -145,7 +169,7 @@ export async function performDryRun(configs, context, octokit, workingDir = proc
145
169
  const currentState = state.imports[configId] || {
146
170
  name: configName,
147
171
  repoId: configId,
148
- ref: config.ref || 'main'
172
+ ref: config.ref || "main",
149
173
  };
150
174
  logger.debug(`Checking ${configName}...`);
151
175
  try {
@@ -159,7 +183,7 @@ export async function performDryRun(configs, context, octokit, workingDir = proc
159
183
  config,
160
184
  state: currentState,
161
185
  needsReimport: false,
162
- error: `Failed to check repository: ${error.message}`
186
+ error: `Failed to check repository: ${error instanceof Error ? error.message : String(error)}`,
163
187
  });
164
188
  }
165
189
  }
@@ -1,5 +1,5 @@
1
- import type { LinkMapping, LinkTransformContext, IncludePattern } from './github.types.js';
2
- import type { Logger } from './github.logger.js';
1
+ import type { LinkMapping, LinkTransformContext, IncludePattern } from "./github.types.js";
2
+ import type { Logger } from "./github.logger.js";
3
3
  /**
4
4
  * Represents an imported file with its content and metadata
5
5
  */
@@ -1,12 +1,12 @@
1
- import { slug } from 'github-slugger';
2
- import path from 'node:path';
1
+ import { slug } from "github-slugger";
2
+ import path from "node:path";
3
3
  /**
4
4
  * Extract anchor fragment from a link
5
5
  */
6
6
  function extractAnchor(link) {
7
7
  const anchorMatch = link.match(/#.*$/);
8
- const anchor = anchorMatch ? anchorMatch[0] : '';
9
- const path = link.replace(/#.*$/, '');
8
+ const anchor = anchorMatch ? anchorMatch[0] : "";
9
+ const path = link.replace(/#.*$/, "");
10
10
  return { path, anchor };
11
11
  }
12
12
  /**
@@ -22,9 +22,9 @@ function isExternalLink(link) {
22
22
  /^ftp:/.test(link) ||
23
23
  /^ftps:\/\//.test(link) ||
24
24
  // Any protocol with ://
25
- link.includes('://') ||
25
+ link.includes("://") ||
26
26
  // Anchor-only links (same page)
27
- link.startsWith('#') ||
27
+ link.startsWith("#") ||
28
28
  // Data URLs
29
29
  /^data:/.test(link) ||
30
30
  // File protocol
@@ -37,7 +37,9 @@ function normalizePath(linkPath, currentFilePath, logger) {
37
37
  logger?.debug(`[normalizePath] BEFORE: linkPath="${linkPath}", currentFilePath="${currentFilePath}"`);
38
38
  // Handle relative paths (including simple relative paths without ./ prefix)
39
39
  // A link is relative if it doesn't start with / or contain a protocol
40
- const isAbsoluteOrExternal = linkPath.startsWith('/') || linkPath.includes('://') || linkPath.startsWith('#');
40
+ const isAbsoluteOrExternal = linkPath.startsWith("/") ||
41
+ linkPath.includes("://") ||
42
+ linkPath.startsWith("#");
41
43
  if (!isAbsoluteOrExternal) {
42
44
  const currentDir = path.dirname(currentFilePath);
43
45
  const resolved = path.posix.normalize(path.posix.join(currentDir, linkPath));
@@ -63,20 +65,20 @@ function applyLinkMappings(linkUrl, linkMappings, context) {
63
65
  // Handle relative links automatically if enabled
64
66
  if (mapping.relativeLinks && context.currentFile.linkContext) {
65
67
  // Check if this is a relative link (doesn't start with /, http, etc.)
66
- if (!linkPath.startsWith('/') && !isExternalLink(linkPath)) {
68
+ if (!linkPath.startsWith("/") && !isExternalLink(linkPath)) {
67
69
  // Check if the link points to a known directory structure
68
- const knownPaths = ['modules/', 'classes/', 'interfaces/', 'enums/'];
69
- const isKnownPath = knownPaths.some(p => linkPath.startsWith(p));
70
+ const knownPaths = ["modules/", "classes/", "interfaces/", "enums/"];
71
+ const isKnownPath = knownPaths.some((p) => linkPath.startsWith(p));
70
72
  if (isKnownPath) {
71
73
  // Strip .md extension from the link path
72
- const cleanLinkPath = linkPath.replace(/\.md$/, '');
74
+ const cleanLinkPath = linkPath.replace(/\.md$/, "");
73
75
  // Convert relative path to absolute path using the target base
74
76
  const targetBase = generateSiteUrl(context.currentFile.linkContext.basePath, context.global.stripPrefixes);
75
77
  // Construct final URL with proper Starlight formatting
76
- let finalUrl = targetBase.replace(/\/$/, '') + '/' + cleanLinkPath;
78
+ let finalUrl = targetBase.replace(/\/$/, "") + "/" + cleanLinkPath;
77
79
  // Add trailing slash if it doesn't end with one and isn't empty
78
- if (finalUrl && !finalUrl.endsWith('/')) {
79
- finalUrl += '/';
80
+ if (finalUrl && !finalUrl.endsWith("/")) {
81
+ finalUrl += "/";
80
82
  }
81
83
  transformedPath = finalUrl;
82
84
  return transformedPath + anchor;
@@ -84,16 +86,21 @@ function applyLinkMappings(linkUrl, linkMappings, context) {
84
86
  }
85
87
  }
86
88
  let matched = false;
87
- let replacement = '';
88
- if (typeof mapping.pattern === 'string') {
89
+ let replacement = "";
90
+ const getLinkTransformContext = () => context.currentFile.linkContext ?? {
91
+ sourcePath: context.currentFile.sourcePath,
92
+ targetPath: context.currentFile.targetPath,
93
+ basePath: "",
94
+ };
95
+ if (typeof mapping.pattern === "string") {
89
96
  // String pattern - exact match or contains
90
97
  if (transformedPath.includes(mapping.pattern)) {
91
98
  matched = true;
92
- if (typeof mapping.replacement === 'string') {
99
+ if (typeof mapping.replacement === "string") {
93
100
  replacement = transformedPath.replace(mapping.pattern, mapping.replacement);
94
101
  }
95
102
  else {
96
- replacement = mapping.replacement(transformedPath, anchor, context);
103
+ replacement = mapping.replacement(transformedPath, anchor, getLinkTransformContext());
97
104
  }
98
105
  }
99
106
  }
@@ -102,11 +109,11 @@ function applyLinkMappings(linkUrl, linkMappings, context) {
102
109
  const match = transformedPath.match(mapping.pattern);
103
110
  if (match) {
104
111
  matched = true;
105
- if (typeof mapping.replacement === 'string') {
112
+ if (typeof mapping.replacement === "string") {
106
113
  replacement = transformedPath.replace(mapping.pattern, mapping.replacement);
107
114
  }
108
115
  else {
109
- replacement = mapping.replacement(transformedPath, anchor, context);
116
+ replacement = mapping.replacement(transformedPath, anchor, getLinkTransformContext());
110
117
  }
111
118
  }
112
119
  }
@@ -131,29 +138,31 @@ function generateSiteUrl(targetPath, stripPrefixes) {
131
138
  }
132
139
  }
133
140
  // Remove leading slash if present
134
- url = url.replace(/^\//, '');
141
+ url = url.replace(/^\//, "");
135
142
  // Remove file extension
136
- url = url.replace(/\.(md|mdx)$/i, '');
143
+ url = url.replace(/\.(md|mdx)$/i, "");
137
144
  // Handle index files - they should resolve to parent directory
138
- if (url.endsWith('/index')) {
139
- url = url.replace('/index', '');
145
+ if (url.endsWith("/index")) {
146
+ url = url.replace("/index", "");
140
147
  }
141
- else if (url === 'index') {
142
- url = '';
148
+ else if (url === "index") {
149
+ url = "";
143
150
  }
144
151
  // Split path into segments and slugify each
145
- const segments = url.split('/').map(segment => segment ? slug(segment) : '');
152
+ const segments = url
153
+ .split("/")
154
+ .map((segment) => (segment ? slug(segment) : ""));
146
155
  // Reconstruct URL
147
- url = segments.filter(s => s).join('/');
156
+ url = segments.filter((s) => s).join("/");
148
157
  // Ensure leading slash
149
- if (url && !url.startsWith('/')) {
150
- url = '/' + url;
158
+ if (url && !url.startsWith("/")) {
159
+ url = "/" + url;
151
160
  }
152
161
  // Add trailing slash for non-empty paths
153
- if (url && !url.endsWith('/')) {
154
- url = url + '/';
162
+ if (url && !url.endsWith("/")) {
163
+ url = url + "/";
155
164
  }
156
- return url || '/';
165
+ return url || "/";
157
166
  }
158
167
  /**
159
168
  * Transform a single markdown link
@@ -177,7 +186,7 @@ function transformLink(linkText, linkUrl, context) {
177
186
  // Apply global path mappings to the normalized path
178
187
  let processedNormalizedPath = normalizedPath;
179
188
  if (context.global.linkMappings) {
180
- const globalMappings = context.global.linkMappings.filter(m => m.global);
189
+ const globalMappings = context.global.linkMappings.filter((m) => m.global);
181
190
  if (globalMappings.length > 0) {
182
191
  processedNormalizedPath = applyLinkMappings(normalizedPath + anchor, globalMappings, context);
183
192
  // Extract path again after global mappings
@@ -188,8 +197,8 @@ function transformLink(linkText, linkUrl, context) {
188
197
  // Check if this links to an imported file
189
198
  let targetPath = context.global.sourceToTargetMap.get(normalizedPath);
190
199
  // If not found and path ends with /, try looking for index.md
191
- if (!targetPath && normalizedPath.endsWith('/')) {
192
- targetPath = context.global.sourceToTargetMap.get(normalizedPath + 'index.md');
200
+ if (!targetPath && normalizedPath.endsWith("/")) {
201
+ targetPath = context.global.sourceToTargetMap.get(normalizedPath + "index.md");
193
202
  }
194
203
  if (targetPath) {
195
204
  // This is an internal link to an imported file
@@ -198,10 +207,10 @@ function transformLink(linkText, linkUrl, context) {
198
207
  }
199
208
  // Apply non-global path mappings to unresolved links
200
209
  if (context.global.linkMappings) {
201
- const nonGlobalMappings = context.global.linkMappings.filter(m => !m.global);
210
+ const nonGlobalMappings = context.global.linkMappings.filter((m) => !m.global);
202
211
  if (nonGlobalMappings.length > 0) {
203
212
  const mappedUrl = applyLinkMappings(processedNormalizedPath + anchor, nonGlobalMappings, context);
204
- if (mappedUrl !== (processedNormalizedPath + anchor)) {
213
+ if (mappedUrl !== processedNormalizedPath + anchor) {
205
214
  return `[${linkText}](${mappedUrl})`;
206
215
  }
207
216
  }
@@ -218,7 +227,7 @@ function transformLink(linkText, linkUrl, context) {
218
227
  }
219
228
  // No transformation matched - strip .md extension from unresolved internal links
220
229
  // This handles links to files that weren't imported but should still use Starlight routing
221
- const cleanPath = processedNormalizedPath.replace(/\.md$/i, '');
230
+ const cleanPath = processedNormalizedPath.replace(/\.md$/i, "");
222
231
  return `[${linkText}](${cleanPath + anchor})`;
223
232
  }
224
233
  /**
@@ -243,7 +252,7 @@ export function globalLinkTransform(importedFiles, options) {
243
252
  };
244
253
  // Transform links in all files
245
254
  const markdownLinkRegex = /\[([^\]]*)\]\(([^)]+)\)/g;
246
- return importedFiles.map(file => ({
255
+ return importedFiles.map((file) => ({
247
256
  ...file,
248
257
  content: file.content.replace(markdownLinkRegex, (match, linkText, linkUrl) => {
249
258
  const linkContext = {
@@ -262,9 +271,7 @@ export function globalLinkTransform(importedFiles, options) {
262
271
  * @returns Inferred cross-section path (e.g., '/reference/api')
263
272
  */
264
273
  function inferCrossSectionPath(basePath) {
265
- return basePath
266
- .replace(/^src\/content\/docs/, '')
267
- .replace(/\/$/, '') || '/';
274
+ return basePath.replace(/^src\/content\/docs/, "").replace(/\/$/, "") || "/";
268
275
  }
269
276
  /**
270
277
  * Generate link mappings automatically from pathMappings in include patterns
@@ -280,25 +287,29 @@ export function generateAutoLinkMappings(includes, stripPrefixes = []) {
280
287
  const inferredCrossSection = inferCrossSectionPath(includePattern.basePath);
281
288
  for (const [sourcePath, mappingValue] of Object.entries(includePattern.pathMappings)) {
282
289
  // Handle both string and enhanced object formats
283
- const targetPath = typeof mappingValue === 'string' ? mappingValue : mappingValue.target;
284
- const crossSectionPath = typeof mappingValue === 'object' && mappingValue.crossSectionPath
290
+ const targetPath = typeof mappingValue === "string" ? mappingValue : mappingValue.target;
291
+ const crossSectionPath = typeof mappingValue === "object" && mappingValue.crossSectionPath
285
292
  ? mappingValue.crossSectionPath
286
293
  : inferredCrossSection;
287
- if (sourcePath.endsWith('/')) {
294
+ if (sourcePath.endsWith("/")) {
288
295
  // Folder mapping - use regex with capture group
289
- const sourcePattern = sourcePath.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
296
+ const sourcePattern = sourcePath.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
290
297
  linkMappings.push({
291
298
  pattern: new RegExp(`^${sourcePattern}(.+)$`),
292
- replacement: (transformedPath, anchor, context) => {
293
- const relativePath = transformedPath.replace(new RegExp(`^${sourcePattern}`), '');
299
+ replacement: (transformedPath, _anchor, _context) => {
300
+ const relativePath = transformedPath.replace(new RegExp(`^${sourcePattern}`), "");
294
301
  let finalPath;
295
- if (crossSectionPath && crossSectionPath !== '/') {
296
- finalPath = targetPath === ''
297
- ? `${crossSectionPath}/${relativePath}`
298
- : `${crossSectionPath}/${targetPath}${relativePath}`;
302
+ if (crossSectionPath && crossSectionPath !== "/") {
303
+ finalPath =
304
+ targetPath === ""
305
+ ? `${crossSectionPath}/${relativePath}`
306
+ : `${crossSectionPath}/${targetPath}${relativePath}`;
299
307
  }
300
308
  else {
301
- finalPath = targetPath === '' ? relativePath : `${targetPath}${relativePath}`;
309
+ finalPath =
310
+ targetPath === ""
311
+ ? relativePath
312
+ : `${targetPath}${relativePath}`;
302
313
  }
303
314
  return generateSiteUrl(finalPath, stripPrefixes);
304
315
  },
@@ -307,11 +318,11 @@ export function generateAutoLinkMappings(includes, stripPrefixes = []) {
307
318
  }
308
319
  else {
309
320
  // File mapping - exact string match
310
- const sourcePattern = sourcePath.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
321
+ const sourcePattern = sourcePath.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
311
322
  linkMappings.push({
312
323
  pattern: new RegExp(`^${sourcePattern}$`),
313
- replacement: (transformedPath, anchor, context) => {
314
- const finalPath = crossSectionPath && crossSectionPath !== '/'
324
+ replacement: (_transformedPath, _anchor, _context) => {
325
+ const finalPath = crossSectionPath && crossSectionPath !== "/"
315
326
  ? `${crossSectionPath}/${targetPath}`
316
327
  : targetPath;
317
328
  return generateSiteUrl(finalPath, stripPrefixes);