@csszyx/unplugin 0.9.9 → 0.10.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.
@@ -3,10 +3,12 @@ import * as fs from 'node:fs';
3
3
  import { createHash } from 'node:crypto';
4
4
  import { hostname } from 'node:os';
5
5
  import lockfile from 'proper-lockfile';
6
+ import { e as escapeHtmlAttribute } from './unplugin.3UumZ5gn.mjs';
6
7
 
7
8
  const DEFAULT_RENAME_RETRIES = 5;
8
9
  const DEFAULT_RENAME_RETRY_DELAY_MS = 10;
9
10
  const DEFAULT_STALE_LOCK_MS = 3e4;
11
+ const STALE_RECOVERY_DISABLED_MS = 2147483647;
10
12
  function resolveNextSafelistStatePaths(rootDir, cacheDir = ".csszyx/cache", outputFile = "csszyx-classes.html") {
11
13
  const resolvedCacheDir = path.resolve(rootDir, cacheDir);
12
14
  return {
@@ -61,11 +63,17 @@ function acquireNextSafelistStateLock(lockPath, pidOrOptions = {}) {
61
63
  const metadata = createLockMetadata(options);
62
64
  const staleAfterMs = options.staleAfterMs ?? DEFAULT_STALE_LOCK_MS;
63
65
  fs.mkdirSync(path.dirname(lockPath), { recursive: true });
66
+ recoverStaleAdvisoryLock(lockPath, staleAfterMs);
64
67
  let releaseAdvisory;
65
68
  try {
66
69
  releaseAdvisory = lockfile.lockSync(lockPath, {
67
70
  realpath: false,
68
- stale: staleAfterMs,
71
+ // Disable proper-lockfile's own stale recovery: its rmdir+remake is
72
+ // not single-winner under concurrent recovery. recoverStaleAdvisoryLock
73
+ // above clears stale locks with one elected winner, so here mkdir is a
74
+ // pure single-winner mutex. A live owner keeps the advisory mtime fresh
75
+ // via `update`, so staleness is still detected — just by us, not here.
76
+ stale: STALE_RECOVERY_DISABLED_MS,
69
77
  update: Math.max(1e3, Math.floor(staleAfterMs / 2)),
70
78
  retries: 0
71
79
  });
@@ -228,9 +236,6 @@ function renderTailwindSourceHtml(classNames) {
228
236
  return `${classNames.map((className) => `<div class="${escapeHtmlAttribute(className)}"></div>`).join("\n")}
229
237
  `;
230
238
  }
231
- function escapeHtmlAttribute(value) {
232
- return value.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
233
- }
234
239
  function createLockMetadata(options) {
235
240
  const now = new Date(options.now ?? Date.now()).toISOString();
236
241
  const pid = options.pid ?? process.pid;
@@ -269,6 +274,44 @@ function readLockMetadata(lockPath) {
269
274
  return null;
270
275
  }
271
276
  }
277
+ function isAdvisoryLockStale(advisoryPath, staleAfterMs) {
278
+ try {
279
+ return fs.statSync(advisoryPath).mtimeMs < Date.now() - staleAfterMs;
280
+ } catch {
281
+ return false;
282
+ }
283
+ }
284
+ function recoverStaleAdvisoryLock(lockPath, staleAfterMs) {
285
+ const advisoryPath = `${lockPath}.lock`;
286
+ if (!isAdvisoryLockStale(advisoryPath, staleAfterMs)) {
287
+ return;
288
+ }
289
+ const electionPath = `${lockPath}.recover`;
290
+ if (isAdvisoryLockStale(electionPath, staleAfterMs)) {
291
+ try {
292
+ fs.rmdirSync(electionPath);
293
+ } catch {
294
+ }
295
+ }
296
+ try {
297
+ fs.mkdirSync(electionPath);
298
+ } catch {
299
+ return;
300
+ }
301
+ try {
302
+ if (isAdvisoryLockStale(advisoryPath, staleAfterMs)) {
303
+ try {
304
+ fs.rmdirSync(advisoryPath);
305
+ } catch {
306
+ }
307
+ }
308
+ } finally {
309
+ try {
310
+ fs.rmdirSync(electionPath);
311
+ } catch {
312
+ }
313
+ }
314
+ }
272
315
  function isLockLive(metadata, options) {
273
316
  const staleAfterMs = options.staleAfterMs ?? DEFAULT_STALE_LOCK_MS;
274
317
  const updatedAt = Date.parse(metadata.updatedAt);
@@ -5,6 +5,7 @@ const fs = require('node:fs');
5
5
  const node_crypto = require('node:crypto');
6
6
  const node_os = require('node:os');
7
7
  const lockfile = require('proper-lockfile');
8
+ const htmlEscape = require('./unplugin.DoBHTRra.cjs');
8
9
 
9
10
  function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e.default : e; }
10
11
 
@@ -27,6 +28,7 @@ const lockfile__default = /*#__PURE__*/_interopDefaultCompat(lockfile);
27
28
  const DEFAULT_RENAME_RETRIES = 5;
28
29
  const DEFAULT_RENAME_RETRY_DELAY_MS = 10;
29
30
  const DEFAULT_STALE_LOCK_MS = 3e4;
31
+ const STALE_RECOVERY_DISABLED_MS = 2147483647;
30
32
  function resolveNextSafelistStatePaths(rootDir, cacheDir = ".csszyx/cache", outputFile = "csszyx-classes.html") {
31
33
  const resolvedCacheDir = path__namespace.resolve(rootDir, cacheDir);
32
34
  return {
@@ -81,11 +83,17 @@ function acquireNextSafelistStateLock(lockPath, pidOrOptions = {}) {
81
83
  const metadata = createLockMetadata(options);
82
84
  const staleAfterMs = options.staleAfterMs ?? DEFAULT_STALE_LOCK_MS;
83
85
  fs__namespace.mkdirSync(path__namespace.dirname(lockPath), { recursive: true });
86
+ recoverStaleAdvisoryLock(lockPath, staleAfterMs);
84
87
  let releaseAdvisory;
85
88
  try {
86
89
  releaseAdvisory = lockfile__default.lockSync(lockPath, {
87
90
  realpath: false,
88
- stale: staleAfterMs,
91
+ // Disable proper-lockfile's own stale recovery: its rmdir+remake is
92
+ // not single-winner under concurrent recovery. recoverStaleAdvisoryLock
93
+ // above clears stale locks with one elected winner, so here mkdir is a
94
+ // pure single-winner mutex. A live owner keeps the advisory mtime fresh
95
+ // via `update`, so staleness is still detected — just by us, not here.
96
+ stale: STALE_RECOVERY_DISABLED_MS,
89
97
  update: Math.max(1e3, Math.floor(staleAfterMs / 2)),
90
98
  retries: 0
91
99
  });
@@ -245,12 +253,9 @@ function renderTailwindSourceHtml(classNames) {
245
253
  if (classNames.length === 0) {
246
254
  return "<!-- csszyx Next safelist: empty -->\n";
247
255
  }
248
- return `${classNames.map((className) => `<div class="${escapeHtmlAttribute(className)}"></div>`).join("\n")}
256
+ return `${classNames.map((className) => `<div class="${htmlEscape.escapeHtmlAttribute(className)}"></div>`).join("\n")}
249
257
  `;
250
258
  }
251
- function escapeHtmlAttribute(value) {
252
- return value.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
253
- }
254
259
  function createLockMetadata(options) {
255
260
  const now = new Date(options.now ?? Date.now()).toISOString();
256
261
  const pid = options.pid ?? process.pid;
@@ -289,6 +294,44 @@ function readLockMetadata(lockPath) {
289
294
  return null;
290
295
  }
291
296
  }
297
+ function isAdvisoryLockStale(advisoryPath, staleAfterMs) {
298
+ try {
299
+ return fs__namespace.statSync(advisoryPath).mtimeMs < Date.now() - staleAfterMs;
300
+ } catch {
301
+ return false;
302
+ }
303
+ }
304
+ function recoverStaleAdvisoryLock(lockPath, staleAfterMs) {
305
+ const advisoryPath = `${lockPath}.lock`;
306
+ if (!isAdvisoryLockStale(advisoryPath, staleAfterMs)) {
307
+ return;
308
+ }
309
+ const electionPath = `${lockPath}.recover`;
310
+ if (isAdvisoryLockStale(electionPath, staleAfterMs)) {
311
+ try {
312
+ fs__namespace.rmdirSync(electionPath);
313
+ } catch {
314
+ }
315
+ }
316
+ try {
317
+ fs__namespace.mkdirSync(electionPath);
318
+ } catch {
319
+ return;
320
+ }
321
+ try {
322
+ if (isAdvisoryLockStale(advisoryPath, staleAfterMs)) {
323
+ try {
324
+ fs__namespace.rmdirSync(advisoryPath);
325
+ } catch {
326
+ }
327
+ }
328
+ } finally {
329
+ try {
330
+ fs__namespace.rmdirSync(electionPath);
331
+ } catch {
332
+ }
333
+ }
334
+ }
292
335
  function isLockLive(metadata, options) {
293
336
  const staleAfterMs = options.staleAfterMs ?? DEFAULT_STALE_LOCK_MS;
294
337
  const updatedAt = Date.parse(metadata.updatedAt);