starlight-cannoli-plugins 1.2.7 → 1.2.9
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/chunk-QZUB4DOG.js +255 -0
- package/dist/{chunk-LF4VMIII.js → chunk-UMYD2SJL.js} +12 -250
- package/dist/cli/cannoli-latex-cleanup.js +4 -5
- package/dist/index.js +2 -1
- package/dist/plugins/remark-latex-compile.js +4 -2
- package/dist/plugins/starlight-latex-compile.js +2 -1
- package/package.json +1 -1
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
// src/plugins/remark-latex-compile/compile.ts
|
|
2
|
+
import { createHash } from "crypto";
|
|
3
|
+
import { spawnSync } from "child_process";
|
|
4
|
+
import {
|
|
5
|
+
existsSync,
|
|
6
|
+
mkdirSync,
|
|
7
|
+
writeFileSync,
|
|
8
|
+
rmSync,
|
|
9
|
+
mkdtempSync
|
|
10
|
+
} from "fs";
|
|
11
|
+
import { join } from "path";
|
|
12
|
+
import { tmpdir } from "os";
|
|
13
|
+
|
|
14
|
+
// src/plugins/remark-latex-compile/error-parser.ts
|
|
15
|
+
function parseLatexError(latexOutput) {
|
|
16
|
+
const lines = latexOutput.split("\n");
|
|
17
|
+
const errors = [];
|
|
18
|
+
const seenMessages = /* @__PURE__ */ new Set();
|
|
19
|
+
let hasFatal = false;
|
|
20
|
+
for (let i = 0; i < lines.length; i++) {
|
|
21
|
+
const line = lines[i];
|
|
22
|
+
if (line.startsWith("!")) {
|
|
23
|
+
const message = line.substring(1).trim();
|
|
24
|
+
if (seenMessages.has(message)) continue;
|
|
25
|
+
seenMessages.add(message);
|
|
26
|
+
const context = [];
|
|
27
|
+
let lineNum;
|
|
28
|
+
for (let j = i + 1; j < Math.min(i + 5, lines.length); j++) {
|
|
29
|
+
const contextLine = lines[j];
|
|
30
|
+
if (contextLine.trim()) {
|
|
31
|
+
context.push(contextLine);
|
|
32
|
+
}
|
|
33
|
+
if (contextLine.startsWith("l.")) {
|
|
34
|
+
const lineMatch = contextLine.match(/^l\.(\d+)/);
|
|
35
|
+
if (lineMatch) {
|
|
36
|
+
lineNum = parseInt(lineMatch[1], 10);
|
|
37
|
+
}
|
|
38
|
+
break;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
errors.push({
|
|
42
|
+
message,
|
|
43
|
+
line: lineNum,
|
|
44
|
+
context: context.slice(0, 3),
|
|
45
|
+
severity: "error"
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
for (let i = 0; i < lines.length; i++) {
|
|
50
|
+
const line = lines[i];
|
|
51
|
+
if (line.includes("Overfull") || line.includes("Underfull")) {
|
|
52
|
+
const msg = line.trim();
|
|
53
|
+
if (!seenMessages.has(msg)) {
|
|
54
|
+
seenMessages.add(msg);
|
|
55
|
+
errors.push({
|
|
56
|
+
message: msg,
|
|
57
|
+
context: [],
|
|
58
|
+
severity: "warning"
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
for (const line of lines) {
|
|
64
|
+
if (line.toLowerCase().includes("emergency stop") || line.toLowerCase().includes("fatal error") || line.toLowerCase().includes("not found")) {
|
|
65
|
+
hasFatal = true;
|
|
66
|
+
break;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
if (errors.length === 0 && latexOutput.length > 0) {
|
|
70
|
+
for (const line of lines) {
|
|
71
|
+
if (line.includes("error") || line.includes("Error") || line.includes("Misplaced") || line.includes("Missing")) {
|
|
72
|
+
const msg = line.trim();
|
|
73
|
+
if (!seenMessages.has(msg)) {
|
|
74
|
+
seenMessages.add(msg);
|
|
75
|
+
errors.push({
|
|
76
|
+
message: msg,
|
|
77
|
+
context: [],
|
|
78
|
+
severity: "error"
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
break;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
if (errors.length === 0) {
|
|
86
|
+
errors.push({
|
|
87
|
+
message: "Unknown LaTeX compilation error",
|
|
88
|
+
context: [],
|
|
89
|
+
severity: "error"
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
return { errors, hasFatal };
|
|
93
|
+
}
|
|
94
|
+
function formatLatexError(parsed) {
|
|
95
|
+
const RED = "\x1B[31m";
|
|
96
|
+
const YELLOW = "\x1B[33m";
|
|
97
|
+
const RESET = "\x1B[0m";
|
|
98
|
+
const errorCount = parsed.errors.filter((e) => e.severity === "error").length;
|
|
99
|
+
const warningCount = parsed.errors.filter(
|
|
100
|
+
(e) => e.severity === "warning"
|
|
101
|
+
).length;
|
|
102
|
+
let output = `${RED}[remark-latex-compile] LaTeX compilation failed${RESET}
|
|
103
|
+
`;
|
|
104
|
+
output += `${RED}${errorCount} error${errorCount !== 1 ? "s" : ""}${RESET}`;
|
|
105
|
+
if (warningCount > 0) {
|
|
106
|
+
output += `, ${YELLOW}${warningCount} warning${warningCount !== 1 ? "s" : ""}${RESET}`;
|
|
107
|
+
}
|
|
108
|
+
output += "\n\n";
|
|
109
|
+
const errorsByType = parsed.errors.reduce(
|
|
110
|
+
(acc, e) => {
|
|
111
|
+
if (!acc[e.severity]) acc[e.severity] = [];
|
|
112
|
+
acc[e.severity].push(e);
|
|
113
|
+
return acc;
|
|
114
|
+
},
|
|
115
|
+
{}
|
|
116
|
+
);
|
|
117
|
+
for (const err of errorsByType["error"] || []) {
|
|
118
|
+
output += `${RED}Error${RESET}`;
|
|
119
|
+
if (err.line) output += ` (line ${err.line})`;
|
|
120
|
+
output += `: ${err.message}
|
|
121
|
+
`;
|
|
122
|
+
if (err.context.length > 0) {
|
|
123
|
+
output += ` Context: ${err.context[0]}
|
|
124
|
+
`;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
for (const warn of errorsByType["warning"] || []) {
|
|
128
|
+
output += `${YELLOW}Warning${RESET}: ${warn.message}
|
|
129
|
+
`;
|
|
130
|
+
}
|
|
131
|
+
return output;
|
|
132
|
+
}
|
|
133
|
+
function formatLatexSourceWithLineNumbers(latexSource, errors) {
|
|
134
|
+
const RED = "\x1B[31m";
|
|
135
|
+
const RESET = "\x1B[0m";
|
|
136
|
+
const lines = latexSource.split("\n");
|
|
137
|
+
const maxLineNum = lines.length;
|
|
138
|
+
const lineNumWidth = String(maxLineNum).length;
|
|
139
|
+
const errorLineNumbers = new Set(errors.map((e) => e.line).filter(Boolean));
|
|
140
|
+
const formattedLines = lines.map((line, index) => {
|
|
141
|
+
const lineNum = index + 1;
|
|
142
|
+
const lineNumStr = String(lineNum);
|
|
143
|
+
const padding = lineNumStr.length < lineNumWidth ? " " : "";
|
|
144
|
+
if (errorLineNumbers.has(lineNum)) {
|
|
145
|
+
return `${padding}${RED}[${lineNumStr}]:${RESET} ${line}`;
|
|
146
|
+
}
|
|
147
|
+
return `${padding}[${lineNumStr}]: ${line}`;
|
|
148
|
+
}).join("\n");
|
|
149
|
+
return formattedLines;
|
|
150
|
+
}
|
|
151
|
+
function createCompilationErrorMessage(latexSource, rawError) {
|
|
152
|
+
const parsed = parseLatexError(rawError);
|
|
153
|
+
const formatted = formatLatexError(parsed);
|
|
154
|
+
const formattedSource = formatLatexSourceWithLineNumbers(
|
|
155
|
+
latexSource,
|
|
156
|
+
parsed.errors
|
|
157
|
+
);
|
|
158
|
+
return `${formatted}
|
|
159
|
+
LaTeX source:
|
|
160
|
+
${formattedSource}
|
|
161
|
+
`;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// src/plugins/remark-latex-compile/compile.ts
|
|
165
|
+
function hashLatexCode(code) {
|
|
166
|
+
const normalized = code.split("\n").map((line) => line.trim()).filter((line) => !line.startsWith("%")).filter(Boolean).join("\n").trim();
|
|
167
|
+
return createHash("md5").update(normalized).digest("hex").slice(0, 16);
|
|
168
|
+
}
|
|
169
|
+
function buildLatexSource(latexCode) {
|
|
170
|
+
if (latexCode.includes("\\documentclass") && latexCode.includes("\\begin{document}")) {
|
|
171
|
+
return latexCode.trim();
|
|
172
|
+
}
|
|
173
|
+
const separatorRegex = /%[ \t]*===/;
|
|
174
|
+
const parts = latexCode.split(separatorRegex);
|
|
175
|
+
let preamble = "";
|
|
176
|
+
let content = latexCode.trim();
|
|
177
|
+
if (parts.length === 2) {
|
|
178
|
+
preamble = parts[0].trim();
|
|
179
|
+
content = parts[1].trim();
|
|
180
|
+
}
|
|
181
|
+
return [
|
|
182
|
+
"\\documentclass[border=5pt]{standalone}",
|
|
183
|
+
preamble,
|
|
184
|
+
"\\begin{document}",
|
|
185
|
+
"\\Large",
|
|
186
|
+
content,
|
|
187
|
+
"\\end{document}"
|
|
188
|
+
].join("\n");
|
|
189
|
+
}
|
|
190
|
+
function compileLatexToSvg(latexCode, svgOutputDir) {
|
|
191
|
+
const hash = hashLatexCode(latexCode);
|
|
192
|
+
const svgPath = join(svgOutputDir, `${hash}.svg`);
|
|
193
|
+
if (existsSync(svgPath)) {
|
|
194
|
+
return { hash, svgPath, wasCompiled: false };
|
|
195
|
+
}
|
|
196
|
+
mkdirSync(svgOutputDir, { recursive: true });
|
|
197
|
+
const workDir = mkdtempSync(join(tmpdir(), "latex-compile-"));
|
|
198
|
+
const texFile = join(workDir, "diagram.tex");
|
|
199
|
+
const pdfFile = join(workDir, "diagram.pdf");
|
|
200
|
+
const latexSource = buildLatexSource(latexCode);
|
|
201
|
+
try {
|
|
202
|
+
writeFileSync(texFile, latexSource, "utf-8");
|
|
203
|
+
const latexResult = spawnSync("pdflatex", [
|
|
204
|
+
"-interaction=nonstopmode",
|
|
205
|
+
"-output-directory",
|
|
206
|
+
workDir,
|
|
207
|
+
texFile
|
|
208
|
+
]);
|
|
209
|
+
if (latexResult.error) {
|
|
210
|
+
const code = latexResult.error.code;
|
|
211
|
+
throw new Error(
|
|
212
|
+
`[remark-latex-compile] pdflatex not found on PATH (${code}).`
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
if (latexResult.status !== 0) {
|
|
216
|
+
const errorOutput = latexResult.stderr?.toString() || latexResult.stdout?.toString() || "";
|
|
217
|
+
const userMessage = createCompilationErrorMessage(
|
|
218
|
+
latexSource,
|
|
219
|
+
errorOutput
|
|
220
|
+
);
|
|
221
|
+
throw new Error(userMessage);
|
|
222
|
+
}
|
|
223
|
+
const dvisvgmResult = spawnSync("dvisvgm", [
|
|
224
|
+
"--pdf",
|
|
225
|
+
"--bbox=dvi",
|
|
226
|
+
pdfFile,
|
|
227
|
+
"-o",
|
|
228
|
+
svgPath
|
|
229
|
+
]);
|
|
230
|
+
if (dvisvgmResult.error) {
|
|
231
|
+
const code = dvisvgmResult.error.code;
|
|
232
|
+
throw new Error(
|
|
233
|
+
`[remark-latex-compile] dvisvgm not found on PATH (${code}).`
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
if (dvisvgmResult.status !== 0) {
|
|
237
|
+
const errorOutput = dvisvgmResult.stderr?.toString() || dvisvgmResult.stdout?.toString() || "";
|
|
238
|
+
throw new Error(
|
|
239
|
+
`[remark-latex-compile] PDF to SVG conversion failed (hash: ${hash}).
|
|
240
|
+
Error: ${errorOutput}`
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
} finally {
|
|
244
|
+
try {
|
|
245
|
+
rmSync(workDir, { recursive: true, force: true });
|
|
246
|
+
} catch {
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
return { hash, svgPath, wasCompiled: true };
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
export {
|
|
253
|
+
hashLatexCode,
|
|
254
|
+
compileLatexToSvg
|
|
255
|
+
};
|
|
@@ -1,246 +1,9 @@
|
|
|
1
|
-
// src/plugins/remark-latex-compile/index.ts
|
|
2
|
-
import { resolve } from "path";
|
|
3
|
-
|
|
4
|
-
// src/plugins/remark-latex-compile/compile.ts
|
|
5
|
-
import { createHash } from "crypto";
|
|
6
|
-
import { spawnSync } from "child_process";
|
|
7
1
|
import {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
writeFileSync,
|
|
11
|
-
rmSync,
|
|
12
|
-
mkdtempSync
|
|
13
|
-
} from "fs";
|
|
14
|
-
import { join } from "path";
|
|
15
|
-
import { tmpdir } from "os";
|
|
16
|
-
|
|
17
|
-
// src/plugins/remark-latex-compile/error-parser.ts
|
|
18
|
-
function parseLatexError(latexOutput) {
|
|
19
|
-
const lines = latexOutput.split("\n");
|
|
20
|
-
const errors = [];
|
|
21
|
-
const seenMessages = /* @__PURE__ */ new Set();
|
|
22
|
-
let hasFatal = false;
|
|
23
|
-
for (let i = 0; i < lines.length; i++) {
|
|
24
|
-
const line = lines[i];
|
|
25
|
-
if (line.startsWith("!")) {
|
|
26
|
-
const message = line.substring(1).trim();
|
|
27
|
-
if (seenMessages.has(message)) continue;
|
|
28
|
-
seenMessages.add(message);
|
|
29
|
-
const context = [];
|
|
30
|
-
let lineNum;
|
|
31
|
-
for (let j = i + 1; j < Math.min(i + 5, lines.length); j++) {
|
|
32
|
-
const contextLine = lines[j];
|
|
33
|
-
if (contextLine.trim()) {
|
|
34
|
-
context.push(contextLine);
|
|
35
|
-
}
|
|
36
|
-
if (contextLine.startsWith("l.")) {
|
|
37
|
-
const lineMatch = contextLine.match(/^l\.(\d+)/);
|
|
38
|
-
if (lineMatch) {
|
|
39
|
-
lineNum = parseInt(lineMatch[1], 10);
|
|
40
|
-
}
|
|
41
|
-
break;
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
errors.push({
|
|
45
|
-
message,
|
|
46
|
-
line: lineNum,
|
|
47
|
-
context: context.slice(0, 3),
|
|
48
|
-
severity: "error"
|
|
49
|
-
});
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
for (let i = 0; i < lines.length; i++) {
|
|
53
|
-
const line = lines[i];
|
|
54
|
-
if (line.includes("Overfull") || line.includes("Underfull")) {
|
|
55
|
-
const msg = line.trim();
|
|
56
|
-
if (!seenMessages.has(msg)) {
|
|
57
|
-
seenMessages.add(msg);
|
|
58
|
-
errors.push({
|
|
59
|
-
message: msg,
|
|
60
|
-
context: [],
|
|
61
|
-
severity: "warning"
|
|
62
|
-
});
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
for (const line of lines) {
|
|
67
|
-
if (line.toLowerCase().includes("emergency stop") || line.toLowerCase().includes("fatal error") || line.toLowerCase().includes("not found")) {
|
|
68
|
-
hasFatal = true;
|
|
69
|
-
break;
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
if (errors.length === 0 && latexOutput.length > 0) {
|
|
73
|
-
for (const line of lines) {
|
|
74
|
-
if (line.includes("error") || line.includes("Error") || line.includes("Misplaced") || line.includes("Missing")) {
|
|
75
|
-
const msg = line.trim();
|
|
76
|
-
if (!seenMessages.has(msg)) {
|
|
77
|
-
seenMessages.add(msg);
|
|
78
|
-
errors.push({
|
|
79
|
-
message: msg,
|
|
80
|
-
context: [],
|
|
81
|
-
severity: "error"
|
|
82
|
-
});
|
|
83
|
-
}
|
|
84
|
-
break;
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
if (errors.length === 0) {
|
|
89
|
-
errors.push({
|
|
90
|
-
message: "Unknown LaTeX compilation error",
|
|
91
|
-
context: [],
|
|
92
|
-
severity: "error"
|
|
93
|
-
});
|
|
94
|
-
}
|
|
95
|
-
return { errors, hasFatal };
|
|
96
|
-
}
|
|
97
|
-
function formatLatexError(parsed) {
|
|
98
|
-
const RED = "\x1B[31m";
|
|
99
|
-
const YELLOW = "\x1B[33m";
|
|
100
|
-
const RESET = "\x1B[0m";
|
|
101
|
-
const errorCount = parsed.errors.filter((e) => e.severity === "error").length;
|
|
102
|
-
const warningCount = parsed.errors.filter(
|
|
103
|
-
(e) => e.severity === "warning"
|
|
104
|
-
).length;
|
|
105
|
-
let output = `${RED}[remark-latex-compile] LaTeX compilation failed${RESET}
|
|
106
|
-
`;
|
|
107
|
-
output += `${RED}${errorCount} error${errorCount !== 1 ? "s" : ""}${RESET}`;
|
|
108
|
-
if (warningCount > 0) {
|
|
109
|
-
output += `, ${YELLOW}${warningCount} warning${warningCount !== 1 ? "s" : ""}${RESET}`;
|
|
110
|
-
}
|
|
111
|
-
output += "\n\n";
|
|
112
|
-
const errorsByType = parsed.errors.reduce(
|
|
113
|
-
(acc, e) => {
|
|
114
|
-
if (!acc[e.severity]) acc[e.severity] = [];
|
|
115
|
-
acc[e.severity].push(e);
|
|
116
|
-
return acc;
|
|
117
|
-
},
|
|
118
|
-
{}
|
|
119
|
-
);
|
|
120
|
-
for (const err of errorsByType["error"] || []) {
|
|
121
|
-
output += `${RED}Error${RESET}`;
|
|
122
|
-
if (err.line) output += ` (line ${err.line})`;
|
|
123
|
-
output += `: ${err.message}
|
|
124
|
-
`;
|
|
125
|
-
if (err.context.length > 0) {
|
|
126
|
-
output += ` Context: ${err.context[0]}
|
|
127
|
-
`;
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
for (const warn of errorsByType["warning"] || []) {
|
|
131
|
-
output += `${YELLOW}Warning${RESET}: ${warn.message}
|
|
132
|
-
`;
|
|
133
|
-
}
|
|
134
|
-
return output;
|
|
135
|
-
}
|
|
136
|
-
function formatLatexSourceWithLineNumbers(latexSource, errors) {
|
|
137
|
-
const RED = "\x1B[31m";
|
|
138
|
-
const RESET = "\x1B[0m";
|
|
139
|
-
const lines = latexSource.split("\n");
|
|
140
|
-
const maxLineNum = lines.length;
|
|
141
|
-
const lineNumWidth = String(maxLineNum).length;
|
|
142
|
-
const errorLineNumbers = new Set(errors.map((e) => e.line).filter(Boolean));
|
|
143
|
-
const formattedLines = lines.map((line, index) => {
|
|
144
|
-
const lineNum = index + 1;
|
|
145
|
-
const lineNumStr = String(lineNum);
|
|
146
|
-
const padding = lineNumStr.length < lineNumWidth ? " " : "";
|
|
147
|
-
if (errorLineNumbers.has(lineNum)) {
|
|
148
|
-
return `${padding}${RED}[${lineNumStr}]:${RESET} ${line}`;
|
|
149
|
-
}
|
|
150
|
-
return `${padding}[${lineNumStr}]: ${line}`;
|
|
151
|
-
}).join("\n");
|
|
152
|
-
return formattedLines;
|
|
153
|
-
}
|
|
154
|
-
function createCompilationErrorMessage(latexSource, rawError) {
|
|
155
|
-
const parsed = parseLatexError(rawError);
|
|
156
|
-
const formatted = formatLatexError(parsed);
|
|
157
|
-
const formattedSource = formatLatexSourceWithLineNumbers(
|
|
158
|
-
latexSource,
|
|
159
|
-
parsed.errors
|
|
160
|
-
);
|
|
161
|
-
return `${formatted}
|
|
162
|
-
LaTeX source:
|
|
163
|
-
${formattedSource}
|
|
164
|
-
`;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
// src/plugins/remark-latex-compile/compile.ts
|
|
168
|
-
function hashLatexCode(code) {
|
|
169
|
-
const normalized = code.split("\n").map((line) => line.trim()).filter((line) => !line.startsWith("%")).filter(Boolean).join("\n").trim();
|
|
170
|
-
return createHash("md5").update(normalized).digest("hex").slice(0, 16);
|
|
171
|
-
}
|
|
172
|
-
function buildLatexSource(latexCode) {
|
|
173
|
-
if (latexCode.includes("\\documentclass") && latexCode.includes("\\begin{document}")) {
|
|
174
|
-
return latexCode.trim();
|
|
175
|
-
}
|
|
176
|
-
const separatorRegex = /%[ \t]*===/;
|
|
177
|
-
const parts = latexCode.split(separatorRegex);
|
|
178
|
-
let preamble = "";
|
|
179
|
-
let content = latexCode.trim();
|
|
180
|
-
if (parts.length === 2) {
|
|
181
|
-
preamble = parts[0].trim();
|
|
182
|
-
content = parts[1].trim();
|
|
183
|
-
}
|
|
184
|
-
return [
|
|
185
|
-
"\\documentclass[border=5pt]{standalone}",
|
|
186
|
-
preamble,
|
|
187
|
-
"\\begin{document}",
|
|
188
|
-
"\\Large",
|
|
189
|
-
content,
|
|
190
|
-
"\\end{document}"
|
|
191
|
-
].join("\n");
|
|
192
|
-
}
|
|
193
|
-
function compileLatexToSvg(latexCode, svgOutputDir) {
|
|
194
|
-
const hash = hashLatexCode(latexCode);
|
|
195
|
-
const svgPath = join(svgOutputDir, `${hash}.svg`);
|
|
196
|
-
if (existsSync(svgPath)) {
|
|
197
|
-
return { hash, svgPath, wasCompiled: false };
|
|
198
|
-
}
|
|
199
|
-
mkdirSync(svgOutputDir, { recursive: true });
|
|
200
|
-
const workDir = mkdtempSync(join(tmpdir(), "latex-compile-"));
|
|
201
|
-
const texFile = join(workDir, "diagram.tex");
|
|
202
|
-
const pdfFile = join(workDir, "diagram.pdf");
|
|
203
|
-
const latexSource = buildLatexSource(latexCode);
|
|
204
|
-
try {
|
|
205
|
-
writeFileSync(texFile, latexSource, "utf-8");
|
|
206
|
-
const latexResult = spawnSync("pdflatex", [
|
|
207
|
-
"-interaction=nonstopmode",
|
|
208
|
-
"-output-directory",
|
|
209
|
-
workDir,
|
|
210
|
-
texFile
|
|
211
|
-
]);
|
|
212
|
-
if (latexResult.status !== 0) {
|
|
213
|
-
const errorOutput = latexResult.stderr?.toString() || latexResult.stdout?.toString() || "";
|
|
214
|
-
const userMessage = createCompilationErrorMessage(
|
|
215
|
-
latexSource,
|
|
216
|
-
errorOutput
|
|
217
|
-
);
|
|
218
|
-
throw new Error(userMessage);
|
|
219
|
-
}
|
|
220
|
-
const dvisvgmResult = spawnSync("dvisvgm", [
|
|
221
|
-
"--pdf",
|
|
222
|
-
"--bbox=dvi",
|
|
223
|
-
pdfFile,
|
|
224
|
-
"-o",
|
|
225
|
-
svgPath
|
|
226
|
-
]);
|
|
227
|
-
if (dvisvgmResult.status !== 0) {
|
|
228
|
-
const errorOutput = dvisvgmResult.stderr?.toString() || dvisvgmResult.stdout?.toString() || "";
|
|
229
|
-
throw new Error(
|
|
230
|
-
`[remark-latex-compile] PDF to SVG conversion failed (hash: ${hash}).
|
|
231
|
-
Error: ${errorOutput}`
|
|
232
|
-
);
|
|
233
|
-
}
|
|
234
|
-
} finally {
|
|
235
|
-
try {
|
|
236
|
-
rmSync(workDir, { recursive: true, force: true });
|
|
237
|
-
} catch {
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
return { hash, svgPath, wasCompiled: true };
|
|
241
|
-
}
|
|
2
|
+
compileLatexToSvg
|
|
3
|
+
} from "./chunk-QZUB4DOG.js";
|
|
242
4
|
|
|
243
5
|
// src/plugins/remark-latex-compile/index.ts
|
|
6
|
+
import { resolve } from "path";
|
|
244
7
|
function extractClassesFromMeta(meta) {
|
|
245
8
|
const classMatch = meta.match(/class="([^"]+)"/);
|
|
246
9
|
if (classMatch && classMatch[1]) {
|
|
@@ -304,11 +67,11 @@ function remarkLatexCompile(options) {
|
|
|
304
67
|
|
|
305
68
|
// src/plugins/remark-latex-compile/astro-integration.ts
|
|
306
69
|
import { readdir, readFile, writeFile } from "fs/promises";
|
|
307
|
-
import { resolve as resolve2, join
|
|
308
|
-
import { createHash
|
|
309
|
-
function
|
|
70
|
+
import { resolve as resolve2, join, extname } from "path";
|
|
71
|
+
import { createHash } from "crypto";
|
|
72
|
+
function hashLatexCode(code) {
|
|
310
73
|
const normalized = code.split("\n").map((line) => line.trim()).join("\n").trim();
|
|
311
|
-
return
|
|
74
|
+
return createHash("md5").update(normalized).digest("hex").slice(0, 16);
|
|
312
75
|
}
|
|
313
76
|
function createAstroLatexIntegration(options) {
|
|
314
77
|
const svgOutputDir = resolve2(options.svgOutputDir);
|
|
@@ -341,7 +104,7 @@ function createAstroLatexIntegration(options) {
|
|
|
341
104
|
async function scanAndCompileLatex(dir, svgOutputDir) {
|
|
342
105
|
const entries = await readdir(dir, { withFileTypes: true });
|
|
343
106
|
for (const entry of entries) {
|
|
344
|
-
const fullPath =
|
|
107
|
+
const fullPath = join(dir, entry.name);
|
|
345
108
|
if (entry.isDirectory()) {
|
|
346
109
|
await scanAndCompileLatex(fullPath, svgOutputDir);
|
|
347
110
|
} else if (entry.isFile()) {
|
|
@@ -380,7 +143,7 @@ async function updateHtmlReferences(buildDir, contentDir, svgOutputDir) {
|
|
|
380
143
|
const latexHashes = [];
|
|
381
144
|
const entries = await readdir(contentDir, { withFileTypes: true });
|
|
382
145
|
for (const entry of entries) {
|
|
383
|
-
const fullPath =
|
|
146
|
+
const fullPath = join(contentDir, entry.name);
|
|
384
147
|
if (entry.isDirectory()) {
|
|
385
148
|
await scanMarkdownForHashes(fullPath, latexHashes);
|
|
386
149
|
}
|
|
@@ -390,7 +153,7 @@ async function updateHtmlReferences(buildDir, contentDir, svgOutputDir) {
|
|
|
390
153
|
async function scanMarkdownForHashes(dir, hashes) {
|
|
391
154
|
const entries = await readdir(dir, { withFileTypes: true });
|
|
392
155
|
for (const entry of entries) {
|
|
393
|
-
const fullPath =
|
|
156
|
+
const fullPath = join(dir, entry.name);
|
|
394
157
|
if (entry.isDirectory()) {
|
|
395
158
|
await scanMarkdownForHashes(fullPath, hashes);
|
|
396
159
|
} else if (entry.isFile() && (entry.name.endsWith(".md") || entry.name.endsWith(".mdx"))) {
|
|
@@ -399,7 +162,7 @@ async function scanMarkdownForHashes(dir, hashes) {
|
|
|
399
162
|
const matches = content.matchAll(latexBlockRegex);
|
|
400
163
|
for (const match of matches) {
|
|
401
164
|
const latexCode = match[1];
|
|
402
|
-
const hash =
|
|
165
|
+
const hash = hashLatexCode(latexCode);
|
|
403
166
|
hashes.push(hash);
|
|
404
167
|
}
|
|
405
168
|
}
|
|
@@ -409,7 +172,7 @@ async function updateHtmlDirWithHashes(dir, hashes, svgOutputDir) {
|
|
|
409
172
|
let hashIndex = 0;
|
|
410
173
|
const entries = await readdir(dir, { withFileTypes: true });
|
|
411
174
|
for (const entry of entries) {
|
|
412
|
-
const fullPath =
|
|
175
|
+
const fullPath = join(dir, entry.name);
|
|
413
176
|
if (entry.isDirectory()) {
|
|
414
177
|
await updateHtmlDirWithHashes(fullPath, hashes, svgOutputDir);
|
|
415
178
|
} else if (entry.isFile() && entry.name.endsWith(".html")) {
|
|
@@ -464,7 +227,6 @@ function starlightLatexCompile(options) {
|
|
|
464
227
|
var starlight_plugin_default = starlightLatexCompile;
|
|
465
228
|
|
|
466
229
|
export {
|
|
467
|
-
compileLatexToSvg,
|
|
468
230
|
starlightLatexCompile,
|
|
469
231
|
starlight_plugin_default,
|
|
470
232
|
remarkLatexCompile
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
hashLatexCode
|
|
4
|
+
} from "../chunk-QZUB4DOG.js";
|
|
5
|
+
import "../chunk-QGM4M3NI.js";
|
|
2
6
|
|
|
3
7
|
// scripts/cli/cannoli-latex-cleanup.ts
|
|
4
8
|
import { readdir, readFile, unlink } from "fs/promises";
|
|
5
9
|
import { resolve, join } from "path";
|
|
6
|
-
import { createHash } from "crypto";
|
|
7
10
|
var args = process.argv.slice(2);
|
|
8
11
|
var svgDir = null;
|
|
9
12
|
var docsDir = "src/content/docs";
|
|
@@ -30,10 +33,6 @@ if (!checkMode && !deleteMode) {
|
|
|
30
33
|
}
|
|
31
34
|
var svgDirPath = resolve(svgDir);
|
|
32
35
|
var docsDirPath = resolve(docsDir);
|
|
33
|
-
function hashLatexCode(code) {
|
|
34
|
-
const normalized = code.split("\n").map((line) => line.trim()).join("\n").trim();
|
|
35
|
-
return createHash("md5").update(normalized).digest("hex").slice(0, 16);
|
|
36
|
-
}
|
|
37
36
|
async function scanMarkdownForHashes(dir, hashes) {
|
|
38
37
|
try {
|
|
39
38
|
const entries = await readdir(dir, { withFileTypes: true });
|
package/dist/index.js
CHANGED
|
@@ -7,11 +7,12 @@ import {
|
|
|
7
7
|
import {
|
|
8
8
|
remarkLatexCompile,
|
|
9
9
|
starlightLatexCompile
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-UMYD2SJL.js";
|
|
11
11
|
import {
|
|
12
12
|
starlightSyncDocsToPublic
|
|
13
13
|
} from "./chunk-GE24XGG7.js";
|
|
14
14
|
import "./chunk-3ATSZG6H.js";
|
|
15
|
+
import "./chunk-QZUB4DOG.js";
|
|
15
16
|
import "./chunk-QGM4M3NI.js";
|
|
16
17
|
|
|
17
18
|
// src/plugins/astro-normalize-paths.ts
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
|
-
compileLatexToSvg,
|
|
3
2
|
remarkLatexCompile,
|
|
4
3
|
starlightLatexCompile
|
|
5
|
-
} from "../chunk-
|
|
4
|
+
} from "../chunk-UMYD2SJL.js";
|
|
5
|
+
import {
|
|
6
|
+
compileLatexToSvg
|
|
7
|
+
} from "../chunk-QZUB4DOG.js";
|
|
6
8
|
import "../chunk-QGM4M3NI.js";
|
|
7
9
|
export {
|
|
8
10
|
compileLatexToSvg,
|
package/package.json
CHANGED