fontmin-rs 0.2.0 → 0.3.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.
@@ -1,5 +1,5 @@
1
1
  import { randomUUID } from 'node:crypto'
2
- import { mkdir, open, readFile, rm, stat } from 'node:fs/promises'
2
+ import { mkdir, open, readFile, readdir, rm, stat } from 'node:fs/promises'
3
3
  import { join } from 'node:path'
4
4
 
5
5
  const CACHE_LOCK_RETRY_COUNT = 200
@@ -8,6 +8,7 @@ const CACHE_LOCK_STALE_MS = 5 * 60_000
8
8
  export async function withCacheLock(cacheRoot, operation) {
9
9
  const lockPath = join(cacheRoot, '.write.lock')
10
10
  const owner = `${process.pid}:${randomUUID()}`
11
+ let recoveredLock = false
11
12
 
12
13
  await mkdir(cacheRoot, { recursive: true })
13
14
 
@@ -24,7 +25,8 @@ export async function withCacheLock(cacheRoot, operation) {
24
25
  const staleOwner = await readStaleLockOwner(lockPath)
25
26
 
26
27
  if (staleOwner !== undefined) {
27
- await removeLockIfOwned(lockPath, staleOwner)
28
+ recoveredLock =
29
+ (await removeLockIfOwned(lockPath, staleOwner)) || recoveredLock
28
30
  continue
29
31
  }
30
32
 
@@ -40,6 +42,16 @@ export async function withCacheLock(cacheRoot, operation) {
40
42
  throw error
41
43
  }
42
44
 
45
+ if (recoveredLock) {
46
+ try {
47
+ await cleanupCacheTemporaryFiles(cacheRoot)
48
+ } catch (error) {
49
+ await lock.close()
50
+ await removeLockIfOwned(lockPath, owner)
51
+ throw error
52
+ }
53
+ }
54
+
43
55
  try {
44
56
  return await operation()
45
57
  } finally {
@@ -58,7 +70,10 @@ async function readStaleLockOwner(path) {
58
70
  stat(path),
59
71
  ])
60
72
 
61
- return Date.now() - lockStat.mtimeMs > CACHE_LOCK_STALE_MS
73
+ const ownerPid = parseOwnerPid(owner)
74
+
75
+ return (ownerPid !== undefined && !isProcessAlive(ownerPid)) ||
76
+ Date.now() - lockStat.mtimeMs > CACHE_LOCK_STALE_MS
62
77
  ? owner
63
78
  : undefined
64
79
  } catch (error) {
@@ -70,6 +85,53 @@ async function readStaleLockOwner(path) {
70
85
  }
71
86
  }
72
87
 
88
+ async function cleanupCacheTemporaryFiles(cacheRoot) {
89
+ await cleanupTemporaryFilesInTree(cacheRoot)
90
+ }
91
+
92
+ async function cleanupTemporaryFilesInTree(path) {
93
+ let entries
94
+
95
+ try {
96
+ entries = await readdir(path, { withFileTypes: true })
97
+ } catch (error) {
98
+ if (hasErrorCode(error, 'ENOENT')) {
99
+ return
100
+ }
101
+
102
+ throw error
103
+ }
104
+
105
+ await Promise.all(
106
+ entries.map(entry => {
107
+ const entryPath = join(path, entry.name)
108
+
109
+ if (entry.isDirectory()) {
110
+ return cleanupTemporaryFilesInTree(entryPath)
111
+ }
112
+
113
+ return entry.isFile() && entry.name.endsWith('.tmp')
114
+ ? rm(entryPath, { force: true })
115
+ : Promise.resolve()
116
+ }),
117
+ )
118
+ }
119
+
120
+ function parseOwnerPid(owner) {
121
+ const pid = Math.trunc(Number(owner.split(':', 1)[0] ?? ''))
122
+
123
+ return Number.isSafeInteger(pid) && pid > 0 ? pid : undefined
124
+ }
125
+
126
+ function isProcessAlive(pid) {
127
+ try {
128
+ process.kill(pid, 0)
129
+ return true
130
+ } catch (error) {
131
+ return !hasErrorCode(error, 'ESRCH')
132
+ }
133
+ }
134
+
73
135
  async function removeLockIfOwned(path, owner) {
74
136
  try {
75
137
  if ((await readFile(path, 'utf8')) !== owner) {
package/dist/cli.mjs CHANGED
@@ -1,11 +1,11 @@
1
- import { _ as woffToTtf, a as inspect, b as findConfig, d as ttfToSvg, f as ttfToWoff, g as woff2ToTtf, n as analyzeCoverage, o as otfToTtf, p as ttfToWoff2, r as eotToTtf, s as subsetTtf, t as optimize, u as ttfToEot, x as loadConfig } from "./optimize-pipeline-4Thm4vQb.mjs";
1
+ import { _ as woffToTtf, a as inspect, b as findConfig, d as ttfToSvg, f as ttfToWoff, g as woff2ToTtf, n as analyzeCoverage, o as otfToTtf, p as ttfToWoff2, r as eotToTtf, s as subsetTtf, t as optimize, u as ttfToEot, x as loadConfig } from "./optimize-pipeline-DuJ2ilJf.mjs";
2
2
  import { deliverySlices, glyph, svgs2ttf } from "./plugins.mjs";
3
3
  import { mkdir, readFile, realpath, rm, writeFile } from "node:fs/promises";
4
4
  import { basename, dirname, extname, isAbsolute, parse, relative, resolve, sep } from "node:path";
5
5
  import { glob } from "tinyglobby";
6
6
  //#region src/cli.mjs
7
7
  const INIT_CONFIG_FILE = "fontmin.config.jsonc";
8
- const FONTMIN_VERSION = "0.2.0";
8
+ const FONTMIN_VERSION = "0.3.0";
9
9
  const DEFAULT_CACHE_DIR = "node_modules/.cache/fontmin-rs";
10
10
  let emitWarnings = true;
11
11
  const DEFAULT_INIT_CONFIG = `{
package/dist/compat.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { t as optimize, y as defineConfig } from "./optimize-pipeline-4Thm4vQb.mjs";
1
+ import { t as optimize, y as defineConfig } from "./optimize-pipeline-DuJ2ilJf.mjs";
2
2
  import { css, deliverySlices, glyph, otf2ttf, svg2ttf, svgs2ttf, ttf2eot, ttf2svg, ttf2woff, ttf2woff2 } from "./plugins.mjs";
3
3
  //#region src/compat.ts
4
4
  var FontminCompat = class {
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { _ as woffToTtf, a as inspect, c as svgFontToTtf, d as ttfToSvg, f as ttfToWoff, g as woff2ToTtf, h as validateWoff2, i as generateFontFaceCss, l as svgsToTtf, m as ttfToWoff2Async, n as analyzeCoverage, o as otfToTtf, p as ttfToWoff2, r as eotToTtf, s as subsetTtf, t as optimize, u as ttfToEot, v as FontminDiagnosticError, x as loadConfig, y as defineConfig } from "./optimize-pipeline-4Thm4vQb.mjs";
1
+ import { _ as woffToTtf, a as inspect, c as svgFontToTtf, d as ttfToSvg, f as ttfToWoff, g as woff2ToTtf, h as validateWoff2, i as generateFontFaceCss, l as svgsToTtf, m as ttfToWoff2Async, n as analyzeCoverage, o as otfToTtf, p as ttfToWoff2, r as eotToTtf, s as subsetTtf, t as optimize, u as ttfToEot, v as FontminDiagnosticError, x as loadConfig, y as defineConfig } from "./optimize-pipeline-DuJ2ilJf.mjs";
2
2
  import { css, definePlugin, deliverySlices, glyph, modernWeb, otf2ttf, svg2ttf, svgs2ttf, ttf2eot, ttf2svg, ttf2woff, ttf2woff2 } from "./plugins.mjs";
3
3
  import FontminCompat from "./compat.mjs";
4
4
  import { fontminCompatPreset } from "./presets.mjs";
@@ -1,5 +1,5 @@
1
1
  import { createRequire } from "node:module";
2
- import { lstat, mkdir, open, readFile, realpath, rename, rm, stat, writeFile } from "node:fs/promises";
2
+ import { lstat, mkdir, open, readFile, readdir, realpath, rename, rm, stat, writeFile } from "node:fs/promises";
3
3
  import { basename, dirname, extname, isAbsolute, join, parse, relative, resolve, sep } from "node:path";
4
4
  import { pathToFileURL } from "node:url";
5
5
  import { parse as parse$1 } from "jsonc-parser";
@@ -462,6 +462,7 @@ const CACHE_LOCK_STALE_MS = 5 * 6e4;
462
462
  async function withCacheLock(cacheRoot, operation) {
463
463
  const lockPath = join(cacheRoot, ".write.lock");
464
464
  const owner = `${process.pid}:${randomUUID()}`;
465
+ let recoveredLock = false;
465
466
  await mkdir(cacheRoot, { recursive: true });
466
467
  for (let attempt = 0; attempt < CACHE_LOCK_RETRY_COUNT; attempt += 1) {
467
468
  let lock;
@@ -471,7 +472,7 @@ async function withCacheLock(cacheRoot, operation) {
471
472
  if (!isNodeErrorWithCode$1(error, "EEXIST")) throw error;
472
473
  const staleOwner = await readStaleLockOwner(lockPath);
473
474
  if (staleOwner !== void 0) {
474
- await removeLockIfOwned(lockPath, staleOwner);
475
+ recoveredLock = await removeLockIfOwned(lockPath, staleOwner) || recoveredLock;
475
476
  continue;
476
477
  }
477
478
  await delay(CACHE_LOCK_RETRY_MS);
@@ -484,6 +485,13 @@ async function withCacheLock(cacheRoot, operation) {
484
485
  await rm(lockPath, { force: true });
485
486
  throw error;
486
487
  }
488
+ if (recoveredLock) try {
489
+ await cleanupCacheTemporaryFiles(cacheRoot);
490
+ } catch (error) {
491
+ await lock.close();
492
+ await removeLockIfOwned(lockPath, owner);
493
+ throw error;
494
+ }
487
495
  try {
488
496
  return await operation();
489
497
  } finally {
@@ -496,12 +504,42 @@ async function withCacheLock(cacheRoot, operation) {
496
504
  async function readStaleLockOwner(path) {
497
505
  try {
498
506
  const [owner, lockStat] = await Promise.all([readFile(path, "utf8"), stat(path)]);
499
- return Date.now() - lockStat.mtimeMs > CACHE_LOCK_STALE_MS ? owner : void 0;
507
+ const ownerPid = parseOwnerPid(owner);
508
+ return ownerPid !== void 0 && !isProcessAlive(ownerPid) || Date.now() - lockStat.mtimeMs > CACHE_LOCK_STALE_MS ? owner : void 0;
500
509
  } catch (error) {
501
510
  if (isNodeErrorWithCode$1(error, "ENOENT")) return;
502
511
  throw error;
503
512
  }
504
513
  }
514
+ async function cleanupCacheTemporaryFiles(cacheRoot) {
515
+ await cleanupTemporaryFilesInTree(cacheRoot);
516
+ }
517
+ async function cleanupTemporaryFilesInTree(path) {
518
+ let entries;
519
+ try {
520
+ entries = await readdir(path, { withFileTypes: true });
521
+ } catch (error) {
522
+ if (isNodeErrorWithCode$1(error, "ENOENT")) return;
523
+ throw error;
524
+ }
525
+ await Promise.all(entries.map((entry) => {
526
+ const entryPath = join(path, entry.name);
527
+ if (entry.isDirectory()) return cleanupTemporaryFilesInTree(entryPath);
528
+ return entry.isFile() && entry.name.endsWith(".tmp") ? rm(entryPath, { force: true }) : Promise.resolve();
529
+ }));
530
+ }
531
+ function parseOwnerPid(owner) {
532
+ const pid = Math.trunc(Number(owner.split(":", 1)[0] ?? ""));
533
+ return Number.isSafeInteger(pid) && pid > 0 ? pid : void 0;
534
+ }
535
+ function isProcessAlive(pid) {
536
+ try {
537
+ process.kill(pid, 0);
538
+ return true;
539
+ } catch (error) {
540
+ return !isNodeErrorWithCode$1(error, "ESRCH");
541
+ }
542
+ }
505
543
  async function removeLockIfOwned(path, owner) {
506
544
  try {
507
545
  if (await readFile(path, "utf8") !== owner) return false;
@@ -1101,7 +1139,7 @@ function internalCacheKey(plugin) {
1101
1139
  //#endregion
1102
1140
  //#region src/optimize-storage.ts
1103
1141
  const CACHE_SCHEMA_VERSION = "v1";
1104
- const FONTMIN_VERSION = "0.2.0";
1142
+ const FONTMIN_VERSION = "0.3.0";
1105
1143
  const DEFAULT_CACHE_DIR = "node_modules/.cache/fontmin-rs";
1106
1144
  let temporaryFileCounter = 0;
1107
1145
  function createPluginContext(cwd, emittedAssets) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fontmin-rs",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Fast font subsetter and converter written in Rust with Node.js bindings.",
5
5
  "keywords": [
6
6
  "font",
@@ -58,7 +58,7 @@
58
58
  "dependencies": {
59
59
  "jsonc-parser": "^3.3.1",
60
60
  "tinyglobby": "^0.2.17",
61
- "@fontmin-rs/wasm": "0.2.0"
61
+ "@fontmin-rs/wasm": "0.3.0"
62
62
  },
63
63
  "devDependencies": {
64
64
  "@ntnyq/tsconfig": "^3.1.0",
@@ -70,7 +70,7 @@
70
70
  "vitest": "^4.1.10"
71
71
  },
72
72
  "optionalDependencies": {
73
- "@fontmin-rs/binding": "0.2.0"
73
+ "@fontmin-rs/binding": "0.3.0"
74
74
  },
75
75
  "engines": {
76
76
  "node": ">=22.0.0"