@larkiny/astro-github-loader 0.11.1 → 0.11.2
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/dist/github.link-transform.js +9 -7
- package/dist/github.link-transform.spec.d.ts +1 -0
- package/dist/github.link-transform.spec.js +222 -0
- package/package.json +1 -1
- package/src/content/docs/algokit/cli/accounts.md +1 -0
- package/src/content/docs/algokit/cli/generate.md +1 -0
- package/src/content/docs/algokit/cli/overview.md +1 -0
- package/src/content/docs/algokit/cli/tasks.md +1 -0
- package/src/content/docs/reference/algokit-cli/index.md +1 -0
- package/src/github.link-transform.ts +11 -8
|
@@ -35,17 +35,15 @@ function isExternalLink(link) {
|
|
|
35
35
|
*/
|
|
36
36
|
function normalizePath(linkPath, currentFilePath, logger) {
|
|
37
37
|
logger?.debug(`[normalizePath] BEFORE: linkPath="${linkPath}", currentFilePath="${currentFilePath}"`);
|
|
38
|
-
// Handle relative paths
|
|
39
|
-
if
|
|
38
|
+
// Handle relative paths (including simple relative paths without ./ prefix)
|
|
39
|
+
// A link is relative if it doesn't start with / or contain a protocol
|
|
40
|
+
const isAbsoluteOrExternal = linkPath.startsWith('/') || linkPath.includes('://') || linkPath.startsWith('#');
|
|
41
|
+
if (!isAbsoluteOrExternal) {
|
|
40
42
|
const currentDir = path.dirname(currentFilePath);
|
|
41
43
|
const resolved = path.posix.normalize(path.posix.join(currentDir, linkPath));
|
|
42
44
|
logger?.debug(`[normalizePath] RELATIVE PATH RESOLVED: "${linkPath}" -> "${resolved}" (currentDir: "${currentDir}")`);
|
|
43
45
|
return resolved;
|
|
44
46
|
}
|
|
45
|
-
// Remove leading './'
|
|
46
|
-
if (linkPath.startsWith('./')) {
|
|
47
|
-
return linkPath.slice(2);
|
|
48
|
-
}
|
|
49
47
|
logger?.debug(`[normalizePath] AFTER: "${linkPath}" (no changes)`);
|
|
50
48
|
return linkPath;
|
|
51
49
|
}
|
|
@@ -188,7 +186,11 @@ function transformLink(linkText, linkUrl, context) {
|
|
|
188
186
|
}
|
|
189
187
|
}
|
|
190
188
|
// Check if this links to an imported file
|
|
191
|
-
|
|
189
|
+
let targetPath = context.global.sourceToTargetMap.get(normalizedPath);
|
|
190
|
+
// 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');
|
|
193
|
+
}
|
|
192
194
|
if (targetPath) {
|
|
193
195
|
// This is an internal link to an imported file
|
|
194
196
|
const siteUrl = generateSiteUrl(targetPath, context.global.stripPrefixes);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { globalLinkTransform } from './github.link-transform.js';
|
|
3
|
+
describe('globalLinkTransform', () => {
|
|
4
|
+
describe('relative link resolution', () => {
|
|
5
|
+
it('should resolve simple relative links without ./ prefix', () => {
|
|
6
|
+
const files = [
|
|
7
|
+
{
|
|
8
|
+
sourcePath: 'docs/front-end-guide/index.md',
|
|
9
|
+
targetPath: 'src/content/docs/reference/api/front-end-guide/overview.md',
|
|
10
|
+
content: '[Designing a language](02-designing-a-language.md)',
|
|
11
|
+
id: 'file1',
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
sourcePath: 'docs/front-end-guide/02-designing-a-language.md',
|
|
15
|
+
targetPath: 'src/content/docs/reference/api/front-end-guide/02-designing-a-language.md',
|
|
16
|
+
content: '# Designing a language',
|
|
17
|
+
id: 'file2',
|
|
18
|
+
},
|
|
19
|
+
];
|
|
20
|
+
const result = globalLinkTransform(files, {
|
|
21
|
+
stripPrefixes: ['src/content/docs'],
|
|
22
|
+
linkMappings: [],
|
|
23
|
+
});
|
|
24
|
+
expect(result[0].content).toBe('[Designing a language](/reference/api/front-end-guide/02-designing-a-language/)');
|
|
25
|
+
});
|
|
26
|
+
it('should resolve relative links with ./ prefix', () => {
|
|
27
|
+
const files = [
|
|
28
|
+
{
|
|
29
|
+
sourcePath: 'docs/front-end-guide/index.md',
|
|
30
|
+
targetPath: 'src/content/docs/reference/api/front-end-guide/overview.md',
|
|
31
|
+
content: '[Intro](./00-introduction.md)',
|
|
32
|
+
id: 'file1',
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
sourcePath: 'docs/front-end-guide/00-introduction.md',
|
|
36
|
+
targetPath: 'src/content/docs/reference/api/front-end-guide/00-introduction.md',
|
|
37
|
+
content: '# Introduction',
|
|
38
|
+
id: 'file2',
|
|
39
|
+
},
|
|
40
|
+
];
|
|
41
|
+
const result = globalLinkTransform(files, {
|
|
42
|
+
stripPrefixes: ['src/content/docs'],
|
|
43
|
+
linkMappings: [],
|
|
44
|
+
});
|
|
45
|
+
expect(result[0].content).toBe('[Intro](/reference/api/front-end-guide/00-introduction/)');
|
|
46
|
+
});
|
|
47
|
+
it('should resolve relative links with ../ prefix', () => {
|
|
48
|
+
const files = [
|
|
49
|
+
{
|
|
50
|
+
sourcePath: 'docs/front-end-guide/subfolder/page.md',
|
|
51
|
+
targetPath: 'src/content/docs/reference/api/front-end-guide/subfolder/page.md',
|
|
52
|
+
content: '[Back to overview](../index.md)',
|
|
53
|
+
id: 'file1',
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
sourcePath: 'docs/front-end-guide/index.md',
|
|
57
|
+
targetPath: 'src/content/docs/reference/api/front-end-guide/overview.md',
|
|
58
|
+
content: '# Overview',
|
|
59
|
+
id: 'file2',
|
|
60
|
+
},
|
|
61
|
+
];
|
|
62
|
+
const result = globalLinkTransform(files, {
|
|
63
|
+
stripPrefixes: ['src/content/docs'],
|
|
64
|
+
linkMappings: [],
|
|
65
|
+
});
|
|
66
|
+
expect(result[0].content).toBe('[Back to overview](/reference/api/front-end-guide/overview/)');
|
|
67
|
+
});
|
|
68
|
+
it('should preserve absolute links', () => {
|
|
69
|
+
const files = [
|
|
70
|
+
{
|
|
71
|
+
sourcePath: 'docs/page.md',
|
|
72
|
+
targetPath: 'src/content/docs/page.md',
|
|
73
|
+
content: '[Absolute link](/some/absolute/path)',
|
|
74
|
+
id: 'file1',
|
|
75
|
+
},
|
|
76
|
+
];
|
|
77
|
+
const result = globalLinkTransform(files, {
|
|
78
|
+
stripPrefixes: ['src/content/docs'],
|
|
79
|
+
linkMappings: [],
|
|
80
|
+
});
|
|
81
|
+
expect(result[0].content).toBe('[Absolute link](/some/absolute/path)');
|
|
82
|
+
});
|
|
83
|
+
it('should preserve external links', () => {
|
|
84
|
+
const files = [
|
|
85
|
+
{
|
|
86
|
+
sourcePath: 'docs/page.md',
|
|
87
|
+
targetPath: 'src/content/docs/page.md',
|
|
88
|
+
content: '[External](https://example.com)',
|
|
89
|
+
id: 'file1',
|
|
90
|
+
},
|
|
91
|
+
];
|
|
92
|
+
const result = globalLinkTransform(files, {
|
|
93
|
+
stripPrefixes: ['src/content/docs'],
|
|
94
|
+
linkMappings: [],
|
|
95
|
+
});
|
|
96
|
+
expect(result[0].content).toBe('[External](https://example.com)');
|
|
97
|
+
});
|
|
98
|
+
it('should preserve anchor-only links', () => {
|
|
99
|
+
const files = [
|
|
100
|
+
{
|
|
101
|
+
sourcePath: 'docs/page.md',
|
|
102
|
+
targetPath: 'src/content/docs/page.md',
|
|
103
|
+
content: '[Jump to section](#my-section)',
|
|
104
|
+
id: 'file1',
|
|
105
|
+
},
|
|
106
|
+
];
|
|
107
|
+
const result = globalLinkTransform(files, {
|
|
108
|
+
stripPrefixes: ['src/content/docs'],
|
|
109
|
+
linkMappings: [],
|
|
110
|
+
});
|
|
111
|
+
expect(result[0].content).toBe('[Jump to section](#my-section)');
|
|
112
|
+
});
|
|
113
|
+
it('should preserve anchors in relative links', () => {
|
|
114
|
+
const files = [
|
|
115
|
+
{
|
|
116
|
+
sourcePath: 'docs/front-end-guide/index.md',
|
|
117
|
+
targetPath: 'src/content/docs/reference/api/front-end-guide/overview.md',
|
|
118
|
+
content: '[Section](02-designing-a-language.md#primitive-types)',
|
|
119
|
+
id: 'file1',
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
sourcePath: 'docs/front-end-guide/02-designing-a-language.md',
|
|
123
|
+
targetPath: 'src/content/docs/reference/api/front-end-guide/02-designing-a-language.md',
|
|
124
|
+
content: '# Designing a language',
|
|
125
|
+
id: 'file2',
|
|
126
|
+
},
|
|
127
|
+
];
|
|
128
|
+
const result = globalLinkTransform(files, {
|
|
129
|
+
stripPrefixes: ['src/content/docs'],
|
|
130
|
+
linkMappings: [],
|
|
131
|
+
});
|
|
132
|
+
expect(result[0].content).toBe('[Section](/reference/api/front-end-guide/02-designing-a-language/#primitive-types)');
|
|
133
|
+
});
|
|
134
|
+
it('should handle multiple links in the same file', () => {
|
|
135
|
+
const files = [
|
|
136
|
+
{
|
|
137
|
+
sourcePath: 'docs/front-end-guide/index.md',
|
|
138
|
+
targetPath: 'src/content/docs/reference/api/front-end-guide/overview.md',
|
|
139
|
+
content: `
|
|
140
|
+
[Introduction](00-introduction.md)
|
|
141
|
+
[Calling puya](01-calling-puya.md)
|
|
142
|
+
[Designing a language](02-designing-a-language.md)
|
|
143
|
+
`.trim(),
|
|
144
|
+
id: 'file1',
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
sourcePath: 'docs/front-end-guide/00-introduction.md',
|
|
148
|
+
targetPath: 'src/content/docs/reference/api/front-end-guide/00-introduction.md',
|
|
149
|
+
content: '# Introduction',
|
|
150
|
+
id: 'file2',
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
sourcePath: 'docs/front-end-guide/01-calling-puya.md',
|
|
154
|
+
targetPath: 'src/content/docs/reference/api/front-end-guide/01-calling-puya.md',
|
|
155
|
+
content: '# Calling Puya',
|
|
156
|
+
id: 'file3',
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
sourcePath: 'docs/front-end-guide/02-designing-a-language.md',
|
|
160
|
+
targetPath: 'src/content/docs/reference/api/front-end-guide/02-designing-a-language.md',
|
|
161
|
+
content: '# Designing a language',
|
|
162
|
+
id: 'file4',
|
|
163
|
+
},
|
|
164
|
+
];
|
|
165
|
+
const result = globalLinkTransform(files, {
|
|
166
|
+
stripPrefixes: ['src/content/docs'],
|
|
167
|
+
linkMappings: [],
|
|
168
|
+
});
|
|
169
|
+
expect(result[0].content).toBe(`
|
|
170
|
+
[Introduction](/reference/api/front-end-guide/00-introduction/)
|
|
171
|
+
[Calling puya](/reference/api/front-end-guide/01-calling-puya/)
|
|
172
|
+
[Designing a language](/reference/api/front-end-guide/02-designing-a-language/)
|
|
173
|
+
`.trim());
|
|
174
|
+
});
|
|
175
|
+
it('should strip .md extension from unresolved relative links', () => {
|
|
176
|
+
const files = [
|
|
177
|
+
{
|
|
178
|
+
sourcePath: 'docs/page.md',
|
|
179
|
+
targetPath: 'src/content/docs/page.md',
|
|
180
|
+
content: '[Unresolved](some-file-not-in-map.md)',
|
|
181
|
+
id: 'file1',
|
|
182
|
+
},
|
|
183
|
+
];
|
|
184
|
+
const result = globalLinkTransform(files, {
|
|
185
|
+
stripPrefixes: ['src/content/docs'],
|
|
186
|
+
linkMappings: [],
|
|
187
|
+
});
|
|
188
|
+
// Should normalize to docs/some-file-not-in-map (current file's dir + relative link)
|
|
189
|
+
// but not resolve to full path since file not in map, so just strips .md
|
|
190
|
+
expect(result[0].content).toBe('[Unresolved](docs/some-file-not-in-map)');
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
describe('index.md handling', () => {
|
|
194
|
+
it('should convert index.md to trailing slash', () => {
|
|
195
|
+
const files = [
|
|
196
|
+
{
|
|
197
|
+
sourcePath: 'docs/page.md',
|
|
198
|
+
targetPath: 'src/content/docs/page.md',
|
|
199
|
+
content: '[Link](subfolder/index.md)',
|
|
200
|
+
id: 'file1',
|
|
201
|
+
},
|
|
202
|
+
{
|
|
203
|
+
sourcePath: 'docs/subfolder/index.md',
|
|
204
|
+
targetPath: 'src/content/docs/subfolder/index.md',
|
|
205
|
+
content: '# Index',
|
|
206
|
+
id: 'file2',
|
|
207
|
+
},
|
|
208
|
+
];
|
|
209
|
+
const result = globalLinkTransform(files, {
|
|
210
|
+
stripPrefixes: ['src/content/docs'],
|
|
211
|
+
linkMappings: [
|
|
212
|
+
{
|
|
213
|
+
pattern: /\/index$/,
|
|
214
|
+
replacement: '/',
|
|
215
|
+
global: true,
|
|
216
|
+
},
|
|
217
|
+
],
|
|
218
|
+
});
|
|
219
|
+
expect(result[0].content).toBe('[Link](/subfolder/)');
|
|
220
|
+
});
|
|
221
|
+
});
|
|
222
|
+
});
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@larkiny/astro-github-loader",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.11.
|
|
4
|
+
"version": "0.11.2",
|
|
5
5
|
"description": "Load content from GitHub repositories into Astro content collections with asset management and content transformations",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"astro",
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Content
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Content
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Content
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Content
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Content
|
|
@@ -104,19 +104,17 @@ function isExternalLink(link: string): boolean {
|
|
|
104
104
|
function normalizePath(linkPath: string, currentFilePath: string, logger?: Logger): string {
|
|
105
105
|
logger?.debug(`[normalizePath] BEFORE: linkPath="${linkPath}", currentFilePath="${currentFilePath}"`);
|
|
106
106
|
|
|
107
|
-
// Handle relative paths
|
|
108
|
-
if
|
|
107
|
+
// Handle relative paths (including simple relative paths without ./ prefix)
|
|
108
|
+
// A link is relative if it doesn't start with / or contain a protocol
|
|
109
|
+
const isAbsoluteOrExternal = linkPath.startsWith('/') || linkPath.includes('://') || linkPath.startsWith('#');
|
|
110
|
+
|
|
111
|
+
if (!isAbsoluteOrExternal) {
|
|
109
112
|
const currentDir = path.dirname(currentFilePath);
|
|
110
113
|
const resolved = path.posix.normalize(path.posix.join(currentDir, linkPath));
|
|
111
114
|
logger?.debug(`[normalizePath] RELATIVE PATH RESOLVED: "${linkPath}" -> "${resolved}" (currentDir: "${currentDir}")`);
|
|
112
115
|
return resolved;
|
|
113
116
|
}
|
|
114
117
|
|
|
115
|
-
// Remove leading './'
|
|
116
|
-
if (linkPath.startsWith('./')) {
|
|
117
|
-
return linkPath.slice(2);
|
|
118
|
-
}
|
|
119
|
-
|
|
120
118
|
logger?.debug(`[normalizePath] AFTER: "${linkPath}" (no changes)`);
|
|
121
119
|
return linkPath;
|
|
122
120
|
}
|
|
@@ -286,7 +284,12 @@ function transformLink(linkText: string, linkUrl: string, context: LinkContext):
|
|
|
286
284
|
}
|
|
287
285
|
|
|
288
286
|
// Check if this links to an imported file
|
|
289
|
-
|
|
287
|
+
let targetPath = context.global.sourceToTargetMap.get(normalizedPath);
|
|
288
|
+
|
|
289
|
+
// 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');
|
|
292
|
+
}
|
|
290
293
|
|
|
291
294
|
if (targetPath) {
|
|
292
295
|
// This is an internal link to an imported file
|