create-asciidoc-multi-book-workspace 0.1.13 → 0.2.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/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.2.0 - 2026-06-23
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- Make generated workspace builds fail when Asciidoctor reports parser diagnostics, including missing `include::` targets that previously only appeared on stderr.
|
|
8
|
+
- Extend generated HTML validation to fail on missing same-page and cross-page fragment anchors, not just missing local files.
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
- Escape quick-reference `xref` syntax examples in the structured writing sample so example text does not render as broken links.
|
|
13
|
+
|
|
3
14
|
## 0.1.13 - 2026-06-21
|
|
4
15
|
|
|
5
16
|
### Changed
|
|
@@ -10,7 +10,8 @@ const DIAGRAM_BLOCK_PATTERN = /^\[(?:actdiag|blockdiag|bpmn|bytefield|c4plantuml
|
|
|
10
10
|
const CATALOG_BOOK_XREF_PATTERN = /xref:books\/([^/\]]+)\/book\.adoc(?:#[^\[]+)?\[/g;
|
|
11
11
|
const XREF_PATTERN = /xref:([^\[#]+)(?:#([A-Za-z0-9_-]+))?\[/g;
|
|
12
12
|
const ANCHOR_PATTERN = /^\[#([A-Za-z0-9_-]+)(?:[.,][^\]]*)?\]$/gm;
|
|
13
|
-
const LOCAL_TARGET_PATTERN = /\b(
|
|
13
|
+
const LOCAL_TARGET_PATTERN = /\b(href|src)="([^"]+)"/g;
|
|
14
|
+
const HTML_ANCHOR_PATTERN = /\b(?:id|name)="([^"]+)"/g;
|
|
14
15
|
const SCHEME_PATTERN = /^[a-zA-Z][a-zA-Z0-9+.-]*:/;
|
|
15
16
|
const HOME_MARKER = "data-multi-book-home";
|
|
16
17
|
const CONTROLS_MARKER = "data-multi-book-controls";
|
|
@@ -153,14 +154,54 @@ async function pruneStaleBookHtmlDirs(rootDir, books) {
|
|
|
153
154
|
}
|
|
154
155
|
}
|
|
155
156
|
}
|
|
157
|
+
function messageText(message) {
|
|
158
|
+
if (typeof message?.getText === "function")
|
|
159
|
+
return message.getText();
|
|
160
|
+
if (typeof message?.getMessage === "function")
|
|
161
|
+
return message.getMessage();
|
|
162
|
+
return String(message);
|
|
163
|
+
}
|
|
164
|
+
function messageLocation(message) {
|
|
165
|
+
const sourceLocation = typeof message?.getSourceLocation === "function" ? message.getSourceLocation() : undefined;
|
|
166
|
+
if (!sourceLocation)
|
|
167
|
+
return "";
|
|
168
|
+
const sourcePath = typeof sourceLocation.getPath === "function" ? sourceLocation.getPath() : undefined;
|
|
169
|
+
const lineNumber = typeof sourceLocation.getLineNumber === "function" ? sourceLocation.getLineNumber() : undefined;
|
|
170
|
+
if (sourcePath && lineNumber)
|
|
171
|
+
return `${sourcePath}: line ${lineNumber}`;
|
|
172
|
+
if (sourcePath)
|
|
173
|
+
return sourcePath;
|
|
174
|
+
if (lineNumber)
|
|
175
|
+
return `line ${lineNumber}`;
|
|
176
|
+
return "";
|
|
177
|
+
}
|
|
178
|
+
function formatAsciidoctorMessage(message) {
|
|
179
|
+
const severity = typeof message?.getSeverity === "function" ? message.getSeverity() : "UNKNOWN";
|
|
180
|
+
const location = messageLocation(message);
|
|
181
|
+
const text = messageText(message);
|
|
182
|
+
return location ? `${severity}: ${location}: ${text}` : `${severity}: ${text}`;
|
|
183
|
+
}
|
|
156
184
|
function convertFile(asciidoctor, input, outputFile, baseDir, attributes = {}) {
|
|
157
|
-
asciidoctor.
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
185
|
+
const loggerManager = asciidoctor.LoggerManager;
|
|
186
|
+
const defaultLogger = loggerManager.getLogger();
|
|
187
|
+
const memoryLogger = asciidoctor.MemoryLogger.create();
|
|
188
|
+
loggerManager.setLogger(memoryLogger);
|
|
189
|
+
try {
|
|
190
|
+
asciidoctor.convertFile(input, {
|
|
191
|
+
safe: "unsafe",
|
|
192
|
+
base_dir: baseDir,
|
|
193
|
+
to_file: outputFile,
|
|
194
|
+
mkdirs: true,
|
|
195
|
+
attributes
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
finally {
|
|
199
|
+
loggerManager.setLogger(defaultLogger);
|
|
200
|
+
}
|
|
201
|
+
const messages = memoryLogger.getMessages();
|
|
202
|
+
if (messages.length > 0) {
|
|
203
|
+
throw new Error(`AsciiDoc strict check failed for ${input}\n${messages.map(formatAsciidoctorMessage).join("\n")}`);
|
|
204
|
+
}
|
|
164
205
|
}
|
|
165
206
|
async function buildHtml(rootDir, books, asciidoctor, useKroki, fetchDiagrams) {
|
|
166
207
|
await mkdir(path.join(rootDir, "build", "html"), { recursive: true });
|
|
@@ -1007,27 +1048,60 @@ async function collectHtmlFiles(dir) {
|
|
|
1007
1048
|
function extractLocalTargets(html) {
|
|
1008
1049
|
const targets = [];
|
|
1009
1050
|
for (const match of html.matchAll(LOCAL_TARGET_PATTERN)) {
|
|
1010
|
-
const rawTarget = match
|
|
1011
|
-
if (rawTarget === "" || rawTarget
|
|
1051
|
+
const [, attribute, rawTarget] = match;
|
|
1052
|
+
if (rawTarget === "" || rawTarget === "#" || rawTarget.startsWith("//") || SCHEME_PATTERN.test(rawTarget)) {
|
|
1012
1053
|
continue;
|
|
1013
1054
|
}
|
|
1014
1055
|
const targetWithoutFragment = rawTarget.split("#", 1)[0].split("?", 1)[0];
|
|
1015
|
-
|
|
1016
|
-
|
|
1056
|
+
const fragment = rawTarget.includes("#") ? rawTarget.split("#", 2)[1].split("?", 1)[0] : "";
|
|
1057
|
+
if (targetWithoutFragment !== "" || fragment !== "")
|
|
1058
|
+
targets.push({ attribute, rawTarget });
|
|
1017
1059
|
}
|
|
1018
1060
|
return targets;
|
|
1019
1061
|
}
|
|
1062
|
+
function decodeUrlFragment(fragment) {
|
|
1063
|
+
try {
|
|
1064
|
+
return decodeURIComponent(fragment);
|
|
1065
|
+
}
|
|
1066
|
+
catch {
|
|
1067
|
+
return fragment;
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1070
|
+
function htmlAnchors(html) {
|
|
1071
|
+
const anchors = new Set();
|
|
1072
|
+
for (const match of html.matchAll(HTML_ANCHOR_PATTERN))
|
|
1073
|
+
anchors.add(match[1]);
|
|
1074
|
+
return anchors;
|
|
1075
|
+
}
|
|
1076
|
+
async function anchorsForHtmlFile(filePath, cache) {
|
|
1077
|
+
const cached = cache.get(filePath);
|
|
1078
|
+
if (cached)
|
|
1079
|
+
return cached;
|
|
1080
|
+
const anchors = htmlAnchors(await readFile(filePath, "utf8"));
|
|
1081
|
+
cache.set(filePath, anchors);
|
|
1082
|
+
return anchors;
|
|
1083
|
+
}
|
|
1020
1084
|
async function missingLocalResources(rootDir) {
|
|
1021
1085
|
const htmlDir = path.join(rootDir, "build", "html");
|
|
1022
1086
|
const htmlFiles = await collectHtmlFiles(htmlDir);
|
|
1023
1087
|
const issues = [];
|
|
1088
|
+
const anchorCache = new Map();
|
|
1024
1089
|
for (const htmlFile of htmlFiles) {
|
|
1025
1090
|
const html = await readFile(htmlFile, "utf8");
|
|
1026
1091
|
for (const target of extractLocalTargets(html)) {
|
|
1027
|
-
const targetPath = target.split("#", 1)[0].split("?", 1)[0];
|
|
1028
|
-
const
|
|
1029
|
-
|
|
1030
|
-
|
|
1092
|
+
const targetPath = target.rawTarget.split("#", 1)[0].split("?", 1)[0];
|
|
1093
|
+
const fragment = target.rawTarget.includes("#") ? target.rawTarget.split("#", 2)[1].split("?", 1)[0] : "";
|
|
1094
|
+
const resolved = targetPath === "" ? htmlFile : path.resolve(path.dirname(htmlFile), targetPath);
|
|
1095
|
+
if (targetPath !== "" && !await existsFile(resolved)) {
|
|
1096
|
+
issues.push(issue("HTML_RESOURCE_MISSING", `${path.relative(htmlDir, htmlFile)} -> ${target.rawTarget}`));
|
|
1097
|
+
continue;
|
|
1098
|
+
}
|
|
1099
|
+
if (target.attribute === "href" && fragment !== "" && (targetPath === "" || resolved.endsWith(".html"))) {
|
|
1100
|
+
const anchors = await anchorsForHtmlFile(resolved, anchorCache);
|
|
1101
|
+
const decodedFragment = decodeUrlFragment(fragment);
|
|
1102
|
+
if (!anchors.has(fragment) && !anchors.has(decodedFragment)) {
|
|
1103
|
+
issues.push(issue("HTML_ANCHOR_MISSING", `${path.relative(htmlDir, htmlFile)} -> ${target.rawTarget}`));
|
|
1104
|
+
}
|
|
1031
1105
|
}
|
|
1032
1106
|
}
|
|
1033
1107
|
}
|
package/package.json
CHANGED
|
@@ -48,10 +48,10 @@
|
|
|
48
48
|
|`summary::` + 下一行说明文本
|
|
49
49
|
|标题下描述列表字段。用于较多字段或多行字段值。
|
|
50
50
|
|
|
51
|
-
|`xref:stable-id[显示文本]`
|
|
51
|
+
|`pass:[xref:stable-id[显示文本]]`
|
|
52
52
|
|显示为链接,投影为 references。
|
|
53
53
|
|
|
54
|
-
|`xref:stable-id[显示文本, rel=depends-on]`
|
|
54
|
+
|`pass:[xref:stable-id[显示文本, rel=depends-on]]`
|
|
55
55
|
|显示为链接,同时声明 depends-on 关系。
|
|
56
56
|
|
|
57
57
|
|`indexterm2:[role]`
|