starlight-cannoli-plugins 2.17.2 → 2.18.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/README.md
CHANGED
|
@@ -144,7 +144,7 @@ dvisvgm --version
|
|
|
144
144
|
- `removeOrphanedSvgs` (optional, default: `false`): When `true`, SVG files that are no longer referenced by any `tex compile` block are deleted automatically. In dev mode, stale SVGs are removed immediately when a block is edited. On build, any remaining orphans are swept at the end.
|
|
145
145
|
- `svgClassname` (optional): Default CSS class(es) applied to every compiled SVG `<img>` element. Individual blocks can override this with a `class` meta tag value. The `tex-compiled` class is always present and cannot be overridden.
|
|
146
146
|
- `texInputDirs` (optional): Directories added to the TeX input search path (`TEXINPUTS`), allowing `\input{}` and `\include{}` to resolve files from your project. Use a trailing `/` to search only that directory, or `//` to search it recursively. Multiple directories are supported. When any file in these directories changes, all cached SVGs are invalidated and recompiled.
|
|
147
|
-
- `
|
|
147
|
+
- `debug` (optional): When set, a JPEG copy of each compiled diagram is written to `debug.outputDir` as a flat file list (no mirrored folder structure). Only blocks that carry a `blockid=<n>` meta tag produce a JPEG — blocks without it are ignored. The filename format is `<originating-file>--<blockid>-<8-char-dir-checksum>.jpg` (e.g. `tex-test--5-a1b2c3d4.jpg`), where the checksum is an MD5 hash of the originating file's directory, namespacing files so that same-named files from different directories don't collide. JPEGs are deleted automatically when their block is removed, its `blockid` changes, or its content changes, and the entire `debug.outputDir` is cleared on dev server startup. Every JPEG (re)write is also appended to `debug.logFile`, which rotates automatically once it grows too large (rolling backups `<logFile>-1.log`, `<logFile>-2.log`, etc.). SVG output is unaffected — the JPEG/log output is complementary and intended for local inspection.
|
|
148
148
|
|
|
149
149
|
```ts
|
|
150
150
|
astroLatexCompile({
|
|
@@ -9,18 +9,67 @@ import { join as join2, relative as relative2, resolve as resolve2 } from "path"
|
|
|
9
9
|
import { visit, SKIP } from "unist-util-visit";
|
|
10
10
|
import { MetaOptions } from "@expressive-code/core";
|
|
11
11
|
|
|
12
|
+
// src/plugins/utils/rotating-file-logger.ts
|
|
13
|
+
import { existsSync, mkdirSync, renameSync, rmSync, statSync } from "fs";
|
|
14
|
+
import { appendFile } from "fs/promises";
|
|
15
|
+
import { dirname, extname } from "path";
|
|
16
|
+
function rotatedPath(filePath, index) {
|
|
17
|
+
const ext = extname(filePath);
|
|
18
|
+
const base = filePath.slice(0, filePath.length - ext.length);
|
|
19
|
+
return `${base}-${index}${ext}`;
|
|
20
|
+
}
|
|
21
|
+
function formatTimestamp(date) {
|
|
22
|
+
const pad = (n, width = 2) => String(n).padStart(width, "0");
|
|
23
|
+
const datePart = `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`;
|
|
24
|
+
const timePart = `${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())},${pad(date.getMilliseconds(), 3)}`;
|
|
25
|
+
return `${datePart} ${timePart}`;
|
|
26
|
+
}
|
|
27
|
+
function rotate(options) {
|
|
28
|
+
const { filePath, backupCount } = options;
|
|
29
|
+
const oldestPath = rotatedPath(filePath, backupCount);
|
|
30
|
+
if (existsSync(oldestPath)) {
|
|
31
|
+
rmSync(oldestPath);
|
|
32
|
+
}
|
|
33
|
+
for (let index = backupCount - 1; index >= 1; index--) {
|
|
34
|
+
const source = rotatedPath(filePath, index);
|
|
35
|
+
if (existsSync(source)) {
|
|
36
|
+
renameSync(source, rotatedPath(filePath, index + 1));
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
if (backupCount >= 1) {
|
|
40
|
+
renameSync(filePath, rotatedPath(filePath, 1));
|
|
41
|
+
} else {
|
|
42
|
+
rmSync(filePath);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
var RotatingFileLogger = class {
|
|
46
|
+
options;
|
|
47
|
+
constructor(options) {
|
|
48
|
+
this.options = options;
|
|
49
|
+
mkdirSync(dirname(options.filePath), { recursive: true });
|
|
50
|
+
}
|
|
51
|
+
async write(message) {
|
|
52
|
+
const { filePath, maxSizeBytes, identifier } = this.options;
|
|
53
|
+
const line = `[${identifier}] ${formatTimestamp(/* @__PURE__ */ new Date())} ${message}${message.endsWith("\n") ? "" : "\n"}`;
|
|
54
|
+
if (existsSync(filePath) && statSync(filePath).size >= maxSizeBytes) {
|
|
55
|
+
rotate(this.options);
|
|
56
|
+
}
|
|
57
|
+
await appendFile(filePath, line);
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
|
|
12
61
|
// src/plugins/astro-latex-compile/utils.ts
|
|
13
62
|
import { createHash } from "crypto";
|
|
14
63
|
import {
|
|
15
|
-
existsSync,
|
|
16
|
-
mkdirSync,
|
|
64
|
+
existsSync as existsSync2,
|
|
65
|
+
mkdirSync as mkdirSync2,
|
|
17
66
|
readdirSync,
|
|
18
67
|
readFileSync,
|
|
19
68
|
writeFileSync,
|
|
20
|
-
rmSync,
|
|
69
|
+
rmSync as rmSync2,
|
|
21
70
|
mkdtempSync
|
|
22
71
|
} from "fs";
|
|
23
|
-
import { basename, dirname, join, relative, resolve } from "path";
|
|
72
|
+
import { basename, dirname as dirname2, extname as extname2, join, relative, resolve } from "path";
|
|
24
73
|
import { tmpdir } from "os";
|
|
25
74
|
import sharp from "sharp";
|
|
26
75
|
|
|
@@ -251,18 +300,18 @@ function html(literalValues, ...interpolatedValues) {
|
|
|
251
300
|
|
|
252
301
|
// src/plugins/astro-latex-compile/utils.ts
|
|
253
302
|
var CONTENT_ROOT = "src/content/docs/";
|
|
254
|
-
function computeJpgPath(
|
|
303
|
+
function computeJpgPath(outputDir, filePath, blockId) {
|
|
255
304
|
const normalized = filePath.replace(/\\/g, "/");
|
|
256
305
|
const idx = normalized.indexOf(CONTENT_ROOT);
|
|
257
306
|
const relativePath = idx !== -1 ? normalized.slice(idx + CONTENT_ROOT.length) : basename(normalized);
|
|
258
|
-
const dir =
|
|
259
|
-
const filename = basename(relativePath);
|
|
260
|
-
const
|
|
261
|
-
const
|
|
262
|
-
return
|
|
307
|
+
const dir = dirname2(relativePath);
|
|
308
|
+
const filename = basename(relativePath, extname2(relativePath));
|
|
309
|
+
const dirChecksum = createHash("md5").update(dir).digest("hex").slice(0, 8);
|
|
310
|
+
const jpgFilename = `${filename}--${blockId}-${dirChecksum}.jpg`;
|
|
311
|
+
return join(resolve(outputDir), jpgFilename);
|
|
263
312
|
}
|
|
264
313
|
async function writeJpgFromSvg(svgPath, jpgPath) {
|
|
265
|
-
|
|
314
|
+
mkdirSync2(dirname2(jpgPath), { recursive: true });
|
|
266
315
|
await sharp(svgPath, { density: 300 }).flatten({ background: { r: 255, g: 255, b: 255 } }).jpeg({ quality: 90 }).toFile(jpgPath);
|
|
267
316
|
}
|
|
268
317
|
function stripAnsi(text) {
|
|
@@ -313,7 +362,7 @@ async function writeJpgError(jpgPath, header, errorText) {
|
|
|
313
362
|
<text x="${padding}" y="${padding + 40}" font-family="monospace" font-size="12" fill="#666666">${escapeXml(header)}</text>
|
|
314
363
|
${textLines}
|
|
315
364
|
</svg>`;
|
|
316
|
-
|
|
365
|
+
mkdirSync2(dirname2(jpgPath), { recursive: true });
|
|
317
366
|
await sharp(Buffer.from(svg)).flatten({ background: { r: 255, g: 255, b: 255 } }).jpeg({ quality: 90 }).toFile(jpgPath);
|
|
318
367
|
}
|
|
319
368
|
function computeLineOffset(_latexCode) {
|
|
@@ -418,10 +467,10 @@ function buildLatexSource(latexCode) {
|
|
|
418
467
|
async function compileLatexToSvg(latexCode, svgOutputDir, texInputDirs = [], inputsSalt = "") {
|
|
419
468
|
const hash = hashLatexCode(latexCode, inputsSalt);
|
|
420
469
|
const svgPath = join(svgOutputDir, `${hash}.svg`);
|
|
421
|
-
if (
|
|
470
|
+
if (existsSync2(svgPath)) {
|
|
422
471
|
return { hash, svgPath, wasCompiled: false };
|
|
423
472
|
}
|
|
424
|
-
|
|
473
|
+
mkdirSync2(svgOutputDir, { recursive: true });
|
|
425
474
|
const workDir = mkdtempSync(join(tmpdir(), "latex-compile-"));
|
|
426
475
|
const texFile = join(workDir, "diagram.tex");
|
|
427
476
|
const pdfFile = join(workDir, "diagram.pdf");
|
|
@@ -479,7 +528,7 @@ Error: ${errorOutput}`
|
|
|
479
528
|
}
|
|
480
529
|
} finally {
|
|
481
530
|
try {
|
|
482
|
-
|
|
531
|
+
rmSync2(workDir, { recursive: true, force: true });
|
|
483
532
|
} catch {
|
|
484
533
|
}
|
|
485
534
|
}
|
|
@@ -487,6 +536,8 @@ Error: ${errorOutput}`
|
|
|
487
536
|
}
|
|
488
537
|
|
|
489
538
|
// src/plugins/astro-latex-compile/index.ts
|
|
539
|
+
var DEBUG_LOG_MAX_SIZE_BYTES = 5 * 1024 * 1024;
|
|
540
|
+
var DEBUG_LOG_BACKUP_COUNT = 3;
|
|
490
541
|
function getFrontmatterOffset(absoluteFilePath) {
|
|
491
542
|
try {
|
|
492
543
|
const original = readFileSync2(absoluteFilePath, "utf-8");
|
|
@@ -499,6 +550,12 @@ function getFrontmatterOffset(absoluteFilePath) {
|
|
|
499
550
|
}
|
|
500
551
|
function latexCompileTransformer(options) {
|
|
501
552
|
const svgOutputDir = resolve2(options.svgOutputDir);
|
|
553
|
+
const debugLogger = options.debug ? new RotatingFileLogger({
|
|
554
|
+
identifier: "astro-latex-compile",
|
|
555
|
+
filePath: options.debug.logFile,
|
|
556
|
+
maxSizeBytes: DEBUG_LOG_MAX_SIZE_BYTES,
|
|
557
|
+
backupCount: DEBUG_LOG_BACKUP_COUNT
|
|
558
|
+
}) : null;
|
|
502
559
|
return async function transformer(tree, file) {
|
|
503
560
|
const nodes = [];
|
|
504
561
|
visit(tree, "code", (node, index, parent) => {
|
|
@@ -516,7 +573,7 @@ function latexCompileTransformer(options) {
|
|
|
516
573
|
nodes.map(async ({ node, index, parent }) => {
|
|
517
574
|
const lineNumberStr = node.position?.start.line !== void 0 ? String(node.position.start.line + frontmatterOffset) : "?";
|
|
518
575
|
const blockId = new MetaOptions(node.meta ?? "").getInteger("blockid");
|
|
519
|
-
const jpgPath = options.
|
|
576
|
+
const jpgPath = options.debug && blockId !== void 0 ? computeJpgPath(options.debug.outputDir, filePath, blockId) : null;
|
|
520
577
|
try {
|
|
521
578
|
const result = await compileLatexToSvg(
|
|
522
579
|
node.value,
|
|
@@ -532,8 +589,8 @@ function latexCompileTransformer(options) {
|
|
|
532
589
|
options._referencedHashes?.add(result.hash);
|
|
533
590
|
if (jpgPath) {
|
|
534
591
|
await writeJpgFromSvg(result.svgPath, jpgPath);
|
|
535
|
-
|
|
536
|
-
|
|
592
|
+
await debugLogger?.write(
|
|
593
|
+
`${relFilePath}:${lineNumberStr}: wrote ${jpgPath}`
|
|
537
594
|
);
|
|
538
595
|
}
|
|
539
596
|
return {
|
|
@@ -588,7 +645,7 @@ ${details}`
|
|
|
588
645
|
);
|
|
589
646
|
options._fileHashMap.set(filePath, newHashes);
|
|
590
647
|
}
|
|
591
|
-
if (options.
|
|
648
|
+
if (options.debug && options._fileJpgPathMap) {
|
|
592
649
|
const newJpgPaths = new Set(
|
|
593
650
|
results.map((r) => r.jpgPath).filter(Boolean)
|
|
594
651
|
);
|
package/dist/index.js
CHANGED
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
} from "./chunk-3UMY7T6G.js";
|
|
10
10
|
import {
|
|
11
11
|
latexCompileTransformer
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-GVS36MVT.js";
|
|
13
13
|
import {
|
|
14
14
|
astroNormalizePaths
|
|
15
15
|
} from "./chunk-TLOFSB33.js";
|
|
@@ -311,12 +311,24 @@ function astroLatexCompile(options) {
|
|
|
311
311
|
if (command === "build") {
|
|
312
312
|
await clearContentLayerCache(config);
|
|
313
313
|
}
|
|
314
|
+
if (command === "dev" && options.debug) {
|
|
315
|
+
const debugOutputDir = resolve2(options.debug.outputDir);
|
|
316
|
+
let entries;
|
|
317
|
+
try {
|
|
318
|
+
entries = await readdir(debugOutputDir);
|
|
319
|
+
} catch {
|
|
320
|
+
entries = [];
|
|
321
|
+
}
|
|
322
|
+
await Promise.all(
|
|
323
|
+
entries.filter((f) => f.endsWith(".jpg")).map((f) => rm(join3(debugOutputDir, f), { force: true }))
|
|
324
|
+
);
|
|
325
|
+
}
|
|
314
326
|
const existingPlugins = Array.isArray(config.markdown?.remarkPlugins) ? config.markdown.remarkPlugins.filter(Boolean) : [];
|
|
315
327
|
const pluginOptions = {
|
|
316
328
|
...options,
|
|
317
329
|
_fileHashMap: options.removeOrphanedSvgs ? fileHashMap : void 0,
|
|
318
330
|
_referencedHashes: command === "build" && options.removeOrphanedSvgs ? referencedHashes : void 0,
|
|
319
|
-
_fileJpgPathMap: options.
|
|
331
|
+
_fileJpgPathMap: options.debug ? fileJpgPathMap : void 0
|
|
320
332
|
};
|
|
321
333
|
updateConfig({
|
|
322
334
|
markdown: {
|
|
@@ -36,14 +36,20 @@ interface LatexCompilePluginOptions {
|
|
|
36
36
|
*/
|
|
37
37
|
texInputDirs?: string[];
|
|
38
38
|
/**
|
|
39
|
-
* When set, a JPEG copy of each compiled diagram is written
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
39
|
+
* When set, a JPEG copy of each compiled diagram is written to a flat
|
|
40
|
+
* directory. Only blocks that carry a `blockid=<n>` meta tag produce a
|
|
41
|
+
* JPEG. Filename format: `<originating-file>-<8-char-dir-checksum>--<blockid>.jpg`,
|
|
42
|
+
* where the checksum namespaces files originating from different
|
|
43
|
+
* directories to avoid collisions between files with the same name.
|
|
43
44
|
* JPEGs are deleted automatically when their block is removed or its content
|
|
44
45
|
* changes. Intended for local inspection, not for publishing.
|
|
45
46
|
*/
|
|
46
|
-
|
|
47
|
+
debug?: {
|
|
48
|
+
/** Directory where debug JPEGs are written, as a flat file list. */
|
|
49
|
+
outputDir: string;
|
|
50
|
+
/** Path to a log file that records every JPEG (re)write. Rotates by size. */
|
|
51
|
+
logFile: string;
|
|
52
|
+
};
|
|
47
53
|
/**
|
|
48
54
|
* @internal Populated by the Astro integration to track which hashes were
|
|
49
55
|
* referenced during a build, used for full orphan cleanup at build:done.
|
package/package.json
CHANGED