starlight-cannoli-plugins 2.3.4 → 2.4.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.
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
// src/plugins/astro-normalize-paths/index.ts
|
|
2
|
+
import { existsSync } from "fs";
|
|
3
|
+
import { readFile, writeFile } from "fs/promises";
|
|
4
|
+
import { glob } from "glob";
|
|
5
|
+
import { dirname, relative, resolve } from "path";
|
|
6
|
+
import { JSDOM } from "jsdom";
|
|
7
|
+
var LINK_ELEMENT_TO_ACCESSOR_MAP = {
|
|
8
|
+
img: "src",
|
|
9
|
+
a: "href"
|
|
10
|
+
};
|
|
11
|
+
var LinkElement = class {
|
|
12
|
+
_element;
|
|
13
|
+
_accessor;
|
|
14
|
+
_href;
|
|
15
|
+
constructor(element) {
|
|
16
|
+
this._element = element;
|
|
17
|
+
this._accessor = this.getAccessorName();
|
|
18
|
+
this._href = this.extractHref(element);
|
|
19
|
+
}
|
|
20
|
+
get href() {
|
|
21
|
+
return this._href;
|
|
22
|
+
}
|
|
23
|
+
set href(newHref) {
|
|
24
|
+
this._href = newHref;
|
|
25
|
+
this._element.setAttribute(this._accessor, newHref);
|
|
26
|
+
}
|
|
27
|
+
getAccessorName() {
|
|
28
|
+
const tagName = this._element.tagName.toLowerCase();
|
|
29
|
+
return LINK_ELEMENT_TO_ACCESSOR_MAP[tagName];
|
|
30
|
+
}
|
|
31
|
+
extractHref(element) {
|
|
32
|
+
return element.getAttribute(this._accessor) ?? "";
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
var SRC_CONTENT_ROOT = "src/content/docs/";
|
|
36
|
+
function astroNormalizePaths() {
|
|
37
|
+
return {
|
|
38
|
+
name: "astro-normalize-paths",
|
|
39
|
+
hooks: {
|
|
40
|
+
"astro:build:done": async ({ dir }) => {
|
|
41
|
+
const htmlFiles = await glob(`${dir.pathname}/**/*.html`);
|
|
42
|
+
const distRoot = dir.pathname.replace(/\/+$/, "");
|
|
43
|
+
await Promise.all(
|
|
44
|
+
htmlFiles.map(async (htmlFilePath) => {
|
|
45
|
+
if (!htmlFilePath.endsWith("/index.html")) return;
|
|
46
|
+
const dom = new JSDOM(await readFile(htmlFilePath, "utf-8"));
|
|
47
|
+
const document = dom.window.document;
|
|
48
|
+
let hasHtmlContentChanged = false;
|
|
49
|
+
const sourceFilePath = resolveSourceFile(htmlFilePath, distRoot);
|
|
50
|
+
const linkElements = getLinks(document);
|
|
51
|
+
for (const link of linkElements) {
|
|
52
|
+
if (!isRelativePath(link.href)) continue;
|
|
53
|
+
const normalizedLinkPath = normalizeAssetPath(
|
|
54
|
+
link.href,
|
|
55
|
+
sourceFilePath
|
|
56
|
+
);
|
|
57
|
+
link.href = normalizedLinkPath;
|
|
58
|
+
hasHtmlContentChanged = true;
|
|
59
|
+
}
|
|
60
|
+
if (hasHtmlContentChanged) {
|
|
61
|
+
await writeFile(htmlFilePath, dom.serialize(), "utf-8");
|
|
62
|
+
}
|
|
63
|
+
})
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
function getLinks(document) {
|
|
70
|
+
const selector = Object.keys(LINK_ELEMENT_TO_ACCESSOR_MAP).join(", ");
|
|
71
|
+
const elements = document.querySelectorAll(selector);
|
|
72
|
+
return Array.from(elements).map((el) => new LinkElement(el));
|
|
73
|
+
}
|
|
74
|
+
function resolveSourceFile(htmlFilePath, distRoot) {
|
|
75
|
+
const normalizedDistDir = distRoot.replace(/\/+$/, "");
|
|
76
|
+
const srcDir = SRC_CONTENT_ROOT.replace(/\/+$/, "");
|
|
77
|
+
const relativeHtmlPath = htmlFilePath.slice(normalizedDistDir.length).replace(/^\/+/, "");
|
|
78
|
+
if (relativeHtmlPath !== "index.html" && !relativeHtmlPath.endsWith("/index.html")) {
|
|
79
|
+
throw new Error(
|
|
80
|
+
`resolveSourceFile: expected a path ending in index.html, got: ${htmlFilePath}`
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
const relativeDir = relativeHtmlPath === "index.html" ? "" : relativeHtmlPath.slice(0, -"/index.html".length);
|
|
84
|
+
const baseDir = relativeDir ? `${srcDir}/${relativeDir}` : srcDir;
|
|
85
|
+
const candidates = [`${baseDir}/index.md`, `${baseDir}/index.mdx`];
|
|
86
|
+
if (relativeDir) {
|
|
87
|
+
candidates.push(
|
|
88
|
+
`${srcDir}/${relativeDir}.md`,
|
|
89
|
+
`${srcDir}/${relativeDir}.mdx`
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
const matches = candidates.filter(existsSync);
|
|
93
|
+
if (matches.length === 1) return matches[0];
|
|
94
|
+
if (matches.length === 0) {
|
|
95
|
+
throw new Error(
|
|
96
|
+
`[astro-normalize-paths] Could not find a source file for ${htmlFilePath}. Expected one of: ${candidates.join(", ")}`
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
throw new Error(
|
|
100
|
+
`[astro-normalize-paths] Ambiguous source files for ${htmlFilePath}: ${matches.join(", ")}. Astro can compile both a directory index file (e.g. reference/schemas/index.md) and a same-named file one level up (e.g. reference/schemas.md) into the same output path. Remove one of the conflicting files to resolve this.`
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
function normalizeAssetPath(relLinkHref, sourceFilePath) {
|
|
104
|
+
let suffix = "";
|
|
105
|
+
let href = relLinkHref;
|
|
106
|
+
const hashIdx = href.indexOf("#");
|
|
107
|
+
if (hashIdx !== -1) {
|
|
108
|
+
suffix = href.slice(hashIdx);
|
|
109
|
+
href = href.slice(0, hashIdx);
|
|
110
|
+
}
|
|
111
|
+
const queryIdx = href.indexOf("?");
|
|
112
|
+
if (queryIdx !== -1) {
|
|
113
|
+
suffix = href.slice(queryIdx) + suffix;
|
|
114
|
+
href = href.slice(0, queryIdx);
|
|
115
|
+
}
|
|
116
|
+
const absoluteLinkedPath = resolve(dirname(sourceFilePath), href);
|
|
117
|
+
const relToRoot = relative(process.cwd(), absoluteLinkedPath);
|
|
118
|
+
const srcContentRoot = SRC_CONTENT_ROOT.replace(/\/+$/, "");
|
|
119
|
+
const relToContentRoot = relToRoot.startsWith(srcContentRoot) ? relToRoot.slice(srcContentRoot.length).replace(/^\/+/, "") : relToRoot;
|
|
120
|
+
return "/" + relToContentRoot.replace(/\\/g, "/") + suffix;
|
|
121
|
+
}
|
|
122
|
+
function isRelativePath(path) {
|
|
123
|
+
return !path.startsWith("/") && !path.startsWith("http://") && !path.startsWith("https://") && !path.startsWith("data:") && !path.startsWith("#") && !path.startsWith("mailto:");
|
|
124
|
+
}
|
|
125
|
+
var astro_normalize_paths_default = astroNormalizePaths;
|
|
126
|
+
|
|
127
|
+
export {
|
|
128
|
+
astroNormalizePaths,
|
|
129
|
+
astro_normalize_paths_default
|
|
130
|
+
};
|
package/dist/index.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { AstroIntegration } from 'astro';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Astro integration that
|
|
5
|
-
* This processes resources in Starlight docs that aren't handled by the rehype plugin.
|
|
4
|
+
* Astro integration that rewrites relative link hrefs in built HTML files to absolute site paths.
|
|
6
5
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
6
|
+
* Relative links break when web hosts differ in whether they append a trailing slash to URLs.
|
|
7
|
+
* This integration runs after the build, resolves every relative href against the original
|
|
8
|
+
* source file's location, and replaces it with an absolute path so behaviour is host-independent.
|
|
9
9
|
*/
|
|
10
10
|
declare function astroNormalizePaths(): AstroIntegration;
|
|
11
11
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "starlight-cannoli-plugins",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "2.
|
|
4
|
+
"version": "2.4.0",
|
|
5
5
|
"description": "Starlight plugins for automatic sidebar generation and link validation",
|
|
6
6
|
"license": "ISC",
|
|
7
7
|
"main": "./dist/index.js",
|
|
@@ -72,6 +72,7 @@
|
|
|
72
72
|
"dependencies": {
|
|
73
73
|
"eslint-cannoli-plugins": "^1.0.13",
|
|
74
74
|
"glob": "^11.0.0",
|
|
75
|
+
"jsdom": "^29.0.2",
|
|
75
76
|
"minimatch": "^9.0.0",
|
|
76
77
|
"unist-util-visit": "^5.0.0",
|
|
77
78
|
"yaml": "^2.4.0"
|
|
@@ -100,6 +101,7 @@
|
|
|
100
101
|
"@hpcc-js/wasm": "^2.33.1",
|
|
101
102
|
"@trivago/prettier-plugin-sort-imports": "^6.0.2",
|
|
102
103
|
"@types/hast": "^3.0.0",
|
|
104
|
+
"@types/jsdom": "^28.0.1",
|
|
103
105
|
"@typescript-eslint/eslint-plugin": "^8.56.1",
|
|
104
106
|
"@typescript-eslint/parser": "^8.56.1",
|
|
105
107
|
"astro": "^5.6.1",
|
package/dist/chunk-AZPHBHBE.js
DELETED
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
// src/plugins/astro-normalize-paths.ts
|
|
2
|
-
import { existsSync } from "fs";
|
|
3
|
-
import { readFile, writeFile } from "fs/promises";
|
|
4
|
-
import { glob } from "glob";
|
|
5
|
-
import { dirname, resolve } from "path";
|
|
6
|
-
function astroNormalizePaths() {
|
|
7
|
-
return {
|
|
8
|
-
name: "astro-normalize-paths",
|
|
9
|
-
hooks: {
|
|
10
|
-
"astro:build:done": async ({ dir }) => {
|
|
11
|
-
const htmlFiles = await glob(`${dir.pathname}/**/*.html`);
|
|
12
|
-
await Promise.all(
|
|
13
|
-
htmlFiles.map(async (htmlFile) => {
|
|
14
|
-
let content = await readFile(htmlFile, "utf-8");
|
|
15
|
-
const originalContent = content;
|
|
16
|
-
const imgRegex = /<img([^>]*?)src=["']([^"']+)["']/g;
|
|
17
|
-
let match;
|
|
18
|
-
while ((match = imgRegex.exec(content)) !== null) {
|
|
19
|
-
const attrs = match[1];
|
|
20
|
-
const src = match[2];
|
|
21
|
-
const normalized = normalizeAssetPath(src, htmlFile, dir.pathname);
|
|
22
|
-
if (normalized && src !== normalized) {
|
|
23
|
-
console.log(`[astro-normalize-paths] Img path resolution:`);
|
|
24
|
-
console.log(` Original: ${src}`);
|
|
25
|
-
console.log(` HTML file: ${htmlFile}`);
|
|
26
|
-
console.log(` Normalized: ${normalized}`);
|
|
27
|
-
content = content.replace(`<img${attrs}src="${src}"`, `<img${attrs}src="${normalized}"`);
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
const anchorRegex = /<a([^>]*?)href=["']([^"']+)["']/g;
|
|
31
|
-
while ((match = anchorRegex.exec(content)) !== null) {
|
|
32
|
-
const attrs = match[1];
|
|
33
|
-
const href = match[2];
|
|
34
|
-
const normalized = normalizeAssetPath(href, htmlFile, dir.pathname);
|
|
35
|
-
if (normalized && href !== normalized) {
|
|
36
|
-
content = content.replace(`<a${attrs}href="${href}"`, `<a${attrs}href="${normalized}"`);
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
if (content !== originalContent) {
|
|
40
|
-
await writeFile(htmlFile, content, "utf-8");
|
|
41
|
-
}
|
|
42
|
-
})
|
|
43
|
-
);
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
};
|
|
47
|
-
}
|
|
48
|
-
function normalizeAssetPath(path, htmlFile, siteRootPath) {
|
|
49
|
-
if (path.startsWith("http") || path.startsWith("data:") || path.startsWith("/")) {
|
|
50
|
-
return null;
|
|
51
|
-
}
|
|
52
|
-
const htmlDir = dirname(htmlFile);
|
|
53
|
-
const resolvedPath = resolve(htmlDir, path);
|
|
54
|
-
const siteRoot = resolve(siteRootPath);
|
|
55
|
-
let absolutePath = resolvedPath.slice(siteRoot.length).replace(/\\/g, "/");
|
|
56
|
-
if (!existsSync(resolvedPath)) {
|
|
57
|
-
const parentDir = dirname(htmlDir);
|
|
58
|
-
const alternativePath = resolve(parentDir, path);
|
|
59
|
-
if (existsSync(alternativePath)) {
|
|
60
|
-
absolutePath = alternativePath.slice(siteRoot.length).replace(/\\/g, "/");
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
const finalPath = "/" + absolutePath.replace(/^\/+/, "");
|
|
64
|
-
return finalPath;
|
|
65
|
-
}
|
|
66
|
-
var astro_normalize_paths_default = astroNormalizePaths;
|
|
67
|
-
|
|
68
|
-
export {
|
|
69
|
-
astroNormalizePaths,
|
|
70
|
-
astro_normalize_paths_default
|
|
71
|
-
};
|