codarch 0.1.3 → 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.
package/index.js CHANGED
@@ -1,13 +1,14 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // apps/cli/src/commands/analyze.ts
4
- import fs6 from "node:fs";
5
- import path5 from "node:path";
4
+ import fs4 from "node:fs";
5
+ import path3 from "node:path";
6
6
 
7
7
  // packages/parsers/src/ts/parser.ts
8
8
  import Parser from "tree-sitter";
9
9
  import TypeScript from "tree-sitter-typescript";
10
- var EXTENSIONS = [".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs"];
10
+
11
+ // packages/parsers/src/ts/measure.ts
11
12
  var FUNCTION_TYPES = /* @__PURE__ */ new Set([
12
13
  "function_declaration",
13
14
  "function_expression",
@@ -28,30 +29,13 @@ var DECISION_TYPES = /* @__PURE__ */ new Set([
28
29
  "conditional_expression"
29
30
  ]);
30
31
  var LOGICAL_OPERATORS = /* @__PURE__ */ new Set(["&&", "||", "??"]);
31
- var TypeScriptParser = class {
32
- name = "typescript";
33
- extensions = EXTENSIONS;
34
- tsParser = new Parser();
35
- tsxParser = new Parser();
36
- constructor() {
37
- this.tsParser.setLanguage(TypeScript.typescript);
38
- this.tsxParser.setLanguage(TypeScript.tsx);
39
- }
40
- parse(source, filePath) {
41
- const parser = usesTsxGrammar(filePath) ? this.tsxParser : this.tsParser;
42
- const root = parser.parse(source, void 0, {
43
- bufferSize: Math.max(64 * 1024, Buffer.byteLength(source, "utf8") + 1)
44
- }).rootNode;
45
- return {
46
- metrics: measureSource(source, root),
47
- imports: collectImports(root)
48
- };
49
- }
50
- };
51
32
  function usesTsxGrammar(filePath) {
52
33
  const lower = filePath.toLowerCase();
53
34
  return lower.endsWith(".tsx") || lower.endsWith(".jsx") || lower.endsWith(".js") || lower.endsWith(".mjs") || lower.endsWith(".cjs");
54
35
  }
36
+ function isComment(text) {
37
+ return text.startsWith("//") || text.startsWith("/*") || text.startsWith("#!");
38
+ }
55
39
  function measureSource(source, root) {
56
40
  const codeLines = /* @__PURE__ */ new Set();
57
41
  const stack = [root];
@@ -60,8 +44,8 @@ function measureSource(source, root) {
60
44
  let maxFunctionLength = 0;
61
45
  while (stack.length > 0) {
62
46
  const node = stack.pop();
63
- if (node.type === "comment" || node.type === "hash_bang_line") continue;
64
47
  if (node.childCount === 0) {
48
+ if (isComment(node.text)) continue;
65
49
  if (node.text.trim() !== "") {
66
50
  for (let row = node.startPosition.row; row <= node.endPosition.row; row++) {
67
51
  codeLines.add(row);
@@ -225,11 +209,175 @@ function decodeStringBody(body) {
225
209
  );
226
210
  }
227
211
 
212
+ // packages/parsers/src/ts/parser.ts
213
+ var EXTENSIONS = [".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs"];
214
+ var TypeScriptParser = class {
215
+ name = "typescript";
216
+ extensions = EXTENSIONS;
217
+ tsParser = new Parser();
218
+ tsxParser = new Parser();
219
+ constructor() {
220
+ this.tsParser.setLanguage(TypeScript.typescript);
221
+ this.tsxParser.setLanguage(TypeScript.tsx);
222
+ }
223
+ parse(source, filePath) {
224
+ const parser = usesTsxGrammar(filePath) ? this.tsxParser : this.tsParser;
225
+ const root = parser.parse(source, void 0, {
226
+ bufferSize: Math.max(64 * 1024, Buffer.byteLength(source, "utf8") + 1)
227
+ }).rootNode;
228
+ const node = root;
229
+ return {
230
+ metrics: measureSource(source, node),
231
+ imports: collectImports(node)
232
+ };
233
+ }
234
+ };
235
+
228
236
  // packages/parsers/src/resolve.ts
229
- import fs from "node:fs";
230
- import path from "node:path";
231
- import { builtinModules } from "node:module";
232
237
  import ts from "typescript";
238
+
239
+ // packages/parsers/src/builtins.ts
240
+ var NODE_BUILTINS = [
241
+ "_http_agent",
242
+ "_http_client",
243
+ "_http_common",
244
+ "_http_incoming",
245
+ "_http_outgoing",
246
+ "_http_server",
247
+ "_stream_duplex",
248
+ "_stream_passthrough",
249
+ "_stream_readable",
250
+ "_stream_transform",
251
+ "_stream_wrap",
252
+ "_stream_writable",
253
+ "_tls_common",
254
+ "_tls_wrap",
255
+ "assert",
256
+ "assert/strict",
257
+ "async_hooks",
258
+ "buffer",
259
+ "child_process",
260
+ "cluster",
261
+ "console",
262
+ "constants",
263
+ "crypto",
264
+ "dgram",
265
+ "diagnostics_channel",
266
+ "dns",
267
+ "dns/promises",
268
+ "domain",
269
+ "events",
270
+ "fs",
271
+ "fs/promises",
272
+ "http",
273
+ "http2",
274
+ "https",
275
+ "inspector",
276
+ "inspector/promises",
277
+ "module",
278
+ "net",
279
+ "os",
280
+ "path",
281
+ "path/posix",
282
+ "path/win32",
283
+ "perf_hooks",
284
+ "process",
285
+ "punycode",
286
+ "querystring",
287
+ "readline",
288
+ "readline/promises",
289
+ "repl",
290
+ "stream",
291
+ "stream/consumers",
292
+ "stream/promises",
293
+ "stream/web",
294
+ "string_decoder",
295
+ "sys",
296
+ "timers",
297
+ "timers/promises",
298
+ "tls",
299
+ "trace_events",
300
+ "tty",
301
+ "url",
302
+ "util",
303
+ "util/types",
304
+ "v8",
305
+ "vm",
306
+ "wasi",
307
+ "worker_threads",
308
+ "zlib",
309
+ "node:sea",
310
+ "node:sqlite",
311
+ "node:test",
312
+ "node:test/reporters"
313
+ ];
314
+ function isNodeBuiltin(specifier) {
315
+ if (specifier.startsWith("node:")) return true;
316
+ return NODE_BUILTINS.includes(specifier);
317
+ }
318
+
319
+ // packages/parsers/src/path.ts
320
+ function join(...segments) {
321
+ const parts = [];
322
+ for (const segment of segments) {
323
+ if (!segment) continue;
324
+ for (const piece of segment.split("/")) {
325
+ if (piece === "" || piece === ".") continue;
326
+ if (piece === "..") {
327
+ parts.pop();
328
+ continue;
329
+ }
330
+ parts.push(piece);
331
+ }
332
+ }
333
+ return `/${parts.join("/")}`;
334
+ }
335
+ function resolve(...segments) {
336
+ let resolved = "";
337
+ let absolute = false;
338
+ for (let index = segments.length - 1; index >= 0 && !absolute; index -= 1) {
339
+ const segment = segments[index];
340
+ if (!segment) continue;
341
+ resolved = resolved === "" ? segment : `${segment}/${resolved}`;
342
+ absolute = segment.startsWith("/");
343
+ }
344
+ if (!absolute) resolved = `/${resolved}`;
345
+ return join(resolved);
346
+ }
347
+ function dirname(target) {
348
+ const normalized = target.replace(/\/+$/, "");
349
+ const index = normalized.lastIndexOf("/");
350
+ if (index <= 0) return "/";
351
+ return normalized.slice(0, index);
352
+ }
353
+ function basename(target) {
354
+ const normalized = target.replace(/\/+$/, "");
355
+ const index = normalized.lastIndexOf("/");
356
+ return index === -1 ? normalized : normalized.slice(index + 1);
357
+ }
358
+ function extname(target) {
359
+ const name = basename(target);
360
+ const index = name.lastIndexOf(".");
361
+ if (index <= 0) return "";
362
+ return name.slice(index);
363
+ }
364
+ function relative(from, target) {
365
+ const fromParts = from.split("/").filter(Boolean);
366
+ const targetParts = target.split("/").filter(Boolean);
367
+ let common = 0;
368
+ while (common < fromParts.length && common < targetParts.length && fromParts[common] === targetParts[common]) {
369
+ common += 1;
370
+ }
371
+ const up = fromParts.length - common;
372
+ const down = targetParts.slice(common);
373
+ const parts = [...Array.from({ length: up }, () => ".."), ...down];
374
+ return parts.join("/");
375
+ }
376
+ function isAbsolute(target) {
377
+ return target.startsWith("/");
378
+ }
379
+
380
+ // packages/parsers/src/resolve.ts
233
381
  var SOURCE_EXTENSIONS = [
234
382
  ".ts",
235
383
  ".tsx",
@@ -255,9 +403,6 @@ var WORKSPACE_SKIP_DIRS = /* @__PURE__ */ new Set([
255
403
  ]);
256
404
  var MAX_WORKSPACE_DEPTH = 8;
257
405
  var MAX_RESOLVE_DEPTH = 8;
258
- var BUILTINS = new Set(
259
- builtinModules.flatMap((name) => [name, `node:${name}`])
260
- );
261
406
  var ASSET_EXTENSIONS = [
262
407
  ".css",
263
408
  ".scss",
@@ -283,31 +428,33 @@ var ASSET_EXTENSIONS = [
283
428
  ".pdf"
284
429
  ];
285
430
  function isAssetSpecifier(specifier) {
286
- const clean = specifier.split(/[?#]/, 1)[0]?.toLowerCase() ?? "";
287
- return ASSET_EXTENSIONS.some((extension) => clean.endsWith(extension));
431
+ const clean2 = specifier.split(/[?#]/, 1)[0]?.toLowerCase() ?? "";
432
+ return ASSET_EXTENSIONS.some((extension) => clean2.endsWith(extension));
288
433
  }
289
434
  var ImportResolver = class {
290
435
  warnings = [];
291
436
  root;
437
+ fileSystem;
292
438
  byPath = /* @__PURE__ */ new Map();
293
439
  packageJsonCache = /* @__PURE__ */ new Map();
294
440
  configCache = /* @__PURE__ */ new Map();
295
441
  workspaces;
296
- constructor(rootInput, files) {
297
- this.root = path.resolve(rootInput);
298
- for (const file of files) this.byPath.set(pathKey(file.absolutePath), file);
299
- this.workspaces = discoverWorkspaces(this.root, this.warnings);
442
+ constructor(rootInput, files, fileSystem) {
443
+ this.root = resolve(rootInput);
444
+ this.fileSystem = fileSystem;
445
+ for (const file of files) this.byPath.set(this.pathKey(file.absolutePath), file);
446
+ this.workspaces = discoverWorkspaces(this.root, this.fileSystem, this.warnings);
300
447
  }
301
448
  resolve(importerPath, specifier) {
302
- if (BUILTINS.has(specifier) || specifier.startsWith("node:")) {
449
+ if (isNodeBuiltin(specifier)) {
303
450
  return { kind: "external", specifier, builtin: true };
304
451
  }
305
452
  if (specifier.startsWith(".") || specifier.startsWith("/")) {
306
- const base = specifier.startsWith("/") ? path.resolve(this.root, `.${specifier}`) : path.resolve(path.dirname(path.resolve(importerPath)), specifier);
453
+ const base = specifier.startsWith("/") ? resolve(this.root, `.${specifier}`) : resolve(dirname(resolve(importerPath)), specifier);
307
454
  const file = this.resolveAsFileOrDirectory(base);
308
455
  return file ? { kind: "internal", file } : { kind: "unresolved", specifier, reason: "no se encontr\xF3 el fichero" };
309
456
  }
310
- const config = this.configFor(path.dirname(path.resolve(importerPath)));
457
+ const config = this.configFor(dirname(resolve(importerPath)));
311
458
  if (config) {
312
459
  const alias = matchAlias(config.aliases, specifier);
313
460
  if (alias) {
@@ -345,7 +492,7 @@ var ImportResolver = class {
345
492
  const subpath = specifier === workspace.name ? "." : `./${specifier.slice(workspace.name.length + 1)}`;
346
493
  for (const target of packageExportTargets(workspace.json.exports, subpath)) {
347
494
  const file = this.resolveAsFileOrDirectory(
348
- path.resolve(workspace.dir, target)
495
+ resolve(workspace.dir, target)
349
496
  );
350
497
  if (file) return file;
351
498
  }
@@ -354,7 +501,7 @@ var ImportResolver = class {
354
501
  const value = workspace.json[field5];
355
502
  if (typeof value !== "string") continue;
356
503
  const file = this.resolveAsFileOrDirectory(
357
- path.resolve(workspace.dir, value)
504
+ resolve(workspace.dir, value)
358
505
  );
359
506
  if (file) return file;
360
507
  }
@@ -365,13 +512,13 @@ var ImportResolver = class {
365
512
  "index.tsx"
366
513
  ]) {
367
514
  const file = this.resolveAsFileOrDirectory(
368
- path.resolve(workspace.dir, fallback)
515
+ resolve(workspace.dir, fallback)
369
516
  );
370
517
  if (file) return file;
371
518
  }
372
519
  } else {
373
520
  const file = this.resolveAsFileOrDirectory(
374
- path.resolve(workspace.dir, subpath.slice(2))
521
+ resolve(workspace.dir, subpath.slice(2))
375
522
  );
376
523
  if (file) return file;
377
524
  }
@@ -380,7 +527,7 @@ var ImportResolver = class {
380
527
  resolveAsFileOrDirectory(base, depth = 0) {
381
528
  if (depth > MAX_RESOLVE_DEPTH) return null;
382
529
  for (const candidate of fileCandidates(base)) {
383
- const file = this.byPath.get(pathKey(candidate));
530
+ const file = this.byPath.get(this.pathKey(candidate));
384
531
  if (file) return file;
385
532
  }
386
533
  const pkg = this.readPackageJson(base);
@@ -389,67 +536,77 @@ var ImportResolver = class {
389
536
  const value = pkg[field5];
390
537
  if (typeof value !== "string") continue;
391
538
  const file = this.resolveAsFileOrDirectory(
392
- path.resolve(base, value),
539
+ resolve(base, value),
393
540
  depth + 1
394
541
  );
395
542
  if (file) return file;
396
543
  }
397
544
  for (const target of packageExportTargets(pkg.exports, ".")) {
398
545
  const file = this.resolveAsFileOrDirectory(
399
- path.resolve(base, target),
546
+ resolve(base, target),
400
547
  depth + 1
401
548
  );
402
549
  if (file) return file;
403
550
  }
404
551
  }
405
552
  for (const candidate of indexCandidates(base)) {
406
- const file = this.byPath.get(pathKey(candidate));
553
+ const file = this.byPath.get(this.pathKey(candidate));
407
554
  if (file) return file;
408
555
  }
409
556
  return null;
410
557
  }
411
558
  readPackageJson(directory) {
412
- const key = pathKey(directory);
559
+ const key = this.pathKey(directory);
413
560
  if (this.packageJsonCache.has(key)) {
414
561
  return this.packageJsonCache.get(key) ?? null;
415
562
  }
416
- const json = readJsonFile(path.join(directory, "package.json"), this.warnings);
563
+ const json = readJsonFile(
564
+ this.fileSystem,
565
+ join(directory, "package.json"),
566
+ this.warnings
567
+ );
417
568
  this.packageJsonCache.set(key, json);
418
569
  return json;
419
570
  }
420
571
  configFor(startDirectory) {
421
572
  const visited = [];
422
- let directory = path.resolve(startDirectory);
573
+ let directory = resolve(startDirectory);
423
574
  while (true) {
424
- const key = pathKey(directory);
575
+ const key = this.pathKey(directory);
425
576
  const cached = this.configCache.get(key);
426
577
  if (cached !== void 0) {
427
578
  for (const visitedKey of visited) this.configCache.set(visitedKey, cached);
428
579
  return cached;
429
580
  }
430
581
  visited.push(key);
431
- const configPath = findTsConfig(directory);
582
+ const configPath = findTsConfig(this.fileSystem, directory);
432
583
  if (configPath) {
433
- const loaded = loadTsConfig(configPath, this.warnings);
584
+ const loaded = loadTsConfig(this.fileSystem, configPath, this.warnings);
434
585
  for (const visitedKey of visited) this.configCache.set(visitedKey, loaded);
435
586
  return loaded;
436
587
  }
437
- if (key === pathKey(this.root)) break;
438
- const parent = path.dirname(directory);
588
+ if (key === this.pathKey(this.root)) break;
589
+ const parent = dirname(directory);
439
590
  if (parent === directory || !isWithin(this.root, parent)) break;
440
591
  directory = parent;
441
592
  }
442
593
  for (const visitedKey of visited) this.configCache.set(visitedKey, null);
443
594
  return null;
444
595
  }
596
+ /**
597
+ * Clave de una ruta para las tablas internas.
598
+ *
599
+ * En sistemas que no distinguen mayúsculas, la clave se normaliza para que
600
+ * `./Foo` encuentre `foo.ts`.
601
+ */
602
+ pathKey(value) {
603
+ const resolved = resolve(value);
604
+ return this.fileSystem.caseSensitive ? resolved : resolved.toLowerCase();
605
+ }
445
606
  };
446
- function pathKey(value) {
447
- const resolved = path.resolve(value);
448
- return process.platform === "win32" ? resolved.toLowerCase() : resolved;
449
- }
450
607
  function isWithin(root, candidate) {
451
- const relative = path.relative(root, candidate);
452
- return relative === "" || !relative.startsWith("..") && !path.isAbsolute(relative);
608
+ const relative2 = relative(root, candidate);
609
+ return relative2 === "" || !relative2.startsWith("..") && !isAbsolute(relative2);
453
610
  }
454
611
  function fileCandidates(base) {
455
612
  const lower = base.toLowerCase();
@@ -467,7 +624,7 @@ function fileCandidates(base) {
467
624
  if (lower.endsWith(".cjs")) {
468
625
  return [`${base.slice(0, -4)}.cts`, base];
469
626
  }
470
- if (path.extname(base) !== "") return [base];
627
+ if (extname(base) !== "") return [base];
471
628
  return [
472
629
  base,
473
630
  ...SOURCE_EXTENSIONS.map((extension) => `${base}${extension}`)
@@ -475,18 +632,29 @@ function fileCandidates(base) {
475
632
  }
476
633
  function indexCandidates(directory) {
477
634
  return SOURCE_EXTENSIONS.map(
478
- (extension) => path.join(directory, `index${extension}`)
635
+ (extension) => join(directory, `index${extension}`)
479
636
  );
480
637
  }
481
- function findTsConfig(directory) {
638
+ function findTsConfig(fileSystem, directory) {
482
639
  for (const name of ["tsconfig.json", "jsconfig.json"]) {
483
- const candidate = path.join(directory, name);
484
- if (fs.existsSync(candidate)) return candidate;
640
+ const candidate = join(directory, name);
641
+ if (fileSystem.exists(candidate)) return candidate;
485
642
  }
486
643
  return null;
487
644
  }
488
- function loadTsConfig(configPath, warnings) {
489
- const read = ts.readConfigFile(configPath, ts.sys.readFile);
645
+ function loadTsConfig(fileSystem, configPath, warnings) {
646
+ const host = {
647
+ useCaseSensitiveFileNames: fileSystem.caseSensitive,
648
+ readDirectory: () => [],
649
+ fileExists: (candidate) => fileSystem.exists(candidate),
650
+ readFile: (candidate) => fileSystem.tryReadFile(candidate) ?? void 0,
651
+ getCurrentDirectory: () => "/",
652
+ onUnRecoverableConfigFileDiagnostic: () => void 0
653
+ };
654
+ const read = ts.readConfigFile(
655
+ configPath,
656
+ (candidate) => fileSystem.tryReadFile(candidate) ?? void 0
657
+ );
490
658
  if (read.error || read.config === void 0) {
491
659
  warnings.push(
492
660
  `no se pudo leer ${configPath}: ${read.error?.messageText ?? "configuraci\xF3n vac\xEDa"}`
@@ -495,13 +663,13 @@ function loadTsConfig(configPath, warnings) {
495
663
  }
496
664
  const parsed = ts.parseJsonConfigFileContent(
497
665
  read.config,
498
- ts.sys,
499
- path.dirname(configPath),
666
+ host,
667
+ dirname(configPath),
500
668
  void 0,
501
669
  configPath
502
670
  );
503
- const configDirectory = path.dirname(configPath);
504
- const baseUrl = path.resolve(configDirectory, parsed.options.baseUrl ?? ".");
671
+ const configDirectory = dirname(configPath);
672
+ const baseUrl = resolve(configDirectory, parsed.options.baseUrl ?? ".");
505
673
  const paths = parsed.options.paths ?? {};
506
674
  const aliases = [];
507
675
  for (const [pattern, targets] of Object.entries(paths)) {
@@ -509,7 +677,7 @@ function loadTsConfig(configPath, warnings) {
509
677
  const star = pattern.indexOf("*");
510
678
  const prefix = star === -1 ? pattern : pattern.slice(0, star);
511
679
  const suffix = star === -1 ? "" : pattern.slice(star + 1);
512
- const resolvedTargets = targets.filter((target) => typeof target === "string").map((target) => path.resolve(baseUrl, target));
680
+ const resolvedTargets = targets.filter((target) => typeof target === "string").map((target) => resolve(baseUrl, target));
513
681
  if (resolvedTargets.length > 0) {
514
682
  aliases.push({
515
683
  prefix,
@@ -602,8 +770,12 @@ function matchWildcardKey(pattern, subpath) {
602
770
  }
603
771
  return subpath.slice(prefix.length, subpath.length - suffix.length);
604
772
  }
605
- function discoverWorkspaces(root, warnings) {
606
- const rootJson = readJsonFile(path.join(root, "package.json"), warnings);
773
+ function discoverWorkspaces(root, fileSystem, warnings) {
774
+ const rootJson = readJsonFile(
775
+ fileSystem,
776
+ join(root, "package.json"),
777
+ warnings
778
+ );
607
779
  if (!rootJson) return [];
608
780
  const patterns = workspacePatterns(rootJson.workspaces);
609
781
  if (patterns.length === 0) return [];
@@ -615,14 +787,15 @@ function discoverWorkspaces(root, warnings) {
615
787
  const current = stack.pop();
616
788
  if (current.relative !== "" && patterns.some((pattern) => pattern.test(current.relative))) {
617
789
  const json = readJsonFile(
618
- path.join(current.directory, "package.json"),
790
+ fileSystem,
791
+ join(current.directory, "package.json"),
619
792
  warnings
620
793
  );
621
794
  if (json && typeof json.name === "string") {
622
795
  const existing = byName.get(json.name);
623
796
  if (existing) {
624
797
  warnings.push(
625
- `workspace duplicado "${json.name}": ${path.relative(root, existing.dir)} y ${path.relative(root, current.directory)}`
798
+ `workspace duplicado "${json.name}": ${relative(root, existing.dir)} y ${relative(root, current.directory)}`
626
799
  );
627
800
  } else {
628
801
  byName.set(json.name, {
@@ -636,7 +809,7 @@ function discoverWorkspaces(root, warnings) {
636
809
  if (current.depth >= MAX_WORKSPACE_DEPTH) continue;
637
810
  let entries;
638
811
  try {
639
- entries = fs.readdirSync(current.directory, { withFileTypes: true });
812
+ entries = fileSystem.readDirectory(current.directory);
640
813
  } catch (error) {
641
814
  warnings.push(
642
815
  `no se pudo leer ${current.relative || "."}: ${error.message}`
@@ -644,10 +817,10 @@ function discoverWorkspaces(root, warnings) {
644
817
  continue;
645
818
  }
646
819
  for (const entry of entries) {
647
- if (!entry.isDirectory() || entry.isSymbolicLink()) continue;
820
+ if (!entry.isDirectory) continue;
648
821
  if (WORKSPACE_SKIP_DIRS.has(entry.name)) continue;
649
822
  stack.push({
650
- directory: path.join(current.directory, entry.name),
823
+ directory: join(current.directory, entry.name),
651
824
  relative: current.relative ? `${current.relative}/${entry.name}` : entry.name,
652
825
  depth: current.depth + 1
653
826
  });
@@ -693,16 +866,15 @@ function globToRegExp(glob) {
693
866
  function escapeRegExp(value) {
694
867
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
695
868
  }
696
- function readJsonFile(file, warnings) {
869
+ function readJsonFile(fileSystem, file, warnings) {
697
870
  let text;
698
871
  try {
699
- text = fs.readFileSync(file, "utf8");
872
+ text = fileSystem.tryReadFile(file);
700
873
  } catch (error) {
701
- if (error.code !== "ENOENT") {
702
- warnings.push(`no se pudo leer ${file}: ${error.message}`);
703
- }
874
+ warnings.push(`no se pudo leer ${file}: ${error.message}`);
704
875
  return null;
705
876
  }
877
+ if (text === null) return null;
706
878
  try {
707
879
  const parsed = JSON.parse(text.replace(/^\uFEFF/, ""));
708
880
  return isRecord(parsed) ? parsed : null;
@@ -881,41 +1053,516 @@ function compareStrings(left, right) {
881
1053
  }
882
1054
 
883
1055
  // packages/parsers/src/analyze.ts
884
- import fs4 from "node:fs";
885
- import path4 from "node:path";
1056
+ import { basename as basename2 } from "node:path";
886
1057
 
887
1058
  // packages/schema/src/errors.ts
888
1059
  var CcJsonError = class extends Error {
889
1060
  path;
890
- constructor(message, path9) {
891
- super(`${message} (en ${path9})`);
1061
+ constructor(message, path7) {
1062
+ super(`${message} (en ${path7})`);
892
1063
  this.name = "CcJsonError";
893
- this.path = path9;
1064
+ this.path = path7;
894
1065
  }
895
1066
  };
896
1067
  function isRecord2(value) {
897
1068
  return typeof value === "object" && value !== null && !Array.isArray(value);
898
1069
  }
899
- function requireRecord(value, path9) {
900
- if (!isRecord2(value)) throw new CcJsonError("se esperaba un objeto", path9);
1070
+ function requireRecord(value, path7) {
1071
+ if (!isRecord2(value)) throw new CcJsonError("se esperaba un objeto", path7);
901
1072
  return value;
902
1073
  }
903
- function requireString(value, path9, field5) {
1074
+ function requireString(value, path7, field5) {
904
1075
  if (typeof value !== "string" || value.length === 0) {
905
- throw new CcJsonError(`"${field5}" debe ser una cadena no vac\xEDa`, path9);
1076
+ throw new CcJsonError(`"${field5}" debe ser una cadena no vac\xEDa`, path7);
906
1077
  }
907
1078
  return value;
908
1079
  }
909
1080
 
1081
+ // node_modules/@noble/hashes/_u64.js
1082
+ var fromNumH = (n) => n / 2 ** 32 | 0;
1083
+ var fromNumL = (n) => n >>> 0;
1084
+ function setU64FromNum(view, byteOffset, n, isLE) {
1085
+ const h = fromNumH(n);
1086
+ const l = fromNumL(n);
1087
+ view.setUint32(byteOffset, isLE ? l : h, isLE);
1088
+ view.setUint32(byteOffset + 4, isLE ? h : l, isLE);
1089
+ }
1090
+
1091
+ // node_modules/@noble/hashes/utils.js
1092
+ function isBytes(a) {
1093
+ return a instanceof Uint8Array || ArrayBuffer.isView(a) && a.constructor.name === "Uint8Array" && "BYTES_PER_ELEMENT" in a && a.BYTES_PER_ELEMENT === 1;
1094
+ }
1095
+ var atitle = (title) => title ? `"${title}" ` : "";
1096
+ function anumber(n, title = "") {
1097
+ if (typeof n !== "number")
1098
+ throw new TypeError(atitle(title) + "expected number, got " + typeof n);
1099
+ if (!Number.isSafeInteger(n) || n < 0)
1100
+ throw new RangeError(atitle(title) + "expected integer >= 0, got " + n);
1101
+ return n;
1102
+ }
1103
+ function abytes(value, length, title = "") {
1104
+ if (isBytes(value) && (length === void 0 || value.length === length))
1105
+ return value;
1106
+ if (length !== void 0)
1107
+ anumber(length, "length");
1108
+ const bytes = isBytes(value);
1109
+ const ofLen = length !== void 0 ? ` of length ${length}` : "";
1110
+ const got = bytes ? `length=${value.length}` : `type=${typeof value}`;
1111
+ const message = atitle(title) + "expected Uint8Array" + ofLen + ", got " + got;
1112
+ if (!bytes)
1113
+ throw new TypeError(message);
1114
+ throw new RangeError(message);
1115
+ }
1116
+ var aobject = (value, label) => {
1117
+ if (value === null || typeof value !== "object" || Array.isArray(value))
1118
+ throw new TypeError((label === "object" ? "" : `"${label}" `) + "expected object, got type=" + typeof value);
1119
+ };
1120
+ var aopts = (value, label) => {
1121
+ aobject(value, label);
1122
+ const proto = Object.getPrototypeOf(value);
1123
+ if (proto !== Object.prototype && proto !== null)
1124
+ throw new TypeError(`"${label}" expected plain object`);
1125
+ if (Object.hasOwn(value, "__proto__"))
1126
+ throw new TypeError(`"${label}.__proto__" is not allowed`);
1127
+ };
1128
+ function aexists(instance, checkFinished = true) {
1129
+ if (instance.destroyed)
1130
+ throw new Error("hash was destroyed");
1131
+ if (checkFinished && instance.finished)
1132
+ throw new Error("digest() was already called");
1133
+ }
1134
+ function aoutput(out, instance) {
1135
+ abytes(out, void 0, "output");
1136
+ const min = instance.outputLen;
1137
+ if (!(out.length >= min)) {
1138
+ throw new RangeError('"output" expected length >= ' + min);
1139
+ }
1140
+ }
1141
+ function clean(...arrays) {
1142
+ for (let i = 0; i < arrays.length; i++) {
1143
+ arrays[i].fill(0);
1144
+ }
1145
+ }
1146
+ function createView(arr) {
1147
+ return new DataView(arr.buffer, arr.byteOffset, arr.byteLength);
1148
+ }
1149
+ function rotr(word, shift) {
1150
+ return word << 32 - shift | word >>> shift;
1151
+ }
1152
+ function rotl(word, shift) {
1153
+ return word << shift | word >>> 32 - shift >>> 0;
1154
+ }
1155
+ function checkOpts(defaults, opts, title = "opts") {
1156
+ aopts(defaults, "defaults");
1157
+ if (opts !== void 0)
1158
+ aopts(opts, title);
1159
+ const merged = Object.assign(/* @__PURE__ */ Object.create(null), defaults, opts);
1160
+ return merged;
1161
+ }
1162
+ function createHasher(hashCons, info = {}) {
1163
+ if (typeof hashCons !== "function")
1164
+ throw new TypeError('"hashCons" expected function, got type=' + typeof hashCons);
1165
+ info = checkOpts({}, info, "info");
1166
+ const hashC = (msg, opts) => hashCons(opts).update(msg).digest();
1167
+ const tmp = hashCons(void 0);
1168
+ hashC.outputLen = tmp.outputLen;
1169
+ hashC.blockLen = tmp.blockLen;
1170
+ hashC.canXOF = tmp.canXOF;
1171
+ hashC.create = (opts) => hashCons(opts);
1172
+ Object.assign(hashC, info);
1173
+ return Object.freeze(hashC);
1174
+ }
1175
+ var oidNist = (suffix) => ({
1176
+ // Current NIST hashAlgs suffixes used here fit in one DER subidentifier octet.
1177
+ // Larger suffix values would need base-128 OID encoding and a different length byte.
1178
+ oid: Uint8Array.from([6, 9, 96, 134, 72, 1, 101, 3, 4, 2, suffix])
1179
+ });
1180
+
1181
+ // node_modules/@noble/hashes/_md.js
1182
+ function Chi(a, b, c) {
1183
+ return a & b ^ ~a & c;
1184
+ }
1185
+ function Maj(a, b, c) {
1186
+ return a & b ^ a & c ^ b & c;
1187
+ }
1188
+ var HashMD = class {
1189
+ blockLen;
1190
+ outputLen;
1191
+ canXOF = false;
1192
+ padOffset;
1193
+ isLE;
1194
+ // For partial updates less than block size
1195
+ buffer;
1196
+ view;
1197
+ finished = false;
1198
+ length = 0;
1199
+ pos = 0;
1200
+ destroyed = false;
1201
+ constructor(blockLen, outputLen, padOffset, isLE) {
1202
+ this.blockLen = blockLen;
1203
+ this.outputLen = outputLen;
1204
+ this.padOffset = padOffset;
1205
+ this.isLE = isLE;
1206
+ this.buffer = new Uint8Array(blockLen);
1207
+ this.view = createView(this.buffer);
1208
+ }
1209
+ update(data) {
1210
+ aexists(this);
1211
+ abytes(data);
1212
+ const { view, buffer, blockLen } = this;
1213
+ const len = data.length;
1214
+ let processed = false;
1215
+ for (let pos = 0; pos < len; ) {
1216
+ const take = Math.min(blockLen - this.pos, len - pos);
1217
+ if (take === blockLen) {
1218
+ const dataView = createView(data);
1219
+ for (; blockLen <= len - pos; pos += blockLen)
1220
+ this.process(dataView, pos);
1221
+ processed = true;
1222
+ continue;
1223
+ }
1224
+ buffer.set(pos === 0 && take === len ? data : data.subarray(pos, pos + take), this.pos);
1225
+ this.pos += take;
1226
+ pos += take;
1227
+ if (this.pos === blockLen) {
1228
+ this.process(view, 0);
1229
+ this.pos = 0;
1230
+ processed = true;
1231
+ }
1232
+ }
1233
+ this.length += data.length;
1234
+ if (processed)
1235
+ this.roundClean();
1236
+ return this;
1237
+ }
1238
+ digestInto(out) {
1239
+ aexists(this);
1240
+ aoutput(out, this);
1241
+ this.finished = true;
1242
+ const { buffer, view, blockLen, isLE } = this;
1243
+ let { pos } = this;
1244
+ buffer[pos++] = 128;
1245
+ buffer.fill(0, pos);
1246
+ if (this.padOffset > blockLen - pos) {
1247
+ this.process(view, 0);
1248
+ buffer.fill(0);
1249
+ }
1250
+ setU64FromNum(view, blockLen - 8, this.length * 8, isLE);
1251
+ this.process(view, 0);
1252
+ this.roundClean();
1253
+ const oview = out === buffer ? view : createView(out);
1254
+ const len = this.outputLen;
1255
+ const outLen = len / 4;
1256
+ const state = this.get();
1257
+ if (len % 4 || outLen > state.length)
1258
+ throw new Error("invalid outputLen");
1259
+ for (let i = 0; i < outLen; i++)
1260
+ oview.setUint32(4 * i, state[i], isLE);
1261
+ }
1262
+ digest() {
1263
+ const { buffer, outputLen } = this;
1264
+ this.digestInto(buffer);
1265
+ const res = buffer.slice(0, outputLen);
1266
+ this.destroy();
1267
+ return res;
1268
+ }
1269
+ _cloneIntoMeta(to) {
1270
+ const { buffer, length, finished, destroyed, pos } = this;
1271
+ to.destroyed = destroyed;
1272
+ to.finished = finished;
1273
+ to.length = length;
1274
+ to.pos = pos;
1275
+ if (pos)
1276
+ to.buffer.set(buffer);
1277
+ return to;
1278
+ }
1279
+ clone() {
1280
+ return this._cloneInto();
1281
+ }
1282
+ };
1283
+ var SHA256_IV = /* @__PURE__ */ Uint32Array.from([
1284
+ 1779033703,
1285
+ 3144134277,
1286
+ 1013904242,
1287
+ 2773480762,
1288
+ 1359893119,
1289
+ 2600822924,
1290
+ 528734635,
1291
+ 1541459225
1292
+ ]);
1293
+
1294
+ // node_modules/@noble/hashes/legacy.js
1295
+ var SHA1_IV = /* @__PURE__ */ Uint32Array.from([
1296
+ 1732584193,
1297
+ 4023233417,
1298
+ 2562383102,
1299
+ 271733878,
1300
+ 3285377520
1301
+ ]);
1302
+ var p32 = /* @__PURE__ */ Math.pow(2, 32);
1303
+ var K = /* @__PURE__ */ Array.from({ length: 64 }, (_, i) => Math.floor(p32 * Math.abs(Math.sin(i + 1))));
1304
+ var MD5_IV = /* @__PURE__ */ SHA1_IV.slice(0, 4);
1305
+ var MD5_W = /* @__PURE__ */ new Uint32Array(16);
1306
+ var MD5_SHIFTS = /* @__PURE__ */ (() => {
1307
+ const S = [
1308
+ [7, 12, 17, 22],
1309
+ [5, 9, 14, 20],
1310
+ [4, 11, 16, 23],
1311
+ [6, 10, 15, 21]
1312
+ ];
1313
+ return Uint8Array.from({ length: 64 }, (_, i) => S[Math.floor(i / 16)][i % 4]);
1314
+ })();
1315
+ var _MD5 = class extends HashMD {
1316
+ A = MD5_IV[0] | 0;
1317
+ B = MD5_IV[1] | 0;
1318
+ C = MD5_IV[2] | 0;
1319
+ D = MD5_IV[3] | 0;
1320
+ constructor() {
1321
+ super(64, 16, 8, true);
1322
+ }
1323
+ get() {
1324
+ const { A, B, C, D } = this;
1325
+ return [A, B, C, D];
1326
+ }
1327
+ set(A, B, C, D) {
1328
+ this.A = A | 0;
1329
+ this.B = B | 0;
1330
+ this.C = C | 0;
1331
+ this.D = D | 0;
1332
+ }
1333
+ _cloneInto(to) {
1334
+ (to ||= new this.constructor()).set(...this.get());
1335
+ return this._cloneIntoMeta(to);
1336
+ }
1337
+ process(view, offset) {
1338
+ for (let i = 0; i < 16; i++, offset += 4)
1339
+ MD5_W[i] = view.getUint32(offset, true);
1340
+ let { A, B, C, D } = this;
1341
+ for (let i = 0; i < 64; i++) {
1342
+ let F, g;
1343
+ if (i < 16) {
1344
+ F = Chi(B, C, D);
1345
+ g = i;
1346
+ } else if (i < 32) {
1347
+ F = Chi(D, B, C);
1348
+ g = (5 * i + 1) % 16;
1349
+ } else if (i < 48) {
1350
+ F = B ^ C ^ D;
1351
+ g = (3 * i + 5) % 16;
1352
+ } else {
1353
+ F = C ^ (B | ~D);
1354
+ g = 7 * i % 16;
1355
+ }
1356
+ F = F + A + K[i] + MD5_W[g];
1357
+ A = D;
1358
+ D = C;
1359
+ C = B;
1360
+ B = B + rotl(F, MD5_SHIFTS[i]);
1361
+ }
1362
+ A = A + this.A | 0;
1363
+ B = B + this.B | 0;
1364
+ C = C + this.C | 0;
1365
+ D = D + this.D | 0;
1366
+ this.set(A, B, C, D);
1367
+ }
1368
+ roundClean() {
1369
+ clean(MD5_W);
1370
+ }
1371
+ destroy() {
1372
+ this.destroyed = true;
1373
+ this.set(0, 0, 0, 0);
1374
+ clean(this.buffer);
1375
+ }
1376
+ };
1377
+ var md5 = /* @__PURE__ */ createHasher(() => new _MD5());
1378
+
1379
+ // node_modules/@noble/hashes/sha2.js
1380
+ var SHA256_K = /* @__PURE__ */ Uint32Array.from([
1381
+ 1116352408,
1382
+ 1899447441,
1383
+ 3049323471,
1384
+ 3921009573,
1385
+ 961987163,
1386
+ 1508970993,
1387
+ 2453635748,
1388
+ 2870763221,
1389
+ 3624381080,
1390
+ 310598401,
1391
+ 607225278,
1392
+ 1426881987,
1393
+ 1925078388,
1394
+ 2162078206,
1395
+ 2614888103,
1396
+ 3248222580,
1397
+ 3835390401,
1398
+ 4022224774,
1399
+ 264347078,
1400
+ 604807628,
1401
+ 770255983,
1402
+ 1249150122,
1403
+ 1555081692,
1404
+ 1996064986,
1405
+ 2554220882,
1406
+ 2821834349,
1407
+ 2952996808,
1408
+ 3210313671,
1409
+ 3336571891,
1410
+ 3584528711,
1411
+ 113926993,
1412
+ 338241895,
1413
+ 666307205,
1414
+ 773529912,
1415
+ 1294757372,
1416
+ 1396182291,
1417
+ 1695183700,
1418
+ 1986661051,
1419
+ 2177026350,
1420
+ 2456956037,
1421
+ 2730485921,
1422
+ 2820302411,
1423
+ 3259730800,
1424
+ 3345764771,
1425
+ 3516065817,
1426
+ 3600352804,
1427
+ 4094571909,
1428
+ 275423344,
1429
+ 430227734,
1430
+ 506948616,
1431
+ 659060556,
1432
+ 883997877,
1433
+ 958139571,
1434
+ 1322822218,
1435
+ 1537002063,
1436
+ 1747873779,
1437
+ 1955562222,
1438
+ 2024104815,
1439
+ 2227730452,
1440
+ 2361852424,
1441
+ 2428436474,
1442
+ 2756734187,
1443
+ 3204031479,
1444
+ 3329325298
1445
+ ]);
1446
+ var SHA256_W = /* @__PURE__ */ new Uint32Array(64);
1447
+ var SHA2_32B = class extends HashMD {
1448
+ // We cannot use array here since array allows indexing by variable
1449
+ // which means optimizer/compiler cannot use registers.
1450
+ // Numeric initializers matter: starting the fields as `undefined` changes
1451
+ // V8's field representation and makes sha256 3x slower (measured).
1452
+ A = 0;
1453
+ B = 0;
1454
+ C = 0;
1455
+ D = 0;
1456
+ E = 0;
1457
+ F = 0;
1458
+ G = 0;
1459
+ H = 0;
1460
+ constructor(outputLen, IV) {
1461
+ super(64, outputLen, 8, false);
1462
+ this.A = IV[0] | 0;
1463
+ this.B = IV[1] | 0;
1464
+ this.C = IV[2] | 0;
1465
+ this.D = IV[3] | 0;
1466
+ this.E = IV[4] | 0;
1467
+ this.F = IV[5] | 0;
1468
+ this.G = IV[6] | 0;
1469
+ this.H = IV[7] | 0;
1470
+ }
1471
+ get() {
1472
+ const { A, B, C, D, E, F, G, H } = this;
1473
+ return [A, B, C, D, E, F, G, H];
1474
+ }
1475
+ // prettier-ignore
1476
+ set(A, B, C, D, E, F, G, H) {
1477
+ this.A = A | 0;
1478
+ this.B = B | 0;
1479
+ this.C = C | 0;
1480
+ this.D = D | 0;
1481
+ this.E = E | 0;
1482
+ this.F = F | 0;
1483
+ this.G = G | 0;
1484
+ this.H = H | 0;
1485
+ }
1486
+ _cloneInto(to) {
1487
+ (to ||= new this.constructor()).set(...this.get());
1488
+ return this._cloneIntoMeta(to);
1489
+ }
1490
+ process(view, offset) {
1491
+ for (let i = 0; i < 16; i++, offset += 4)
1492
+ SHA256_W[i] = view.getUint32(offset, false);
1493
+ for (let i = 16; i < 64; i++) {
1494
+ const W15 = SHA256_W[i - 15];
1495
+ const W2 = SHA256_W[i - 2];
1496
+ const s0 = rotr(W15, 7) ^ rotr(W15, 18) ^ W15 >>> 3;
1497
+ const s1 = rotr(W2, 17) ^ rotr(W2, 19) ^ W2 >>> 10;
1498
+ SHA256_W[i] = s1 + SHA256_W[i - 7] + s0 + SHA256_W[i - 16] | 0;
1499
+ }
1500
+ let { A, B, C, D, E, F, G, H } = this;
1501
+ for (let i = 0; i < 64; i++) {
1502
+ const sigma1 = rotr(E, 6) ^ rotr(E, 11) ^ rotr(E, 25);
1503
+ const T1 = H + sigma1 + Chi(E, F, G) + SHA256_K[i] + SHA256_W[i] | 0;
1504
+ const sigma0 = rotr(A, 2) ^ rotr(A, 13) ^ rotr(A, 22);
1505
+ const T2 = sigma0 + Maj(A, B, C) | 0;
1506
+ H = G;
1507
+ G = F;
1508
+ F = E;
1509
+ E = D + T1 | 0;
1510
+ D = C;
1511
+ C = B;
1512
+ B = A;
1513
+ A = T1 + T2 | 0;
1514
+ }
1515
+ A = A + this.A | 0;
1516
+ B = B + this.B | 0;
1517
+ C = C + this.C | 0;
1518
+ D = D + this.D | 0;
1519
+ E = E + this.E | 0;
1520
+ F = F + this.F | 0;
1521
+ G = G + this.G | 0;
1522
+ H = H + this.H | 0;
1523
+ this.set(A, B, C, D, E, F, G, H);
1524
+ }
1525
+ roundClean() {
1526
+ clean(SHA256_W);
1527
+ }
1528
+ destroy() {
1529
+ this.destroyed = true;
1530
+ this.set(0, 0, 0, 0, 0, 0, 0, 0);
1531
+ clean(this.buffer);
1532
+ }
1533
+ };
1534
+ var _SHA256 = class extends SHA2_32B {
1535
+ constructor() {
1536
+ super(32, SHA256_IV);
1537
+ }
1538
+ };
1539
+ var sha256 = /* @__PURE__ */ createHasher(
1540
+ () => new _SHA256(),
1541
+ /* @__PURE__ */ oidNist(1)
1542
+ );
1543
+
1544
+ // packages/schema/src/hash.ts
1545
+ var encoder = new TextEncoder();
1546
+ function sha256Hex(input) {
1547
+ return toHex(sha256(encoder.encode(input)));
1548
+ }
1549
+ function md5Hex(input) {
1550
+ return toHex(md5(encoder.encode(input)));
1551
+ }
1552
+ function toHex(bytes) {
1553
+ let hex = "";
1554
+ for (const byte of bytes) hex += byte.toString(16).padStart(2, "0");
1555
+ return hex;
1556
+ }
1557
+
910
1558
  // packages/schema/src/identity.ts
911
- import { createHash } from "node:crypto";
912
1559
  var ID_LENGTH = 16;
913
1560
  function normalizeName(name) {
914
1561
  return name.normalize("NFC");
915
1562
  }
916
- function pathSegments(path9) {
1563
+ function pathSegments(path7) {
917
1564
  const segments = [];
918
- for (const raw of path9.replace(/\\/g, "/").split("/")) {
1565
+ for (const raw of path7.replace(/\\/g, "/").split("/")) {
919
1566
  const segment = normalizeName(raw);
920
1567
  if (segment === "" || segment === ".") continue;
921
1568
  if (segment === "..") {
@@ -929,18 +1576,18 @@ function pathSegments(path9) {
929
1576
  function canonicalPath(segments) {
930
1577
  return `/${segments.map(normalizeName).join("/")}`;
931
1578
  }
932
- function idFromCanonicalPath(path9, type = "File") {
933
- const canonical = path9.startsWith("/") ? path9 : `/${path9}`;
934
- return createHash("sha256").update(type + canonical, "utf8").digest("hex").slice(0, ID_LENGTH);
1579
+ function idFromCanonicalPath(path7, type = "File") {
1580
+ const canonical = path7.startsWith("/") ? path7 : `/${path7}`;
1581
+ return sha256Hex(type + canonical).slice(0, ID_LENGTH);
935
1582
  }
936
1583
  function nodeId(type, segments) {
937
1584
  return idFromCanonicalPath(canonicalPath(segments), type);
938
1585
  }
939
- function fileId(path9) {
940
- return nodeId("File", pathSegments(path9));
1586
+ function fileId(path7) {
1587
+ return nodeId("File", pathSegments(path7));
941
1588
  }
942
- function folderId(path9) {
943
- return nodeId("Folder", pathSegments(path9));
1589
+ function folderId(path7) {
1590
+ return nodeId("Folder", pathSegments(path7));
944
1591
  }
945
1592
 
946
1593
  // packages/schema/src/legacy.ts
@@ -996,15 +1643,15 @@ function normalizeLegacyCcJson(input) {
996
1643
  const metrics = {};
997
1644
  const built = [];
998
1645
  const ids = /* @__PURE__ */ new Map();
999
- const buildNode = (value, parentPath, path9, depth) => {
1646
+ const buildNode = (value, parentPath, path7, depth) => {
1000
1647
  if (depth > MAX_DEPTH) {
1001
- throw new CcJsonError(`profundidad m\xE1xima ${MAX_DEPTH} superada`, path9);
1648
+ throw new CcJsonError(`profundidad m\xE1xima ${MAX_DEPTH} superada`, path7);
1002
1649
  }
1003
- const raw = requireRecord(value, path9);
1004
- const name = readNodeName(raw.name, `${path9}.name`);
1650
+ const raw = requireRecord(value, path7);
1651
+ const name = readNodeName(raw.name, `${path7}.name`);
1005
1652
  const type = raw.type;
1006
1653
  if (type !== "File" && type !== "Folder") {
1007
- throw new CcJsonError('"type" debe ser "File" o "Folder"', `${path9}.type`);
1654
+ throw new CcJsonError('"type" debe ser "File" o "Folder"', `${path7}.type`);
1008
1655
  }
1009
1656
  const fullPath = [...parentPath, name];
1010
1657
  const id = nodeId(type, fullPath.slice(1));
@@ -1012,13 +1659,13 @@ function normalizeLegacyCcJson(input) {
1012
1659
  if (previous !== void 0) {
1013
1660
  throw new CcJsonError(
1014
1661
  `id duplicado "${id}" para "${previous}" y "${fullPath.join("/")}"`,
1015
- `${path9}.name`
1662
+ `${path7}.name`
1016
1663
  );
1017
1664
  }
1018
1665
  ids.set(id, fullPath.join("/"));
1019
1666
  built.push({ id, name, fullPath: fullPath.join("/") });
1020
1667
  const node = { id, name, type };
1021
- const attributes = readMetrics(raw.attributes, `${path9}.attributes`);
1668
+ const attributes = readMetrics(raw.attributes, `${path7}.attributes`);
1022
1669
  if (attributes) metrics[id] = attributes;
1023
1670
  const contentHash = readOptionalString(raw.checksum) ?? readOptionalString(raw.contentHash);
1024
1671
  if (contentHash) node.contentHash = contentHash;
@@ -1026,14 +1673,14 @@ function normalizeLegacyCcJson(input) {
1026
1673
  if (link) node.link = link;
1027
1674
  if (raw.children !== void 0) {
1028
1675
  if (!Array.isArray(raw.children)) {
1029
- throw new CcJsonError('"children" debe ser un array', `${path9}.children`);
1676
+ throw new CcJsonError('"children" debe ser un array', `${path7}.children`);
1030
1677
  }
1031
1678
  if (type === "File" && raw.children.length > 0) {
1032
- throw new CcJsonError("un fichero no puede tener hijos", `${path9}.children`);
1679
+ throw new CcJsonError("un fichero no puede tener hijos", `${path7}.children`);
1033
1680
  }
1034
1681
  if (type === "Folder" && raw.children.length > 0) {
1035
1682
  node.children = raw.children.map(
1036
- (child, index) => buildNode(child, fullPath, `${path9}.children[${index}]`, depth + 1)
1683
+ (child, index) => buildNode(child, fullPath, `${path7}.children[${index}]`, depth + 1)
1037
1684
  );
1038
1685
  }
1039
1686
  }
@@ -1050,15 +1697,15 @@ function normalizeLegacyCcJson(input) {
1050
1697
  throw new CcJsonError('"edges" debe ser un array', `${base}.edges`);
1051
1698
  }
1052
1699
  edgesRaw.forEach((edge, index) => {
1053
- const path9 = `${base}.edges[${index}]`;
1054
- const raw = requireRecord(edge, path9);
1055
- const fromName = requireString(raw.fromNodeName, `${path9}.fromNodeName`, "fromNodeName");
1056
- const toName = requireString(raw.toNodeName, `${path9}.toNodeName`, "toNodeName");
1700
+ const path7 = `${base}.edges[${index}]`;
1701
+ const raw = requireRecord(edge, path7);
1702
+ const fromName = requireString(raw.fromNodeName, `${path7}.fromNodeName`, "fromNodeName");
1703
+ const toName = requireString(raw.toNodeName, `${path7}.toNodeName`, "toNodeName");
1057
1704
  const normalized = {
1058
- fromId: resolveEndpoint(fromName, pathIndex, stripNames, `${path9}.fromNodeName`),
1059
- toId: resolveEndpoint(toName, pathIndex, stripNames, `${path9}.toNodeName`)
1705
+ fromId: resolveEndpoint(fromName, pathIndex, stripNames, `${path7}.fromNodeName`),
1706
+ toId: resolveEndpoint(toName, pathIndex, stripNames, `${path7}.toNodeName`)
1060
1707
  };
1061
- const attributes = readMetrics(raw.attributes, `${path9}.attributes`);
1708
+ const attributes = readMetrics(raw.attributes, `${path7}.attributes`);
1062
1709
  if (attributes) normalized.attributes = attributes;
1063
1710
  if (typeof raw.isCyclic === "boolean") normalized.isCyclic = raw.isCyclic;
1064
1711
  if (typeof raw.isPointingUpwards === "boolean") {
@@ -1128,10 +1775,10 @@ function buildPathIndex(built, multipleRoots) {
1128
1775
  for (const node of built) {
1129
1776
  const segments = node.fullPath.split("/");
1130
1777
  if (segments.length > 1) add(segments.slice(1).join("/"), node.id);
1131
- const relative = segments.slice(multipleRoots ? 2 : 1).join("/");
1132
- if (relative !== "") {
1133
- add(relative, node.id);
1134
- add(`root/${relative}`, node.id);
1778
+ const relative2 = segments.slice(multipleRoots ? 2 : 1).join("/");
1779
+ if (relative2 !== "") {
1780
+ add(relative2, node.id);
1781
+ add(`root/${relative2}`, node.id);
1135
1782
  } else if (segments.length === 1) {
1136
1783
  add("root", node.id);
1137
1784
  }
@@ -1143,10 +1790,10 @@ function buildPathIndex(built, multipleRoots) {
1143
1790
  }
1144
1791
  return index;
1145
1792
  }
1146
- function resolveEndpoint(endpoint, index, stripNames, path9) {
1793
+ function resolveEndpoint(endpoint, index, stripNames, path7) {
1147
1794
  const segments = canonicalSegments(endpoint);
1148
1795
  if (segments.length === 0) {
1149
- throw new CcJsonError("extremo de arista vac\xEDo", path9);
1796
+ throw new CcJsonError("extremo de arista vac\xEDo", path7);
1150
1797
  }
1151
1798
  const candidates = [segments.join("/")];
1152
1799
  if (segments.length > 1 && stripNames.has(segments[0])) {
@@ -1158,7 +1805,7 @@ function resolveEndpoint(endpoint, index, stripNames, path9) {
1158
1805
  }
1159
1806
  const bare = index.get(segments[segments.length - 1]);
1160
1807
  if (bare) return bare;
1161
- throw new CcJsonError(`no se pudo resolver el nodo "${endpoint}"`, path9);
1808
+ throw new CcJsonError(`no se pudo resolver el nodo "${endpoint}"`, path7);
1162
1809
  }
1163
1810
  function canonicalSegments(value) {
1164
1811
  const result = [];
@@ -1173,85 +1820,85 @@ function canonicalSegments(value) {
1173
1820
  }
1174
1821
  return result;
1175
1822
  }
1176
- function readNodeName(value, path9) {
1177
- const name = normalizeName(requireString(value, path9, "name"));
1823
+ function readNodeName(value, path7) {
1824
+ const name = normalizeName(requireString(value, path7, "name"));
1178
1825
  if (name === "." || name === "..") {
1179
- throw new CcJsonError('el nombre no puede ser "." ni ".."', path9);
1826
+ throw new CcJsonError('el nombre no puede ser "." ni ".."', path7);
1180
1827
  }
1181
1828
  if (name.includes("/")) {
1182
- throw new CcJsonError('el nombre no puede contener "/"', path9);
1829
+ throw new CcJsonError('el nombre no puede contener "/"', path7);
1183
1830
  }
1184
1831
  return name;
1185
1832
  }
1186
1833
  function readOptionalString(value) {
1187
1834
  return typeof value === "string" && value.length > 0 ? value : void 0;
1188
1835
  }
1189
- function readMetrics(value, path9) {
1836
+ function readMetrics(value, path7) {
1190
1837
  if (value === void 0) return void 0;
1191
- const raw = requireRecord(value, path9);
1838
+ const raw = requireRecord(value, path7);
1192
1839
  const out = {};
1193
1840
  for (const [metric, metricValue] of Object.entries(raw)) {
1194
1841
  if (typeof metricValue !== "number" || !Number.isFinite(metricValue)) {
1195
1842
  throw new CcJsonError(
1196
1843
  `la m\xE9trica "${metric}" debe ser un n\xFAmero finito`,
1197
- `${path9}["${metric}"]`
1844
+ `${path7}["${metric}"]`
1198
1845
  );
1199
1846
  }
1200
1847
  out[metric] = metricValue;
1201
1848
  }
1202
1849
  return Object.keys(out).length > 0 ? out : void 0;
1203
1850
  }
1204
- function readAttributeTypes(value, path9) {
1851
+ function readAttributeTypes(value, path7) {
1205
1852
  if (value === void 0) return {};
1206
- const raw = requireRecord(value, path9);
1853
+ const raw = requireRecord(value, path7);
1207
1854
  if ("nodes" in raw || "edges" in raw) {
1208
- const nodes2 = raw.nodes === void 0 ? void 0 : readTypeMap(raw.nodes, `${path9}.nodes`);
1209
- const edges = raw.edges === void 0 ? void 0 : readTypeMap(raw.edges, `${path9}.edges`);
1855
+ const nodes2 = raw.nodes === void 0 ? void 0 : readTypeMap(raw.nodes, `${path7}.nodes`);
1856
+ const edges = raw.edges === void 0 ? void 0 : readTypeMap(raw.edges, `${path7}.edges`);
1210
1857
  return {
1211
1858
  ...nodes2 && Object.keys(nodes2).length > 0 ? { nodes: nodes2 } : {},
1212
1859
  ...edges && Object.keys(edges).length > 0 ? { edges } : {}
1213
1860
  };
1214
1861
  }
1215
- const nodes = readTypeMap(raw, path9);
1862
+ const nodes = readTypeMap(raw, path7);
1216
1863
  return Object.keys(nodes).length > 0 ? { nodes } : {};
1217
1864
  }
1218
- function readTypeMap(value, path9) {
1219
- const raw = requireRecord(value, path9);
1865
+ function readTypeMap(value, path7) {
1866
+ const raw = requireRecord(value, path7);
1220
1867
  const out = {};
1221
1868
  for (const [metric, type] of Object.entries(raw)) {
1222
1869
  if (type !== "absolute" && type !== "relative") {
1223
1870
  throw new CcJsonError(
1224
1871
  `el tipo de "${metric}" debe ser "absolute" o "relative"`,
1225
- `${path9}["${metric}"]`
1872
+ `${path7}["${metric}"]`
1226
1873
  );
1227
1874
  }
1228
1875
  out[metric] = type;
1229
1876
  }
1230
1877
  return out;
1231
1878
  }
1232
- function readDescriptors(value, path9) {
1879
+ function readDescriptors(value, path7) {
1233
1880
  if (value === void 0) return {};
1234
- const raw = requireRecord(value, path9);
1881
+ const raw = requireRecord(value, path7);
1235
1882
  if ("nodes" in raw || "edges" in raw) {
1236
- const nodes = raw.nodes === void 0 ? void 0 : readDescriptorMap(raw.nodes, `${path9}.nodes`);
1237
- const edges = raw.edges === void 0 ? void 0 : readDescriptorMap(raw.edges, `${path9}.edges`);
1883
+ const nodes = raw.nodes === void 0 ? void 0 : readDescriptorMap(raw.nodes, `${path7}.nodes`);
1884
+ const edges = raw.edges === void 0 ? void 0 : readDescriptorMap(raw.edges, `${path7}.edges`);
1238
1885
  return {
1239
1886
  ...nodes && Object.keys(nodes).length > 0 ? { nodes } : {},
1240
1887
  ...edges && Object.keys(edges).length > 0 ? { edges } : {}
1241
1888
  };
1242
1889
  }
1243
- const flat = readDescriptorMap(raw, path9);
1890
+ const flat = readDescriptorMap(raw, path7);
1244
1891
  return Object.keys(flat).length > 0 ? { flat } : {};
1245
1892
  }
1246
- function readDescriptorMap(value, path9) {
1247
- const raw = requireRecord(value, path9);
1893
+ function readDescriptorMap(value, path7) {
1894
+ const raw = requireRecord(value, path7);
1248
1895
  const out = {};
1249
1896
  for (const [metric, descriptor] of Object.entries(raw)) {
1250
- const record = requireRecord(descriptor, `${path9}["${metric}"]`);
1897
+ const record = requireRecord(descriptor, `${path7}["${metric}"]`);
1251
1898
  if (record.direction !== void 0 && typeof record.direction !== "number") {
1252
1899
  throw new CcJsonError(
1253
1900
  `"direction" de "${metric}" debe ser un n\xFAmero`,
1254
- `${path9}["${metric}"].direction`
1901
+ `${path7}["${metric}"].direction`
1255
1902
  );
1256
1903
  }
1257
1904
  out[metric] = record;
@@ -1276,26 +1923,26 @@ function parseCcJson2(input) {
1276
1923
  const roots = Array.isArray(filesRaw) ? filesRaw : [filesRaw];
1277
1924
  if (roots.length === 0) throw new CcJsonError("el \xE1rbol est\xE1 vac\xEDo", "$.files");
1278
1925
  const ids = /* @__PURE__ */ new Set();
1279
- const validateNode = (value, path9, depth) => {
1280
- if (depth > MAX_DEPTH2) throw new CcJsonError(`profundidad m\xE1xima ${MAX_DEPTH2} superada`, path9);
1281
- const raw = requireRecord(value, path9);
1282
- const id = requireString(raw.id, `${path9}.id`, "id");
1283
- if (ids.has(id)) throw new CcJsonError(`id duplicado "${id}"`, `${path9}.id`);
1926
+ const validateNode = (value, path7, depth) => {
1927
+ if (depth > MAX_DEPTH2) throw new CcJsonError(`profundidad m\xE1xima ${MAX_DEPTH2} superada`, path7);
1928
+ const raw = requireRecord(value, path7);
1929
+ const id = requireString(raw.id, `${path7}.id`, "id");
1930
+ if (ids.has(id)) throw new CcJsonError(`id duplicado "${id}"`, `${path7}.id`);
1284
1931
  ids.add(id);
1285
- const name = requireString(raw.name, `${path9}.name`, "name");
1932
+ const name = requireString(raw.name, `${path7}.name`, "name");
1286
1933
  const type = raw.type;
1287
1934
  if (type !== "File" && type !== "Folder") {
1288
- throw new CcJsonError('"type" debe ser "File" o "Folder"', `${path9}.type`);
1935
+ throw new CcJsonError('"type" debe ser "File" o "Folder"', `${path7}.type`);
1289
1936
  }
1290
1937
  const node = { id, name, type };
1291
1938
  if (typeof raw.contentHash === "string") node.contentHash = raw.contentHash;
1292
1939
  if (typeof raw.link === "string") node.link = raw.link;
1293
1940
  if (raw.children !== void 0) {
1294
1941
  if (!Array.isArray(raw.children)) {
1295
- throw new CcJsonError('"children" debe ser un array', `${path9}.children`);
1942
+ throw new CcJsonError('"children" debe ser un array', `${path7}.children`);
1296
1943
  }
1297
1944
  node.children = raw.children.map(
1298
- (child, index) => validateNode(child, `${path9}.children[${index}]`, depth + 1)
1945
+ (child, index) => validateNode(child, `${path7}.children[${index}]`, depth + 1)
1299
1946
  );
1300
1947
  }
1301
1948
  return node;
@@ -1326,10 +1973,10 @@ function parseCcJson2(input) {
1326
1973
  throw new CcJsonError('"edges" debe ser un array', "$.lenses.dependency.edges");
1327
1974
  }
1328
1975
  dependency.edges.forEach((edge, index) => {
1329
- const path9 = `$.lenses.dependency.edges[${index}]`;
1330
- const raw = requireRecord(edge, path9);
1331
- requireString(raw.fromId, `${path9}.fromId`, "fromId");
1332
- requireString(raw.toId, `${path9}.toId`, "toId");
1976
+ const path7 = `$.lenses.dependency.edges[${index}]`;
1977
+ const raw = requireRecord(edge, path7);
1978
+ requireString(raw.fromId, `${path7}.fromId`, "fromId");
1979
+ requireString(raw.toId, `${path7}.toId`, "toId");
1333
1980
  });
1334
1981
  }
1335
1982
  }
@@ -1418,9 +2065,9 @@ function topByMetric(cc, metric, limit = 10) {
1418
2065
  const ranked = [];
1419
2066
  for (const [id, values] of Object.entries(attributes)) {
1420
2067
  const value = values[metric];
1421
- const path9 = paths.get(id);
1422
- if (value === void 0 || path9 === void 0) continue;
1423
- ranked.push({ id, path: path9, value });
2068
+ const path7 = paths.get(id);
2069
+ if (value === void 0 || path7 === void 0) continue;
2070
+ ranked.push({ id, path: path7, value });
1424
2071
  }
1425
2072
  ranked.sort((a, b) => b.value - a.value || a.path.localeCompare(b.path));
1426
2073
  return ranked.slice(0, limit);
@@ -1430,155 +2077,6 @@ function defaultMetric(stats) {
1430
2077
  return stats.metricCoverage[0]?.name;
1431
2078
  }
1432
2079
 
1433
- // packages/parsers/src/git.ts
1434
- import { spawnSync } from "node:child_process";
1435
- import fs2 from "node:fs";
1436
- import path2 from "node:path";
1437
- var COMMIT_PREFIX = "__CODARCH_COMMIT__";
1438
- var DEFAULT_MAX_BUFFER = 128 * 1024 * 1024;
1439
- var EMPTY = {
1440
- metrics: /* @__PURE__ */ new Map(),
1441
- commits: 0,
1442
- authors: 0,
1443
- shallow: false,
1444
- enabled: false,
1445
- warnings: []
1446
- };
1447
- function collectGitChurn(rootInput, files, options = {}) {
1448
- if (options.enabled === false || files.length === 0) {
1449
- return { ...EMPTY, metrics: /* @__PURE__ */ new Map() };
1450
- }
1451
- const root = realPath(rootInput);
1452
- const maxBuffer = options.maxBuffer ?? DEFAULT_MAX_BUFFER;
1453
- const warnings = [];
1454
- const topLevel = runGit(root, ["rev-parse", "--show-toplevel"], maxBuffer);
1455
- if (!topLevel.ok) {
1456
- return {
1457
- ...EMPTY,
1458
- metrics: /* @__PURE__ */ new Map(),
1459
- warnings: [`no hay historial git utilizable: ${topLevel.error}`]
1460
- };
1461
- }
1462
- const repoRoot = realPath(topLevel.stdout.trim());
1463
- const shallowResult = runGit(
1464
- repoRoot,
1465
- ["rev-parse", "--is-shallow-repository"],
1466
- maxBuffer
1467
- );
1468
- const shallow = shallowResult.ok && shallowResult.stdout.trim().toLowerCase() === "true";
1469
- if (shallow) {
1470
- warnings.push(
1471
- "el repositorio es shallow: el churn puede estar incompleto"
1472
- );
1473
- }
1474
- const log = runGit(
1475
- repoRoot,
1476
- [
1477
- "-c",
1478
- "core.quotePath=false",
1479
- "log",
1480
- "--no-merges",
1481
- "--numstat",
1482
- `--format=${COMMIT_PREFIX}%H%x09%ae`
1483
- ],
1484
- maxBuffer
1485
- );
1486
- if (!log.ok) {
1487
- return {
1488
- ...EMPTY,
1489
- shallow,
1490
- warnings: [...warnings, `no se pudo leer git log: ${log.error}`]
1491
- };
1492
- }
1493
- const wanted = new Set(files);
1494
- const commitsByFile = /* @__PURE__ */ new Map();
1495
- const authorsByFile = /* @__PURE__ */ new Map();
1496
- const authors = /* @__PURE__ */ new Set();
1497
- let commits = 0;
1498
- let currentAuthor = "";
1499
- let touched = /* @__PURE__ */ new Set();
1500
- const flushCommit = () => {
1501
- if (!currentAuthor || touched.size === 0) return;
1502
- commits++;
1503
- authors.add(currentAuthor);
1504
- for (const file of touched) {
1505
- commitsByFile.set(file, (commitsByFile.get(file) ?? 0) + 1);
1506
- let fileAuthors = authorsByFile.get(file);
1507
- if (!fileAuthors) {
1508
- fileAuthors = /* @__PURE__ */ new Set();
1509
- authorsByFile.set(file, fileAuthors);
1510
- }
1511
- fileAuthors.add(currentAuthor);
1512
- }
1513
- touched = /* @__PURE__ */ new Set();
1514
- currentAuthor = "";
1515
- };
1516
- for (const rawLine of log.stdout.split(/\r?\n/)) {
1517
- if (rawLine.startsWith(COMMIT_PREFIX)) {
1518
- flushCommit();
1519
- const fields2 = rawLine.slice(COMMIT_PREFIX.length).split(" ");
1520
- const author = fields2[1]?.trim() ?? "";
1521
- currentAuthor = author || "(sin autor)";
1522
- continue;
1523
- }
1524
- if (!currentAuthor || rawLine === "") continue;
1525
- const fields = rawLine.split(" ");
1526
- if (fields.length < 3) continue;
1527
- const gitPath = fields.slice(2).join(" ");
1528
- if (!gitPath) continue;
1529
- const relative = path2.relative(root, path2.resolve(repoRoot, gitPath));
1530
- if (relative === "" || relative === ".." || relative.startsWith(`..${path2.sep}`) || path2.isAbsolute(relative)) {
1531
- continue;
1532
- }
1533
- const normalized = relative.split(path2.sep).join("/");
1534
- if (!wanted.has(normalized)) continue;
1535
- touched.add(normalized);
1536
- }
1537
- flushCommit();
1538
- const metrics = /* @__PURE__ */ new Map();
1539
- for (const file of files) {
1540
- metrics.set(file, {
1541
- commits: commitsByFile.get(file) ?? 0,
1542
- authors: authorsByFile.get(file)?.size ?? 0
1543
- });
1544
- }
1545
- return {
1546
- metrics,
1547
- commits,
1548
- authors: authors.size,
1549
- shallow,
1550
- enabled: true,
1551
- warnings
1552
- };
1553
- }
1554
- function realPath(input) {
1555
- const resolved = path2.resolve(input);
1556
- try {
1557
- return fs2.realpathSync.native(resolved);
1558
- } catch {
1559
- return resolved;
1560
- }
1561
- }
1562
- function runGit(cwd, args, maxBuffer) {
1563
- const result = spawnSync("git", args, {
1564
- cwd,
1565
- encoding: "utf8",
1566
- maxBuffer,
1567
- windowsHide: true
1568
- });
1569
- if (result.error) {
1570
- return { ok: false, stdout: "", error: result.error.message };
1571
- }
1572
- if (result.status !== 0) {
1573
- return {
1574
- ok: false,
1575
- stdout: result.stdout ?? "",
1576
- error: (result.stderr ?? "").trim() || `git exited with ${result.status}`
1577
- };
1578
- }
1579
- return { ok: true, stdout: result.stdout ?? "", error: "" };
1580
- }
1581
-
1582
2080
  // packages/parsers/src/migration.ts
1583
2081
  var EXTENSION_LANGUAGE = {
1584
2082
  ".ts": "ts",
@@ -1722,9 +2220,9 @@ function jsIsBridge(source, specifiers) {
1722
2220
  return specifiers.some((specifier) => ffiKindForSpecifier(specifier) !== null) || JS_FFI.test(source);
1723
2221
  }
1724
2222
  function ffiKindForSpecifier(specifier) {
1725
- const clean = specifier.split(/[?#]/, 1)[0]?.toLowerCase() ?? "";
1726
- if (clean.endsWith(".wasm")) return "wasm";
1727
- if (clean.endsWith(".node")) return "native";
2223
+ const clean2 = specifier.split(/[?#]/, 1)[0]?.toLowerCase() ?? "";
2224
+ if (clean2.endsWith(".wasm")) return "wasm";
2225
+ if (clean2.endsWith(".node")) return "native";
1728
2226
  return null;
1729
2227
  }
1730
2228
  function buildMigration(files, links) {
@@ -1814,7 +2312,6 @@ function compareStrings2(left, right) {
1814
2312
  }
1815
2313
 
1816
2314
  // packages/parsers/src/snapshot.ts
1817
- import { createHash as createHash2 } from "node:crypto";
1818
2315
  var METRIC_DESCRIPTORS = {
1819
2316
  loc: {
1820
2317
  title: "L\xEDneas",
@@ -1989,8 +2486,8 @@ function buildSnapshot(input) {
1989
2486
  isCyclic: edge.isCyclic
1990
2487
  }));
1991
2488
  const dependencyNodes = {};
1992
- for (const [path9, metrics] of input.graph.metrics) {
1993
- dependencyNodes[fileId(path9)] = { level: metrics.dependencyLevel };
2489
+ for (const [path7, metrics] of input.graph.metrics) {
2490
+ dependencyNodes[fileId(path7)] = { level: metrics.dependencyLevel };
1994
2491
  }
1995
2492
  const migrationLens = input.migration ? migrationLensFrom(input.migration) : void 0;
1996
2493
  const lenses = {
@@ -2039,7 +2536,7 @@ function migrationLensFrom(migration) {
2039
2536
  };
2040
2537
  }
2041
2538
  function checksumFor(files, lenses) {
2042
- return createHash2("md5").update(stableStringify({ files, lenses }), "utf8").digest("hex");
2539
+ return md5Hex(stableStringify({ files, lenses }));
2043
2540
  }
2044
2541
  function stableStringify(value) {
2045
2542
  if (value === null || value === void 0) return "null";
@@ -2076,9 +2573,9 @@ function compareStrings3(left, right) {
2076
2573
  }
2077
2574
 
2078
2575
  // packages/parsers/src/walk.ts
2079
- import fs3 from "node:fs";
2080
- import path3 from "node:path";
2081
2576
  import ignore from "ignore";
2577
+
2578
+ // packages/parsers/src/extensions.ts
2082
2579
  var DEFAULT_EXTENSIONS = [
2083
2580
  ".ts",
2084
2581
  ".tsx",
@@ -2089,29 +2586,26 @@ var DEFAULT_EXTENSIONS = [
2089
2586
  ".mjs",
2090
2587
  ".cjs"
2091
2588
  ];
2589
+
2590
+ // packages/parsers/src/walk.ts
2092
2591
  var ALWAYS_IGNORED = [".git", "node_modules", ".codarch"];
2093
- function walkSourceFiles(rootInput, options = {}) {
2094
- const root = path3.resolve(rootInput);
2095
- const stat = fs3.statSync(root);
2096
- if (!stat.isDirectory()) {
2097
- throw new Error(`no es un directorio: ${rootInput}`);
2098
- }
2592
+ function walkSourceFiles(fileSystem, options = {}) {
2099
2593
  const extensions = options.extensions ?? DEFAULT_EXTENSIONS;
2100
2594
  const warnings = [];
2101
2595
  const files = [];
2102
2596
  const rootMatcher = ignore().add(ALWAYS_IGNORED);
2103
2597
  for (const pattern of options.ignore ?? []) rootMatcher.add(pattern);
2104
- const rootGitignore = readGitignore(root, "", warnings);
2598
+ const rootGitignore = readGitignore(fileSystem, "/", "", warnings);
2105
2599
  if (rootGitignore) rootMatcher.add(rootGitignore);
2106
2600
  const initialLayers = [{ base: "", matcher: rootMatcher }];
2107
2601
  const stack = [
2108
- { absolutePath: root, relativePath: "", layers: initialLayers }
2602
+ { absolutePath: "/", relativePath: "", layers: initialLayers }
2109
2603
  ];
2110
2604
  while (stack.length > 0) {
2111
2605
  const directory = stack.pop();
2112
2606
  let entries;
2113
2607
  try {
2114
- entries = fs3.readdirSync(directory.absolutePath, { withFileTypes: true });
2608
+ entries = fileSystem.readDirectory(directory.absolutePath);
2115
2609
  } catch (error) {
2116
2610
  warnings.push(
2117
2611
  `no se pudo leer ${directory.relativePath || "."}: ${error.message}`
@@ -2122,15 +2616,14 @@ function walkSourceFiles(rootInput, options = {}) {
2122
2616
  (left, right) => left.name < right.name ? -1 : left.name > right.name ? 1 : 0
2123
2617
  );
2124
2618
  for (const entry of entries) {
2125
- if (entry.isSymbolicLink()) continue;
2126
2619
  const relativePath2 = directory.relativePath ? `${directory.relativePath}/${entry.name}` : entry.name;
2127
- const absolutePath = path3.join(directory.absolutePath, entry.name);
2128
- if (entry.isDirectory()) {
2620
+ const absolutePath = directory.absolutePath === "/" ? `/${entry.name}` : `${directory.absolutePath}/${entry.name}`;
2621
+ if (entry.isDirectory) {
2129
2622
  if (isIgnored(directory.layers, relativePath2, true)) continue;
2130
- const nested = readGitignore(absolutePath, relativePath2, warnings);
2623
+ const nested = readGitignore(fileSystem, absolutePath, relativePath2, warnings);
2131
2624
  const layers = nested ? [...directory.layers, { base: relativePath2, matcher: ignore().add(nested) }] : directory.layers;
2132
2625
  stack.push({ absolutePath, relativePath: relativePath2, layers });
2133
- } else if (entry.isFile()) {
2626
+ } else if (entry.isFile) {
2134
2627
  if (isIgnored(directory.layers, relativePath2, false)) continue;
2135
2628
  if (!matchesExtension(entry.name, extensions)) continue;
2136
2629
  files.push({ absolutePath, relativePath: relativePath2 });
@@ -2143,16 +2636,14 @@ function walkSourceFiles(rootInput, options = {}) {
2143
2636
  warnings.sort();
2144
2637
  return { files, warnings };
2145
2638
  }
2146
- function readGitignore(directory, relativeBase, warnings) {
2147
- const file = path3.join(directory, ".gitignore");
2639
+ function readGitignore(fileSystem, absoluteDirectory, relativeBase, warnings) {
2640
+ const file = absoluteDirectory === "/" ? "/.gitignore" : `${absoluteDirectory}/.gitignore`;
2148
2641
  try {
2149
- return fs3.readFileSync(file, "utf8");
2642
+ return fileSystem.tryReadFile(file);
2150
2643
  } catch (error) {
2151
- if (error.code !== "ENOENT") {
2152
- warnings.push(
2153
- `no se pudo leer ${relativeBase ? `${relativeBase}/` : ""}.gitignore: ${error.message}`
2154
- );
2155
- }
2644
+ warnings.push(
2645
+ `no se pudo leer ${relativeBase ? `${relativeBase}/` : ""}.gitignore: ${error.message}`
2646
+ );
2156
2647
  return null;
2157
2648
  }
2158
2649
  }
@@ -2173,19 +2664,20 @@ function matchesExtension(name, extensions) {
2173
2664
  return extensions.some((extension) => lower.endsWith(extension));
2174
2665
  }
2175
2666
 
2176
- // packages/parsers/src/analyze.ts
2177
- function analyzeRepository(rootInput, options = {}) {
2667
+ // packages/parsers/src/analyze-core.ts
2668
+ function analyzeFiles(options) {
2178
2669
  const started = Date.now();
2179
- const root = path4.resolve(rootInput);
2180
- const projectName = options.projectName ?? readProjectName(root);
2181
- const walked = walkSourceFiles(root, {
2670
+ const fileSystem = options.fileSystem;
2671
+ const root = options.root ?? "/";
2672
+ const projectName = options.projectName ?? readProjectName(fileSystem, root, options.fallbackName);
2673
+ const walked = walkSourceFiles(fileSystem, {
2182
2674
  ignore: options.ignore,
2183
2675
  extensions: options.extensions ?? MIGRATION_EXTENSIONS
2184
2676
  });
2185
2677
  const warnings = [...walked.warnings];
2186
- const resolver = new ImportResolver(root, walked.files);
2678
+ const resolver = new ImportResolver(root, walked.files, fileSystem);
2187
2679
  warnings.push(...resolver.warnings);
2188
- const parser = new TypeScriptParser();
2680
+ const parser = options.parser;
2189
2681
  const parsedFiles = [];
2190
2682
  const migrationFiles = [];
2191
2683
  const migrationLinks = [];
@@ -2215,7 +2707,7 @@ function analyzeRepository(rootInput, options = {}) {
2215
2707
  }
2216
2708
  let source;
2217
2709
  try {
2218
- source = fs4.readFileSync(file.absolutePath, "utf8");
2710
+ source = fileSystem.readFile(file.absolutePath);
2219
2711
  } catch (error) {
2220
2712
  warnings.push(
2221
2713
  `no se pudo leer ${file.relativePath}: ${error.message}`
@@ -2311,18 +2803,15 @@ function analyzeRepository(rootInput, options = {}) {
2311
2803
  )
2312
2804
  });
2313
2805
  }
2314
- const git = collectGitChurn(
2315
- root,
2316
- parsedFiles.map((file) => file.relativePath),
2317
- { enabled: options.git !== false }
2318
- );
2319
- warnings.push(...git.warnings);
2320
- if (git.enabled) {
2806
+ let churnResult = null;
2807
+ if (options.churn) {
2808
+ churnResult = options.churn(parsedFiles.map((file) => file.relativePath));
2809
+ warnings.push(...churnResult.warnings);
2321
2810
  for (const file of parsedFiles) {
2322
- const churn = git.metrics.get(file.relativePath);
2323
- if (!churn) continue;
2324
- file.metrics.number_of_commits = churn.commits;
2325
- file.metrics.number_of_authors = churn.authors;
2811
+ const metrics = churnResult.metrics.get(file.relativePath);
2812
+ if (!metrics) continue;
2813
+ file.metrics.number_of_commits = metrics.commits;
2814
+ file.metrics.number_of_authors = metrics.authors;
2326
2815
  }
2327
2816
  }
2328
2817
  const graph = buildGraph(
@@ -2382,19 +2871,14 @@ function analyzeRepository(rootInput, options = {}) {
2382
2871
  builtinImports,
2383
2872
  typeOnlyImports,
2384
2873
  unresolvedImports: unresolved.length,
2385
- git: {
2386
- enabled: git.enabled,
2387
- commits: git.commits,
2388
- authors: git.authors,
2389
- shallow: git.shallow
2390
- },
2391
2874
  migration: migration.summary,
2875
+ churn: churnResult,
2392
2876
  durationMs: Date.now() - started
2393
2877
  };
2394
2878
  }
2395
- function readProjectName(root) {
2879
+ function readProjectName(fileSystem, root, fallbackName) {
2396
2880
  try {
2397
- const text = fs4.readFileSync(path4.join(root, "package.json"), "utf8");
2881
+ const text = fileSystem.readFile(join(root, "package.json"));
2398
2882
  const parsed = JSON.parse(text.replace(/^\uFEFF/, ""));
2399
2883
  if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed) && typeof parsed.name === "string") {
2400
2884
  const name = parsed.name.trim();
@@ -2402,7 +2886,232 @@ function readProjectName(root) {
2402
2886
  }
2403
2887
  } catch {
2404
2888
  }
2405
- return path4.basename(root) || "Proyecto";
2889
+ return fallbackName ?? basename(root) ?? "Proyecto";
2890
+ }
2891
+
2892
+ // packages/parsers/src/git.ts
2893
+ import { spawnSync } from "node:child_process";
2894
+ import fs from "node:fs";
2895
+ import path from "node:path";
2896
+ var COMMIT_PREFIX = "__CODARCH_COMMIT__";
2897
+ var DEFAULT_MAX_BUFFER = 128 * 1024 * 1024;
2898
+ var EMPTY = {
2899
+ metrics: /* @__PURE__ */ new Map(),
2900
+ commits: 0,
2901
+ authors: 0,
2902
+ shallow: false,
2903
+ enabled: false,
2904
+ warnings: []
2905
+ };
2906
+ function collectGitChurn(rootInput, files, options = {}) {
2907
+ if (options.enabled === false || files.length === 0) {
2908
+ return { ...EMPTY, metrics: /* @__PURE__ */ new Map() };
2909
+ }
2910
+ const root = realPath(rootInput);
2911
+ const maxBuffer = options.maxBuffer ?? DEFAULT_MAX_BUFFER;
2912
+ const warnings = [];
2913
+ const topLevel = runGit(root, ["rev-parse", "--show-toplevel"], maxBuffer);
2914
+ if (!topLevel.ok) {
2915
+ return {
2916
+ ...EMPTY,
2917
+ metrics: /* @__PURE__ */ new Map(),
2918
+ warnings: [`no hay historial git utilizable: ${topLevel.error}`]
2919
+ };
2920
+ }
2921
+ const repoRoot = realPath(topLevel.stdout.trim());
2922
+ const shallowResult = runGit(
2923
+ repoRoot,
2924
+ ["rev-parse", "--is-shallow-repository"],
2925
+ maxBuffer
2926
+ );
2927
+ const shallow = shallowResult.ok && shallowResult.stdout.trim().toLowerCase() === "true";
2928
+ if (shallow) {
2929
+ warnings.push(
2930
+ "el repositorio es shallow: el churn puede estar incompleto"
2931
+ );
2932
+ }
2933
+ const log = runGit(
2934
+ repoRoot,
2935
+ [
2936
+ "-c",
2937
+ "core.quotePath=false",
2938
+ "log",
2939
+ "--no-merges",
2940
+ "--numstat",
2941
+ `--format=${COMMIT_PREFIX}%H%x09%ae`
2942
+ ],
2943
+ maxBuffer
2944
+ );
2945
+ if (!log.ok) {
2946
+ return {
2947
+ ...EMPTY,
2948
+ shallow,
2949
+ warnings: [...warnings, `no se pudo leer git log: ${log.error}`]
2950
+ };
2951
+ }
2952
+ const wanted = new Set(files);
2953
+ const commitsByFile = /* @__PURE__ */ new Map();
2954
+ const authorsByFile = /* @__PURE__ */ new Map();
2955
+ const authors = /* @__PURE__ */ new Set();
2956
+ let commits = 0;
2957
+ let currentAuthor = "";
2958
+ let touched = /* @__PURE__ */ new Set();
2959
+ const flushCommit = () => {
2960
+ if (!currentAuthor || touched.size === 0) return;
2961
+ commits++;
2962
+ authors.add(currentAuthor);
2963
+ for (const file of touched) {
2964
+ commitsByFile.set(file, (commitsByFile.get(file) ?? 0) + 1);
2965
+ let fileAuthors = authorsByFile.get(file);
2966
+ if (!fileAuthors) {
2967
+ fileAuthors = /* @__PURE__ */ new Set();
2968
+ authorsByFile.set(file, fileAuthors);
2969
+ }
2970
+ fileAuthors.add(currentAuthor);
2971
+ }
2972
+ touched = /* @__PURE__ */ new Set();
2973
+ currentAuthor = "";
2974
+ };
2975
+ for (const rawLine of log.stdout.split(/\r?\n/)) {
2976
+ if (rawLine.startsWith(COMMIT_PREFIX)) {
2977
+ flushCommit();
2978
+ const fields2 = rawLine.slice(COMMIT_PREFIX.length).split(" ");
2979
+ const author = fields2[1]?.trim() ?? "";
2980
+ currentAuthor = author || "(sin autor)";
2981
+ continue;
2982
+ }
2983
+ if (!currentAuthor || rawLine === "") continue;
2984
+ const fields = rawLine.split(" ");
2985
+ if (fields.length < 3) continue;
2986
+ const gitPath = fields.slice(2).join(" ");
2987
+ if (!gitPath) continue;
2988
+ const relative2 = path.relative(root, path.resolve(repoRoot, gitPath));
2989
+ if (relative2 === "" || relative2 === ".." || relative2.startsWith(`..${path.sep}`) || path.isAbsolute(relative2)) {
2990
+ continue;
2991
+ }
2992
+ const normalized = relative2.split(path.sep).join("/");
2993
+ if (!wanted.has(normalized)) continue;
2994
+ touched.add(normalized);
2995
+ }
2996
+ flushCommit();
2997
+ const metrics = /* @__PURE__ */ new Map();
2998
+ for (const file of files) {
2999
+ metrics.set(file, {
3000
+ commits: commitsByFile.get(file) ?? 0,
3001
+ authors: authorsByFile.get(file)?.size ?? 0
3002
+ });
3003
+ }
3004
+ return {
3005
+ metrics,
3006
+ commits,
3007
+ authors: authors.size,
3008
+ shallow,
3009
+ enabled: true,
3010
+ warnings
3011
+ };
3012
+ }
3013
+ function realPath(input) {
3014
+ const resolved = path.resolve(input);
3015
+ try {
3016
+ return fs.realpathSync.native(resolved);
3017
+ } catch {
3018
+ return resolved;
3019
+ }
3020
+ }
3021
+ function runGit(cwd, args, maxBuffer) {
3022
+ const result = spawnSync("git", args, {
3023
+ cwd,
3024
+ encoding: "utf8",
3025
+ maxBuffer,
3026
+ windowsHide: true
3027
+ });
3028
+ if (result.error) {
3029
+ return { ok: false, stdout: "", error: result.error.message };
3030
+ }
3031
+ if (result.status !== 0) {
3032
+ return {
3033
+ ok: false,
3034
+ stdout: result.stdout ?? "",
3035
+ error: (result.stderr ?? "").trim() || `git exited with ${result.status}`
3036
+ };
3037
+ }
3038
+ return { ok: true, stdout: result.stdout ?? "", error: "" };
3039
+ }
3040
+
3041
+ // packages/parsers/src/node-fs.ts
3042
+ import fs2 from "node:fs";
3043
+ import path2 from "node:path";
3044
+ var NodeFileSystem = class {
3045
+ caseSensitive = process.platform !== "win32";
3046
+ root;
3047
+ constructor(root) {
3048
+ this.root = path2.resolve(root);
3049
+ }
3050
+ readFile(target) {
3051
+ return fs2.readFileSync(this.native(target), "utf8");
3052
+ }
3053
+ tryReadFile(target) {
3054
+ try {
3055
+ return fs2.readFileSync(this.native(target), "utf8");
3056
+ } catch (error) {
3057
+ if (error.code !== "ENOENT") {
3058
+ throw error;
3059
+ }
3060
+ return null;
3061
+ }
3062
+ }
3063
+ readDirectory(target) {
3064
+ const entries = fs2.readdirSync(this.native(target), { withFileTypes: true });
3065
+ return entries.map((entry) => ({
3066
+ name: entry.name,
3067
+ isDirectory: entry.isDirectory(),
3068
+ isFile: entry.isFile()
3069
+ }));
3070
+ }
3071
+ exists(target) {
3072
+ return fs2.existsSync(this.native(target));
3073
+ }
3074
+ /** Ruta real en disco de una ruta virtual. */
3075
+ native(target) {
3076
+ const relative2 = target.replace(/^\/+/, "");
3077
+ return relative2 === "" ? this.root : path2.join(this.root, relative2);
3078
+ }
3079
+ };
3080
+
3081
+ // packages/parsers/src/analyze.ts
3082
+ function analyzeRepository(rootInput, options = {}) {
3083
+ const fileSystem = options.fileSystem ?? new NodeFileSystem(rootInput);
3084
+ const useGit = options.git !== false;
3085
+ const result = analyzeFiles({
3086
+ fileSystem,
3087
+ root: options.root ?? "/",
3088
+ projectName: options.projectName,
3089
+ fallbackName: basename2(rootInput),
3090
+ ignore: options.ignore,
3091
+ extensions: options.extensions,
3092
+ parser: new TypeScriptParser(),
3093
+ churn: (relativePaths) => {
3094
+ const git = collectGitChurn(rootInput, relativePaths, { enabled: useGit });
3095
+ return {
3096
+ metrics: git.metrics,
3097
+ warnings: git.warnings,
3098
+ enabled: git.enabled,
3099
+ commits: git.commits,
3100
+ authors: git.authors,
3101
+ shallow: git.shallow
3102
+ };
3103
+ }
3104
+ });
3105
+ const churn = result.churn;
3106
+ return {
3107
+ ...result,
3108
+ git: {
3109
+ enabled: churn?.enabled ?? false,
3110
+ commits: churn?.commits ?? 0,
3111
+ authors: churn?.authors ?? 0,
3112
+ shallow: churn?.shallow ?? false
3113
+ }
3114
+ };
2406
3115
  }
2407
3116
 
2408
3117
  // packages/metrics/src/risk.ts
@@ -2541,8 +3250,8 @@ function hashString(value) {
2541
3250
  }
2542
3251
  return hash >>> 0;
2543
3252
  }
2544
- function sectionForPath(path9) {
2545
- const folders = path9.split("/").filter(Boolean).slice(0, -1);
3253
+ function sectionForPath(path7) {
3254
+ const folders = path7.split("/").filter(Boolean).slice(0, -1);
2546
3255
  if (folders.length === 0) return "(ra\xEDz)";
2547
3256
  if (folders.length === 1) return folders[0];
2548
3257
  return folders.slice(0, 3).join("/");
@@ -2612,7 +3321,7 @@ function buildRenderModel(cc, options = {}) {
2612
3321
  const fileIndexById = /* @__PURE__ */ new Map();
2613
3322
  walkFiles(cc.files, (entry) => {
2614
3323
  const fullPath = entry.path;
2615
- const path9 = prefix && fullPath.startsWith(prefix) ? fullPath.slice(prefix.length) : fullPath;
3324
+ const path7 = prefix && fullPath.startsWith(prefix) ? fullPath.slice(prefix.length) : fullPath;
2616
3325
  const values = attributes[entry.node.id];
2617
3326
  const rawArea = valueOf(values, areaMetric);
2618
3327
  const index = files.length;
@@ -2620,7 +3329,7 @@ function buildRenderModel(cc, options = {}) {
2620
3329
  files.push({
2621
3330
  index,
2622
3331
  nodeId: entry.node.id,
2623
- path: path9,
3332
+ path: path7,
2624
3333
  name: entry.node.name,
2625
3334
  section: -1,
2626
3335
  area: Math.max(1, rawArea ?? 0),
@@ -2783,7 +3492,7 @@ function buildRenderModel(cc, options = {}) {
2783
3492
  };
2784
3493
  }
2785
3494
  function buildRoots(cc, singleRoot, fileIndexById, files) {
2786
- const build = (node, path9) => {
3495
+ const build = (node, path7) => {
2787
3496
  if (node.type === "File") {
2788
3497
  const index = fileIndexById.get(node.id);
2789
3498
  const file = index === void 0 ? void 0 : files[index];
@@ -2791,7 +3500,7 @@ function buildRoots(cc, singleRoot, fileIndexById, files) {
2791
3500
  kind: "file",
2792
3501
  key: `f:${index ?? -1}`,
2793
3502
  name: node.name,
2794
- path: path9,
3503
+ path: path7,
2795
3504
  value: file?.area ?? 1,
2796
3505
  fileCount: 1,
2797
3506
  section: file?.section ?? 0,
@@ -2799,13 +3508,13 @@ function buildRoots(cc, singleRoot, fileIndexById, files) {
2799
3508
  };
2800
3509
  }
2801
3510
  const children = (node.children ?? []).map(
2802
- (child) => build(child, path9 ? `${path9}/${child.name}` : child.name)
3511
+ (child) => build(child, path7 ? `${path7}/${child.name}` : child.name)
2803
3512
  );
2804
3513
  const folder = {
2805
3514
  kind: "folder",
2806
- key: `d:${path9}`,
3515
+ key: `d:${path7}`,
2807
3516
  name: node.name,
2808
- path: path9,
3517
+ path: path7,
2809
3518
  value: children.reduce((sum, child) => sum + child.value, 0) || 1,
2810
3519
  fileCount: children.reduce((sum, child) => sum + child.fileCount, 0),
2811
3520
  section: -1,
@@ -3449,10 +4158,10 @@ function renderHtml(model) {
3449
4158
  }
3450
4159
 
3451
4160
  // apps/cli/src/io.ts
3452
- import fs5 from "node:fs";
4161
+ import fs3 from "node:fs";
3453
4162
  import zlib from "node:zlib";
3454
4163
  function readJsonFile2(file) {
3455
- const buffer = fs5.readFileSync(file);
4164
+ const buffer = fs3.readFileSync(file);
3456
4165
  const isGzip = buffer.length > 2 && buffer[0] === 31 && buffer[1] === 139;
3457
4166
  const raw = isGzip ? zlib.gunzipSync(buffer) : buffer;
3458
4167
  const text = raw.toString("utf8");
@@ -3500,14 +4209,14 @@ function runAnalyze(options) {
3500
4209
  }
3501
4210
  throw error;
3502
4211
  }
3503
- const outDirectory = path5.resolve(
3504
- options.out ?? path5.join(path5.resolve(root), ".codarch")
4212
+ const outDirectory = path3.resolve(
4213
+ options.out ?? path3.join(path3.resolve(root), ".codarch")
3505
4214
  );
3506
- const snapshotPath = path5.join(outDirectory, "snapshot.cc.json");
3507
- const mapPath = path5.join(outDirectory, "map.html");
4215
+ const snapshotPath = path3.join(outDirectory, "snapshot.cc.json");
4216
+ const mapPath = path3.join(outDirectory, "map.html");
3508
4217
  try {
3509
- fs6.mkdirSync(outDirectory, { recursive: true });
3510
- fs6.writeFileSync(
4218
+ fs4.mkdirSync(outDirectory, { recursive: true });
4219
+ fs4.writeFileSync(
3511
4220
  snapshotPath,
3512
4221
  `${JSON.stringify(cc, null, 2)}
3513
4222
  `,
@@ -3529,7 +4238,7 @@ function runAnalyze(options) {
3529
4238
  title: options.title
3530
4239
  });
3531
4240
  const html = renderHtml(model);
3532
- fs6.writeFileSync(mapPath, html, "utf8");
4241
+ fs4.writeFileSync(mapPath, html, "utf8");
3533
4242
  mapBytes = Buffer.byteLength(html, "utf8");
3534
4243
  } catch (error) {
3535
4244
  mapError = error instanceof RenderError ? error.message : error.message;
@@ -3538,14 +4247,14 @@ function runAnalyze(options) {
3538
4247
  mapError = "no hay ficheros TypeScript/JavaScript que dibujar";
3539
4248
  }
3540
4249
  const snapshotBytes = Buffer.byteLength(
3541
- fs6.readFileSync(snapshotPath),
4250
+ fs4.readFileSync(snapshotPath),
3542
4251
  "utf8"
3543
4252
  );
3544
4253
  const lines = [];
3545
4254
  lines.push("");
3546
4255
  lines.push(" CodArch \xB7 an\xE1lisis");
3547
4256
  lines.push("");
3548
- lines.push(field("ra\xEDz", path5.resolve(root)));
4257
+ lines.push(field("ra\xEDz", path3.resolve(root)));
3549
4258
  lines.push(field("snapshot", `${snapshotPath} (${formatBytes(snapshotBytes)})`));
3550
4259
  if (mapError) {
3551
4260
  lines.push(field("mapa", `omitido (${mapError})`));
@@ -3615,11 +4324,11 @@ function runAnalyze(options) {
3615
4324
  }
3616
4325
 
3617
4326
  // apps/cli/src/commands/diff.ts
3618
- import fs8 from "node:fs";
3619
- import path6 from "node:path";
4327
+ import fs6 from "node:fs";
4328
+ import path4 from "node:path";
3620
4329
 
3621
4330
  // packages/metrics/src/config.ts
3622
- import fs7 from "node:fs";
4331
+ import fs5 from "node:fs";
3623
4332
  import { parse as parseYaml } from "yaml";
3624
4333
  var ConfigError = class extends Error {
3625
4334
  constructor(message) {
@@ -3641,7 +4350,7 @@ function parseRuleConfig(input) {
3641
4350
  function loadRuleConfig(file) {
3642
4351
  let text;
3643
4352
  try {
3644
- text = fs7.readFileSync(file, "utf8");
4353
+ text = fs5.readFileSync(file, "utf8");
3645
4354
  } catch (error) {
3646
4355
  throw new ConfigError(
3647
4356
  `no se pudo leer ${file}: ${error.message}`
@@ -3771,29 +4480,29 @@ function parseRules(value, layerNames) {
3771
4480
  });
3772
4481
  return rules;
3773
4482
  }
3774
- function parseSeverity(value, path9) {
4483
+ function parseSeverity(value, path7) {
3775
4484
  if (value === void 0) return "error";
3776
4485
  if (typeof value !== "string" || !SEVERITIES.has(value)) {
3777
- throw new ConfigError(`${path9} debe ser info, warn o error`);
4486
+ throw new ConfigError(`${path7} debe ser info, warn o error`);
3778
4487
  }
3779
4488
  return value;
3780
4489
  }
3781
- function optionalNumber(value, path9) {
4490
+ function optionalNumber(value, path7) {
3782
4491
  if (value === void 0) return void 0;
3783
4492
  if (typeof value !== "number" || !Number.isFinite(value)) {
3784
- throw new ConfigError(`${path9} debe ser un n\xFAmero`);
4493
+ throw new ConfigError(`${path7} debe ser un n\xFAmero`);
3785
4494
  }
3786
4495
  return value;
3787
4496
  }
3788
- function asObject(value, path9) {
4497
+ function asObject(value, path7) {
3789
4498
  if (typeof value !== "object" || value === null || Array.isArray(value)) {
3790
- throw new ConfigError(`${path9} debe ser un objeto`);
4499
+ throw new ConfigError(`${path7} debe ser un objeto`);
3791
4500
  }
3792
4501
  return value;
3793
4502
  }
3794
- function asString(value, path9) {
4503
+ function asString(value, path7) {
3795
4504
  if (typeof value !== "string" || value.trim() === "") {
3796
- throw new ConfigError(`${path9} debe ser un texto no vac\xEDo`);
4505
+ throw new ConfigError(`${path7} debe ser un texto no vac\xEDo`);
3797
4506
  }
3798
4507
  return value.trim();
3799
4508
  }
@@ -4059,11 +4768,11 @@ function summarize(index) {
4059
4768
  }
4060
4769
  function difference(present, absent) {
4061
4770
  const files = [];
4062
- for (const [id, path9] of present.paths) {
4771
+ for (const [id, path7] of present.paths) {
4063
4772
  if (absent.paths.has(id)) continue;
4064
4773
  files.push({
4065
4774
  id,
4066
- path: path9,
4775
+ path: path7,
4067
4776
  metrics: sortedMetrics(present.metrics[id])
4068
4777
  });
4069
4778
  }
@@ -4071,7 +4780,7 @@ function difference(present, absent) {
4071
4780
  }
4072
4781
  function changedFiles(base, head) {
4073
4782
  const changed = [];
4074
- for (const [id, path9] of head.paths) {
4783
+ for (const [id, path7] of head.paths) {
4075
4784
  if (!base.paths.has(id)) continue;
4076
4785
  const baseValues = base.metrics[id] ?? {};
4077
4786
  const headValues = head.metrics[id] ?? {};
@@ -4091,7 +4800,7 @@ function changedFiles(base, head) {
4091
4800
  delta: baseValue !== null && headValue !== null ? headValue - baseValue : null
4092
4801
  });
4093
4802
  }
4094
- if (changes.length > 0) changed.push({ id, path: path9, changes });
4803
+ if (changes.length > 0) changed.push({ id, path: path7, changes });
4095
4804
  }
4096
4805
  return changed.sort(compareChangedFiles);
4097
4806
  }
@@ -4348,13 +5057,13 @@ function runDiff(options) {
4348
5057
  console.error(`no se pudo comparar: ${error.message}`);
4349
5058
  return 1;
4350
5059
  }
4351
- const outPath = path6.resolve(options.out ?? "report.json");
5060
+ const outPath = path4.resolve(options.out ?? "report.json");
4352
5061
  let reportBytes = 0;
4353
5062
  try {
4354
- fs8.mkdirSync(path6.dirname(outPath), { recursive: true });
5063
+ fs6.mkdirSync(path4.dirname(outPath), { recursive: true });
4355
5064
  const text = `${JSON.stringify(report, null, 2)}
4356
5065
  `;
4357
- fs8.writeFileSync(outPath, text, "utf8");
5066
+ fs6.writeFileSync(outPath, text, "utf8");
4358
5067
  reportBytes = Buffer.byteLength(text, "utf8");
4359
5068
  } catch (error) {
4360
5069
  console.error(
@@ -4490,7 +5199,7 @@ function loadSnapshot(file) {
4490
5199
  }
4491
5200
  function defaultConfigPath() {
4492
5201
  for (const file of [".codarch.yml", ".codarch.yaml"]) {
4493
- if (fs8.existsSync(file)) return file;
5202
+ if (fs6.existsSync(file)) return file;
4494
5203
  }
4495
5204
  return void 0;
4496
5205
  }
@@ -4542,7 +5251,7 @@ function runInspect(options) {
4542
5251
  const metric = options.metric ?? defaultMetric(stats);
4543
5252
  const top = metric ? topByMetric(cc, metric, options.top) : [];
4544
5253
  const rootPrefix = cc.files.length === 1 ? `${cc.files[0]?.name}/` : "";
4545
- const displayPath = (path9) => rootPrefix && path9.startsWith(rootPrefix) ? path9.slice(rootPrefix.length) : path9;
5254
+ const displayPath = (path7) => rootPrefix && path7.startsWith(rootPrefix) ? path7.slice(rootPrefix.length) : path7;
4546
5255
  if (options.metric && !stats.metricCoverage.some((entry) => entry.name === options.metric)) {
4547
5256
  console.error(`la m\xE9trica "${options.metric}" no existe en este fichero`);
4548
5257
  return 1;
@@ -4621,8 +5330,8 @@ function runInspect(options) {
4621
5330
  }
4622
5331
 
4623
5332
  // apps/cli/src/commands/render.ts
4624
- import fs9 from "node:fs";
4625
- import path7 from "node:path";
5333
+ import fs7 from "node:fs";
5334
+ import path5 from "node:path";
4626
5335
  function defaultOutputPath(input) {
4627
5336
  const lower = input.toLowerCase();
4628
5337
  const suffixes = [".cc.json.gz", ".cc.json", ".json.gz", ".json", ".gz"];
@@ -4632,7 +5341,7 @@ function defaultOutputPath(input) {
4632
5341
  return `${input}.html`;
4633
5342
  }
4634
5343
  function defaultProjectName(input) {
4635
- let name = path7.basename(input.replace(/\\/g, "/"));
5344
+ let name = path5.basename(input.replace(/\\/g, "/"));
4636
5345
  const lower = name.toLowerCase();
4637
5346
  const suffixes = [".cc.json.gz", ".cc.json", ".json.gz", ".json", ".gz"];
4638
5347
  for (const suffix of suffixes) {
@@ -4699,8 +5408,8 @@ function runRender(options) {
4699
5408
  const elapsed = Date.now() - started;
4700
5409
  const out = options.out ?? defaultOutputPath(options.file);
4701
5410
  try {
4702
- fs9.mkdirSync(path7.dirname(path7.resolve(out)), { recursive: true });
4703
- fs9.writeFileSync(out, html, "utf8");
5411
+ fs7.mkdirSync(path5.dirname(path5.resolve(out)), { recursive: true });
5412
+ fs7.writeFileSync(out, html, "utf8");
4704
5413
  } catch (error) {
4705
5414
  console.error(`no se pudo escribir ${out}: ${error.message}`);
4706
5415
  return 1;
@@ -4741,9 +5450,9 @@ function runRender(options) {
4741
5450
  }
4742
5451
 
4743
5452
  // apps/cli/src/commands/serve.ts
4744
- import fs10 from "node:fs";
5453
+ import fs8 from "node:fs";
4745
5454
  import http from "node:http";
4746
- import path8 from "node:path";
5455
+ import path6 from "node:path";
4747
5456
  import { fileURLToPath } from "node:url";
4748
5457
  var MIME = {
4749
5458
  ".html": "text/html; charset=utf-8",
@@ -4769,7 +5478,7 @@ var IGNORED_BROWSE = /* @__PURE__ */ new Set([
4769
5478
  "target"
4770
5479
  ]);
4771
5480
  function runServe(options) {
4772
- const root = path8.resolve(options.root ?? ".");
5481
+ const root = path6.resolve(options.root ?? ".");
4773
5482
  const webRoot = options.web === false ? null : findWebRoot();
4774
5483
  if (options.web !== false && !webRoot) {
4775
5484
  console.error(
@@ -4784,10 +5493,10 @@ function runServe(options) {
4784
5493
  const startWatcher = () => {
4785
5494
  if (watcher) return null;
4786
5495
  try {
4787
- watcher = fs10.watch(state.root, { recursive: true }, (_event, filename) => {
5496
+ watcher = fs8.watch(state.root, { recursive: true }, (_event, filename) => {
4788
5497
  if (!filename) return;
4789
- const relative = filename.toString().replace(/\\/g, "/");
4790
- if (IGNORED_WATCH.test(relative)) return;
5498
+ const relative2 = filename.toString().replace(/\\/g, "/");
5499
+ if (IGNORED_WATCH.test(relative2)) return;
4791
5500
  scheduleRefresh();
4792
5501
  });
4793
5502
  return null;
@@ -4847,8 +5556,8 @@ function runServe(options) {
4847
5556
  broadcast(clients, "watch", { watch: state.watch });
4848
5557
  };
4849
5558
  const switchRoot = (nextRoot) => {
4850
- const resolved = path8.resolve(nextRoot);
4851
- const stat = fs10.statSync(resolved);
5559
+ const resolved = path6.resolve(nextRoot);
5560
+ const stat = fs8.statSync(resolved);
4852
5561
  if (!stat.isDirectory()) throw new Error("la ruta no es un directorio");
4853
5562
  const next = analyzeState(resolved, state.watch);
4854
5563
  stopWatcher();
@@ -4870,7 +5579,7 @@ function runServe(options) {
4870
5579
  return state;
4871
5580
  };
4872
5581
  const meta = () => ({
4873
- root: path8.basename(state.root),
5582
+ root: path6.basename(state.root),
4874
5583
  rootPath: state.root,
4875
5584
  watch: state.watch,
4876
5585
  files: state.files,
@@ -5034,15 +5743,15 @@ function analyzeState(root, watch) {
5034
5743
  };
5035
5744
  }
5036
5745
  function browseDirectories(root, requested) {
5037
- const target = requested?.trim() ? path8.resolve(requested) : root;
5038
- const stat = fs10.statSync(target);
5746
+ const target = requested?.trim() ? path6.resolve(requested) : root;
5747
+ const stat = fs8.statSync(target);
5039
5748
  if (!stat.isDirectory()) throw new Error("la ruta no es un directorio");
5040
- const entries = fs10.readdirSync(target, { withFileTypes: true }).filter((entry) => entry.isDirectory() && !IGNORED_BROWSE.has(entry.name)).sort((left, right) => left.name.localeCompare(right.name)).slice(0, 500).map((entry) => ({
5749
+ const entries = fs8.readdirSync(target, { withFileTypes: true }).filter((entry) => entry.isDirectory() && !IGNORED_BROWSE.has(entry.name)).sort((left, right) => left.name.localeCompare(right.name)).slice(0, 500).map((entry) => ({
5041
5750
  name: entry.name,
5042
- path: path8.join(target, entry.name),
5751
+ path: path6.join(target, entry.name),
5043
5752
  kind: "directory"
5044
5753
  }));
5045
- const parent = path8.dirname(target);
5754
+ const parent = path6.dirname(target);
5046
5755
  return {
5047
5756
  path: target,
5048
5757
  parent: parent === target ? null : parent,
@@ -5078,9 +5787,9 @@ function originAllowed(request, options) {
5078
5787
  function findWebRoot() {
5079
5788
  const candidates = [
5080
5789
  fileURLToPath(new URL("./web", import.meta.url)),
5081
- path8.resolve(process.cwd(), "apps", "web", "dist"),
5082
- path8.resolve(
5083
- path8.dirname(fileURLToPath(import.meta.url)),
5790
+ path6.resolve(process.cwd(), "apps", "web", "dist"),
5791
+ path6.resolve(
5792
+ path6.dirname(fileURLToPath(import.meta.url)),
5084
5793
  "..",
5085
5794
  "..",
5086
5795
  "..",
@@ -5089,14 +5798,14 @@ function findWebRoot() {
5089
5798
  )
5090
5799
  ];
5091
5800
  for (const candidate of candidates) {
5092
- if (fs10.existsSync(path8.join(candidate, "index.html"))) return candidate;
5801
+ if (fs8.existsSync(path6.join(candidate, "index.html"))) return candidate;
5093
5802
  }
5094
5803
  return null;
5095
5804
  }
5096
5805
  function readReport(root) {
5097
- const file = path8.join(root, ".codarch", "report.json");
5806
+ const file = path6.join(root, ".codarch", "report.json");
5098
5807
  try {
5099
- return JSON.parse(fs10.readFileSync(file, "utf8"));
5808
+ return JSON.parse(fs8.readFileSync(file, "utf8"));
5100
5809
  } catch (error) {
5101
5810
  if (error.code !== "ENOENT") {
5102
5811
  console.error(`no se pudo leer ${file}: ${error.message}`);
@@ -5113,17 +5822,17 @@ function serveStatic(webRoot, pathname, headOnly, response) {
5113
5822
  response.end();
5114
5823
  return;
5115
5824
  }
5116
- const relative = decoded === "/" ? "index.html" : decoded.replace(/^\/+/, "");
5117
- let file = path8.resolve(webRoot, relative);
5118
- if (!isWithin2(webRoot, file) || !fs10.existsSync(file) || !fs10.statSync(file).isFile()) {
5119
- file = path8.join(webRoot, "index.html");
5825
+ const relative2 = decoded === "/" ? "index.html" : decoded.replace(/^\/+/, "");
5826
+ let file = path6.resolve(webRoot, relative2);
5827
+ if (!isWithin2(webRoot, file) || !fs8.existsSync(file) || !fs8.statSync(file).isFile()) {
5828
+ file = path6.join(webRoot, "index.html");
5120
5829
  }
5121
5830
  try {
5122
- const body = fs10.readFileSync(file);
5831
+ const body = fs8.readFileSync(file);
5123
5832
  response.writeHead(200, {
5124
- "Content-Type": MIME[path8.extname(file).toLowerCase()] ?? "application/octet-stream",
5833
+ "Content-Type": MIME[path6.extname(file).toLowerCase()] ?? "application/octet-stream",
5125
5834
  "Content-Length": body.length,
5126
- "Cache-Control": file.includes(`${path8.sep}assets${path8.sep}`) ? "public, max-age=31536000, immutable" : "no-store",
5835
+ "Cache-Control": file.includes(`${path6.sep}assets${path6.sep}`) ? "public, max-age=31536000, immutable" : "no-store",
5127
5836
  "X-Content-Type-Options": "nosniff"
5128
5837
  });
5129
5838
  if (headOnly) response.end();
@@ -5134,8 +5843,8 @@ function serveStatic(webRoot, pathname, headOnly, response) {
5134
5843
  }
5135
5844
  }
5136
5845
  function isWithin2(root, candidate) {
5137
- const relative = path8.relative(root, candidate);
5138
- return relative === "" || !relative.startsWith("..") && !path8.isAbsolute(relative);
5846
+ const relative2 = path6.relative(root, candidate);
5847
+ return relative2 === "" || !relative2.startsWith("..") && !path6.isAbsolute(relative2);
5139
5848
  }
5140
5849
  function sendJson(response, value) {
5141
5850
  const body = `${JSON.stringify(value)}
@@ -5173,7 +5882,7 @@ data: ${JSON.stringify(data)}
5173
5882
  }
5174
5883
 
5175
5884
  // apps/cli/src/index.ts
5176
- var VERSION = true ? "0.1.3" : "0.0.0";
5885
+ var VERSION = true ? "0.1.4" : "0.0.0";
5177
5886
  var USAGE = `
5178
5887
  CodArch \xB7 mapa vivo de la arquitectura de tu software
5179
5888