starlight-cannoli-plugins 2.10.5 → 2.11.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,6 +144,7 @@ dvisvgm --version
144
144
  - `svgOutputDir` (required): Directory where compiled SVG files are written. Must be inside `public/` so Astro serves them as static assets.
145
145
  - `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.
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.
147
+ - `tempOutputDir` (optional): When set, a JPEG copy of each compiled diagram is written to this directory, mirroring the folder structure of `src/content/docs/`. The filename format is `<originating-file>--<line-number>.jpg` (e.g. `tex-test.md--11.jpg`). Useful for previewing or post-processing diagrams outside the browser. JPEGs are deleted automatically when their corresponding block fails compilation or is removed. SVG output is unaffected — the JPEG is complementary.
147
148
 
148
149
  ```ts
149
150
  astroLatexCompile({
@@ -1,6 +1,7 @@
1
1
  // src/plugins/astro-latex-compile/index.ts
2
+ import { existsSync as existsSync2 } from "fs";
2
3
  import { rm } from "fs/promises";
3
- import { join as join2, resolve } from "path";
4
+ import { join as join2, resolve as resolve2 } from "path";
4
5
  import { visit, SKIP } from "unist-util-visit";
5
6
  import { MetaOptions } from "@expressive-code/core";
6
7
 
@@ -13,8 +14,9 @@ import {
13
14
  rmSync,
14
15
  mkdtempSync
15
16
  } from "fs";
16
- import { join } from "path";
17
+ import { basename, dirname, join, resolve } from "path";
17
18
  import { tmpdir } from "os";
19
+ import sharp from "sharp";
18
20
 
19
21
  // src/plugins/astro-latex-compile/error-parser.ts
20
22
  function parseLatexError(latexOutput) {
@@ -167,7 +169,7 @@ ${formattedSource}
167
169
  // src/plugins/utils/process-utils.ts
168
170
  import { spawn } from "child_process";
169
171
  function execProcess(command, args, options) {
170
- return new Promise((resolve2, reject) => {
172
+ return new Promise((resolve3, reject) => {
171
173
  let stdout = "";
172
174
  let stderr = "";
173
175
  const proc = spawn(command, args, { env: options?.env });
@@ -181,12 +183,27 @@ function execProcess(command, args, options) {
181
183
  reject(err);
182
184
  });
183
185
  proc.on("close", (code) => {
184
- resolve2({ status: code ?? 1, stdout, stderr });
186
+ resolve3({ status: code ?? 1, stdout, stderr });
185
187
  });
186
188
  });
187
189
  }
188
190
 
189
191
  // src/plugins/astro-latex-compile/utils.ts
192
+ var CONTENT_ROOT = "src/content/docs/";
193
+ function computeJpgPath(tempOutputDir, filePath, lineNumber) {
194
+ const normalized = filePath.replace(/\\/g, "/");
195
+ const idx = normalized.indexOf(CONTENT_ROOT);
196
+ const relativePath = idx !== -1 ? normalized.slice(idx + CONTENT_ROOT.length) : basename(normalized);
197
+ const dir = dirname(relativePath);
198
+ const filename = basename(relativePath);
199
+ const jpgFilename = `${filename}--${lineNumber}.jpg`;
200
+ const base = resolve(tempOutputDir);
201
+ return dir === "." ? join(base, jpgFilename) : join(base, dir, jpgFilename);
202
+ }
203
+ async function writeJpgFromSvg(svgPath, jpgPath) {
204
+ mkdirSync(dirname(jpgPath), { recursive: true });
205
+ await sharp(svgPath).flatten({ background: { r: 255, g: 255, b: 255 } }).jpeg({ quality: 90 }).toFile(jpgPath);
206
+ }
190
207
  function hashLatexCode(code) {
191
208
  const normalized = code.split("\n").map((line) => line.trim()).filter((line) => !line.startsWith("%")).filter(Boolean).join("\n").trim();
192
209
  return createHash("md5").update(normalized).digest("hex").slice(0, 16);
@@ -285,7 +302,7 @@ Error: ${errorOutput}`
285
302
 
286
303
  // src/plugins/astro-latex-compile/index.ts
287
304
  function remarkLatexCompile(options) {
288
- const svgOutputDir = resolve(options.svgOutputDir);
305
+ const svgOutputDir = resolve2(options.svgOutputDir);
289
306
  return async function transformer(tree, file) {
290
307
  const nodes = [];
291
308
  visit(tree, "code", (node, index, parent) => {
@@ -298,7 +315,9 @@ function remarkLatexCompile(options) {
298
315
  const filePath = file.path || "unknown";
299
316
  const results = await Promise.all(
300
317
  nodes.map(async ({ node, index, parent }) => {
301
- const lineNumber = node.position?.start.line ?? "?";
318
+ const lineNumber = node.position?.start.line;
319
+ const lineNumberStr = lineNumber ?? "?";
320
+ const jpgPath = options.tempOutputDir && lineNumber !== void 0 ? computeJpgPath(options.tempOutputDir, filePath, lineNumber) : null;
302
321
  try {
303
322
  const result = await compileLatexToSvg(
304
323
  node.value,
@@ -307,20 +326,43 @@ function remarkLatexCompile(options) {
307
326
  );
308
327
  if (result.wasCompiled) {
309
328
  console.log(
310
- `[remark-latex-compile] ${filePath}:${lineNumber}: compiled ${result.hash}.svg`
329
+ `[remark-latex-compile] ${filePath}:${lineNumberStr}: compiled ${result.hash}.svg`
311
330
  );
312
331
  }
313
332
  options._referencedHashes?.add(result.hash);
314
- return { index, parent, result, error: null, hash: result.hash };
333
+ if (jpgPath && !existsSync2(jpgPath)) {
334
+ await writeJpgFromSvg(result.svgPath, jpgPath);
335
+ console.log(
336
+ `[remark-latex-compile] ${filePath}:${lineNumberStr}: wrote ${jpgPath}`
337
+ );
338
+ }
339
+ return {
340
+ index,
341
+ parent,
342
+ result,
343
+ error: null,
344
+ hash: result.hash,
345
+ jpgPath
346
+ };
315
347
  } catch (err) {
316
348
  const errorMsg = err instanceof Error ? err.message : String(err);
317
349
  const match = errorMsg.match(/\n\n([\s\S]+)/);
318
350
  const details = match ? match[1] : errorMsg;
319
351
  console.error(
320
- `[remark-latex-compile] ${filePath}:${lineNumber}
352
+ `[remark-latex-compile] ${filePath}:${lineNumberStr}
321
353
  ${details}`
322
354
  );
323
- return { index, parent, result: null, error: err, hash: null };
355
+ if (jpgPath) {
356
+ await rm(jpgPath, { force: true });
357
+ }
358
+ return {
359
+ index,
360
+ parent,
361
+ result: null,
362
+ error: err,
363
+ hash: null,
364
+ jpgPath: null
365
+ };
324
366
  }
325
367
  })
326
368
  );
@@ -342,6 +384,20 @@ ${details}`
342
384
  );
343
385
  options._fileHashMap.set(filePath, newHashes);
344
386
  }
387
+ if (options.tempOutputDir && options._fileJpgPathMap) {
388
+ const newJpgPaths = new Set(
389
+ results.map((r) => r.jpgPath).filter(Boolean)
390
+ );
391
+ const oldJpgPaths = options._fileJpgPathMap.get(filePath) ?? /* @__PURE__ */ new Set();
392
+ const staleJpgPaths = [...oldJpgPaths].filter((p) => !newJpgPaths.has(p));
393
+ for (const stalePath of staleJpgPaths) {
394
+ console.warn(
395
+ `[remark-latex-compile] Removing orphaned jpg: ${stalePath}`
396
+ );
397
+ }
398
+ await Promise.all(staleJpgPaths.map((p) => rm(p, { force: true })));
399
+ options._fileJpgPathMap.set(filePath, newJpgPaths);
400
+ }
345
401
  for (let i = results.length - 1; i >= 0; i--) {
346
402
  const { index, parent, result } = results[i];
347
403
  const { node } = nodes[i];
package/dist/index.js CHANGED
@@ -9,7 +9,7 @@ import {
9
9
  } from "./chunk-3UMY7T6G.js";
10
10
  import {
11
11
  remarkLatexCompile
12
- } from "./chunk-Q33KZ2RB.js";
12
+ } from "./chunk-JIURY757.js";
13
13
  import {
14
14
  astroNormalizePaths
15
15
  } from "./chunk-TLOFSB33.js";
@@ -243,6 +243,7 @@ async function clearContentLayerCache(config) {
243
243
  function astroLatexCompile(options) {
244
244
  const referencedHashes = /* @__PURE__ */ new Set();
245
245
  const fileHashMap = /* @__PURE__ */ new Map();
246
+ const fileJpgPathMap = /* @__PURE__ */ new Map();
246
247
  return {
247
248
  name: "astro-latex-compile",
248
249
  hooks: {
@@ -255,7 +256,8 @@ function astroLatexCompile(options) {
255
256
  const remarkOptions = {
256
257
  ...options,
257
258
  _fileHashMap: options.removeOrphanedSvgs ? fileHashMap : void 0,
258
- _referencedHashes: command === "build" && options.removeOrphanedSvgs ? referencedHashes : void 0
259
+ _referencedHashes: command === "build" && options.removeOrphanedSvgs ? referencedHashes : void 0,
260
+ _fileJpgPathMap: options.tempOutputDir ? fileJpgPathMap : void 0
259
261
  };
260
262
  updateConfig({
261
263
  markdown: {
@@ -29,6 +29,15 @@ interface RemarkLatexCompileOptions {
29
29
  * search (e.g. "src/latex/" or "src/latex//").
30
30
  */
31
31
  texInputDirs?: string[];
32
+ /**
33
+ * When set, a JPEG copy of each compiled diagram is written here, mirroring
34
+ * the folder structure of `src/content/docs/`. Filename format:
35
+ * `<originating-file>--<line-number>.jpg`.
36
+ * JPEGs are deleted automatically when their corresponding block fails
37
+ * compilation or is removed. The purpose of this directory is not for publishing these to the public,
38
+ * but to provide an easier way to locally inspect the generated diagrams.
39
+ */
40
+ tempOutputDir?: string;
32
41
  /**
33
42
  * @internal Populated by the Astro integration to track which hashes were
34
43
  * referenced during a build, used for full orphan cleanup at build:done.
@@ -39,6 +48,11 @@ interface RemarkLatexCompileOptions {
39
48
  * previous remark run. Used to delete stale SVGs when a block changes.
40
49
  */
41
50
  _fileHashMap?: Map<string, Set<string>>;
51
+ /**
52
+ * @internal Maps each file path to the set of JPG paths it produced on the
53
+ * previous remark run. Used to delete stale JPGs when a block changes.
54
+ */
55
+ _fileJpgPathMap?: Map<string, Set<string>>;
42
56
  }
43
57
  declare function remarkLatexCompile(options: RemarkLatexCompileOptions): (tree: Root, file: VFile) => Promise<void>;
44
58
 
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  compileLatexToSvg,
3
3
  remarkLatexCompile
4
- } from "../chunk-Q33KZ2RB.js";
4
+ } from "../chunk-JIURY757.js";
5
5
  import "../chunk-4VNS5WPM.js";
6
6
  export {
7
7
  compileLatexToSvg,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "starlight-cannoli-plugins",
3
3
  "type": "module",
4
- "version": "2.10.5",
4
+ "version": "2.11.0",
5
5
  "description": "Starlight plugins for automatic sidebar generation and link validation",
6
6
  "license": "ISC",
7
7
  "main": "./dist/index.js",
@@ -74,6 +74,7 @@
74
74
  "glob": "^11.0.0",
75
75
  "jsdom": "^29.0.2",
76
76
  "minimatch": "^9.0.0",
77
+ "sharp": "^0.34.5",
77
78
  "unist-util-visit": "^5.0.0",
78
79
  "yaml": "^2.4.0"
79
80
  },
@@ -123,7 +124,6 @@
123
124
  "rehype-raw": "^7.0.0",
124
125
  "remark-math": "^6.0.0",
125
126
  "sass-embedded": "^1.97.3",
126
- "sharp": "^0.34.2",
127
127
  "tsup": "^8.3.0",
128
128
  "tsx": "^4.21.0",
129
129
  "typescript": "^5.9.3",