domflax 0.1.2 → 0.2.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 +66 -31
- package/dist/chunk-EYQXQQQH.js +336 -0
- package/dist/chunk-EYQXQQQH.js.map +1 -0
- package/dist/{chunk-DNHOGPYV.js → chunk-FPT4EJ6Q.js} +1100 -1551
- package/dist/chunk-FPT4EJ6Q.js.map +1 -0
- package/dist/chunk-JBM3MJRM.js +382 -0
- package/dist/chunk-JBM3MJRM.js.map +1 -0
- package/dist/{chunk-DWLB7FRR.js → chunk-TTJEXWAC.js} +322 -9
- package/dist/chunk-TTJEXWAC.js.map +1 -0
- package/dist/{chunk-6WVVF6AD.js → chunk-U5GOONKV.js} +5 -2
- package/dist/{chunk-6WVVF6AD.js.map → chunk-U5GOONKV.js.map} +1 -1
- package/dist/cli.cjs +3010 -2789
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +268 -232
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +1684 -1649
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +255 -498
- package/dist/index.d.ts +255 -498
- package/dist/index.js +17 -37
- package/dist/{pattern-F5xBtIE-.d.cts → pattern-DotR_dHs.d.cts} +1 -1
- package/dist/pattern-kit.cjs +60 -1
- package/dist/pattern-kit.cjs.map +1 -1
- package/dist/pattern-kit.d.cts +2 -2
- package/dist/pattern-kit.d.ts +2 -2
- package/dist/pattern-kit.js +2 -2
- package/dist/{pattern-CV607P87.d.ts → pattern-urm5uuwj.d.ts} +1 -1
- package/dist/{resolve-ops-DIwEelH-.d.ts → resolve-ops-D8aQina5.d.cts} +20 -0
- package/dist/{resolve-ops-DIwEelH-.d.cts → resolve-ops-D8aQina5.d.ts} +20 -0
- package/dist/verify.d.cts +1 -1
- package/dist/verify.d.ts +1 -1
- package/dist/verify.js +1 -1
- package/dist/webpack-loader.cjs +1615 -1633
- package/dist/webpack-loader.cjs.map +1 -1
- package/dist/webpack-loader.d.cts +8 -2
- package/dist/webpack-loader.d.ts +8 -2
- package/dist/webpack-loader.js +8 -5
- package/dist/webpack-loader.js.map +1 -1
- package/dist/worker.cjs +5337 -0
- package/dist/worker.cjs.map +1 -0
- package/dist/worker.d.cts +2 -0
- package/dist/worker.d.ts +2 -0
- package/dist/worker.js +72 -0
- package/dist/worker.js.map +1 -0
- package/package.json +4 -2
- package/dist/chunk-DNHOGPYV.js.map +0 -1
- package/dist/chunk-DOQEBGWB.js +0 -188
- package/dist/chunk-DOQEBGWB.js.map +0 -1
- 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
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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-EYQXQQQH.js";
|
|
8
|
+
import "./chunk-FPT4EJ6Q.js";
|
|
9
|
+
import "./chunk-TTJEXWAC.js";
|
|
16
10
|
import {
|
|
17
11
|
__commonJS,
|
|
12
|
+
__dirname,
|
|
18
13
|
__toESM,
|
|
19
14
|
init_esm_shims
|
|
20
|
-
} from "./chunk-
|
|
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,
|
|
@@ -182,12 +185,15 @@ function parseInvocation(argv) {
|
|
|
182
185
|
css: { type: "string", multiple: true },
|
|
183
186
|
"dry-run": { type: "boolean", default: false },
|
|
184
187
|
report: { type: "boolean", default: false },
|
|
188
|
+
details: { type: "boolean", default: false },
|
|
185
189
|
"dangerously-overwrite-source": { type: "boolean", default: false },
|
|
186
190
|
"no-git-check": { type: "boolean", default: false },
|
|
187
191
|
"no-interactive": { type: "boolean", default: false },
|
|
188
192
|
yes: { type: "boolean", short: "y", default: false },
|
|
189
193
|
safety: { type: "string" },
|
|
190
|
-
"project-root": { type: "string" }
|
|
194
|
+
"project-root": { type: "string" },
|
|
195
|
+
"max-memory": { type: "string" },
|
|
196
|
+
concurrency: { type: "string" }
|
|
191
197
|
}
|
|
192
198
|
});
|
|
193
199
|
const provider = values.provider ?? DEFAULT_PROVIDER;
|
|
@@ -201,12 +207,15 @@ function parseInvocation(argv) {
|
|
|
201
207
|
css: values.css ?? [],
|
|
202
208
|
dryRun: values["dry-run"] === true,
|
|
203
209
|
report: values.report === true,
|
|
210
|
+
details: values.details === true,
|
|
204
211
|
dangerouslyOverwriteSource: values["dangerously-overwrite-source"] === true,
|
|
205
212
|
noGitCheck: values["no-git-check"] === true,
|
|
206
213
|
interactive: values["no-interactive"] !== true && values.yes !== true,
|
|
207
214
|
passes: null,
|
|
208
215
|
safety: toSafety(values.safety),
|
|
209
|
-
projectRoot: values["project-root"] ?? null
|
|
216
|
+
projectRoot: values["project-root"] ?? null,
|
|
217
|
+
maxMemory: toPositiveInt(values["max-memory"], "--max-memory"),
|
|
218
|
+
concurrency: toPositiveInt(values.concurrency, "--concurrency")
|
|
210
219
|
};
|
|
211
220
|
}
|
|
212
221
|
function shouldPrompt(options, isTty) {
|
|
@@ -215,7 +224,7 @@ function shouldPrompt(options, isTty) {
|
|
|
215
224
|
var USAGE = [
|
|
216
225
|
"Usage: domflax [paths...] [options]",
|
|
217
226
|
"",
|
|
218
|
-
"Optimizes .jsx/.tsx files (flatten redundant wrappers + compress class sets).",
|
|
227
|
+
"Optimizes .jsx/.tsx/.html files (flatten redundant wrappers + compress class sets).",
|
|
219
228
|
"Source is READ-ONLY by default \u2014 output goes to --out or ./domflax-out.",
|
|
220
229
|
"",
|
|
221
230
|
"Arguments:",
|
|
@@ -227,221 +236,225 @@ var USAGE = [
|
|
|
227
236
|
" --css <file...> stylesheets feeding the custom-CSS provider",
|
|
228
237
|
" --dry-run print per-file diffs; write nothing",
|
|
229
238
|
" --report print a summary of what changed",
|
|
239
|
+
" --details print per-file optimization stats (nodes/classes/bytes)",
|
|
230
240
|
" --dangerously-overwrite-source overwrite source in place (needs a clean git tree)",
|
|
231
241
|
" --no-git-check skip the clean-git-tree gate",
|
|
232
242
|
" --safety <0|1|2|3> optimization aggressiveness (default: 2)",
|
|
243
|
+
" --max-memory <MB> memory budget; caps pool RAM AND parallelism (default: ~70% free RAM)",
|
|
244
|
+
" --concurrency <N> max parallel workers (still clamped by --max-memory)",
|
|
233
245
|
" --yes, --no-interactive never launch the wizard (CI-safe)",
|
|
234
246
|
"",
|
|
247
|
+
"Many files are processed across CPU cores by a memory-bounded worker pool; small jobs run inline.",
|
|
235
248
|
"With no paths in an interactive terminal, a guided wizard launches."
|
|
236
249
|
].join("\n");
|
|
237
250
|
|
|
238
|
-
// ../cli/src/
|
|
251
|
+
// ../cli/src/pool.ts
|
|
239
252
|
init_esm_shims();
|
|
240
|
-
import {
|
|
253
|
+
import { existsSync } from "fs";
|
|
254
|
+
import * as os from "os";
|
|
241
255
|
import * as path from "path";
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
256
|
+
import { fileURLToPath } from "url";
|
|
257
|
+
import { Worker } from "worker_threads";
|
|
258
|
+
var PER_WORKER_MB = 160;
|
|
259
|
+
var MIN_OLD_GEN_MB = 64;
|
|
260
|
+
function emptyTotals() {
|
|
261
|
+
return { files: 0, changed: 0, nodesRemoved: 0, classesSaved: 0, bytesSaved: 0 };
|
|
245
262
|
}
|
|
246
|
-
function
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
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 };
|
|
263
|
+
function addStats(totals, stats, changed) {
|
|
264
|
+
totals.files += 1;
|
|
265
|
+
if (changed) totals.changed += 1;
|
|
266
|
+
totals.nodesRemoved += stats.nodesRemoved;
|
|
267
|
+
totals.classesSaved += stats.classesSaved;
|
|
268
|
+
totals.bytesSaved += stats.bytesSaved;
|
|
287
269
|
}
|
|
288
|
-
|
|
289
|
-
|
|
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 });
|
|
270
|
+
function clamp(n, lo, hi) {
|
|
271
|
+
return Math.max(lo, Math.min(hi, n));
|
|
296
272
|
}
|
|
297
|
-
function
|
|
298
|
-
const
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
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;
|
|
273
|
+
function computeWorkerCount(options) {
|
|
274
|
+
const cpus2 = Math.max(1, os.cpus().length);
|
|
275
|
+
const freeMB = Math.floor(os.freemem() / (1024 * 1024));
|
|
276
|
+
const budgetMB = Math.max(PER_WORKER_MB, options.maxMemory ?? Math.floor(freeMB * 0.7));
|
|
277
|
+
const byMemory = Math.max(1, Math.floor(budgetMB / PER_WORKER_MB));
|
|
278
|
+
const target = options.concurrency ?? Math.max(1, cpus2 - 1);
|
|
279
|
+
const workers = clamp(Math.min(target, byMemory), 1, byMemory);
|
|
280
|
+
const perWorkerCapMB = Math.max(MIN_OLD_GEN_MB, Math.floor(budgetMB / workers));
|
|
281
|
+
return { workers, budgetMB, perWorkerCapMB };
|
|
313
282
|
}
|
|
314
|
-
function
|
|
315
|
-
|
|
316
|
-
const set = new Set(names);
|
|
317
|
-
return builtinPatterns.filter((p2) => set.has(p2.name));
|
|
283
|
+
function inlineThreshold(workers) {
|
|
284
|
+
return Math.max(4, 2 * workers);
|
|
318
285
|
}
|
|
319
|
-
function
|
|
320
|
-
|
|
321
|
-
const lower = clean.toLowerCase();
|
|
322
|
-
if (lower.endsWith(".tsx")) return "tsx";
|
|
323
|
-
if (lower.endsWith(".jsx")) return "jsx";
|
|
324
|
-
return null;
|
|
286
|
+
function shouldUsePool(fileCount, plan) {
|
|
287
|
+
return plan.workers > 1 && fileCount > inlineThreshold(plan.workers);
|
|
325
288
|
}
|
|
326
|
-
function
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
total += m2[1].split(/\s+/).filter((t) => t.length > 0).length;
|
|
289
|
+
function moduleDir() {
|
|
290
|
+
try {
|
|
291
|
+
return path.dirname(fileURLToPath(import.meta.url));
|
|
292
|
+
} catch {
|
|
293
|
+
return typeof __dirname !== "undefined" ? __dirname : process.cwd();
|
|
332
294
|
}
|
|
333
|
-
return total;
|
|
334
295
|
}
|
|
335
|
-
function
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
classesBefore: 0,
|
|
348
|
-
classesAfter: 0,
|
|
349
|
-
classesSaved: 0,
|
|
350
|
-
bytesBefore: bytes(code),
|
|
351
|
-
bytesAfter: bytes(code),
|
|
352
|
-
bytesSaved: 0
|
|
353
|
-
}
|
|
354
|
-
};
|
|
296
|
+
function resolveWorkerPath() {
|
|
297
|
+
const dir = moduleDir();
|
|
298
|
+
const candidates = [
|
|
299
|
+
path.join(dir, "worker.cjs"),
|
|
300
|
+
path.join(dir, "worker.js"),
|
|
301
|
+
path.join(dir, "..", "dist", "worker.cjs"),
|
|
302
|
+
path.join(dir, "..", "dist", "worker.js")
|
|
303
|
+
];
|
|
304
|
+
for (const c of candidates) {
|
|
305
|
+
if (existsSync(c)) return c;
|
|
306
|
+
}
|
|
307
|
+
return candidates[0];
|
|
355
308
|
}
|
|
356
|
-
function
|
|
357
|
-
const
|
|
358
|
-
const
|
|
359
|
-
const
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
309
|
+
function runPool(files, init, plan, onWrote) {
|
|
310
|
+
const workerPath = resolveWorkerPath();
|
|
311
|
+
const totals = emptyTotals();
|
|
312
|
+
const wrote = [];
|
|
313
|
+
const changedFiles = [];
|
|
314
|
+
const errors = [];
|
|
315
|
+
let failures = 0;
|
|
316
|
+
const budgetBytes = plan.budgetMB * 1024 * 1024;
|
|
317
|
+
let nextIndex = 0;
|
|
318
|
+
let completed = 0;
|
|
319
|
+
const total = files.length;
|
|
320
|
+
let respawns = 0;
|
|
321
|
+
const maxRespawns = total + plan.workers + 8;
|
|
322
|
+
return new Promise((resolve3) => {
|
|
323
|
+
const handles = /* @__PURE__ */ new Set();
|
|
324
|
+
const finishIfDone = () => {
|
|
325
|
+
if (completed < total) return;
|
|
326
|
+
for (const h2 of handles) {
|
|
327
|
+
if (!h2.dead) {
|
|
328
|
+
try {
|
|
329
|
+
h2.worker.postMessage({ type: "stop" });
|
|
330
|
+
} catch {
|
|
331
|
+
}
|
|
332
|
+
void h2.worker.terminate();
|
|
333
|
+
}
|
|
368
334
|
}
|
|
369
|
-
|
|
370
|
-
|
|
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
|
|
335
|
+
handles.clear();
|
|
336
|
+
resolve3({ totals, failures, wrote, changedFiles, errors });
|
|
382
337
|
};
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
bytesBefore: bytes(code),
|
|
409
|
-
bytesAfter: bytes(out),
|
|
410
|
-
bytesSaved: bytes(code) - bytes(out)
|
|
338
|
+
const recordFailure = (file, error) => {
|
|
339
|
+
failures += 1;
|
|
340
|
+
completed += 1;
|
|
341
|
+
errors.push({ path: file, error });
|
|
342
|
+
};
|
|
343
|
+
const dispatch = (h2) => {
|
|
344
|
+
if (h2.dead) return;
|
|
345
|
+
if (nextIndex >= total) {
|
|
346
|
+
h2.current = null;
|
|
347
|
+
try {
|
|
348
|
+
h2.worker.postMessage({ type: "stop" });
|
|
349
|
+
} catch {
|
|
350
|
+
}
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
if (process.memoryUsage().rss > budgetBytes) {
|
|
354
|
+
setTimeout(() => dispatch(h2), 25);
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
const file = files[nextIndex++];
|
|
358
|
+
h2.current = file;
|
|
359
|
+
try {
|
|
360
|
+
h2.worker.postMessage({ type: "file", path: file });
|
|
361
|
+
} catch {
|
|
362
|
+
onWorkerDown(h2);
|
|
411
363
|
}
|
|
412
364
|
};
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
365
|
+
const onMessage = (h2, msg) => {
|
|
366
|
+
if (msg.type === "ready") {
|
|
367
|
+
h2.ready = true;
|
|
368
|
+
dispatch(h2);
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
h2.current = null;
|
|
372
|
+
if (msg.ok) {
|
|
373
|
+
addStats(totals, msg.stats, msg.changed);
|
|
374
|
+
if (msg.wrote) {
|
|
375
|
+
wrote.push(msg.wrote);
|
|
376
|
+
changedFiles.push({ dest: msg.wrote, stats: msg.stats });
|
|
377
|
+
onWrote?.(msg.wrote);
|
|
378
|
+
}
|
|
379
|
+
} else {
|
|
380
|
+
failures += 1;
|
|
381
|
+
errors.push({ path: msg.path, error: msg.error });
|
|
382
|
+
}
|
|
383
|
+
completed += 1;
|
|
384
|
+
if (completed >= total) {
|
|
385
|
+
finishIfDone();
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
dispatch(h2);
|
|
389
|
+
};
|
|
390
|
+
const drainRemaining = (reason) => {
|
|
391
|
+
while (nextIndex < total) recordFailure(files[nextIndex++], reason);
|
|
392
|
+
finishIfDone();
|
|
393
|
+
};
|
|
394
|
+
const onWorkerDown = (h2) => {
|
|
395
|
+
if (h2.dead) return;
|
|
396
|
+
h2.dead = true;
|
|
397
|
+
handles.delete(h2);
|
|
398
|
+
const lost = h2.current;
|
|
399
|
+
h2.current = null;
|
|
400
|
+
if (lost !== null) recordFailure(lost, "worker crashed while processing this file");
|
|
401
|
+
void h2.worker.terminate();
|
|
402
|
+
if (completed >= total) {
|
|
403
|
+
finishIfDone();
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
if (nextIndex >= total) {
|
|
407
|
+
if (handles.size === 0) finishIfDone();
|
|
408
|
+
return;
|
|
409
|
+
}
|
|
410
|
+
if (respawns < maxRespawns) {
|
|
411
|
+
respawns += 1;
|
|
412
|
+
spawn();
|
|
413
|
+
} else if (handles.size === 0) {
|
|
414
|
+
drainRemaining("worker pool exhausted its respawn budget (memory cap too small?)");
|
|
415
|
+
}
|
|
416
|
+
};
|
|
417
|
+
const spawn = () => {
|
|
418
|
+
let worker;
|
|
419
|
+
try {
|
|
420
|
+
worker = new Worker(workerPath, {
|
|
421
|
+
workerData: init,
|
|
422
|
+
resourceLimits: { maxOldGenerationSizeMb: plan.perWorkerCapMB }
|
|
423
|
+
});
|
|
424
|
+
} catch {
|
|
425
|
+
if (nextIndex < total) recordFailure(files[nextIndex++], "failed to spawn worker");
|
|
426
|
+
if (completed >= total) finishIfDone();
|
|
427
|
+
else if (handles.size === 0 && nextIndex < total) spawn();
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
const h2 = { worker, current: null, ready: false, dead: false };
|
|
431
|
+
handles.add(h2);
|
|
432
|
+
worker.on("message", (m2) => onMessage(h2, m2));
|
|
433
|
+
worker.on("error", () => onWorkerDown(h2));
|
|
434
|
+
worker.on("exit", (code) => {
|
|
435
|
+
if (code !== 0) onWorkerDown(h2);
|
|
436
|
+
});
|
|
437
|
+
};
|
|
438
|
+
const initial = Math.min(plan.workers, total);
|
|
439
|
+
for (let i = 0; i < initial; i++) spawn();
|
|
440
|
+
if (initial === 0) finishIfDone();
|
|
441
|
+
});
|
|
424
442
|
}
|
|
425
443
|
|
|
426
444
|
// ../cli/src/walk.ts
|
|
427
445
|
init_esm_shims();
|
|
428
446
|
import * as fs from "fs";
|
|
429
447
|
import * as path2 from "path";
|
|
430
|
-
var SUPPORTED_EXTS = [".jsx", ".tsx"];
|
|
431
|
-
var HTML_EXTS = [".html", ".htm"];
|
|
448
|
+
var SUPPORTED_EXTS = [".jsx", ".tsx", ".html", ".htm"];
|
|
432
449
|
var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", "domflax-out"]);
|
|
433
450
|
function isSupported(file) {
|
|
434
451
|
const lower = file.toLowerCase();
|
|
435
452
|
return SUPPORTED_EXTS.some((ext) => lower.endsWith(ext));
|
|
436
453
|
}
|
|
437
|
-
function isHtml(file) {
|
|
438
|
-
const lower = file.toLowerCase();
|
|
439
|
-
return HTML_EXTS.some((ext) => lower.endsWith(ext));
|
|
440
|
-
}
|
|
441
454
|
function hasGlobMagic(p2) {
|
|
442
455
|
return /[*?[\]{}]/.test(p2);
|
|
443
456
|
}
|
|
444
|
-
function walkDir(dir, out
|
|
457
|
+
function walkDir(dir, out) {
|
|
445
458
|
let entries;
|
|
446
459
|
try {
|
|
447
460
|
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
@@ -452,10 +465,9 @@ function walkDir(dir, out, counts) {
|
|
|
452
465
|
const full = path2.join(dir, entry.name);
|
|
453
466
|
if (entry.isDirectory()) {
|
|
454
467
|
if (SKIP_DIRS.has(entry.name)) continue;
|
|
455
|
-
walkDir(full, out
|
|
468
|
+
walkDir(full, out);
|
|
456
469
|
} else if (entry.isFile()) {
|
|
457
470
|
if (isSupported(entry.name)) out.push(full);
|
|
458
|
-
else if (isHtml(entry.name)) counts.html += 1;
|
|
459
471
|
}
|
|
460
472
|
}
|
|
461
473
|
}
|
|
@@ -466,7 +478,6 @@ function globSyncMaybe() {
|
|
|
466
478
|
function discoverInputs(paths) {
|
|
467
479
|
const files = [];
|
|
468
480
|
const warnings = [];
|
|
469
|
-
const counts = { html: 0 };
|
|
470
481
|
const seen = /* @__PURE__ */ new Set();
|
|
471
482
|
const push = (f) => {
|
|
472
483
|
const abs = path2.resolve(f);
|
|
@@ -490,12 +501,11 @@ function discoverInputs(paths) {
|
|
|
490
501
|
stat = null;
|
|
491
502
|
}
|
|
492
503
|
if (stat?.isDirectory()) {
|
|
493
|
-
walkDir(path2.resolve(p2), files
|
|
504
|
+
walkDir(path2.resolve(p2), files);
|
|
494
505
|
continue;
|
|
495
506
|
}
|
|
496
507
|
if (stat?.isFile()) {
|
|
497
508
|
if (isSupported(p2)) push(p2);
|
|
498
|
-
else if (isHtml(p2)) counts.html += 1;
|
|
499
509
|
else warnings.push(`unsupported file type, skipped: ${p2}`);
|
|
500
510
|
continue;
|
|
501
511
|
}
|
|
@@ -507,8 +517,7 @@ function discoverInputs(paths) {
|
|
|
507
517
|
}
|
|
508
518
|
const matched = glob(p2);
|
|
509
519
|
const supported = matched.filter(isSupported);
|
|
510
|
-
|
|
511
|
-
if (supported.length === 0) warnings.push(`no .jsx/.tsx files matched: ${p2}`);
|
|
520
|
+
if (supported.length === 0) warnings.push(`no .jsx/.tsx/.html files matched: ${p2}`);
|
|
512
521
|
for (const m2 of supported) push(m2);
|
|
513
522
|
continue;
|
|
514
523
|
}
|
|
@@ -523,11 +532,6 @@ function discoverInputs(paths) {
|
|
|
523
532
|
deduped.push(abs);
|
|
524
533
|
}
|
|
525
534
|
}
|
|
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
535
|
return { files: deduped, inputRoot, warnings };
|
|
532
536
|
}
|
|
533
537
|
|
|
@@ -1305,12 +1309,8 @@ async function runWizard(base) {
|
|
|
1305
1309
|
}
|
|
1306
1310
|
|
|
1307
1311
|
// ../cli/src/index.ts
|
|
1308
|
-
function
|
|
1309
|
-
|
|
1310
|
-
if (changed) totals.changed += 1;
|
|
1311
|
-
totals.nodesRemoved += stats.nodesRemoved;
|
|
1312
|
-
totals.classesSaved += stats.classesSaved;
|
|
1313
|
-
totals.bytesSaved += stats.bytesSaved;
|
|
1312
|
+
function fileDetail(stats) {
|
|
1313
|
+
return `${stats.nodesRemoved} nodes, ${stats.classesSaved} classes, ${stats.bytesSaved} bytes`;
|
|
1314
1314
|
}
|
|
1315
1315
|
function printReport(totals) {
|
|
1316
1316
|
console.log("");
|
|
@@ -1321,23 +1321,8 @@ function printReport(totals) {
|
|
|
1321
1321
|
console.log(` classes saved : ${totals.classesSaved}`);
|
|
1322
1322
|
console.log(` bytes saved : ${totals.bytesSaved}`);
|
|
1323
1323
|
}
|
|
1324
|
-
|
|
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;
|
|
1324
|
+
function runInline(files, options, inputRoot, plan, totals) {
|
|
1339
1325
|
const transform = createTransform(options);
|
|
1340
|
-
const totals = { files: 0, changed: 0, nodesRemoved: 0, classesSaved: 0, bytesSaved: 0 };
|
|
1341
1326
|
let failures = 0;
|
|
1342
1327
|
for (const file of files) {
|
|
1343
1328
|
let code;
|
|
@@ -1352,8 +1337,13 @@ async function execute(options) {
|
|
|
1352
1337
|
addStats(totals, result.stats, result.changed);
|
|
1353
1338
|
if (options.dryRun) {
|
|
1354
1339
|
const rel = path4.relative(inputRoot, file) || path4.basename(file);
|
|
1355
|
-
if (result.changed)
|
|
1356
|
-
|
|
1340
|
+
if (result.changed) {
|
|
1341
|
+
console.log(
|
|
1342
|
+
options.details ? `domflax: ${rel} \u2014 ${fileDetail(result.stats)}` : unifiedDiff(code, result.code, rel)
|
|
1343
|
+
);
|
|
1344
|
+
} else if (!options.report && !options.details) {
|
|
1345
|
+
console.log(` (unchanged) ${rel}`);
|
|
1346
|
+
}
|
|
1357
1347
|
continue;
|
|
1358
1348
|
}
|
|
1359
1349
|
if (!result.changed) continue;
|
|
@@ -1366,12 +1356,58 @@ async function execute(options) {
|
|
|
1366
1356
|
try {
|
|
1367
1357
|
mkdirSync(path4.dirname(target.value), { recursive: true });
|
|
1368
1358
|
writeFileSync(target.value, result.code, "utf8");
|
|
1369
|
-
|
|
1359
|
+
const rel = path4.relative(process.cwd(), target.value) || target.value;
|
|
1360
|
+
console.log(options.details ? `domflax: wrote ${rel} \u2014 ${fileDetail(result.stats)}` : `domflax: wrote ${rel}`);
|
|
1370
1361
|
} catch (err) {
|
|
1371
1362
|
console.error(`domflax: cannot write ${target.value}: ${String(err?.message ?? err)}`);
|
|
1372
1363
|
failures += 1;
|
|
1373
1364
|
}
|
|
1374
1365
|
}
|
|
1366
|
+
return failures;
|
|
1367
|
+
}
|
|
1368
|
+
async function execute(options) {
|
|
1369
|
+
const { files, inputRoot, warnings } = discoverInputs(options.paths);
|
|
1370
|
+
for (const w2 of warnings) console.error(`domflax: ${w2}`);
|
|
1371
|
+
if (files.length === 0) {
|
|
1372
|
+
console.error("domflax: no .jsx/.tsx files found for the given paths");
|
|
1373
|
+
return { exitCode: 1 };
|
|
1374
|
+
}
|
|
1375
|
+
const projectRoot = options.projectRoot ?? process.cwd();
|
|
1376
|
+
const gitClean = options.dangerouslyOverwriteSource && !options.noGitCheck ? isGitClean(projectRoot) : true;
|
|
1377
|
+
const planned = planWrites(options, gitClean);
|
|
1378
|
+
if (!planned.ok) {
|
|
1379
|
+
console.error(`domflax: ${planned.error}`);
|
|
1380
|
+
return { exitCode: 1 };
|
|
1381
|
+
}
|
|
1382
|
+
const plan = planned.value;
|
|
1383
|
+
const poolPlan = computeWorkerCount(options);
|
|
1384
|
+
const usePool = !options.dryRun && shouldUsePool(files.length, poolPlan);
|
|
1385
|
+
const totals = emptyTotals();
|
|
1386
|
+
let failures = 0;
|
|
1387
|
+
if (usePool) {
|
|
1388
|
+
const outcome = await runPool(
|
|
1389
|
+
files,
|
|
1390
|
+
{ options, inputRoot, plan },
|
|
1391
|
+
poolPlan
|
|
1392
|
+
// Per-file "wrote" lines are collected and printed in deterministic (sorted) order below.
|
|
1393
|
+
);
|
|
1394
|
+
Object.assign(totals, outcome.totals);
|
|
1395
|
+
failures = outcome.failures;
|
|
1396
|
+
for (const { path: p2, error } of outcome.errors) {
|
|
1397
|
+
console.error(`domflax: failed ${path4.relative(process.cwd(), p2) || p2}: ${error}`);
|
|
1398
|
+
}
|
|
1399
|
+
if (options.details) {
|
|
1400
|
+
for (const { dest, stats } of [...outcome.changedFiles].sort((a, b3) => a.dest.localeCompare(b3.dest))) {
|
|
1401
|
+
console.log(`domflax: wrote ${path4.relative(process.cwd(), dest) || dest} \u2014 ${fileDetail(stats)}`);
|
|
1402
|
+
}
|
|
1403
|
+
} else {
|
|
1404
|
+
for (const dest of [...outcome.wrote].sort()) {
|
|
1405
|
+
console.log(`domflax: wrote ${path4.relative(process.cwd(), dest) || dest}`);
|
|
1406
|
+
}
|
|
1407
|
+
}
|
|
1408
|
+
} else {
|
|
1409
|
+
failures += runInline(files, options, inputRoot, plan, totals);
|
|
1410
|
+
}
|
|
1375
1411
|
if (options.dryRun) {
|
|
1376
1412
|
console.log("\ndomflax: dry run \u2014 no files were written.");
|
|
1377
1413
|
} else if (totals.changed === 0) {
|