domflax 0.1.2 → 0.1.4

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.
Files changed (46) hide show
  1. package/README.md +25 -8
  2. package/dist/{chunk-DNHOGPYV.js → chunk-3Z5ZWLXX.js} +407 -51
  3. package/dist/chunk-3Z5ZWLXX.js.map +1 -0
  4. package/dist/{chunk-DOQEBGWB.js → chunk-5FWENSD2.js} +63 -8
  5. package/dist/chunk-5FWENSD2.js.map +1 -0
  6. package/dist/chunk-EVENAJYI.js +336 -0
  7. package/dist/chunk-EVENAJYI.js.map +1 -0
  8. package/dist/{chunk-DWLB7FRR.js → chunk-H5KTGI3A.js} +153 -7
  9. package/dist/chunk-H5KTGI3A.js.map +1 -0
  10. package/dist/{chunk-6WVVF6AD.js → chunk-U5GOONKV.js} +5 -2
  11. package/dist/{chunk-6WVVF6AD.js.map → chunk-U5GOONKV.js.map} +1 -1
  12. package/dist/cli.cjs +995 -166
  13. package/dist/cli.cjs.map +1 -1
  14. package/dist/cli.js +245 -229
  15. package/dist/cli.js.map +1 -1
  16. package/dist/index.cjs +614 -68
  17. package/dist/index.cjs.map +1 -1
  18. package/dist/index.d.cts +34 -18
  19. package/dist/index.d.ts +34 -18
  20. package/dist/index.js +4 -4
  21. package/dist/{pattern-F5xBtIE-.d.cts → pattern-CP9_HpVK.d.cts} +1 -1
  22. package/dist/{pattern-CV607P87.d.ts → pattern-CYgsv-jO.d.ts} +1 -1
  23. package/dist/pattern-kit.cjs.map +1 -1
  24. package/dist/pattern-kit.d.cts +2 -2
  25. package/dist/pattern-kit.d.ts +2 -2
  26. package/dist/pattern-kit.js +2 -2
  27. package/dist/{resolve-ops-DIwEelH-.d.ts → resolve-ops-Ci7LgYHC.d.cts} +9 -0
  28. package/dist/{resolve-ops-DIwEelH-.d.cts → resolve-ops-Ci7LgYHC.d.ts} +9 -0
  29. package/dist/verify.d.cts +1 -1
  30. package/dist/verify.d.ts +1 -1
  31. package/dist/verify.js +1 -1
  32. package/dist/webpack-loader.cjs +614 -68
  33. package/dist/webpack-loader.cjs.map +1 -1
  34. package/dist/webpack-loader.d.cts +2 -2
  35. package/dist/webpack-loader.d.ts +2 -2
  36. package/dist/webpack-loader.js +4 -4
  37. package/dist/worker.cjs +5955 -0
  38. package/dist/worker.cjs.map +1 -0
  39. package/dist/worker.d.cts +2 -0
  40. package/dist/worker.d.ts +2 -0
  41. package/dist/worker.js +72 -0
  42. package/dist/worker.js.map +1 -0
  43. package/package.json +4 -2
  44. package/dist/chunk-DNHOGPYV.js.map +0 -1
  45. package/dist/chunk-DOQEBGWB.js.map +0 -1
  46. package/dist/chunk-DWLB7FRR.js.map +0 -1
package/dist/cli.js CHANGED
@@ -1,23 +1,18 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
- builtinPatterns,
4
- createCssResolver,
5
- createJsxBackend,
6
- createJsxFrontend,
7
- createTailwindResolver
8
- } from "./chunk-DNHOGPYV.js";
9
- import {
10
- buildSelectorIndex,
11
- createSyntheticSink,
12
- normalizer,
13
- runPasses,
14
- syncClassesFromComputed
15
- } from "./chunk-DWLB7FRR.js";
3
+ createTransform,
4
+ destinationFor,
5
+ isGitClean,
6
+ planWrites
7
+ } from "./chunk-EVENAJYI.js";
8
+ import "./chunk-3Z5ZWLXX.js";
9
+ import "./chunk-H5KTGI3A.js";
16
10
  import {
17
11
  __commonJS,
12
+ __dirname,
18
13
  __toESM,
19
14
  init_esm_shims
20
- } from "./chunk-6WVVF6AD.js";
15
+ } from "./chunk-U5GOONKV.js";
21
16
 
22
17
  // ../../node_modules/sisteransi/src/index.js
23
18
  var require_src = __commonJS({
@@ -172,6 +167,14 @@ function toSafety(raw) {
172
167
  if (n === 0 || n === 1 || n === 2 || n === 3) return n;
173
168
  throw new Error(`domflax: invalid --safety "${raw}" (expected 0, 1, 2 or 3)`);
174
169
  }
170
+ function toPositiveInt(raw, flag) {
171
+ if (raw === void 0) return null;
172
+ const n = Number(raw);
173
+ if (!Number.isInteger(n) || n < 1) {
174
+ throw new Error(`domflax: invalid ${flag} "${raw}" (expected a positive integer)`);
175
+ }
176
+ return n;
177
+ }
175
178
  function parseInvocation(argv) {
176
179
  const { values, positionals } = parseArgs({
177
180
  args: argv,
@@ -187,7 +190,9 @@ function parseInvocation(argv) {
187
190
  "no-interactive": { type: "boolean", default: false },
188
191
  yes: { type: "boolean", short: "y", default: false },
189
192
  safety: { type: "string" },
190
- "project-root": { type: "string" }
193
+ "project-root": { type: "string" },
194
+ "max-memory": { type: "string" },
195
+ concurrency: { type: "string" }
191
196
  }
192
197
  });
193
198
  const provider = values.provider ?? DEFAULT_PROVIDER;
@@ -206,7 +211,9 @@ function parseInvocation(argv) {
206
211
  interactive: values["no-interactive"] !== true && values.yes !== true,
207
212
  passes: null,
208
213
  safety: toSafety(values.safety),
209
- projectRoot: values["project-root"] ?? null
214
+ projectRoot: values["project-root"] ?? null,
215
+ maxMemory: toPositiveInt(values["max-memory"], "--max-memory"),
216
+ concurrency: toPositiveInt(values.concurrency, "--concurrency")
210
217
  };
211
218
  }
212
219
  function shouldPrompt(options, isTty) {
@@ -230,218 +237,219 @@ var USAGE = [
230
237
  " --dangerously-overwrite-source overwrite source in place (needs a clean git tree)",
231
238
  " --no-git-check skip the clean-git-tree gate",
232
239
  " --safety <0|1|2|3> optimization aggressiveness (default: 2)",
240
+ " --max-memory <MB> memory budget; caps pool RAM AND parallelism (default: ~70% free RAM)",
241
+ " --concurrency <N> max parallel workers (still clamped by --max-memory)",
233
242
  " --yes, --no-interactive never launch the wizard (CI-safe)",
234
243
  "",
244
+ "Many files are processed across CPU cores by a memory-bounded worker pool; small jobs run inline.",
235
245
  "With no paths in an interactive terminal, a guided wizard launches."
236
246
  ].join("\n");
237
247
 
238
- // ../cli/src/safety.ts
248
+ // ../cli/src/pool.ts
239
249
  init_esm_shims();
240
- import { execFileSync } from "child_process";
250
+ import { existsSync } from "fs";
251
+ import * as os from "os";
241
252
  import * as path from "path";
242
- var DISPOSABLE_DIRS = /* @__PURE__ */ new Set(["dist", "build", "out", ".next"]);
243
- function isDisposablePath(file) {
244
- return path.resolve(file).split(path.sep).some((seg) => DISPOSABLE_DIRS.has(seg));
253
+ import { fileURLToPath } from "url";
254
+ import { Worker } from "worker_threads";
255
+ var PER_WORKER_MB = 160;
256
+ var MIN_OLD_GEN_MB = 64;
257
+ function emptyTotals() {
258
+ return { files: 0, changed: 0, nodesRemoved: 0, classesSaved: 0, bytesSaved: 0 };
245
259
  }
246
- function isGitClean(cwd) {
247
- try {
248
- const out = execFileSync("git", ["status", "--porcelain"], {
249
- cwd,
250
- encoding: "utf8",
251
- stdio: ["ignore", "pipe", "ignore"]
252
- });
253
- return out.trim().length === 0;
254
- } catch {
255
- return false;
256
- }
257
- }
258
- function planWrites(options, gitClean) {
259
- if (options.dangerouslyOverwriteSource) {
260
- if (!options.noGitCheck && !gitClean) {
261
- return {
262
- ok: false,
263
- error: "refusing --dangerously-overwrite-source: git working tree is not clean. Commit or stash first, or pass --no-git-check to override."
264
- };
265
- }
266
- return { ok: true, value: { mode: "overwrite-source", outDir: null } };
267
- }
268
- const outDir = path.resolve(options.out ?? "domflax-out");
269
- return { ok: true, value: { mode: "out-dir", outDir } };
270
- }
271
- function destinationFor(file, inputRoot, plan) {
272
- const absFile = path.resolve(file);
273
- if (plan.mode === "overwrite-source") {
274
- return { ok: true, value: absFile };
275
- }
276
- const outDir = plan.outDir;
277
- const rel = path.relative(inputRoot, absFile);
278
- const safeRel = rel === "" || rel.startsWith("..") || path.isAbsolute(rel) ? path.basename(absFile) : rel;
279
- const dest = path.join(outDir, safeRel);
280
- if (path.resolve(dest) === absFile && !isDisposablePath(absFile)) {
281
- return {
282
- ok: false,
283
- error: `refusing to overwrite source file ${absFile}: the output path resolves onto the source. Choose a different --out, or pass --dangerously-overwrite-source (with a clean git tree).`
284
- };
285
- }
286
- return { ok: true, value: dest };
260
+ function addStats(totals, stats, changed) {
261
+ totals.files += 1;
262
+ if (changed) totals.changed += 1;
263
+ totals.nodesRemoved += stats.nodesRemoved;
264
+ totals.classesSaved += stats.classesSaved;
265
+ totals.bytesSaved += stats.bytesSaved;
287
266
  }
288
-
289
- // ../cli/src/transform.ts
290
- init_esm_shims();
291
- function buildResolver(provider, css, projectRoot) {
292
- if (provider === "custom") {
293
- return createCssResolver([], { files: css, projectRoot });
294
- }
295
- return createTailwindResolver({ projectRoot });
267
+ function clamp(n, lo, hi) {
268
+ return Math.max(lo, Math.min(hi, n));
296
269
  }
297
- function buildPasses(patterns) {
298
- const byPhase = /* @__PURE__ */ new Map();
299
- for (const p2 of patterns) {
300
- const phase = p2.category.split("/", 1)[0] ?? "flatten";
301
- let bucket = byPhase.get(phase);
302
- if (!bucket) {
303
- bucket = [];
304
- byPhase.set(phase, bucket);
305
- }
306
- bucket.push(p2);
307
- }
308
- const passes = [];
309
- for (const [phase, pats] of byPhase) {
310
- passes.push({ phase, category: `${phase}/builtin`, patterns: pats });
311
- }
312
- return passes;
270
+ function computeWorkerCount(options) {
271
+ const cpus2 = Math.max(1, os.cpus().length);
272
+ const freeMB = Math.floor(os.freemem() / (1024 * 1024));
273
+ const budgetMB = Math.max(PER_WORKER_MB, options.maxMemory ?? Math.floor(freeMB * 0.7));
274
+ const byMemory = Math.max(1, Math.floor(budgetMB / PER_WORKER_MB));
275
+ const target = options.concurrency ?? Math.max(1, cpus2 - 1);
276
+ const workers = clamp(Math.min(target, byMemory), 1, byMemory);
277
+ const perWorkerCapMB = Math.max(MIN_OLD_GEN_MB, Math.floor(budgetMB / workers));
278
+ return { workers, budgetMB, perWorkerCapMB };
313
279
  }
314
- function selectPatterns(names) {
315
- if (names === null) return builtinPatterns;
316
- const set = new Set(names);
317
- return builtinPatterns.filter((p2) => set.has(p2.name));
280
+ function inlineThreshold(workers) {
281
+ return Math.max(4, 2 * workers);
318
282
  }
319
- function jsxKindOf(id) {
320
- const clean = id.split("?", 1)[0] ?? id;
321
- const lower = clean.toLowerCase();
322
- if (lower.endsWith(".tsx")) return "tsx";
323
- if (lower.endsWith(".jsx")) return "jsx";
324
- return null;
283
+ function shouldUsePool(fileCount, plan) {
284
+ return plan.workers > 1 && fileCount > inlineThreshold(plan.workers);
325
285
  }
326
- function countClassTokens(code) {
327
- let total = 0;
328
- const re = /\b(?:className|class)\s*=\s*"([^"]*)"/g;
329
- let m2;
330
- while ((m2 = re.exec(code)) !== null) {
331
- total += m2[1].split(/\s+/).filter((t) => t.length > 0).length;
286
+ function moduleDir() {
287
+ try {
288
+ return path.dirname(fileURLToPath(import.meta.url));
289
+ } catch {
290
+ return typeof __dirname !== "undefined" ? __dirname : process.cwd();
332
291
  }
333
- return total;
334
- }
335
- function bytes(s) {
336
- return Buffer.byteLength(s, "utf8");
337
292
  }
338
- function passthroughResult(code) {
339
- return {
340
- code,
341
- changed: false,
342
- passthrough: true,
343
- stats: {
344
- nodesIn: 0,
345
- nodesOut: 0,
346
- nodesRemoved: 0,
347
- classesBefore: 0,
348
- classesAfter: 0,
349
- classesSaved: 0,
350
- bytesBefore: bytes(code),
351
- bytesAfter: bytes(code),
352
- bytesSaved: 0
353
- }
354
- };
293
+ function resolveWorkerPath() {
294
+ const dir = moduleDir();
295
+ const candidates = [
296
+ path.join(dir, "worker.cjs"),
297
+ path.join(dir, "worker.js"),
298
+ path.join(dir, "..", "dist", "worker.cjs"),
299
+ path.join(dir, "..", "dist", "worker.js")
300
+ ];
301
+ for (const c of candidates) {
302
+ if (existsSync(c)) return c;
303
+ }
304
+ return candidates[0];
355
305
  }
356
- function createTransform(options) {
357
- const projectRoot = options.projectRoot ?? process.cwd();
358
- const resolver = buildResolver(options.provider, options.css, projectRoot);
359
- const patterns = selectPatterns(options.passes);
360
- function prepare(code, id, kind, gate) {
361
- const parsed = createJsxFrontend().parse(code, {
362
- id,
363
- kind,
364
- resolver,
365
- normalizer,
366
- config: {},
367
- onDiagnostic: () => {
306
+ function runPool(files, init, plan, onWrote) {
307
+ const workerPath = resolveWorkerPath();
308
+ const totals = emptyTotals();
309
+ const wrote = [];
310
+ const errors = [];
311
+ let failures = 0;
312
+ const budgetBytes = plan.budgetMB * 1024 * 1024;
313
+ let nextIndex = 0;
314
+ let completed = 0;
315
+ const total = files.length;
316
+ let respawns = 0;
317
+ const maxRespawns = total + plan.workers + 8;
318
+ return new Promise((resolve3) => {
319
+ const handles = /* @__PURE__ */ new Set();
320
+ const finishIfDone = () => {
321
+ if (completed < total) return;
322
+ for (const h2 of handles) {
323
+ if (!h2.dead) {
324
+ try {
325
+ h2.worker.postMessage({ type: "stop" });
326
+ } catch {
327
+ }
328
+ void h2.worker.terminate();
329
+ }
368
330
  }
369
- });
370
- const doc = parsed.doc;
371
- const nodesIn = doc.nodes.size;
372
- for (const node of doc.nodes.values()) node.meta.safetyFloor = 3;
373
- const ctx = {
374
- doc,
375
- safetyCeiling: options.safety,
376
- normalizer,
377
- // Real CSS-selector-safety index from the active resolver (custom-CSS reports combinator /
378
- // structural-pseudo coupling; Tailwind has none → null index, behaviour unchanged).
379
- selectors: buildSelectorIndex(doc, resolver),
380
- resolver,
381
- gate
331
+ handles.clear();
332
+ resolve3({ totals, failures, wrote, errors });
382
333
  };
383
- return { doc, ctx, passes: buildPasses(patterns), nodesIn };
384
- }
385
- function finish(code, optimized, id, nodesIn) {
386
- syncClassesFromComputed(optimized, resolver, normalizer);
387
- const printed = createJsxBackend().print(
388
- optimized,
389
- { moduleId: id, ops: [], provenance: /* @__PURE__ */ new Map() },
390
- { normalizer, resolver, sink: createSyntheticSink(), eol: "\n", onDiagnostic: () => {
391
- } }
392
- );
393
- const out = printed.code;
394
- const nodesOut = optimized.nodes.size;
395
- const classesBefore = countClassTokens(code);
396
- const classesAfter = countClassTokens(out);
397
- return {
398
- code: out,
399
- changed: out !== code,
400
- passthrough: false,
401
- stats: {
402
- nodesIn,
403
- nodesOut,
404
- nodesRemoved: Math.max(0, nodesIn - nodesOut),
405
- classesBefore,
406
- classesAfter,
407
- classesSaved: Math.max(0, classesBefore - classesAfter),
408
- bytesBefore: bytes(code),
409
- bytesAfter: bytes(out),
410
- bytesSaved: bytes(code) - bytes(out)
334
+ const recordFailure = (file, error) => {
335
+ failures += 1;
336
+ completed += 1;
337
+ errors.push({ path: file, error });
338
+ };
339
+ const dispatch = (h2) => {
340
+ if (h2.dead) return;
341
+ if (nextIndex >= total) {
342
+ h2.current = null;
343
+ try {
344
+ h2.worker.postMessage({ type: "stop" });
345
+ } catch {
346
+ }
347
+ return;
348
+ }
349
+ if (process.memoryUsage().rss > budgetBytes) {
350
+ setTimeout(() => dispatch(h2), 25);
351
+ return;
352
+ }
353
+ const file = files[nextIndex++];
354
+ h2.current = file;
355
+ try {
356
+ h2.worker.postMessage({ type: "file", path: file });
357
+ } catch {
358
+ onWorkerDown(h2);
411
359
  }
412
360
  };
413
- }
414
- return {
415
- resolver,
416
- transformFile(code, id) {
417
- const kind = jsxKindOf(id);
418
- if (kind === null) return passthroughResult(code);
419
- const { doc, ctx, passes, nodesIn } = prepare(code, id, kind, "provably-safe");
420
- const { doc: optimized } = runPasses(doc, passes, ctx);
421
- return finish(code, optimized, id, nodesIn);
422
- }
423
- };
361
+ const onMessage = (h2, msg) => {
362
+ if (msg.type === "ready") {
363
+ h2.ready = true;
364
+ dispatch(h2);
365
+ return;
366
+ }
367
+ h2.current = null;
368
+ if (msg.ok) {
369
+ addStats(totals, msg.stats, msg.changed);
370
+ if (msg.wrote) {
371
+ wrote.push(msg.wrote);
372
+ onWrote?.(msg.wrote);
373
+ }
374
+ } else {
375
+ failures += 1;
376
+ errors.push({ path: msg.path, error: msg.error });
377
+ }
378
+ completed += 1;
379
+ if (completed >= total) {
380
+ finishIfDone();
381
+ return;
382
+ }
383
+ dispatch(h2);
384
+ };
385
+ const drainRemaining = (reason) => {
386
+ while (nextIndex < total) recordFailure(files[nextIndex++], reason);
387
+ finishIfDone();
388
+ };
389
+ const onWorkerDown = (h2) => {
390
+ if (h2.dead) return;
391
+ h2.dead = true;
392
+ handles.delete(h2);
393
+ const lost = h2.current;
394
+ h2.current = null;
395
+ if (lost !== null) recordFailure(lost, "worker crashed while processing this file");
396
+ void h2.worker.terminate();
397
+ if (completed >= total) {
398
+ finishIfDone();
399
+ return;
400
+ }
401
+ if (nextIndex >= total) {
402
+ if (handles.size === 0) finishIfDone();
403
+ return;
404
+ }
405
+ if (respawns < maxRespawns) {
406
+ respawns += 1;
407
+ spawn();
408
+ } else if (handles.size === 0) {
409
+ drainRemaining("worker pool exhausted its respawn budget (memory cap too small?)");
410
+ }
411
+ };
412
+ const spawn = () => {
413
+ let worker;
414
+ try {
415
+ worker = new Worker(workerPath, {
416
+ workerData: init,
417
+ resourceLimits: { maxOldGenerationSizeMb: plan.perWorkerCapMB }
418
+ });
419
+ } catch {
420
+ if (nextIndex < total) recordFailure(files[nextIndex++], "failed to spawn worker");
421
+ if (completed >= total) finishIfDone();
422
+ else if (handles.size === 0 && nextIndex < total) spawn();
423
+ return;
424
+ }
425
+ const h2 = { worker, current: null, ready: false, dead: false };
426
+ handles.add(h2);
427
+ worker.on("message", (m2) => onMessage(h2, m2));
428
+ worker.on("error", () => onWorkerDown(h2));
429
+ worker.on("exit", (code) => {
430
+ if (code !== 0) onWorkerDown(h2);
431
+ });
432
+ };
433
+ const initial = Math.min(plan.workers, total);
434
+ for (let i = 0; i < initial; i++) spawn();
435
+ if (initial === 0) finishIfDone();
436
+ });
424
437
  }
425
438
 
426
439
  // ../cli/src/walk.ts
427
440
  init_esm_shims();
428
441
  import * as fs from "fs";
429
442
  import * as path2 from "path";
430
- var SUPPORTED_EXTS = [".jsx", ".tsx"];
431
- var HTML_EXTS = [".html", ".htm"];
443
+ var SUPPORTED_EXTS = [".jsx", ".tsx", ".html", ".htm"];
432
444
  var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", "domflax-out"]);
433
445
  function isSupported(file) {
434
446
  const lower = file.toLowerCase();
435
447
  return SUPPORTED_EXTS.some((ext) => lower.endsWith(ext));
436
448
  }
437
- function isHtml(file) {
438
- const lower = file.toLowerCase();
439
- return HTML_EXTS.some((ext) => lower.endsWith(ext));
440
- }
441
449
  function hasGlobMagic(p2) {
442
450
  return /[*?[\]{}]/.test(p2);
443
451
  }
444
- function walkDir(dir, out, counts) {
452
+ function walkDir(dir, out) {
445
453
  let entries;
446
454
  try {
447
455
  entries = fs.readdirSync(dir, { withFileTypes: true });
@@ -452,10 +460,9 @@ function walkDir(dir, out, counts) {
452
460
  const full = path2.join(dir, entry.name);
453
461
  if (entry.isDirectory()) {
454
462
  if (SKIP_DIRS.has(entry.name)) continue;
455
- walkDir(full, out, counts);
463
+ walkDir(full, out);
456
464
  } else if (entry.isFile()) {
457
465
  if (isSupported(entry.name)) out.push(full);
458
- else if (isHtml(entry.name)) counts.html += 1;
459
466
  }
460
467
  }
461
468
  }
@@ -466,7 +473,6 @@ function globSyncMaybe() {
466
473
  function discoverInputs(paths) {
467
474
  const files = [];
468
475
  const warnings = [];
469
- const counts = { html: 0 };
470
476
  const seen = /* @__PURE__ */ new Set();
471
477
  const push = (f) => {
472
478
  const abs = path2.resolve(f);
@@ -490,12 +496,11 @@ function discoverInputs(paths) {
490
496
  stat = null;
491
497
  }
492
498
  if (stat?.isDirectory()) {
493
- walkDir(path2.resolve(p2), files, counts);
499
+ walkDir(path2.resolve(p2), files);
494
500
  continue;
495
501
  }
496
502
  if (stat?.isFile()) {
497
503
  if (isSupported(p2)) push(p2);
498
- else if (isHtml(p2)) counts.html += 1;
499
504
  else warnings.push(`unsupported file type, skipped: ${p2}`);
500
505
  continue;
501
506
  }
@@ -507,8 +512,7 @@ function discoverInputs(paths) {
507
512
  }
508
513
  const matched = glob(p2);
509
514
  const supported = matched.filter(isSupported);
510
- counts.html += matched.filter(isHtml).length;
511
- if (supported.length === 0) warnings.push(`no .jsx/.tsx files matched: ${p2}`);
515
+ if (supported.length === 0) warnings.push(`no .jsx/.tsx/.html files matched: ${p2}`);
512
516
  for (const m2 of supported) push(m2);
513
517
  continue;
514
518
  }
@@ -523,11 +527,6 @@ function discoverInputs(paths) {
523
527
  deduped.push(abs);
524
528
  }
525
529
  }
526
- if (deduped.length === 0 && counts.html > 0) {
527
- warnings.push(
528
- `found ${counts.html} .html file${counts.html === 1 ? "" : "s"} but HTML optimization isn't supported yet (domflax currently optimizes .jsx/.tsx source; HTML is on the roadmap: https://github.com/Krishnesh-Mishra/domflax#roadmap).`
529
- );
530
- }
531
530
  return { files: deduped, inputRoot, warnings };
532
531
  }
533
532
 
@@ -1305,13 +1304,6 @@ async function runWizard(base) {
1305
1304
  }
1306
1305
 
1307
1306
  // ../cli/src/index.ts
1308
- function addStats(totals, stats, changed) {
1309
- totals.files += 1;
1310
- if (changed) totals.changed += 1;
1311
- totals.nodesRemoved += stats.nodesRemoved;
1312
- totals.classesSaved += stats.classesSaved;
1313
- totals.bytesSaved += stats.bytesSaved;
1314
- }
1315
1307
  function printReport(totals) {
1316
1308
  console.log("");
1317
1309
  console.log("domflax report");
@@ -1321,23 +1313,8 @@ function printReport(totals) {
1321
1313
  console.log(` classes saved : ${totals.classesSaved}`);
1322
1314
  console.log(` bytes saved : ${totals.bytesSaved}`);
1323
1315
  }
1324
- async function execute(options) {
1325
- const { files, inputRoot, warnings } = discoverInputs(options.paths);
1326
- for (const w2 of warnings) console.error(`domflax: ${w2}`);
1327
- if (files.length === 0) {
1328
- console.error("domflax: no .jsx/.tsx files found for the given paths");
1329
- return { exitCode: 1 };
1330
- }
1331
- const projectRoot = options.projectRoot ?? process.cwd();
1332
- const gitClean = options.dangerouslyOverwriteSource && !options.noGitCheck ? isGitClean(projectRoot) : true;
1333
- const planned = planWrites(options, gitClean);
1334
- if (!planned.ok) {
1335
- console.error(`domflax: ${planned.error}`);
1336
- return { exitCode: 1 };
1337
- }
1338
- const plan = planned.value;
1316
+ function runInline(files, options, inputRoot, plan, totals) {
1339
1317
  const transform = createTransform(options);
1340
- const totals = { files: 0, changed: 0, nodesRemoved: 0, classesSaved: 0, bytesSaved: 0 };
1341
1318
  let failures = 0;
1342
1319
  for (const file of files) {
1343
1320
  let code;
@@ -1372,6 +1349,45 @@ async function execute(options) {
1372
1349
  failures += 1;
1373
1350
  }
1374
1351
  }
1352
+ return failures;
1353
+ }
1354
+ async function execute(options) {
1355
+ const { files, inputRoot, warnings } = discoverInputs(options.paths);
1356
+ for (const w2 of warnings) console.error(`domflax: ${w2}`);
1357
+ if (files.length === 0) {
1358
+ console.error("domflax: no .jsx/.tsx files found for the given paths");
1359
+ return { exitCode: 1 };
1360
+ }
1361
+ const projectRoot = options.projectRoot ?? process.cwd();
1362
+ const gitClean = options.dangerouslyOverwriteSource && !options.noGitCheck ? isGitClean(projectRoot) : true;
1363
+ const planned = planWrites(options, gitClean);
1364
+ if (!planned.ok) {
1365
+ console.error(`domflax: ${planned.error}`);
1366
+ return { exitCode: 1 };
1367
+ }
1368
+ const plan = planned.value;
1369
+ const poolPlan = computeWorkerCount(options);
1370
+ const usePool = !options.dryRun && shouldUsePool(files.length, poolPlan);
1371
+ const totals = emptyTotals();
1372
+ let failures = 0;
1373
+ if (usePool) {
1374
+ const outcome = await runPool(
1375
+ files,
1376
+ { options, inputRoot, plan },
1377
+ poolPlan
1378
+ // Per-file "wrote" lines are collected and printed in deterministic (sorted) order below.
1379
+ );
1380
+ Object.assign(totals, outcome.totals);
1381
+ failures = outcome.failures;
1382
+ for (const { path: p2, error } of outcome.errors) {
1383
+ console.error(`domflax: failed ${path4.relative(process.cwd(), p2) || p2}: ${error}`);
1384
+ }
1385
+ for (const dest of [...outcome.wrote].sort()) {
1386
+ console.log(`domflax: wrote ${path4.relative(process.cwd(), dest) || dest}`);
1387
+ }
1388
+ } else {
1389
+ failures += runInline(files, options, inputRoot, plan, totals);
1390
+ }
1375
1391
  if (options.dryRun) {
1376
1392
  console.log("\ndomflax: dry run \u2014 no files were written.");
1377
1393
  } else if (totals.changed === 0) {