@jxsuite/compiler 0.25.5 → 0.26.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jxsuite/compiler",
3
- "version": "0.25.5",
3
+ "version": "0.26.1",
4
4
  "description": "Jx static HTML compiler, island detector, and site builder",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -41,16 +41,16 @@
41
41
  },
42
42
  "dependencies": {
43
43
  "@apidevtools/json-schema-ref-parser": "^15.3.5",
44
- "@jxsuite/parser": "^0.25.5",
45
- "@jxsuite/runtime": "^0.25.5",
46
- "@jxsuite/schema": "^0.25.5",
44
+ "@jxsuite/parser": "^0.26.1",
45
+ "@jxsuite/runtime": "^0.26.1",
46
+ "@jxsuite/schema": "^0.26.1",
47
47
  "remark-gfm": "^4.0.1",
48
48
  "remark-stringify": "^11.0.0",
49
49
  "sharp": "^0.34.5",
50
50
  "unified": "^11.0.5"
51
51
  },
52
52
  "devDependencies": {
53
- "@happy-dom/global-registrator": "^20.9.0",
53
+ "@happy-dom/global-registrator": "^20.10.2",
54
54
  "@types/mdast": "^4.0.4"
55
55
  }
56
56
  }
@@ -2,11 +2,14 @@
2
2
  * Image-cache.js — Content-hash based cache for processed image variants.
3
3
  *
4
4
  * Stores a manifest of previously processed images so that unchanged sources can skip re-encoding
5
- * on subsequent builds. Cache lives in .cache/images/.
5
+ * on subsequent builds. Cache lives in the npm global cache directory under jxsuite-images/ so that
6
+ * CI environments (e.g. Cloudflare Pages) can persist it via their npm cache layer.
6
7
  */
7
8
 
8
9
  import { readFileSync, writeFileSync, mkdirSync, existsSync } from "node:fs";
9
- import { resolve } from "node:path";
10
+ import { resolve, basename } from "node:path";
11
+ import { execSync } from "node:child_process";
12
+ import { tmpdir } from "node:os";
10
13
  import { contentHash, configHash } from "./image-optimizer.ts";
11
14
 
12
15
  import type { ImageManifest } from "./image-optimizer.ts";
@@ -23,6 +26,56 @@ export interface CacheManifest {
23
26
  entries: Record<string, CacheEntry>; // Cached entries keyed by content+config hash
24
27
  }
25
28
 
29
+ // ─── Cache directory resolution ──────────────────────────────────────────────
30
+
31
+ let _npmCacheBase: string | null | undefined = undefined; // undefined = not yet resolved
32
+
33
+ /** Reset the memoized npm cache base so the next call re-evaluates. Tests only. */
34
+ export function _testResetNpmCacheBase() {
35
+ _npmCacheBase = undefined;
36
+ }
37
+
38
+ /**
39
+ * Directly set the memoized npm cache base. Pass null to force the project-local fallback. Tests
40
+ * only.
41
+ */
42
+ export function _testSetNpmCacheBase(value: string | null) {
43
+ _npmCacheBase = value;
44
+ }
45
+
46
+ /**
47
+ * Return the image cache directory for a project.
48
+ *
49
+ * Prefers the npm global cache (`npm config get cache`) so CI environments that cache ~/.npm (e.g.
50
+ * Cloudflare Pages) automatically persist optimized images across builds. Falls back to
51
+ * .cache/images inside the project root when npm is unavailable.
52
+ *
53
+ * Result is memoized — `npm config get cache` is only spawned once per process.
54
+ *
55
+ * @param {string} projectRoot
56
+ * @returns {string}
57
+ */
58
+ export function getImageCacheDir(projectRoot: string): string {
59
+ if (_npmCacheBase === undefined) {
60
+ try {
61
+ const result = execSync("npm config get cache", {
62
+ encoding: "utf-8",
63
+ stdio: ["ignore", "pipe", "ignore"],
64
+ cwd: tmpdir(), // avoid ENOWORKSPACES when cwd is inside an npm workspace
65
+ }).trim();
66
+ _npmCacheBase = result && result !== "undefined" ? result : null;
67
+ } catch {
68
+ _npmCacheBase = null;
69
+ }
70
+ }
71
+ if (_npmCacheBase) {
72
+ return resolve(_npmCacheBase, "jxsuite-images", basename(projectRoot));
73
+ }
74
+ return resolve(projectRoot, ".cache/images");
75
+ }
76
+
77
+ // ─── Cache key ───────────────────────────────────────────────────────────────
78
+
26
79
  /**
27
80
  * Build a cache key from source file content and config.
28
81
  *
@@ -34,6 +87,8 @@ export function cacheKey(srcPath: string, config: ImageConfig) {
34
87
  return `${contentHash(srcPath)}:${configHash(config)}`;
35
88
  }
36
89
 
90
+ // ─── Load / save ─────────────────────────────────────────────────────────────
91
+
37
92
  /**
38
93
  * Load the cache manifest from disk, or return an empty one.
39
94
  *
@@ -41,7 +96,7 @@ export function cacheKey(srcPath: string, config: ImageConfig) {
41
96
  * @returns {CacheManifest}
42
97
  */
43
98
  export function loadCache(projectRoot: string) {
44
- const manifestPath = resolve(projectRoot, ".cache/images/manifest.json");
99
+ const manifestPath = resolve(getImageCacheDir(projectRoot), "manifest.json");
45
100
  if (!existsSync(manifestPath)) {
46
101
  return { version: 1, entries: {} };
47
102
  }
@@ -59,11 +114,13 @@ export function loadCache(projectRoot: string) {
59
114
  * @param {CacheManifest} cache
60
115
  */
61
116
  export function saveCache(projectRoot: string, cache: CacheManifest) {
62
- const cacheDir = resolve(projectRoot, ".cache/images");
117
+ const cacheDir = getImageCacheDir(projectRoot);
63
118
  mkdirSync(cacheDir, { recursive: true });
64
119
  writeFileSync(resolve(cacheDir, "manifest.json"), JSON.stringify(cache, null, 2), "utf8");
65
120
  }
66
121
 
122
+ // ─── Entry access ─────────────────────────────────────────────────────────────
123
+
67
124
  /**
68
125
  * Check if a cache entry exists and its output files are still present.
69
126
  *
@@ -9,7 +9,7 @@
9
9
  import { existsSync } from "node:fs";
10
10
  import { resolve, extname, basename } from "node:path";
11
11
  import { processImage, buildSrcset, contentHash, configHash } from "./image-optimizer.ts";
12
- import { getCached, setCached } from "./image-cache.ts";
12
+ import { getCached, setCached, getImageCacheDir } from "./image-cache.ts";
13
13
 
14
14
  import type { ImageConfig } from "./image-optimizer.ts";
15
15
  import type { ImageManifest } from "./image-optimizer.ts";
@@ -44,8 +44,7 @@ async function resolveManifest(
44
44
  }
45
45
 
46
46
  if (!cached) console.log(` Optimizing ${basename(absoluteSrc)}...`);
47
- const cacheImgDir = resolve(projectRoot, ".cache/images");
48
- const manifest = await processImage(absoluteSrc, cacheImgDir, config);
47
+ const manifest = await processImage(absoluteSrc, getImageCacheDir(projectRoot), config);
49
48
  setCached(cache, key, src, manifest);
50
49
  return manifest;
51
50
  }
@@ -44,7 +44,7 @@ import { loadContentTypes, loadContentConfig, resolveContentTypeRefs } from "./c
44
44
  import { resolvePrototypes } from "./prototype-resolver.ts";
45
45
  import { compileMarkdown } from "../targets/compile-markdown.ts";
46
46
  import { transformImageNodes } from "./image-transform.ts";
47
- import { loadCache, saveCache } from "./image-cache.ts";
47
+ import { loadCache, saveCache, getImageCacheDir } from "./image-cache.ts";
48
48
  import type {
49
49
  JxElement,
50
50
  JxMutableNode,
@@ -257,7 +257,7 @@ export async function buildSite(
257
257
  // ── 6b. Save image cache and copy variants to dist ──────────────────────
258
258
  if (imageCache && projectConfig.images.optimize) {
259
259
  saveCache(projectRoot, imageCache);
260
- const cacheOptimizedDir = resolve(projectRoot, ".cache/images/_optimized");
260
+ const cacheOptimizedDir = resolve(getImageCacheDir(projectRoot), "_optimized");
261
261
  if (existsSync(cacheOptimizedDir)) {
262
262
  const distOptimizedDir = resolve(outDir, "images/_optimized");
263
263
  mkdirSync(distOptimizedDir, { recursive: true });