starlight-cannoli-plugins 1.0.2 → 1.0.3
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.
|
@@ -1,16 +1,33 @@
|
|
|
1
1
|
// src/plugins/rehype-validate-links.ts
|
|
2
2
|
import { existsSync } from "fs";
|
|
3
3
|
import { sync as globSync } from "glob";
|
|
4
|
-
import {
|
|
5
|
-
import { dirname, join, relative, resolve } from "path";
|
|
4
|
+
import { dirname as dirname2, join, relative, resolve as resolve2 } from "path";
|
|
6
5
|
import { visit } from "unist-util-visit";
|
|
6
|
+
|
|
7
|
+
// src/plugins/utils/path-utils.ts
|
|
8
|
+
import { dirname, resolve } from "path";
|
|
9
|
+
import { minimatch } from "minimatch";
|
|
7
10
|
var PROJECT_DOCS_DIR = "src/content/docs";
|
|
11
|
+
function isExternalPath(path) {
|
|
12
|
+
return path.startsWith("http") || path.startsWith("data:") || path.startsWith("/");
|
|
13
|
+
}
|
|
8
14
|
function matchesSkipPattern(path, patterns) {
|
|
9
15
|
if (!patterns || patterns.length === 0) {
|
|
10
16
|
return false;
|
|
11
17
|
}
|
|
12
18
|
return patterns.some((pattern) => minimatch(path, pattern));
|
|
13
19
|
}
|
|
20
|
+
function normalizePath(path, fromFilePath, siteRootPath) {
|
|
21
|
+
if (isExternalPath(path)) {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
const fileDir = dirname(fromFilePath);
|
|
25
|
+
const resolvedPath = resolve(fileDir, path);
|
|
26
|
+
const siteRoot = resolve(siteRootPath);
|
|
27
|
+
return "/" + resolve(resolvedPath).slice(siteRoot.length).replace(/\\/g, "/");
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// src/plugins/rehype-validate-links.ts
|
|
14
31
|
function getResolvedLink(href, currentFilePath) {
|
|
15
32
|
let skipValidation = false;
|
|
16
33
|
let processedHref = href;
|
|
@@ -38,10 +55,10 @@ function getResolvedLink(href, currentFilePath) {
|
|
|
38
55
|
relativePath = withoutFragment.slice(1);
|
|
39
56
|
projectAbsolute = join(PROJECT_DOCS_DIR, relativePath);
|
|
40
57
|
} else {
|
|
41
|
-
const currentFileDir =
|
|
42
|
-
const resolvedAbsPath =
|
|
58
|
+
const currentFileDir = dirname2(currentFilePath);
|
|
59
|
+
const resolvedAbsPath = resolve2(currentFileDir, withoutFragment);
|
|
43
60
|
projectAbsolute = resolvedAbsPath;
|
|
44
|
-
const docsRootAbsolute =
|
|
61
|
+
const docsRootAbsolute = resolve2(PROJECT_DOCS_DIR);
|
|
45
62
|
relativePath = relative(docsRootAbsolute, resolvedAbsPath);
|
|
46
63
|
}
|
|
47
64
|
const hasExtension = /\.[a-z0-9]+$/i.test(projectAbsolute);
|
|
@@ -96,51 +113,23 @@ function rehypeValidateLinks(options) {
|
|
|
96
113
|
);
|
|
97
114
|
return;
|
|
98
115
|
}
|
|
99
|
-
visit(tree, "element", (node
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
if (
|
|
103
|
-
|
|
104
|
-
attributeName = "href";
|
|
105
|
-
} else if (node.tagName === "img") {
|
|
106
|
-
resourcePath = node.properties?.src;
|
|
107
|
-
attributeName = "src";
|
|
108
|
-
}
|
|
109
|
-
if (!resourcePath || !attributeName) return;
|
|
110
|
-
const link = getResolvedLink(resourcePath, filePath);
|
|
116
|
+
visit(tree, "element", (node) => {
|
|
117
|
+
if (node.tagName !== "a") return;
|
|
118
|
+
const href = node.properties?.href;
|
|
119
|
+
if (!href) return;
|
|
120
|
+
const link = getResolvedLink(href, filePath);
|
|
111
121
|
if (!link) return;
|
|
112
|
-
if (link.skipValidation)
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
return;
|
|
116
|
-
}
|
|
117
|
-
if (node.properties?.["data-no-link-check"] !== void 0) {
|
|
118
|
-
node.properties = node.properties || {};
|
|
119
|
-
node.properties[attributeName] = link.site_absolute_href;
|
|
120
|
-
return;
|
|
121
|
-
}
|
|
122
|
-
if (matchesSkipPattern(link.site_absolute_href, options?.skipPatterns)) {
|
|
123
|
-
node.properties = node.properties || {};
|
|
124
|
-
node.properties[attributeName] = link.site_absolute_href;
|
|
125
|
-
return;
|
|
126
|
-
}
|
|
127
|
-
if (index !== void 0 && parent && "children" in parent && Array.isArray(parent.children)) {
|
|
128
|
-
const nextNode = parent.children[index + 1];
|
|
129
|
-
if (nextNode && "type" in nextNode && nextNode.type === "comment" && "value" in nextNode && typeof nextNode.value === "string" && nextNode.value.includes("no-link-check")) {
|
|
130
|
-
node.properties = node.properties || {};
|
|
131
|
-
node.properties[attributeName] = link.site_absolute_href;
|
|
132
|
-
return;
|
|
133
|
-
}
|
|
134
|
-
}
|
|
122
|
+
if (link.skipValidation) return;
|
|
123
|
+
if (node.properties?.["data-no-link-check"] !== void 0) return;
|
|
124
|
+
if (matchesSkipPattern(link.site_absolute_href, options?.skipPatterns)) return;
|
|
135
125
|
validateLink(link);
|
|
136
|
-
node.properties = node.properties || {};
|
|
137
|
-
node.properties[attributeName] = link.site_absolute_href;
|
|
138
126
|
});
|
|
139
127
|
};
|
|
140
128
|
}
|
|
141
129
|
var rehype_validate_links_default = rehypeValidateLinks;
|
|
142
130
|
|
|
143
131
|
export {
|
|
132
|
+
normalizePath,
|
|
144
133
|
rehypeValidateLinks,
|
|
145
134
|
rehype_validate_links_default
|
|
146
135
|
};
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
export { starlightIndexOnlySidebar } from './plugins/starlight-index-only-sidebar.js';
|
|
2
2
|
export { default as rehypeValidateLinks } from './plugins/rehype-validate-links.js';
|
|
3
|
+
import { AstroIntegration } from 'astro';
|
|
3
4
|
import '@astrojs/starlight/types';
|
|
4
5
|
import 'hast';
|
|
5
6
|
import 'vfile';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Astro integration that normalizes img src and anchor href attributes to absolute paths in HTML files after build.
|
|
10
|
+
* This processes resources in Starlight docs that aren't handled by the rehype plugin.
|
|
11
|
+
*/
|
|
12
|
+
declare function astroNormalizePaths(): AstroIntegration;
|
|
13
|
+
|
|
14
|
+
export { astroNormalizePaths };
|
package/dist/index.js
CHANGED
|
@@ -2,9 +2,55 @@ import {
|
|
|
2
2
|
starlightIndexOnlySidebar
|
|
3
3
|
} from "./chunk-NTIYGHZG.js";
|
|
4
4
|
import {
|
|
5
|
+
normalizePath,
|
|
5
6
|
rehypeValidateLinks
|
|
6
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-H377D3RC.js";
|
|
8
|
+
|
|
9
|
+
// src/plugins/astro-normalize-paths.ts
|
|
10
|
+
import { readFileSync, writeFileSync } from "fs";
|
|
11
|
+
import { sync as globSync } from "glob";
|
|
12
|
+
function astroNormalizePaths() {
|
|
13
|
+
return {
|
|
14
|
+
name: "astro-normalize-paths",
|
|
15
|
+
hooks: {
|
|
16
|
+
"astro:build:done": async ({ dir }) => {
|
|
17
|
+
const htmlFiles = globSync(`${dir.pathname}/**/*.html`);
|
|
18
|
+
for (const htmlFile of htmlFiles) {
|
|
19
|
+
let content = readFileSync(htmlFile, "utf-8");
|
|
20
|
+
const originalContent = content;
|
|
21
|
+
const imgRegex = /<img([^>]*?)src=["']([^"']+)["']/g;
|
|
22
|
+
let match;
|
|
23
|
+
while ((match = imgRegex.exec(content)) !== null) {
|
|
24
|
+
const attrs = match[1];
|
|
25
|
+
const src = match[2];
|
|
26
|
+
const normalized = normalizePath(src, htmlFile, dir.pathname);
|
|
27
|
+
if (normalized && src !== normalized) {
|
|
28
|
+
const oldTag = `<img${attrs}src="${src}"`;
|
|
29
|
+
const newTag = `<img${attrs}src="${normalized}"`;
|
|
30
|
+
content = content.replace(oldTag, newTag);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
const anchorRegex = /<a([^>]*?)href=["']([^"']+)["']/g;
|
|
34
|
+
while ((match = anchorRegex.exec(content)) !== null) {
|
|
35
|
+
const attrs = match[1];
|
|
36
|
+
const href = match[2];
|
|
37
|
+
const normalized = normalizePath(href, htmlFile, dir.pathname);
|
|
38
|
+
if (normalized && href !== normalized) {
|
|
39
|
+
const oldTag = `<a${attrs}href="${href}"`;
|
|
40
|
+
const newTag = `<a${attrs}href="${normalized}"`;
|
|
41
|
+
content = content.replace(oldTag, newTag);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
if (content !== originalContent) {
|
|
45
|
+
writeFileSync(htmlFile, content, "utf-8");
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
}
|
|
7
52
|
export {
|
|
53
|
+
astroNormalizePaths,
|
|
8
54
|
rehypeValidateLinks,
|
|
9
55
|
starlightIndexOnlySidebar
|
|
10
56
|
};
|
|
@@ -5,7 +5,8 @@ type TRehypeValidateLinksOptions = {
|
|
|
5
5
|
skipPatterns?: string[];
|
|
6
6
|
};
|
|
7
7
|
/**
|
|
8
|
-
* Rehype plugin to validate all internal links
|
|
8
|
+
* Rehype plugin to validate all internal links (checks validity only, no path normalization).
|
|
9
|
+
* This plugin verifies that link targets exist; path normalization is handled by separate integrations.
|
|
9
10
|
*/
|
|
10
11
|
declare function rehypeValidateLinks(options?: TRehypeValidateLinksOptions): (tree: Root, file: VFile) => void;
|
|
11
12
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "starlight-cannoli-plugins",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "1.0.
|
|
4
|
+
"version": "1.0.3",
|
|
5
5
|
"description": "Starlight plugins for automatic sidebar generation and link validation",
|
|
6
6
|
"license": "ISC",
|
|
7
7
|
"main": "./dist/index.js",
|
|
@@ -19,6 +19,10 @@
|
|
|
19
19
|
"import": "./dist/plugins/rehype-validate-links.js",
|
|
20
20
|
"types": "./dist/plugins/rehype-validate-links.d.ts"
|
|
21
21
|
},
|
|
22
|
+
"./astro-normalize-paths": {
|
|
23
|
+
"import": "./dist/plugins/astro-normalize-paths.js",
|
|
24
|
+
"types": "./dist/plugins/astro-normalize-paths.d.ts"
|
|
25
|
+
},
|
|
22
26
|
"./styles": "./src/styles/",
|
|
23
27
|
"./styles/*": "./src/styles/*"
|
|
24
28
|
},
|