codarch 0.1.3 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.js +2124 -500
- package/package.json +2 -2
- package/web/assets/index-BRgqIG_S.js +9 -0
- package/web/assets/index-C_1au_Hz.css +1 -0
- package/web/assets/tree-sitter-B8lY9Ae0.wasm +0 -0
- package/web/assets/tree-sitter-tsx-DFihddBo.wasm +0 -0
- package/web/assets/tree-sitter-typescript-BQpamM5l.wasm +0 -0
- package/web/assets/worker-tF5VFTCV.js +461 -0
- package/web/favicon.png +0 -0
- package/web/icon.png +0 -0
- package/web/index.html +4 -2
- package/web/assets/index-DhLrsTc_.js +0 -9
- package/web/assets/index-Dmmkr41i.css +0 -1
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
|
|
5
|
-
import
|
|
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
|
-
|
|
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
|
|
287
|
-
return ASSET_EXTENSIONS.some((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 =
|
|
298
|
-
|
|
299
|
-
this.
|
|
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 (
|
|
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("/") ?
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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 =
|
|
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 =
|
|
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
|
|
452
|
-
return
|
|
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 (
|
|
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) =>
|
|
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 =
|
|
484
|
-
if (
|
|
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
|
|
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
|
-
|
|
499
|
-
|
|
666
|
+
host,
|
|
667
|
+
dirname(configPath),
|
|
500
668
|
void 0,
|
|
501
669
|
configPath
|
|
502
670
|
);
|
|
503
|
-
const configDirectory =
|
|
504
|
-
const 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) =>
|
|
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(
|
|
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
|
-
|
|
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}": ${
|
|
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 =
|
|
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
|
|
820
|
+
if (!entry.isDirectory) continue;
|
|
648
821
|
if (WORKSPACE_SKIP_DIRS.has(entry.name)) continue;
|
|
649
822
|
stack.push({
|
|
650
|
-
directory:
|
|
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 =
|
|
872
|
+
text = fileSystem.tryReadFile(file);
|
|
700
873
|
} catch (error) {
|
|
701
|
-
|
|
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
|
|
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,
|
|
891
|
-
super(`${message} (en ${
|
|
1061
|
+
constructor(message, path7) {
|
|
1062
|
+
super(`${message} (en ${path7})`);
|
|
892
1063
|
this.name = "CcJsonError";
|
|
893
|
-
this.path =
|
|
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,
|
|
900
|
-
if (!isRecord2(value)) throw new CcJsonError("se esperaba un objeto",
|
|
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,
|
|
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`,
|
|
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(
|
|
1563
|
+
function pathSegments(path7) {
|
|
917
1564
|
const segments = [];
|
|
918
|
-
for (const raw of
|
|
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(
|
|
933
|
-
const canonical =
|
|
934
|
-
return
|
|
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(
|
|
940
|
-
return nodeId("File", pathSegments(
|
|
1586
|
+
function fileId(path7) {
|
|
1587
|
+
return nodeId("File", pathSegments(path7));
|
|
941
1588
|
}
|
|
942
|
-
function folderId(
|
|
943
|
-
return nodeId("Folder", pathSegments(
|
|
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,
|
|
1646
|
+
const buildNode = (value, parentPath, path7, depth) => {
|
|
1000
1647
|
if (depth > MAX_DEPTH) {
|
|
1001
|
-
throw new CcJsonError(`profundidad m\xE1xima ${MAX_DEPTH} superada`,
|
|
1648
|
+
throw new CcJsonError(`profundidad m\xE1xima ${MAX_DEPTH} superada`, path7);
|
|
1002
1649
|
}
|
|
1003
|
-
const raw = requireRecord(value,
|
|
1004
|
-
const name = readNodeName(raw.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"', `${
|
|
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
|
-
`${
|
|
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, `${
|
|
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', `${
|
|
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", `${
|
|
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, `${
|
|
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
|
|
1054
|
-
const raw = requireRecord(edge,
|
|
1055
|
-
const fromName = requireString(raw.fromNodeName, `${
|
|
1056
|
-
const toName = requireString(raw.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, `${
|
|
1059
|
-
toId: resolveEndpoint(toName, pathIndex, stripNames, `${
|
|
1705
|
+
fromId: resolveEndpoint(fromName, pathIndex, stripNames, `${path7}.fromNodeName`),
|
|
1706
|
+
toId: resolveEndpoint(toName, pathIndex, stripNames, `${path7}.toNodeName`)
|
|
1060
1707
|
};
|
|
1061
|
-
const attributes = readMetrics(raw.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
|
|
1132
|
-
if (
|
|
1133
|
-
add(
|
|
1134
|
-
add(`root/${
|
|
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,
|
|
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",
|
|
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}"`,
|
|
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,
|
|
1177
|
-
const name = normalizeName(requireString(value,
|
|
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 ".."',
|
|
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 "/"',
|
|
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,
|
|
1836
|
+
function readMetrics(value, path7) {
|
|
1190
1837
|
if (value === void 0) return void 0;
|
|
1191
|
-
const raw = requireRecord(value,
|
|
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
|
-
`${
|
|
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,
|
|
1851
|
+
function readAttributeTypes(value, path7) {
|
|
1205
1852
|
if (value === void 0) return {};
|
|
1206
|
-
const raw = requireRecord(value,
|
|
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, `${
|
|
1209
|
-
const edges = raw.edges === void 0 ? void 0 : readTypeMap(raw.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,
|
|
1862
|
+
const nodes = readTypeMap(raw, path7);
|
|
1216
1863
|
return Object.keys(nodes).length > 0 ? { nodes } : {};
|
|
1217
1864
|
}
|
|
1218
|
-
function readTypeMap(value,
|
|
1219
|
-
const raw = requireRecord(value,
|
|
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
|
-
`${
|
|
1872
|
+
`${path7}["${metric}"]`
|
|
1226
1873
|
);
|
|
1227
1874
|
}
|
|
1228
1875
|
out[metric] = type;
|
|
1229
1876
|
}
|
|
1230
1877
|
return out;
|
|
1231
1878
|
}
|
|
1232
|
-
function readDescriptors(value,
|
|
1879
|
+
function readDescriptors(value, path7) {
|
|
1233
1880
|
if (value === void 0) return {};
|
|
1234
|
-
const raw = requireRecord(value,
|
|
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, `${
|
|
1237
|
-
const edges = raw.edges === void 0 ? void 0 : readDescriptorMap(raw.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,
|
|
1890
|
+
const flat = readDescriptorMap(raw, path7);
|
|
1244
1891
|
return Object.keys(flat).length > 0 ? { flat } : {};
|
|
1245
1892
|
}
|
|
1246
|
-
function readDescriptorMap(value,
|
|
1247
|
-
const raw = requireRecord(value,
|
|
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, `${
|
|
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
|
-
`${
|
|
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,
|
|
1280
|
-
if (depth > MAX_DEPTH2) throw new CcJsonError(`profundidad m\xE1xima ${MAX_DEPTH2} superada`,
|
|
1281
|
-
const raw = requireRecord(value,
|
|
1282
|
-
const id = requireString(raw.id, `${
|
|
1283
|
-
if (ids.has(id)) throw new CcJsonError(`id duplicado "${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, `${
|
|
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"', `${
|
|
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', `${
|
|
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, `${
|
|
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
|
|
1330
|
-
const raw = requireRecord(edge,
|
|
1331
|
-
requireString(raw.fromId, `${
|
|
1332
|
-
requireString(raw.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
|
|
1422
|
-
if (value === void 0 ||
|
|
1423
|
-
ranked.push({ id, path:
|
|
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",
|
|
@@ -1597,8 +2095,8 @@ var MIGRATION_EXTENSIONS = Object.keys(EXTENSION_LANGUAGE);
|
|
|
1597
2095
|
var LANGUAGE_ORDER = ["ts", "js", "rust", "wasm", "node"];
|
|
1598
2096
|
var RUST_BRIDGE = /#\[\s*(?:napi|wasm_bindgen)(?:\(|\])|\bnapi_derive\b|\bnapi::|\bwasm_bindgen(?:::|\b)|\bwasm-pack\b/;
|
|
1599
2097
|
var JS_FFI = /\bWebAssembly\s*\.\s*(?:instantiate|compile|Module|Instance)\b|\bprocess\.dlopen\b/;
|
|
1600
|
-
function languageForPath(
|
|
1601
|
-
const lower =
|
|
2098
|
+
function languageForPath(relativePath3) {
|
|
2099
|
+
const lower = relativePath3.toLowerCase();
|
|
1602
2100
|
const dot = lower.lastIndexOf(".");
|
|
1603
2101
|
if (dot === -1) return null;
|
|
1604
2102
|
return EXTENSION_LANGUAGE[lower.slice(dot)] ?? null;
|
|
@@ -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
|
|
1726
|
-
if (
|
|
1727
|
-
if (
|
|
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 [
|
|
1993
|
-
dependencyNodes[fileId(
|
|
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 = {
|
|
@@ -2022,8 +2519,8 @@ function buildSnapshot(input) {
|
|
|
2022
2519
|
}
|
|
2023
2520
|
function migrationLensFrom(migration) {
|
|
2024
2521
|
const nodes = {};
|
|
2025
|
-
for (const [
|
|
2026
|
-
nodes[fileId(
|
|
2522
|
+
for (const [relativePath3, node] of migration.nodes) {
|
|
2523
|
+
nodes[fileId(relativePath3)] = node;
|
|
2027
2524
|
}
|
|
2028
2525
|
const edges = migration.edges.map((edge) => ({
|
|
2029
2526
|
fromId: fileId(edge.from),
|
|
@@ -2039,7 +2536,7 @@ function migrationLensFrom(migration) {
|
|
|
2039
2536
|
};
|
|
2040
2537
|
}
|
|
2041
2538
|
function checksumFor(files, lenses) {
|
|
2042
|
-
return
|
|
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(
|
|
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(
|
|
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:
|
|
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 =
|
|
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,18 +2616,17 @@ 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
|
-
|
|
2126
|
-
const
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
const
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
if (isIgnored(directory.layers, relativePath2, false)) continue;
|
|
2619
|
+
const relativePath3 = directory.relativePath ? `${directory.relativePath}/${entry.name}` : entry.name;
|
|
2620
|
+
const absolutePath = directory.absolutePath === "/" ? `/${entry.name}` : `${directory.absolutePath}/${entry.name}`;
|
|
2621
|
+
if (entry.isDirectory) {
|
|
2622
|
+
if (isIgnored(directory.layers, relativePath3, true)) continue;
|
|
2623
|
+
const nested = readGitignore(fileSystem, absolutePath, relativePath3, warnings);
|
|
2624
|
+
const layers = nested ? [...directory.layers, { base: relativePath3, matcher: ignore().add(nested) }] : directory.layers;
|
|
2625
|
+
stack.push({ absolutePath, relativePath: relativePath3, layers });
|
|
2626
|
+
} else if (entry.isFile) {
|
|
2627
|
+
if (isIgnored(directory.layers, relativePath3, false)) continue;
|
|
2135
2628
|
if (!matchesExtension(entry.name, extensions)) continue;
|
|
2136
|
-
files.push({ absolutePath, relativePath:
|
|
2629
|
+
files.push({ absolutePath, relativePath: relativePath3 });
|
|
2137
2630
|
}
|
|
2138
2631
|
}
|
|
2139
2632
|
}
|
|
@@ -2143,23 +2636,21 @@ function walkSourceFiles(rootInput, options = {}) {
|
|
|
2143
2636
|
warnings.sort();
|
|
2144
2637
|
return { files, warnings };
|
|
2145
2638
|
}
|
|
2146
|
-
function readGitignore(
|
|
2147
|
-
const file =
|
|
2639
|
+
function readGitignore(fileSystem, absoluteDirectory, relativeBase, warnings) {
|
|
2640
|
+
const file = absoluteDirectory === "/" ? "/.gitignore" : `${absoluteDirectory}/.gitignore`;
|
|
2148
2641
|
try {
|
|
2149
|
-
return
|
|
2642
|
+
return fileSystem.tryReadFile(file);
|
|
2150
2643
|
} catch (error) {
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
);
|
|
2155
|
-
}
|
|
2644
|
+
warnings.push(
|
|
2645
|
+
`no se pudo leer ${relativeBase ? `${relativeBase}/` : ""}.gitignore: ${error.message}`
|
|
2646
|
+
);
|
|
2156
2647
|
return null;
|
|
2157
2648
|
}
|
|
2158
2649
|
}
|
|
2159
|
-
function isIgnored(layers,
|
|
2650
|
+
function isIgnored(layers, relativePath3, isDirectory) {
|
|
2160
2651
|
let ignored = false;
|
|
2161
2652
|
for (const layer of layers) {
|
|
2162
|
-
const localPath = layer.base ?
|
|
2653
|
+
const localPath = layer.base ? relativePath3.slice(layer.base.length + 1) : relativePath3;
|
|
2163
2654
|
if (!localPath) continue;
|
|
2164
2655
|
const probe = isDirectory ? `${localPath}/` : localPath;
|
|
2165
2656
|
const result = layer.matcher.test(probe);
|
|
@@ -2173,28 +2664,36 @@ 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
|
|
2667
|
+
// packages/parsers/src/analyze-core.ts
|
|
2668
|
+
function analyzeFiles(options) {
|
|
2178
2669
|
const started = Date.now();
|
|
2179
|
-
const
|
|
2180
|
-
const
|
|
2181
|
-
const
|
|
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 =
|
|
2680
|
+
const parser = options.parser;
|
|
2189
2681
|
const parsedFiles = [];
|
|
2190
|
-
const
|
|
2682
|
+
const migrationFiles2 = [];
|
|
2191
2683
|
const migrationLinks = [];
|
|
2192
2684
|
const links = [];
|
|
2193
2685
|
const unresolved = [];
|
|
2194
2686
|
let externalImports = 0;
|
|
2195
2687
|
let builtinImports = 0;
|
|
2196
2688
|
let typeOnlyImports = 0;
|
|
2689
|
+
let processed = 0;
|
|
2197
2690
|
for (const file of walked.files) {
|
|
2691
|
+
processed += 1;
|
|
2692
|
+
options.onProgress?.({
|
|
2693
|
+
done: processed,
|
|
2694
|
+
total: walked.files.length,
|
|
2695
|
+
current: file.relativePath
|
|
2696
|
+
});
|
|
2198
2697
|
const language = languageForPath(file.relativePath);
|
|
2199
2698
|
if (!language) continue;
|
|
2200
2699
|
if (isNativeLanguage(language)) {
|
|
@@ -2202,7 +2701,7 @@ function analyzeRepository(rootInput, options = {}) {
|
|
|
2202
2701
|
relativePath: file.relativePath,
|
|
2203
2702
|
metrics: { loc: 0, rloc: 0 }
|
|
2204
2703
|
});
|
|
2205
|
-
|
|
2704
|
+
migrationFiles2.push({
|
|
2206
2705
|
relativePath: file.relativePath,
|
|
2207
2706
|
language,
|
|
2208
2707
|
loc: 0,
|
|
@@ -2215,7 +2714,7 @@ function analyzeRepository(rootInput, options = {}) {
|
|
|
2215
2714
|
}
|
|
2216
2715
|
let source;
|
|
2217
2716
|
try {
|
|
2218
|
-
source =
|
|
2717
|
+
source = fileSystem.readFile(file.absolutePath);
|
|
2219
2718
|
} catch (error) {
|
|
2220
2719
|
warnings.push(
|
|
2221
2720
|
`no se pudo leer ${file.relativePath}: ${error.message}`
|
|
@@ -2228,7 +2727,7 @@ function analyzeRepository(rootInput, options = {}) {
|
|
|
2228
2727
|
relativePath: file.relativePath,
|
|
2229
2728
|
metrics: { loc: measured.loc, rloc: measured.rloc }
|
|
2230
2729
|
});
|
|
2231
|
-
|
|
2730
|
+
migrationFiles2.push({
|
|
2232
2731
|
relativePath: file.relativePath,
|
|
2233
2732
|
language,
|
|
2234
2733
|
loc: measured.loc,
|
|
@@ -2298,7 +2797,7 @@ function analyzeRepository(rootInput, options = {}) {
|
|
|
2298
2797
|
unresolvedImports: fileUnresolvedImports
|
|
2299
2798
|
}
|
|
2300
2799
|
});
|
|
2301
|
-
|
|
2800
|
+
migrationFiles2.push({
|
|
2302
2801
|
relativePath: file.relativePath,
|
|
2303
2802
|
language,
|
|
2304
2803
|
loc: parsed.metrics.loc ?? 0,
|
|
@@ -2311,18 +2810,15 @@ function analyzeRepository(rootInput, options = {}) {
|
|
|
2311
2810
|
)
|
|
2312
2811
|
});
|
|
2313
2812
|
}
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
parsedFiles.map((file) => file.relativePath)
|
|
2317
|
-
|
|
2318
|
-
);
|
|
2319
|
-
warnings.push(...git.warnings);
|
|
2320
|
-
if (git.enabled) {
|
|
2813
|
+
let churnResult = null;
|
|
2814
|
+
if (options.churn) {
|
|
2815
|
+
churnResult = options.churn(parsedFiles.map((file) => file.relativePath));
|
|
2816
|
+
warnings.push(...churnResult.warnings);
|
|
2321
2817
|
for (const file of parsedFiles) {
|
|
2322
|
-
const
|
|
2323
|
-
if (!
|
|
2324
|
-
file.metrics.number_of_commits =
|
|
2325
|
-
file.metrics.number_of_authors =
|
|
2818
|
+
const metrics = churnResult.metrics.get(file.relativePath);
|
|
2819
|
+
if (!metrics) continue;
|
|
2820
|
+
file.metrics.number_of_commits = metrics.commits;
|
|
2821
|
+
file.metrics.number_of_authors = metrics.authors;
|
|
2326
2822
|
}
|
|
2327
2823
|
}
|
|
2328
2824
|
const graph = buildGraph(
|
|
@@ -2339,10 +2835,10 @@ function analyzeRepository(rootInput, options = {}) {
|
|
|
2339
2835
|
file.metrics.dependencyLevel = metrics.dependencyLevel;
|
|
2340
2836
|
}
|
|
2341
2837
|
}
|
|
2342
|
-
for (const file of
|
|
2838
|
+
for (const file of migrationFiles2) {
|
|
2343
2839
|
file.fanIn = graph.metrics.get(file.relativePath)?.fanIn ?? 0;
|
|
2344
2840
|
}
|
|
2345
|
-
const migration = buildMigration(
|
|
2841
|
+
const migration = buildMigration(migrationFiles2, migrationLinks);
|
|
2346
2842
|
for (const file of parsedFiles) {
|
|
2347
2843
|
const node = migration.nodes.get(file.relativePath);
|
|
2348
2844
|
if (!node) continue;
|
|
@@ -2370,39 +2866,259 @@ function analyzeRepository(rootInput, options = {}) {
|
|
|
2370
2866
|
}
|
|
2371
2867
|
warnings.sort();
|
|
2372
2868
|
return {
|
|
2373
|
-
snapshot,
|
|
2374
|
-
files: parsedFiles.length,
|
|
2375
|
-
folders,
|
|
2376
|
-
edges: graph.edges.length,
|
|
2377
|
-
cycles: graph.cycles,
|
|
2378
|
-
metricNames: [...metricNames].sort(),
|
|
2379
|
-
warnings,
|
|
2380
|
-
unresolved,
|
|
2381
|
-
externalImports,
|
|
2382
|
-
builtinImports,
|
|
2383
|
-
typeOnlyImports,
|
|
2384
|
-
unresolvedImports: unresolved.length,
|
|
2869
|
+
snapshot,
|
|
2870
|
+
files: parsedFiles.length,
|
|
2871
|
+
folders,
|
|
2872
|
+
edges: graph.edges.length,
|
|
2873
|
+
cycles: graph.cycles,
|
|
2874
|
+
metricNames: [...metricNames].sort(),
|
|
2875
|
+
warnings,
|
|
2876
|
+
unresolved,
|
|
2877
|
+
externalImports,
|
|
2878
|
+
builtinImports,
|
|
2879
|
+
typeOnlyImports,
|
|
2880
|
+
unresolvedImports: unresolved.length,
|
|
2881
|
+
migration: migration.summary,
|
|
2882
|
+
churn: churnResult,
|
|
2883
|
+
durationMs: Date.now() - started
|
|
2884
|
+
};
|
|
2885
|
+
}
|
|
2886
|
+
function readProjectName(fileSystem, root, fallbackName) {
|
|
2887
|
+
try {
|
|
2888
|
+
const text = fileSystem.readFile(join(root, "package.json"));
|
|
2889
|
+
const parsed = JSON.parse(text.replace(/^\uFEFF/, ""));
|
|
2890
|
+
if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed) && typeof parsed.name === "string") {
|
|
2891
|
+
const name = parsed.name.trim();
|
|
2892
|
+
if (name) return name;
|
|
2893
|
+
}
|
|
2894
|
+
} catch {
|
|
2895
|
+
}
|
|
2896
|
+
return fallbackName ?? basename(root) ?? "Proyecto";
|
|
2897
|
+
}
|
|
2898
|
+
|
|
2899
|
+
// packages/parsers/src/git.ts
|
|
2900
|
+
import { spawnSync } from "node:child_process";
|
|
2901
|
+
import fs from "node:fs";
|
|
2902
|
+
import path from "node:path";
|
|
2903
|
+
var COMMIT_PREFIX = "__CODARCH_COMMIT__";
|
|
2904
|
+
var DEFAULT_MAX_BUFFER = 128 * 1024 * 1024;
|
|
2905
|
+
var EMPTY = {
|
|
2906
|
+
metrics: /* @__PURE__ */ new Map(),
|
|
2907
|
+
commits: 0,
|
|
2908
|
+
authors: 0,
|
|
2909
|
+
shallow: false,
|
|
2910
|
+
enabled: false,
|
|
2911
|
+
warnings: []
|
|
2912
|
+
};
|
|
2913
|
+
function collectGitChurn(rootInput, files, options = {}) {
|
|
2914
|
+
if (options.enabled === false || files.length === 0) {
|
|
2915
|
+
return { ...EMPTY, metrics: /* @__PURE__ */ new Map() };
|
|
2916
|
+
}
|
|
2917
|
+
const root = realPath(rootInput);
|
|
2918
|
+
const maxBuffer = options.maxBuffer ?? DEFAULT_MAX_BUFFER;
|
|
2919
|
+
const warnings = [];
|
|
2920
|
+
const topLevel = runGit(root, ["rev-parse", "--show-toplevel"], maxBuffer);
|
|
2921
|
+
if (!topLevel.ok) {
|
|
2922
|
+
return {
|
|
2923
|
+
...EMPTY,
|
|
2924
|
+
metrics: /* @__PURE__ */ new Map(),
|
|
2925
|
+
warnings: [`no hay historial git utilizable: ${topLevel.error}`]
|
|
2926
|
+
};
|
|
2927
|
+
}
|
|
2928
|
+
const repoRoot = realPath(topLevel.stdout.trim());
|
|
2929
|
+
const shallowResult = runGit(
|
|
2930
|
+
repoRoot,
|
|
2931
|
+
["rev-parse", "--is-shallow-repository"],
|
|
2932
|
+
maxBuffer
|
|
2933
|
+
);
|
|
2934
|
+
const shallow = shallowResult.ok && shallowResult.stdout.trim().toLowerCase() === "true";
|
|
2935
|
+
if (shallow) {
|
|
2936
|
+
warnings.push(
|
|
2937
|
+
"el repositorio es shallow: el churn puede estar incompleto"
|
|
2938
|
+
);
|
|
2939
|
+
}
|
|
2940
|
+
const log = runGit(
|
|
2941
|
+
repoRoot,
|
|
2942
|
+
[
|
|
2943
|
+
"-c",
|
|
2944
|
+
"core.quotePath=false",
|
|
2945
|
+
"log",
|
|
2946
|
+
"--no-merges",
|
|
2947
|
+
"--numstat",
|
|
2948
|
+
`--format=${COMMIT_PREFIX}%H%x09%ae`
|
|
2949
|
+
],
|
|
2950
|
+
maxBuffer
|
|
2951
|
+
);
|
|
2952
|
+
if (!log.ok) {
|
|
2953
|
+
return {
|
|
2954
|
+
...EMPTY,
|
|
2955
|
+
shallow,
|
|
2956
|
+
warnings: [...warnings, `no se pudo leer git log: ${log.error}`]
|
|
2957
|
+
};
|
|
2958
|
+
}
|
|
2959
|
+
const wanted = new Set(files);
|
|
2960
|
+
const commitsByFile = /* @__PURE__ */ new Map();
|
|
2961
|
+
const authorsByFile = /* @__PURE__ */ new Map();
|
|
2962
|
+
const authors = /* @__PURE__ */ new Set();
|
|
2963
|
+
let commits = 0;
|
|
2964
|
+
let currentAuthor = "";
|
|
2965
|
+
let touched = /* @__PURE__ */ new Set();
|
|
2966
|
+
const flushCommit = () => {
|
|
2967
|
+
if (!currentAuthor || touched.size === 0) return;
|
|
2968
|
+
commits++;
|
|
2969
|
+
authors.add(currentAuthor);
|
|
2970
|
+
for (const file of touched) {
|
|
2971
|
+
commitsByFile.set(file, (commitsByFile.get(file) ?? 0) + 1);
|
|
2972
|
+
let fileAuthors = authorsByFile.get(file);
|
|
2973
|
+
if (!fileAuthors) {
|
|
2974
|
+
fileAuthors = /* @__PURE__ */ new Set();
|
|
2975
|
+
authorsByFile.set(file, fileAuthors);
|
|
2976
|
+
}
|
|
2977
|
+
fileAuthors.add(currentAuthor);
|
|
2978
|
+
}
|
|
2979
|
+
touched = /* @__PURE__ */ new Set();
|
|
2980
|
+
currentAuthor = "";
|
|
2981
|
+
};
|
|
2982
|
+
for (const rawLine of log.stdout.split(/\r?\n/)) {
|
|
2983
|
+
if (rawLine.startsWith(COMMIT_PREFIX)) {
|
|
2984
|
+
flushCommit();
|
|
2985
|
+
const fields2 = rawLine.slice(COMMIT_PREFIX.length).split(" ");
|
|
2986
|
+
const author = fields2[1]?.trim() ?? "";
|
|
2987
|
+
currentAuthor = author || "(sin autor)";
|
|
2988
|
+
continue;
|
|
2989
|
+
}
|
|
2990
|
+
if (!currentAuthor || rawLine === "") continue;
|
|
2991
|
+
const fields = rawLine.split(" ");
|
|
2992
|
+
if (fields.length < 3) continue;
|
|
2993
|
+
const gitPath = fields.slice(2).join(" ");
|
|
2994
|
+
if (!gitPath) continue;
|
|
2995
|
+
const relative2 = path.relative(root, path.resolve(repoRoot, gitPath));
|
|
2996
|
+
if (relative2 === "" || relative2 === ".." || relative2.startsWith(`..${path.sep}`) || path.isAbsolute(relative2)) {
|
|
2997
|
+
continue;
|
|
2998
|
+
}
|
|
2999
|
+
const normalized = relative2.split(path.sep).join("/");
|
|
3000
|
+
if (!wanted.has(normalized)) continue;
|
|
3001
|
+
touched.add(normalized);
|
|
3002
|
+
}
|
|
3003
|
+
flushCommit();
|
|
3004
|
+
const metrics = /* @__PURE__ */ new Map();
|
|
3005
|
+
for (const file of files) {
|
|
3006
|
+
metrics.set(file, {
|
|
3007
|
+
commits: commitsByFile.get(file) ?? 0,
|
|
3008
|
+
authors: authorsByFile.get(file)?.size ?? 0
|
|
3009
|
+
});
|
|
3010
|
+
}
|
|
3011
|
+
return {
|
|
3012
|
+
metrics,
|
|
3013
|
+
commits,
|
|
3014
|
+
authors: authors.size,
|
|
3015
|
+
shallow,
|
|
3016
|
+
enabled: true,
|
|
3017
|
+
warnings
|
|
3018
|
+
};
|
|
3019
|
+
}
|
|
3020
|
+
function realPath(input) {
|
|
3021
|
+
const resolved = path.resolve(input);
|
|
3022
|
+
try {
|
|
3023
|
+
return fs.realpathSync.native(resolved);
|
|
3024
|
+
} catch {
|
|
3025
|
+
return resolved;
|
|
3026
|
+
}
|
|
3027
|
+
}
|
|
3028
|
+
function runGit(cwd, args, maxBuffer) {
|
|
3029
|
+
const result = spawnSync("git", args, {
|
|
3030
|
+
cwd,
|
|
3031
|
+
encoding: "utf8",
|
|
3032
|
+
maxBuffer,
|
|
3033
|
+
windowsHide: true
|
|
3034
|
+
});
|
|
3035
|
+
if (result.error) {
|
|
3036
|
+
return { ok: false, stdout: "", error: result.error.message };
|
|
3037
|
+
}
|
|
3038
|
+
if (result.status !== 0) {
|
|
3039
|
+
return {
|
|
3040
|
+
ok: false,
|
|
3041
|
+
stdout: result.stdout ?? "",
|
|
3042
|
+
error: (result.stderr ?? "").trim() || `git exited with ${result.status}`
|
|
3043
|
+
};
|
|
3044
|
+
}
|
|
3045
|
+
return { ok: true, stdout: result.stdout ?? "", error: "" };
|
|
3046
|
+
}
|
|
3047
|
+
|
|
3048
|
+
// packages/parsers/src/node-fs.ts
|
|
3049
|
+
import fs2 from "node:fs";
|
|
3050
|
+
import path2 from "node:path";
|
|
3051
|
+
var NodeFileSystem = class {
|
|
3052
|
+
caseSensitive = process.platform !== "win32";
|
|
3053
|
+
root;
|
|
3054
|
+
constructor(root) {
|
|
3055
|
+
this.root = path2.resolve(root);
|
|
3056
|
+
}
|
|
3057
|
+
readFile(target) {
|
|
3058
|
+
return fs2.readFileSync(this.native(target), "utf8");
|
|
3059
|
+
}
|
|
3060
|
+
tryReadFile(target) {
|
|
3061
|
+
try {
|
|
3062
|
+
return fs2.readFileSync(this.native(target), "utf8");
|
|
3063
|
+
} catch (error) {
|
|
3064
|
+
if (error.code !== "ENOENT") {
|
|
3065
|
+
throw error;
|
|
3066
|
+
}
|
|
3067
|
+
return null;
|
|
3068
|
+
}
|
|
3069
|
+
}
|
|
3070
|
+
readDirectory(target) {
|
|
3071
|
+
const entries = fs2.readdirSync(this.native(target), { withFileTypes: true });
|
|
3072
|
+
return entries.map((entry) => ({
|
|
3073
|
+
name: entry.name,
|
|
3074
|
+
isDirectory: entry.isDirectory(),
|
|
3075
|
+
isFile: entry.isFile()
|
|
3076
|
+
}));
|
|
3077
|
+
}
|
|
3078
|
+
exists(target) {
|
|
3079
|
+
return fs2.existsSync(this.native(target));
|
|
3080
|
+
}
|
|
3081
|
+
/** Ruta real en disco de una ruta virtual. */
|
|
3082
|
+
native(target) {
|
|
3083
|
+
const relative2 = target.replace(/^\/+/, "");
|
|
3084
|
+
return relative2 === "" ? this.root : path2.join(this.root, relative2);
|
|
3085
|
+
}
|
|
3086
|
+
};
|
|
3087
|
+
|
|
3088
|
+
// packages/parsers/src/analyze.ts
|
|
3089
|
+
function analyzeRepository(rootInput, options = {}) {
|
|
3090
|
+
const fileSystem = options.fileSystem ?? new NodeFileSystem(rootInput);
|
|
3091
|
+
const useGit = options.git !== false;
|
|
3092
|
+
const result = analyzeFiles({
|
|
3093
|
+
fileSystem,
|
|
3094
|
+
root: options.root ?? "/",
|
|
3095
|
+
projectName: options.projectName,
|
|
3096
|
+
fallbackName: basename2(rootInput),
|
|
3097
|
+
ignore: options.ignore,
|
|
3098
|
+
extensions: options.extensions,
|
|
3099
|
+
parser: new TypeScriptParser(),
|
|
3100
|
+
churn: (relativePaths) => {
|
|
3101
|
+
const git = collectGitChurn(rootInput, relativePaths, { enabled: useGit });
|
|
3102
|
+
return {
|
|
3103
|
+
metrics: git.metrics,
|
|
3104
|
+
warnings: git.warnings,
|
|
3105
|
+
enabled: git.enabled,
|
|
3106
|
+
commits: git.commits,
|
|
3107
|
+
authors: git.authors,
|
|
3108
|
+
shallow: git.shallow
|
|
3109
|
+
};
|
|
3110
|
+
}
|
|
3111
|
+
});
|
|
3112
|
+
const churn = result.churn;
|
|
3113
|
+
return {
|
|
3114
|
+
...result,
|
|
2385
3115
|
git: {
|
|
2386
|
-
enabled:
|
|
2387
|
-
commits:
|
|
2388
|
-
authors:
|
|
2389
|
-
shallow:
|
|
2390
|
-
},
|
|
2391
|
-
migration: migration.summary,
|
|
2392
|
-
durationMs: Date.now() - started
|
|
2393
|
-
};
|
|
2394
|
-
}
|
|
2395
|
-
function readProjectName(root) {
|
|
2396
|
-
try {
|
|
2397
|
-
const text = fs4.readFileSync(path4.join(root, "package.json"), "utf8");
|
|
2398
|
-
const parsed = JSON.parse(text.replace(/^\uFEFF/, ""));
|
|
2399
|
-
if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed) && typeof parsed.name === "string") {
|
|
2400
|
-
const name = parsed.name.trim();
|
|
2401
|
-
if (name) return name;
|
|
3116
|
+
enabled: churn?.enabled ?? false,
|
|
3117
|
+
commits: churn?.commits ?? 0,
|
|
3118
|
+
authors: churn?.authors ?? 0,
|
|
3119
|
+
shallow: churn?.shallow ?? false
|
|
2402
3120
|
}
|
|
2403
|
-
}
|
|
2404
|
-
}
|
|
2405
|
-
return path4.basename(root) || "Proyecto";
|
|
3121
|
+
};
|
|
2406
3122
|
}
|
|
2407
3123
|
|
|
2408
3124
|
// packages/metrics/src/risk.ts
|
|
@@ -2471,6 +3187,318 @@ function clamp01(value) {
|
|
|
2471
3187
|
return Math.max(0, Math.min(1, value));
|
|
2472
3188
|
}
|
|
2473
3189
|
|
|
3190
|
+
// packages/metrics/src/profiles.ts
|
|
3191
|
+
var DOMAIN_PROFILES = [
|
|
3192
|
+
{
|
|
3193
|
+
id: "capas",
|
|
3194
|
+
title: "Capas por convenci\xF3n",
|
|
3195
|
+
description: "Tres capas: el dominio no depende de nada, la aplicaci\xF3n no depende de la infraestructura y la infraestructura no depende de la aplicaci\xF3n.",
|
|
3196
|
+
when: "Para repos con src/domain, src/application y src/infra.",
|
|
3197
|
+
layers: [
|
|
3198
|
+
{
|
|
3199
|
+
name: "domain",
|
|
3200
|
+
patterns: [
|
|
3201
|
+
"src/domain/**",
|
|
3202
|
+
"src/core/**",
|
|
3203
|
+
"src/dominio/**",
|
|
3204
|
+
"src/nucleo/**",
|
|
3205
|
+
"domain/**"
|
|
3206
|
+
]
|
|
3207
|
+
},
|
|
3208
|
+
{
|
|
3209
|
+
name: "application",
|
|
3210
|
+
patterns: [
|
|
3211
|
+
"src/application/**",
|
|
3212
|
+
"src/app/**",
|
|
3213
|
+
"src/use-cases/**",
|
|
3214
|
+
"src/usecases/**",
|
|
3215
|
+
"src/aplicacion/**",
|
|
3216
|
+
"src/casos-de-uso/**",
|
|
3217
|
+
"application/**"
|
|
3218
|
+
]
|
|
3219
|
+
},
|
|
3220
|
+
{
|
|
3221
|
+
name: "infrastructure",
|
|
3222
|
+
patterns: [
|
|
3223
|
+
"src/infrastructure/**",
|
|
3224
|
+
"src/infra/**",
|
|
3225
|
+
"src/adapters/**",
|
|
3226
|
+
"src/persistence/**",
|
|
3227
|
+
"src/infraestructura/**",
|
|
3228
|
+
"src/adaptadores/**",
|
|
3229
|
+
"infrastructure/**"
|
|
3230
|
+
]
|
|
3231
|
+
}
|
|
3232
|
+
],
|
|
3233
|
+
rules: [
|
|
3234
|
+
{
|
|
3235
|
+
type: "forbidden",
|
|
3236
|
+
name: "dominio-sin-capas-exteriores",
|
|
3237
|
+
from: "domain",
|
|
3238
|
+
to: "application",
|
|
3239
|
+
severity: "warn",
|
|
3240
|
+
message: "El dominio no debe depender de la aplicaci\xF3n"
|
|
3241
|
+
},
|
|
3242
|
+
{
|
|
3243
|
+
type: "forbidden",
|
|
3244
|
+
name: "dominio-sin-infraestructura",
|
|
3245
|
+
from: "domain",
|
|
3246
|
+
to: "infrastructure",
|
|
3247
|
+
severity: "warn",
|
|
3248
|
+
message: "El dominio no debe depender de la infraestructura"
|
|
3249
|
+
},
|
|
3250
|
+
{
|
|
3251
|
+
type: "forbidden",
|
|
3252
|
+
name: "aplicacion-sin-infraestructura",
|
|
3253
|
+
from: "application",
|
|
3254
|
+
to: "infrastructure",
|
|
3255
|
+
severity: "warn",
|
|
3256
|
+
message: "La aplicaci\xF3n no debe depender de la infraestructura"
|
|
3257
|
+
}
|
|
3258
|
+
]
|
|
3259
|
+
},
|
|
3260
|
+
{
|
|
3261
|
+
id: "hexagonal",
|
|
3262
|
+
title: "Ports & adapters",
|
|
3263
|
+
description: "El dominio en el centro, la aplicaci\xF3n con los puertos y los adaptadores por fuera: la aplicaci\xF3n y el dominio no dependen de los adaptadores.",
|
|
3264
|
+
when: "Para repos con puertos (interfaces) en el centro y adaptadores fuera.",
|
|
3265
|
+
layers: [
|
|
3266
|
+
{
|
|
3267
|
+
name: "domain",
|
|
3268
|
+
patterns: [
|
|
3269
|
+
"src/domain/**",
|
|
3270
|
+
"src/core/**",
|
|
3271
|
+
"src/model/**",
|
|
3272
|
+
"src/dominio/**",
|
|
3273
|
+
"src/modelo/**",
|
|
3274
|
+
"domain/**"
|
|
3275
|
+
]
|
|
3276
|
+
},
|
|
3277
|
+
{
|
|
3278
|
+
name: "application",
|
|
3279
|
+
patterns: [
|
|
3280
|
+
"src/application/**",
|
|
3281
|
+
"src/app/**",
|
|
3282
|
+
"src/use-cases/**",
|
|
3283
|
+
"src/usecases/**",
|
|
3284
|
+
"src/ports/**",
|
|
3285
|
+
"src/puertos/**",
|
|
3286
|
+
"application/**"
|
|
3287
|
+
]
|
|
3288
|
+
},
|
|
3289
|
+
{
|
|
3290
|
+
name: "adapters",
|
|
3291
|
+
patterns: [
|
|
3292
|
+
"src/adapters/**",
|
|
3293
|
+
"src/infra/**",
|
|
3294
|
+
"src/infrastructure/**",
|
|
3295
|
+
"src/persistence/**",
|
|
3296
|
+
"src/adaptadores/**",
|
|
3297
|
+
"src/infraestructura/**",
|
|
3298
|
+
"adapters/**"
|
|
3299
|
+
]
|
|
3300
|
+
}
|
|
3301
|
+
],
|
|
3302
|
+
rules: [
|
|
3303
|
+
{
|
|
3304
|
+
type: "forbidden",
|
|
3305
|
+
name: "dominio-sin-adaptadores",
|
|
3306
|
+
from: "domain",
|
|
3307
|
+
to: "adapters",
|
|
3308
|
+
severity: "warn",
|
|
3309
|
+
message: "El dominio no debe depender de los adaptadores"
|
|
3310
|
+
},
|
|
3311
|
+
{
|
|
3312
|
+
type: "forbidden",
|
|
3313
|
+
name: "aplicacion-sin-adaptadores",
|
|
3314
|
+
from: "application",
|
|
3315
|
+
to: "adapters",
|
|
3316
|
+
severity: "warn",
|
|
3317
|
+
message: "La aplicaci\xF3n habla con los adaptadores por puertos, no directamente"
|
|
3318
|
+
}
|
|
3319
|
+
]
|
|
3320
|
+
},
|
|
3321
|
+
{
|
|
3322
|
+
id: "onion",
|
|
3323
|
+
title: "Clean Architecture (cebolla)",
|
|
3324
|
+
description: "Cuatro anillos: el dominio en el centro, luego la aplicaci\xF3n, la infraestructura y la presentaci\xF3n fuera. Las dependencias van hacia dentro y el anillo exterior no se importa desde dentro.",
|
|
3325
|
+
when: "Para repos con una capa de presentaci\xF3n o de interfaz propia.",
|
|
3326
|
+
layers: [
|
|
3327
|
+
{
|
|
3328
|
+
name: "domain",
|
|
3329
|
+
patterns: [
|
|
3330
|
+
"src/domain/**",
|
|
3331
|
+
"src/entities/**",
|
|
3332
|
+
"src/value-objects/**",
|
|
3333
|
+
"src/dominio/**",
|
|
3334
|
+
"src/entidades/**",
|
|
3335
|
+
"domain/**"
|
|
3336
|
+
]
|
|
3337
|
+
},
|
|
3338
|
+
{
|
|
3339
|
+
name: "application",
|
|
3340
|
+
patterns: [
|
|
3341
|
+
"src/application/**",
|
|
3342
|
+
"src/use-cases/**",
|
|
3343
|
+
"src/usecases/**",
|
|
3344
|
+
"src/services/**",
|
|
3345
|
+
"src/aplicacion/**",
|
|
3346
|
+
"src/casos-de-uso/**",
|
|
3347
|
+
"application/**"
|
|
3348
|
+
]
|
|
3349
|
+
},
|
|
3350
|
+
{
|
|
3351
|
+
name: "infrastructure",
|
|
3352
|
+
patterns: [
|
|
3353
|
+
"src/infrastructure/**",
|
|
3354
|
+
"src/infra/**",
|
|
3355
|
+
"src/persistence/**",
|
|
3356
|
+
"src/repositories/**",
|
|
3357
|
+
"src/infraestructura/**",
|
|
3358
|
+
"infrastructure/**"
|
|
3359
|
+
]
|
|
3360
|
+
},
|
|
3361
|
+
{
|
|
3362
|
+
name: "presentation",
|
|
3363
|
+
patterns: [
|
|
3364
|
+
"src/presentation/**",
|
|
3365
|
+
"src/ui/**",
|
|
3366
|
+
"src/controllers/**",
|
|
3367
|
+
"src/pages/**",
|
|
3368
|
+
"src/components/**",
|
|
3369
|
+
"src/presentacion/**",
|
|
3370
|
+
"src/interfaz/**",
|
|
3371
|
+
"presentation/**"
|
|
3372
|
+
]
|
|
3373
|
+
}
|
|
3374
|
+
],
|
|
3375
|
+
rules: [
|
|
3376
|
+
{
|
|
3377
|
+
type: "forbidden",
|
|
3378
|
+
name: "dominio-sin-capas-exteriores",
|
|
3379
|
+
from: "domain",
|
|
3380
|
+
to: "application",
|
|
3381
|
+
severity: "warn",
|
|
3382
|
+
message: "El dominio no debe depender de la aplicaci\xF3n"
|
|
3383
|
+
},
|
|
3384
|
+
{
|
|
3385
|
+
type: "forbidden",
|
|
3386
|
+
name: "dominio-sin-infraestructura",
|
|
3387
|
+
from: "domain",
|
|
3388
|
+
to: "infrastructure",
|
|
3389
|
+
severity: "warn",
|
|
3390
|
+
message: "El dominio no debe depender de la infraestructura"
|
|
3391
|
+
},
|
|
3392
|
+
{
|
|
3393
|
+
type: "forbidden",
|
|
3394
|
+
name: "dominio-sin-presentacion",
|
|
3395
|
+
from: "domain",
|
|
3396
|
+
to: "presentation",
|
|
3397
|
+
severity: "warn",
|
|
3398
|
+
message: "El dominio no debe depender de la presentaci\xF3n"
|
|
3399
|
+
},
|
|
3400
|
+
{
|
|
3401
|
+
type: "forbidden",
|
|
3402
|
+
name: "aplicacion-sin-infraestructura",
|
|
3403
|
+
from: "application",
|
|
3404
|
+
to: "infrastructure",
|
|
3405
|
+
severity: "warn",
|
|
3406
|
+
message: "La aplicaci\xF3n no debe depender de la infraestructura"
|
|
3407
|
+
},
|
|
3408
|
+
{
|
|
3409
|
+
type: "forbidden",
|
|
3410
|
+
name: "aplicacion-sin-presentacion",
|
|
3411
|
+
from: "application",
|
|
3412
|
+
to: "presentation",
|
|
3413
|
+
severity: "warn",
|
|
3414
|
+
message: "La aplicaci\xF3n no debe depender de la presentaci\xF3n"
|
|
3415
|
+
},
|
|
3416
|
+
{
|
|
3417
|
+
type: "forbidden",
|
|
3418
|
+
name: "infraestructura-sin-presentacion",
|
|
3419
|
+
from: "infrastructure",
|
|
3420
|
+
to: "presentation",
|
|
3421
|
+
severity: "warn",
|
|
3422
|
+
message: "La infraestructura y la presentaci\xF3n se hablan por los puertos, no entre ellas"
|
|
3423
|
+
}
|
|
3424
|
+
]
|
|
3425
|
+
},
|
|
3426
|
+
{
|
|
3427
|
+
id: "ddd",
|
|
3428
|
+
title: "DDD por carpetas",
|
|
3429
|
+
description: "Las carpetas del dominio driven design: el modelo dentro, los servicios de aplicaci\xF3n y los repositorios en la infraestructura.",
|
|
3430
|
+
when: "Para repos con modelo, servicios y repositorios como carpetas.",
|
|
3431
|
+
layers: [
|
|
3432
|
+
{
|
|
3433
|
+
name: "domain",
|
|
3434
|
+
patterns: [
|
|
3435
|
+
"src/domain/**",
|
|
3436
|
+
"src/model/**",
|
|
3437
|
+
"src/entities/**",
|
|
3438
|
+
"src/value-objects/**",
|
|
3439
|
+
"src/dominio/**",
|
|
3440
|
+
"src/modelo/**",
|
|
3441
|
+
"domain/**"
|
|
3442
|
+
]
|
|
3443
|
+
},
|
|
3444
|
+
{
|
|
3445
|
+
name: "application",
|
|
3446
|
+
patterns: [
|
|
3447
|
+
"src/application/**",
|
|
3448
|
+
"src/services/**",
|
|
3449
|
+
"src/use-cases/**",
|
|
3450
|
+
"src/aplicacion/**",
|
|
3451
|
+
"src/servicios/**",
|
|
3452
|
+
"application/**"
|
|
3453
|
+
]
|
|
3454
|
+
},
|
|
3455
|
+
{
|
|
3456
|
+
name: "infrastructure",
|
|
3457
|
+
patterns: [
|
|
3458
|
+
"src/infrastructure/**",
|
|
3459
|
+
"src/repositories/**",
|
|
3460
|
+
"src/persistence/**",
|
|
3461
|
+
"src/infraestructura/**",
|
|
3462
|
+
"src/repositorios/**",
|
|
3463
|
+
"infrastructure/**"
|
|
3464
|
+
]
|
|
3465
|
+
}
|
|
3466
|
+
],
|
|
3467
|
+
rules: [
|
|
3468
|
+
{
|
|
3469
|
+
type: "forbidden",
|
|
3470
|
+
name: "dominio-sin-capas-exteriores",
|
|
3471
|
+
from: "domain",
|
|
3472
|
+
to: "application",
|
|
3473
|
+
severity: "warn",
|
|
3474
|
+
message: "El modelo de dominio no debe depender de los servicios"
|
|
3475
|
+
},
|
|
3476
|
+
{
|
|
3477
|
+
type: "forbidden",
|
|
3478
|
+
name: "dominio-sin-infraestructura",
|
|
3479
|
+
from: "domain",
|
|
3480
|
+
to: "infrastructure",
|
|
3481
|
+
severity: "warn",
|
|
3482
|
+
message: "El modelo de dominio no debe depender de los repositorios"
|
|
3483
|
+
},
|
|
3484
|
+
{
|
|
3485
|
+
type: "forbidden",
|
|
3486
|
+
name: "aplicacion-sin-infraestructura",
|
|
3487
|
+
from: "application",
|
|
3488
|
+
to: "infrastructure",
|
|
3489
|
+
severity: "warn",
|
|
3490
|
+
message: "Los servicios hablan con los repositorios por puertos, no directamente"
|
|
3491
|
+
}
|
|
3492
|
+
]
|
|
3493
|
+
}
|
|
3494
|
+
];
|
|
3495
|
+
function findDomainProfile(id) {
|
|
3496
|
+
return DOMAIN_PROFILES.find((profile) => profile.id === id);
|
|
3497
|
+
}
|
|
3498
|
+
function domainProfileIds() {
|
|
3499
|
+
return DOMAIN_PROFILES.map((profile) => profile.id);
|
|
3500
|
+
}
|
|
3501
|
+
|
|
2474
3502
|
// packages/render/src/model.ts
|
|
2475
3503
|
var RenderError = class extends Error {
|
|
2476
3504
|
constructor(message) {
|
|
@@ -2541,8 +3569,8 @@ function hashString(value) {
|
|
|
2541
3569
|
}
|
|
2542
3570
|
return hash >>> 0;
|
|
2543
3571
|
}
|
|
2544
|
-
function sectionForPath(
|
|
2545
|
-
const folders =
|
|
3572
|
+
function sectionForPath(path7) {
|
|
3573
|
+
const folders = path7.split("/").filter(Boolean).slice(0, -1);
|
|
2546
3574
|
if (folders.length === 0) return "(ra\xEDz)";
|
|
2547
3575
|
if (folders.length === 1) return folders[0];
|
|
2548
3576
|
return folders.slice(0, 3).join("/");
|
|
@@ -2612,7 +3640,7 @@ function buildRenderModel(cc, options = {}) {
|
|
|
2612
3640
|
const fileIndexById = /* @__PURE__ */ new Map();
|
|
2613
3641
|
walkFiles(cc.files, (entry) => {
|
|
2614
3642
|
const fullPath = entry.path;
|
|
2615
|
-
const
|
|
3643
|
+
const path7 = prefix && fullPath.startsWith(prefix) ? fullPath.slice(prefix.length) : fullPath;
|
|
2616
3644
|
const values = attributes[entry.node.id];
|
|
2617
3645
|
const rawArea = valueOf(values, areaMetric);
|
|
2618
3646
|
const index = files.length;
|
|
@@ -2620,7 +3648,7 @@ function buildRenderModel(cc, options = {}) {
|
|
|
2620
3648
|
files.push({
|
|
2621
3649
|
index,
|
|
2622
3650
|
nodeId: entry.node.id,
|
|
2623
|
-
path:
|
|
3651
|
+
path: path7,
|
|
2624
3652
|
name: entry.node.name,
|
|
2625
3653
|
section: -1,
|
|
2626
3654
|
area: Math.max(1, rawArea ?? 0),
|
|
@@ -2783,7 +3811,7 @@ function buildRenderModel(cc, options = {}) {
|
|
|
2783
3811
|
};
|
|
2784
3812
|
}
|
|
2785
3813
|
function buildRoots(cc, singleRoot, fileIndexById, files) {
|
|
2786
|
-
const build = (node,
|
|
3814
|
+
const build = (node, path7) => {
|
|
2787
3815
|
if (node.type === "File") {
|
|
2788
3816
|
const index = fileIndexById.get(node.id);
|
|
2789
3817
|
const file = index === void 0 ? void 0 : files[index];
|
|
@@ -2791,7 +3819,7 @@ function buildRoots(cc, singleRoot, fileIndexById, files) {
|
|
|
2791
3819
|
kind: "file",
|
|
2792
3820
|
key: `f:${index ?? -1}`,
|
|
2793
3821
|
name: node.name,
|
|
2794
|
-
path:
|
|
3822
|
+
path: path7,
|
|
2795
3823
|
value: file?.area ?? 1,
|
|
2796
3824
|
fileCount: 1,
|
|
2797
3825
|
section: file?.section ?? 0,
|
|
@@ -2799,13 +3827,13 @@ function buildRoots(cc, singleRoot, fileIndexById, files) {
|
|
|
2799
3827
|
};
|
|
2800
3828
|
}
|
|
2801
3829
|
const children = (node.children ?? []).map(
|
|
2802
|
-
(child) => build(child,
|
|
3830
|
+
(child) => build(child, path7 ? `${path7}/${child.name}` : child.name)
|
|
2803
3831
|
);
|
|
2804
3832
|
const folder = {
|
|
2805
3833
|
kind: "folder",
|
|
2806
|
-
key: `d:${
|
|
3834
|
+
key: `d:${path7}`,
|
|
2807
3835
|
name: node.name,
|
|
2808
|
-
path:
|
|
3836
|
+
path: path7,
|
|
2809
3837
|
value: children.reduce((sum, child) => sum + child.value, 0) || 1,
|
|
2810
3838
|
fileCount: children.reduce((sum, child) => sum + child.fileCount, 0),
|
|
2811
3839
|
section: -1,
|
|
@@ -3449,10 +4477,10 @@ function renderHtml(model) {
|
|
|
3449
4477
|
}
|
|
3450
4478
|
|
|
3451
4479
|
// apps/cli/src/io.ts
|
|
3452
|
-
import
|
|
4480
|
+
import fs3 from "node:fs";
|
|
3453
4481
|
import zlib from "node:zlib";
|
|
3454
4482
|
function readJsonFile2(file) {
|
|
3455
|
-
const buffer =
|
|
4483
|
+
const buffer = fs3.readFileSync(file);
|
|
3456
4484
|
const isGzip = buffer.length > 2 && buffer[0] === 31 && buffer[1] === 139;
|
|
3457
4485
|
const raw = isGzip ? zlib.gunzipSync(buffer) : buffer;
|
|
3458
4486
|
const text = raw.toString("utf8");
|
|
@@ -3500,14 +4528,14 @@ function runAnalyze(options) {
|
|
|
3500
4528
|
}
|
|
3501
4529
|
throw error;
|
|
3502
4530
|
}
|
|
3503
|
-
const outDirectory =
|
|
3504
|
-
options.out ??
|
|
4531
|
+
const outDirectory = path3.resolve(
|
|
4532
|
+
options.out ?? path3.join(path3.resolve(root), ".codarch")
|
|
3505
4533
|
);
|
|
3506
|
-
const snapshotPath =
|
|
3507
|
-
const mapPath =
|
|
4534
|
+
const snapshotPath = path3.join(outDirectory, "snapshot.cc.json");
|
|
4535
|
+
const mapPath = path3.join(outDirectory, "map.html");
|
|
3508
4536
|
try {
|
|
3509
|
-
|
|
3510
|
-
|
|
4537
|
+
fs4.mkdirSync(outDirectory, { recursive: true });
|
|
4538
|
+
fs4.writeFileSync(
|
|
3511
4539
|
snapshotPath,
|
|
3512
4540
|
`${JSON.stringify(cc, null, 2)}
|
|
3513
4541
|
`,
|
|
@@ -3529,7 +4557,7 @@ function runAnalyze(options) {
|
|
|
3529
4557
|
title: options.title
|
|
3530
4558
|
});
|
|
3531
4559
|
const html = renderHtml(model);
|
|
3532
|
-
|
|
4560
|
+
fs4.writeFileSync(mapPath, html, "utf8");
|
|
3533
4561
|
mapBytes = Buffer.byteLength(html, "utf8");
|
|
3534
4562
|
} catch (error) {
|
|
3535
4563
|
mapError = error instanceof RenderError ? error.message : error.message;
|
|
@@ -3538,14 +4566,14 @@ function runAnalyze(options) {
|
|
|
3538
4566
|
mapError = "no hay ficheros TypeScript/JavaScript que dibujar";
|
|
3539
4567
|
}
|
|
3540
4568
|
const snapshotBytes = Buffer.byteLength(
|
|
3541
|
-
|
|
4569
|
+
fs4.readFileSync(snapshotPath),
|
|
3542
4570
|
"utf8"
|
|
3543
4571
|
);
|
|
3544
4572
|
const lines = [];
|
|
3545
4573
|
lines.push("");
|
|
3546
4574
|
lines.push(" CodArch \xB7 an\xE1lisis");
|
|
3547
4575
|
lines.push("");
|
|
3548
|
-
lines.push(field("ra\xEDz",
|
|
4576
|
+
lines.push(field("ra\xEDz", path3.resolve(root)));
|
|
3549
4577
|
lines.push(field("snapshot", `${snapshotPath} (${formatBytes(snapshotBytes)})`));
|
|
3550
4578
|
if (mapError) {
|
|
3551
4579
|
lines.push(field("mapa", `omitido (${mapError})`));
|
|
@@ -3615,11 +4643,11 @@ function runAnalyze(options) {
|
|
|
3615
4643
|
}
|
|
3616
4644
|
|
|
3617
4645
|
// apps/cli/src/commands/diff.ts
|
|
3618
|
-
import
|
|
3619
|
-
import
|
|
4646
|
+
import fs6 from "node:fs";
|
|
4647
|
+
import path4 from "node:path";
|
|
3620
4648
|
|
|
3621
4649
|
// packages/metrics/src/config.ts
|
|
3622
|
-
import
|
|
4650
|
+
import fs5 from "node:fs";
|
|
3623
4651
|
import { parse as parseYaml } from "yaml";
|
|
3624
4652
|
var ConfigError = class extends Error {
|
|
3625
4653
|
constructor(message) {
|
|
@@ -3633,15 +4661,108 @@ function parseRuleConfig(input) {
|
|
|
3633
4661
|
if (root.version !== 1) {
|
|
3634
4662
|
throw new ConfigError("config.version debe ser 1");
|
|
3635
4663
|
}
|
|
3636
|
-
const
|
|
4664
|
+
const own = parseInlineProfiles(root.profiles);
|
|
4665
|
+
const profile = parseProfile(root.profile, own);
|
|
4666
|
+
const profileSeverity = parseProfileSeverity(root.profile_severity);
|
|
4667
|
+
const layers = mergeLayers(profile, parseLayers(root.layers));
|
|
3637
4668
|
const layerNames = new Set(layers.map((layer) => layer.name));
|
|
3638
|
-
const rules =
|
|
3639
|
-
|
|
4669
|
+
const rules = mergeRules(
|
|
4670
|
+
profile,
|
|
4671
|
+
profileSeverity,
|
|
4672
|
+
parseRules(root.rules, layerNames),
|
|
4673
|
+
layerNames
|
|
4674
|
+
);
|
|
4675
|
+
return { version: 1, profile: profile?.id ?? null, layers, rules };
|
|
4676
|
+
}
|
|
4677
|
+
function parseInlineProfiles(value) {
|
|
4678
|
+
const profiles = /* @__PURE__ */ new Map();
|
|
4679
|
+
if (value === void 0 || value === null) return profiles;
|
|
4680
|
+
if (!Array.isArray(value)) {
|
|
4681
|
+
throw new ConfigError("config.profiles debe ser una lista");
|
|
4682
|
+
}
|
|
4683
|
+
value.forEach((raw, index) => {
|
|
4684
|
+
const entry = asObject(raw, `config.profiles[${index}]`);
|
|
4685
|
+
const id = asString(entry.id, `config.profiles[${index}].id`);
|
|
4686
|
+
const title = asString(entry.title, `config.profiles[${index}].title`);
|
|
4687
|
+
const description = entry.description === void 0 ? "" : asString(entry.description, `config.profiles[${index}].description`);
|
|
4688
|
+
const layers = parseLayers(entry.layers).map((layer) => ({
|
|
4689
|
+
name: layer.name,
|
|
4690
|
+
patterns: layer.patterns
|
|
4691
|
+
}));
|
|
4692
|
+
if (layers.length === 0) {
|
|
4693
|
+
throw new ConfigError(
|
|
4694
|
+
`config.profiles[${index}] necesita al menos una capa`
|
|
4695
|
+
);
|
|
4696
|
+
}
|
|
4697
|
+
const layerNames = new Set(layers.map((layer) => layer.name));
|
|
4698
|
+
const rules = parseRules(entry.rules, layerNames).filter(
|
|
4699
|
+
(rule) => rule.type === "forbidden"
|
|
4700
|
+
);
|
|
4701
|
+
profiles.set(id, {
|
|
4702
|
+
id,
|
|
4703
|
+
title,
|
|
4704
|
+
description,
|
|
4705
|
+
when: `Perfil propio del equipo, definido en ${"el archivo de reglas"}.`,
|
|
4706
|
+
layers,
|
|
4707
|
+
rules
|
|
4708
|
+
});
|
|
4709
|
+
});
|
|
4710
|
+
return profiles;
|
|
4711
|
+
}
|
|
4712
|
+
function parseProfile(value, own) {
|
|
4713
|
+
if (value === void 0 || value === null) return null;
|
|
4714
|
+
const id = asString(value, "config.profile");
|
|
4715
|
+
const profile = own.get(id) ?? findDomainProfile(id);
|
|
4716
|
+
if (!profile) {
|
|
4717
|
+
const available = [...own.keys(), ...domainProfileIds()].join(", ");
|
|
4718
|
+
throw new ConfigError(
|
|
4719
|
+
`config.profile desconocido: ${id}. Disponibles: ${available}`
|
|
4720
|
+
);
|
|
4721
|
+
}
|
|
4722
|
+
return profile;
|
|
4723
|
+
}
|
|
4724
|
+
function parseProfileSeverity(value) {
|
|
4725
|
+
if (value === void 0 || value === null) return "warn";
|
|
4726
|
+
if (typeof value !== "string" || !SEVERITIES.has(value)) {
|
|
4727
|
+
throw new ConfigError(
|
|
4728
|
+
"config.profile_severity debe ser info, warn o error"
|
|
4729
|
+
);
|
|
4730
|
+
}
|
|
4731
|
+
return value;
|
|
4732
|
+
}
|
|
4733
|
+
function mergeLayers(profile, own) {
|
|
4734
|
+
const layers = /* @__PURE__ */ new Map();
|
|
4735
|
+
for (const layer of profile?.layers ?? []) layers.set(layer.name, layer);
|
|
4736
|
+
for (const layer of own) layers.set(layer.name, layer);
|
|
4737
|
+
return [...layers.values()];
|
|
4738
|
+
}
|
|
4739
|
+
function mergeRules(profile, profileSeverity, own, layerNames) {
|
|
4740
|
+
const rules = /* @__PURE__ */ new Map();
|
|
4741
|
+
for (const rule of profile?.rules ?? []) {
|
|
4742
|
+
rules.set(rule.name, {
|
|
4743
|
+
...rule,
|
|
4744
|
+
severity: profileSeverity,
|
|
4745
|
+
source: "profile"
|
|
4746
|
+
});
|
|
4747
|
+
}
|
|
4748
|
+
for (const rule of own) {
|
|
4749
|
+
rules.set(rule.name, { ...rule, source: "config" });
|
|
4750
|
+
}
|
|
4751
|
+
for (const rule of rules.values()) {
|
|
4752
|
+
if (rule.type !== "forbidden") continue;
|
|
4753
|
+
if (layerNames.has(rule.from) && layerNames.has(rule.to)) continue;
|
|
4754
|
+
if (rule.source === "profile") {
|
|
4755
|
+
throw new ConfigError(
|
|
4756
|
+
`la regla "${rule.name}" del perfil apunta a una capa que no existe: ${rule.from} \u2192 ${rule.to}`
|
|
4757
|
+
);
|
|
4758
|
+
}
|
|
4759
|
+
}
|
|
4760
|
+
return [...rules.values()];
|
|
3640
4761
|
}
|
|
3641
4762
|
function loadRuleConfig(file) {
|
|
3642
4763
|
let text;
|
|
3643
4764
|
try {
|
|
3644
|
-
text =
|
|
4765
|
+
text = fs5.readFileSync(file, "utf8");
|
|
3645
4766
|
} catch (error) {
|
|
3646
4767
|
throw new ConfigError(
|
|
3647
4768
|
`no se pudo leer ${file}: ${error.message}`
|
|
@@ -3771,29 +4892,29 @@ function parseRules(value, layerNames) {
|
|
|
3771
4892
|
});
|
|
3772
4893
|
return rules;
|
|
3773
4894
|
}
|
|
3774
|
-
function parseSeverity(value,
|
|
4895
|
+
function parseSeverity(value, path7) {
|
|
3775
4896
|
if (value === void 0) return "error";
|
|
3776
4897
|
if (typeof value !== "string" || !SEVERITIES.has(value)) {
|
|
3777
|
-
throw new ConfigError(`${
|
|
4898
|
+
throw new ConfigError(`${path7} debe ser info, warn o error`);
|
|
3778
4899
|
}
|
|
3779
4900
|
return value;
|
|
3780
4901
|
}
|
|
3781
|
-
function optionalNumber(value,
|
|
4902
|
+
function optionalNumber(value, path7) {
|
|
3782
4903
|
if (value === void 0) return void 0;
|
|
3783
4904
|
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
3784
|
-
throw new ConfigError(`${
|
|
4905
|
+
throw new ConfigError(`${path7} debe ser un n\xFAmero`);
|
|
3785
4906
|
}
|
|
3786
4907
|
return value;
|
|
3787
4908
|
}
|
|
3788
|
-
function asObject(value,
|
|
4909
|
+
function asObject(value, path7) {
|
|
3789
4910
|
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
3790
|
-
throw new ConfigError(`${
|
|
4911
|
+
throw new ConfigError(`${path7} debe ser un objeto`);
|
|
3791
4912
|
}
|
|
3792
4913
|
return value;
|
|
3793
4914
|
}
|
|
3794
|
-
function asString(value,
|
|
4915
|
+
function asString(value, path7) {
|
|
3795
4916
|
if (typeof value !== "string" || value.trim() === "") {
|
|
3796
|
-
throw new ConfigError(`${
|
|
4917
|
+
throw new ConfigError(`${path7} debe ser un texto no vac\xEDo`);
|
|
3797
4918
|
}
|
|
3798
4919
|
return value.trim();
|
|
3799
4920
|
}
|
|
@@ -3803,6 +4924,7 @@ import { minimatch } from "minimatch";
|
|
|
3803
4924
|
function emptyRuleReport() {
|
|
3804
4925
|
return {
|
|
3805
4926
|
configured: false,
|
|
4927
|
+
profile: null,
|
|
3806
4928
|
summary: {
|
|
3807
4929
|
layers: 0,
|
|
3808
4930
|
rules: 0,
|
|
@@ -3839,6 +4961,7 @@ function evaluateRules(base, head, config) {
|
|
|
3839
4961
|
resolved.sort(compareViolations);
|
|
3840
4962
|
return {
|
|
3841
4963
|
configured: true,
|
|
4964
|
+
profile: config.profile ?? null,
|
|
3842
4965
|
summary: {
|
|
3843
4966
|
layers: config.layers.length,
|
|
3844
4967
|
rules: config.rules.length,
|
|
@@ -3894,6 +5017,7 @@ function evaluateForbidden(rule, input, layers, violations) {
|
|
|
3894
5017
|
rule: rule.name,
|
|
3895
5018
|
type: "forbidden",
|
|
3896
5019
|
severity: rule.severity,
|
|
5020
|
+
source: rule.source ?? "config",
|
|
3897
5021
|
message: rule.message ?? `${edge.from} no puede depender de ${edge.to}`,
|
|
3898
5022
|
files: [edge.from, edge.to],
|
|
3899
5023
|
edge: {
|
|
@@ -3918,6 +5042,7 @@ function evaluateThreshold(rule, input, violations) {
|
|
|
3918
5042
|
rule: rule.name,
|
|
3919
5043
|
type: "threshold",
|
|
3920
5044
|
severity: rule.severity,
|
|
5045
|
+
source: rule.source ?? "config",
|
|
3921
5046
|
message: rule.message ?? `${file.path}: ${rule.metric} = ${value} (${limit})`,
|
|
3922
5047
|
files: [file.path],
|
|
3923
5048
|
metric: {
|
|
@@ -3937,6 +5062,7 @@ function evaluateCycles(rule, cycles, violations) {
|
|
|
3937
5062
|
rule: rule.name,
|
|
3938
5063
|
type: "cycles",
|
|
3939
5064
|
severity: rule.severity,
|
|
5065
|
+
source: rule.source ?? "config",
|
|
3940
5066
|
message: rule.message ?? cycleMessage(paths),
|
|
3941
5067
|
files: paths,
|
|
3942
5068
|
cycle: {
|
|
@@ -3983,7 +5109,7 @@ function diffSnapshots(base, head, options = {}) {
|
|
|
3983
5109
|
options.rules
|
|
3984
5110
|
) : emptyRuleReport();
|
|
3985
5111
|
return {
|
|
3986
|
-
schemaVersion: "1.
|
|
5112
|
+
schemaVersion: "1.2",
|
|
3987
5113
|
base: summarize(baseIndex),
|
|
3988
5114
|
head: summarize(headIndex),
|
|
3989
5115
|
summary: {
|
|
@@ -4003,9 +5129,94 @@ function diffSnapshots(base, head, options = {}) {
|
|
|
4003
5129
|
files: { added, removed, changed },
|
|
4004
5130
|
edges: edgeDiff,
|
|
4005
5131
|
cycles: { introduced: introducedCycles, fixed: fixedCycles },
|
|
4006
|
-
hotspots
|
|
5132
|
+
hotspots,
|
|
5133
|
+
migration: buildMigrationReport(baseIndex, headIndex, top)
|
|
5134
|
+
};
|
|
5135
|
+
}
|
|
5136
|
+
function buildMigrationReport(baseIndex, headIndex, top) {
|
|
5137
|
+
const baseLens = baseIndex.cc.lenses?.migration;
|
|
5138
|
+
const headLens = headIndex.cc.lenses?.migration;
|
|
5139
|
+
if (!baseLens && !headLens) return null;
|
|
5140
|
+
const baseNodes = baseLens?.nodes ?? {};
|
|
5141
|
+
const headNodes = headLens?.nodes ?? {};
|
|
5142
|
+
const changes = [];
|
|
5143
|
+
for (const id of /* @__PURE__ */ new Set([...Object.keys(baseNodes), ...Object.keys(headNodes)])) {
|
|
5144
|
+
const base = baseNodes[id];
|
|
5145
|
+
const head = headNodes[id];
|
|
5146
|
+
if (!base && !head) continue;
|
|
5147
|
+
const reference = head ?? base;
|
|
5148
|
+
const baseDebt = base ? base.debt ?? 0 : null;
|
|
5149
|
+
const headDebt = head ? head.debt ?? 0 : null;
|
|
5150
|
+
const delta = baseDebt === null || headDebt === null ? null : headDebt - baseDebt;
|
|
5151
|
+
if (delta === null || delta === 0) continue;
|
|
5152
|
+
changes.push({
|
|
5153
|
+
id,
|
|
5154
|
+
path: headIndex.paths.get(id) ?? baseIndex.paths.get(id) ?? id,
|
|
5155
|
+
language: reference.language,
|
|
5156
|
+
status: reference.status,
|
|
5157
|
+
base: baseDebt,
|
|
5158
|
+
head: headDebt,
|
|
5159
|
+
delta
|
|
5160
|
+
});
|
|
5161
|
+
}
|
|
5162
|
+
const increased = changes.filter((change) => (change.delta ?? 0) > 0).sort(
|
|
5163
|
+
(left, right) => (right.delta ?? 0) - (left.delta ?? 0) || compareByPath(left, right)
|
|
5164
|
+
);
|
|
5165
|
+
const decreased = changes.filter((change) => (change.delta ?? 0) < 0).sort(
|
|
5166
|
+
(left, right) => (left.delta ?? 0) - (right.delta ?? 0) || compareByPath(left, right)
|
|
5167
|
+
);
|
|
5168
|
+
const topDebt = [];
|
|
5169
|
+
for (const [id, node] of Object.entries(headNodes)) {
|
|
5170
|
+
if (!node.debt) continue;
|
|
5171
|
+
topDebt.push({
|
|
5172
|
+
id,
|
|
5173
|
+
path: headIndex.paths.get(id) ?? id,
|
|
5174
|
+
language: node.language,
|
|
5175
|
+
status: node.status,
|
|
5176
|
+
debt: node.debt,
|
|
5177
|
+
rloc: node.rloc,
|
|
5178
|
+
crossings: node.crossings
|
|
5179
|
+
});
|
|
5180
|
+
}
|
|
5181
|
+
topDebt.sort(
|
|
5182
|
+
(left, right) => right.debt - left.debt || compareByPath(left, right)
|
|
5183
|
+
);
|
|
5184
|
+
const baseSummary = baseLens?.summary;
|
|
5185
|
+
const headSummary = headLens?.summary;
|
|
5186
|
+
return {
|
|
5187
|
+
languages: {
|
|
5188
|
+
base: baseSummary?.languages ?? {},
|
|
5189
|
+
head: headSummary?.languages ?? {}
|
|
5190
|
+
},
|
|
5191
|
+
status: {
|
|
5192
|
+
base: baseSummary?.status ?? emptyStatus(),
|
|
5193
|
+
head: headSummary?.status ?? emptyStatus()
|
|
5194
|
+
},
|
|
5195
|
+
debt: {
|
|
5196
|
+
base: baseSummary?.migrationDebt ?? 0,
|
|
5197
|
+
head: headSummary?.migrationDebt ?? 0,
|
|
5198
|
+
delta: (headSummary?.migrationDebt ?? 0) - (baseSummary?.migrationDebt ?? 0)
|
|
5199
|
+
},
|
|
5200
|
+
crossings: {
|
|
5201
|
+
base: baseSummary?.ffiCrossings ?? 0,
|
|
5202
|
+
head: headSummary?.ffiCrossings ?? 0,
|
|
5203
|
+
delta: (headSummary?.ffiCrossings ?? 0) - (baseSummary?.ffiCrossings ?? 0)
|
|
5204
|
+
},
|
|
5205
|
+
bridgeFiles: {
|
|
5206
|
+
base: baseSummary?.bridgeFiles ?? 0,
|
|
5207
|
+
head: headSummary?.bridgeFiles ?? 0,
|
|
5208
|
+
delta: (headSummary?.bridgeFiles ?? 0) - (baseSummary?.bridgeFiles ?? 0)
|
|
5209
|
+
},
|
|
5210
|
+
files: {
|
|
5211
|
+
increased: increased.slice(0, top),
|
|
5212
|
+
decreased: decreased.slice(0, top)
|
|
5213
|
+
},
|
|
5214
|
+
topDebt: topDebt.slice(0, top)
|
|
4007
5215
|
};
|
|
4008
5216
|
}
|
|
5217
|
+
function emptyStatus() {
|
|
5218
|
+
return { js: 0, rust: 0, bridge: 0, native: 0 };
|
|
5219
|
+
}
|
|
4009
5220
|
function indexSnapshot(cc) {
|
|
4010
5221
|
const paths = /* @__PURE__ */ new Map();
|
|
4011
5222
|
for (const root of cc.files) {
|
|
@@ -4059,19 +5270,19 @@ function summarize(index) {
|
|
|
4059
5270
|
}
|
|
4060
5271
|
function difference(present, absent) {
|
|
4061
5272
|
const files = [];
|
|
4062
|
-
for (const [id,
|
|
5273
|
+
for (const [id, path7] of present.paths) {
|
|
4063
5274
|
if (absent.paths.has(id)) continue;
|
|
4064
5275
|
files.push({
|
|
4065
5276
|
id,
|
|
4066
|
-
path:
|
|
5277
|
+
path: path7,
|
|
4067
5278
|
metrics: sortedMetrics(present.metrics[id])
|
|
4068
5279
|
});
|
|
4069
5280
|
}
|
|
4070
|
-
return files.sort(
|
|
5281
|
+
return files.sort(compareByPath);
|
|
4071
5282
|
}
|
|
4072
5283
|
function changedFiles(base, head) {
|
|
4073
5284
|
const changed = [];
|
|
4074
|
-
for (const [id,
|
|
5285
|
+
for (const [id, path7] of head.paths) {
|
|
4075
5286
|
if (!base.paths.has(id)) continue;
|
|
4076
5287
|
const baseValues = base.metrics[id] ?? {};
|
|
4077
5288
|
const headValues = head.metrics[id] ?? {};
|
|
@@ -4091,7 +5302,7 @@ function changedFiles(base, head) {
|
|
|
4091
5302
|
delta: baseValue !== null && headValue !== null ? headValue - baseValue : null
|
|
4092
5303
|
});
|
|
4093
5304
|
}
|
|
4094
|
-
if (changes.length > 0) changed.push({ id, path:
|
|
5305
|
+
if (changes.length > 0) changed.push({ id, path: path7, changes });
|
|
4095
5306
|
}
|
|
4096
5307
|
return changed.sort(compareChangedFiles);
|
|
4097
5308
|
}
|
|
@@ -4281,7 +5492,7 @@ function finiteNumber(value) {
|
|
|
4281
5492
|
function edgeKey(fromId, toId) {
|
|
4282
5493
|
return `${fromId}\0${toId}`;
|
|
4283
5494
|
}
|
|
4284
|
-
function
|
|
5495
|
+
function compareByPath(left, right) {
|
|
4285
5496
|
return compareStrings5(left.path, right.path) || compareStrings5(left.id, right.id);
|
|
4286
5497
|
}
|
|
4287
5498
|
function compareChangedFiles(left, right) {
|
|
@@ -4300,6 +5511,209 @@ function compareStrings5(left, right) {
|
|
|
4300
5511
|
return left < right ? -1 : left > right ? 1 : 0;
|
|
4301
5512
|
}
|
|
4302
5513
|
|
|
5514
|
+
// packages/metrics/src/suggest.ts
|
|
5515
|
+
var ROOT_FOLDERS = /* @__PURE__ */ new Set([
|
|
5516
|
+
"src",
|
|
5517
|
+
"lib",
|
|
5518
|
+
"libs",
|
|
5519
|
+
"source",
|
|
5520
|
+
"app",
|
|
5521
|
+
"apps",
|
|
5522
|
+
"packages",
|
|
5523
|
+
"pkg"
|
|
5524
|
+
]);
|
|
5525
|
+
function suggestLayers(cc) {
|
|
5526
|
+
const paths = /* @__PURE__ */ new Map();
|
|
5527
|
+
for (const root of cc.files) {
|
|
5528
|
+
walkFiles([root], (entry) => {
|
|
5529
|
+
paths.set(entry.node.id, relativePath2(root, entry.path));
|
|
5530
|
+
});
|
|
5531
|
+
}
|
|
5532
|
+
const edges = cc.lenses?.dependency?.edges ?? [];
|
|
5533
|
+
const groups = /* @__PURE__ */ new Map();
|
|
5534
|
+
let ungrouped = 0;
|
|
5535
|
+
const groupOf = (path7) => {
|
|
5536
|
+
const parts = path7.split("/").filter(Boolean);
|
|
5537
|
+
if (parts.length === 0) return null;
|
|
5538
|
+
const directory = parts.slice(0, -1);
|
|
5539
|
+
if (directory.length === 0) {
|
|
5540
|
+
ungrouped += 1;
|
|
5541
|
+
return null;
|
|
5542
|
+
}
|
|
5543
|
+
if (directory.length === 1 && ROOT_FOLDERS.has(directory[0])) {
|
|
5544
|
+
ungrouped += 1;
|
|
5545
|
+
return null;
|
|
5546
|
+
}
|
|
5547
|
+
const name = directory.slice(0, 2).join("/");
|
|
5548
|
+
let group = groups.get(name);
|
|
5549
|
+
if (!group) {
|
|
5550
|
+
group = {
|
|
5551
|
+
name,
|
|
5552
|
+
pattern: `${name}/**`,
|
|
5553
|
+
files: 0,
|
|
5554
|
+
dependsOn: /* @__PURE__ */ new Set(),
|
|
5555
|
+
usedBy: /* @__PURE__ */ new Set()
|
|
5556
|
+
};
|
|
5557
|
+
groups.set(name, group);
|
|
5558
|
+
}
|
|
5559
|
+
group.files += 1;
|
|
5560
|
+
return group;
|
|
5561
|
+
};
|
|
5562
|
+
for (const path7 of paths.values()) groupOf(path7);
|
|
5563
|
+
const groupEdges = [];
|
|
5564
|
+
for (const edge of edges) {
|
|
5565
|
+
const fromPath = paths.get(edge.fromId);
|
|
5566
|
+
const toPath = paths.get(edge.toId);
|
|
5567
|
+
if (!fromPath || !toPath) continue;
|
|
5568
|
+
const from = groupOf(fromPath);
|
|
5569
|
+
const to = groupOf(toPath);
|
|
5570
|
+
if (!from || !to || from.name === to.name) continue;
|
|
5571
|
+
from.dependsOn.add(to.name);
|
|
5572
|
+
to.usedBy.add(from.name);
|
|
5573
|
+
groupEdges.push({ from, to });
|
|
5574
|
+
}
|
|
5575
|
+
const depthOf = (name, visiting = /* @__PURE__ */ new Set()) => {
|
|
5576
|
+
const group = groups.get(name);
|
|
5577
|
+
if (!group) return 0;
|
|
5578
|
+
if (visiting.has(name)) return 0;
|
|
5579
|
+
visiting.add(name);
|
|
5580
|
+
let depth = 0;
|
|
5581
|
+
for (const dependency of group.dependsOn) {
|
|
5582
|
+
depth = Math.max(depth, depthOf(dependency, visiting) + 1);
|
|
5583
|
+
}
|
|
5584
|
+
visiting.delete(name);
|
|
5585
|
+
return depth;
|
|
5586
|
+
};
|
|
5587
|
+
const layers = [...groups.values()].map((group) => ({
|
|
5588
|
+
name: group.name,
|
|
5589
|
+
patterns: [group.pattern],
|
|
5590
|
+
files: group.files,
|
|
5591
|
+
dependsOn: [...group.dependsOn].sort(),
|
|
5592
|
+
usedBy: [...group.usedBy].sort(),
|
|
5593
|
+
depth: depthOf(group.name)
|
|
5594
|
+
})).sort((left, right) => left.depth - right.depth || left.name.localeCompare(right.name));
|
|
5595
|
+
const rules = [];
|
|
5596
|
+
for (const layer of layers) {
|
|
5597
|
+
for (const outer of layer.usedBy) {
|
|
5598
|
+
rules.push({
|
|
5599
|
+
name: `${slug(layer.name)}-sin-${slug(outer)}`,
|
|
5600
|
+
type: "forbidden",
|
|
5601
|
+
from: layer.name,
|
|
5602
|
+
to: outer,
|
|
5603
|
+
severity: "warn",
|
|
5604
|
+
message: `${layer.name} no debe depender de ${outer}`
|
|
5605
|
+
});
|
|
5606
|
+
}
|
|
5607
|
+
}
|
|
5608
|
+
rules.sort((left, right) => left.name.localeCompare(right.name));
|
|
5609
|
+
const notes = [];
|
|
5610
|
+
if (ungrouped > 0) {
|
|
5611
|
+
notes.push(
|
|
5612
|
+
`${ungrouped} ficheros en la ra\xEDz o sin carpeta: no forman capa (suelen ser puntos de entrada).`
|
|
5613
|
+
);
|
|
5614
|
+
}
|
|
5615
|
+
if (layers.length === 0) {
|
|
5616
|
+
notes.push("No se ha encontrado ninguna carpeta con c\xF3digo.");
|
|
5617
|
+
}
|
|
5618
|
+
if (layers.length === 1) {
|
|
5619
|
+
notes.push(
|
|
5620
|
+
"Solo hay una capa: no se puede deducir una estructura. Prueba con un perfil del cat\xE1logo."
|
|
5621
|
+
);
|
|
5622
|
+
}
|
|
5623
|
+
const fits = matchProfiles(groups, groupEdges);
|
|
5624
|
+
const best = fits.find((fit) => fit.matched >= 2) ?? null;
|
|
5625
|
+
const profile = best ? findDomainProfile(best.id) ?? null : null;
|
|
5626
|
+
const proposedLayers = profile ? profile.layers.map((layer) => {
|
|
5627
|
+
const group = groupForLayer(layer, groups);
|
|
5628
|
+
return {
|
|
5629
|
+
name: layer.name,
|
|
5630
|
+
patterns: [...layer.patterns],
|
|
5631
|
+
files: group?.files ?? 0,
|
|
5632
|
+
dependsOn: [],
|
|
5633
|
+
usedBy: [],
|
|
5634
|
+
depth: 0
|
|
5635
|
+
};
|
|
5636
|
+
}) : layers;
|
|
5637
|
+
const proposedRules = profile ? profile.rules.map((rule) => ({
|
|
5638
|
+
name: rule.name,
|
|
5639
|
+
type: "forbidden",
|
|
5640
|
+
from: rule.from,
|
|
5641
|
+
to: rule.to,
|
|
5642
|
+
severity: "warn",
|
|
5643
|
+
message: rule.message ?? `${rule.from} no debe depender de ${rule.to}`
|
|
5644
|
+
})) : rules;
|
|
5645
|
+
return {
|
|
5646
|
+
detected: layers,
|
|
5647
|
+
layers: proposedLayers,
|
|
5648
|
+
rules: proposedRules,
|
|
5649
|
+
proposal: profile ? "profile" : "deduced",
|
|
5650
|
+
profile: profile?.id ?? null,
|
|
5651
|
+
ungrouped,
|
|
5652
|
+
notes,
|
|
5653
|
+
fits
|
|
5654
|
+
};
|
|
5655
|
+
}
|
|
5656
|
+
function groupForLayer(layer, groups) {
|
|
5657
|
+
for (const candidate of groups.values()) {
|
|
5658
|
+
if (layer.patterns.some((pattern) => covers(pattern, candidate.name))) {
|
|
5659
|
+
return candidate;
|
|
5660
|
+
}
|
|
5661
|
+
}
|
|
5662
|
+
return void 0;
|
|
5663
|
+
}
|
|
5664
|
+
function matchProfiles(groups, groupEdges) {
|
|
5665
|
+
const totalFiles = [...groups.values()].reduce(
|
|
5666
|
+
(sum, group) => sum + group.files,
|
|
5667
|
+
0
|
|
5668
|
+
);
|
|
5669
|
+
return DOMAIN_PROFILES.map((profile) => {
|
|
5670
|
+
const layerOf = /* @__PURE__ */ new Map();
|
|
5671
|
+
for (const layer of profile.layers) {
|
|
5672
|
+
const group = [...groups.values()].find(
|
|
5673
|
+
(candidate) => layer.patterns.some((pattern) => covers(pattern, candidate.name))
|
|
5674
|
+
);
|
|
5675
|
+
if (group) layerOf.set(layer.name, group.name);
|
|
5676
|
+
}
|
|
5677
|
+
const covered = [...layerOf.values()].reduce(
|
|
5678
|
+
(sum, name) => sum + (groups.get(name)?.files ?? 0),
|
|
5679
|
+
0
|
|
5680
|
+
);
|
|
5681
|
+
const broken = [];
|
|
5682
|
+
for (const rule of profile.rules) {
|
|
5683
|
+
const fromGroup = layerOf.get(rule.from);
|
|
5684
|
+
const toGroup = layerOf.get(rule.to);
|
|
5685
|
+
if (!fromGroup || !toGroup) continue;
|
|
5686
|
+
const violates = groupEdges.some(
|
|
5687
|
+
(edge) => edge.from.name === fromGroup && edge.to.name === toGroup
|
|
5688
|
+
);
|
|
5689
|
+
if (violates) broken.push(rule.message ?? `${rule.from} no puede depender de ${rule.to}`);
|
|
5690
|
+
}
|
|
5691
|
+
return {
|
|
5692
|
+
id: profile.id,
|
|
5693
|
+
title: profile.title,
|
|
5694
|
+
matched: layerOf.size,
|
|
5695
|
+
total: profile.layers.length,
|
|
5696
|
+
coverage: totalFiles === 0 ? 0 : Math.round(covered / totalFiles * 100),
|
|
5697
|
+
broken
|
|
5698
|
+
};
|
|
5699
|
+
}).sort(
|
|
5700
|
+
(left, right) => right.matched - left.matched || right.coverage - left.coverage || left.id.localeCompare(right.id)
|
|
5701
|
+
);
|
|
5702
|
+
}
|
|
5703
|
+
function covers(pattern, name) {
|
|
5704
|
+
const prefix = pattern.endsWith("/**") ? pattern.slice(0, -3) : pattern;
|
|
5705
|
+
return name === prefix || name.startsWith(`${prefix}/`);
|
|
5706
|
+
}
|
|
5707
|
+
function slug(name) {
|
|
5708
|
+
return name.replaceAll("/", "-");
|
|
5709
|
+
}
|
|
5710
|
+
function relativePath2(root, fullPath) {
|
|
5711
|
+
if (fullPath === root.name) return "";
|
|
5712
|
+
const prefix = `${root.name}/`;
|
|
5713
|
+
const stripped = fullPath.startsWith(prefix) ? fullPath.slice(prefix.length) : fullPath;
|
|
5714
|
+
return stripped.replace(/^\/+/, "");
|
|
5715
|
+
}
|
|
5716
|
+
|
|
4303
5717
|
// apps/cli/src/commands/diff.ts
|
|
4304
5718
|
var numberFormat2 = new Intl.NumberFormat("es-ES");
|
|
4305
5719
|
function num2(value) {
|
|
@@ -4348,13 +5762,13 @@ function runDiff(options) {
|
|
|
4348
5762
|
console.error(`no se pudo comparar: ${error.message}`);
|
|
4349
5763
|
return 1;
|
|
4350
5764
|
}
|
|
4351
|
-
const outPath =
|
|
5765
|
+
const outPath = path4.resolve(options.out ?? "report.json");
|
|
4352
5766
|
let reportBytes = 0;
|
|
4353
5767
|
try {
|
|
4354
|
-
|
|
5768
|
+
fs6.mkdirSync(path4.dirname(outPath), { recursive: true });
|
|
4355
5769
|
const text = `${JSON.stringify(report, null, 2)}
|
|
4356
5770
|
`;
|
|
4357
|
-
|
|
5771
|
+
fs6.writeFileSync(outPath, text, "utf8");
|
|
4358
5772
|
reportBytes = Buffer.byteLength(text, "utf8");
|
|
4359
5773
|
} catch (error) {
|
|
4360
5774
|
console.error(
|
|
@@ -4490,7 +5904,7 @@ function loadSnapshot(file) {
|
|
|
4490
5904
|
}
|
|
4491
5905
|
function defaultConfigPath() {
|
|
4492
5906
|
for (const file of [".codarch.yml", ".codarch.yaml"]) {
|
|
4493
|
-
if (
|
|
5907
|
+
if (fs6.existsSync(file)) return file;
|
|
4494
5908
|
}
|
|
4495
5909
|
return void 0;
|
|
4496
5910
|
}
|
|
@@ -4542,7 +5956,7 @@ function runInspect(options) {
|
|
|
4542
5956
|
const metric = options.metric ?? defaultMetric(stats);
|
|
4543
5957
|
const top = metric ? topByMetric(cc, metric, options.top) : [];
|
|
4544
5958
|
const rootPrefix = cc.files.length === 1 ? `${cc.files[0]?.name}/` : "";
|
|
4545
|
-
const displayPath = (
|
|
5959
|
+
const displayPath = (path7) => rootPrefix && path7.startsWith(rootPrefix) ? path7.slice(rootPrefix.length) : path7;
|
|
4546
5960
|
if (options.metric && !stats.metricCoverage.some((entry) => entry.name === options.metric)) {
|
|
4547
5961
|
console.error(`la m\xE9trica "${options.metric}" no existe en este fichero`);
|
|
4548
5962
|
return 1;
|
|
@@ -4621,8 +6035,8 @@ function runInspect(options) {
|
|
|
4621
6035
|
}
|
|
4622
6036
|
|
|
4623
6037
|
// apps/cli/src/commands/render.ts
|
|
4624
|
-
import
|
|
4625
|
-
import
|
|
6038
|
+
import fs7 from "node:fs";
|
|
6039
|
+
import path5 from "node:path";
|
|
4626
6040
|
function defaultOutputPath(input) {
|
|
4627
6041
|
const lower = input.toLowerCase();
|
|
4628
6042
|
const suffixes = [".cc.json.gz", ".cc.json", ".json.gz", ".json", ".gz"];
|
|
@@ -4632,7 +6046,7 @@ function defaultOutputPath(input) {
|
|
|
4632
6046
|
return `${input}.html`;
|
|
4633
6047
|
}
|
|
4634
6048
|
function defaultProjectName(input) {
|
|
4635
|
-
let name =
|
|
6049
|
+
let name = path5.basename(input.replace(/\\/g, "/"));
|
|
4636
6050
|
const lower = name.toLowerCase();
|
|
4637
6051
|
const suffixes = [".cc.json.gz", ".cc.json", ".json.gz", ".json", ".gz"];
|
|
4638
6052
|
for (const suffix of suffixes) {
|
|
@@ -4699,8 +6113,8 @@ function runRender(options) {
|
|
|
4699
6113
|
const elapsed = Date.now() - started;
|
|
4700
6114
|
const out = options.out ?? defaultOutputPath(options.file);
|
|
4701
6115
|
try {
|
|
4702
|
-
|
|
4703
|
-
|
|
6116
|
+
fs7.mkdirSync(path5.dirname(path5.resolve(out)), { recursive: true });
|
|
6117
|
+
fs7.writeFileSync(out, html, "utf8");
|
|
4704
6118
|
} catch (error) {
|
|
4705
6119
|
console.error(`no se pudo escribir ${out}: ${error.message}`);
|
|
4706
6120
|
return 1;
|
|
@@ -4741,9 +6155,9 @@ function runRender(options) {
|
|
|
4741
6155
|
}
|
|
4742
6156
|
|
|
4743
6157
|
// apps/cli/src/commands/serve.ts
|
|
4744
|
-
import
|
|
6158
|
+
import fs8 from "node:fs";
|
|
4745
6159
|
import http from "node:http";
|
|
4746
|
-
import
|
|
6160
|
+
import path6 from "node:path";
|
|
4747
6161
|
import { fileURLToPath } from "node:url";
|
|
4748
6162
|
var MIME = {
|
|
4749
6163
|
".html": "text/html; charset=utf-8",
|
|
@@ -4769,7 +6183,7 @@ var IGNORED_BROWSE = /* @__PURE__ */ new Set([
|
|
|
4769
6183
|
"target"
|
|
4770
6184
|
]);
|
|
4771
6185
|
function runServe(options) {
|
|
4772
|
-
const root =
|
|
6186
|
+
const root = path6.resolve(options.root ?? ".");
|
|
4773
6187
|
const webRoot = options.web === false ? null : findWebRoot();
|
|
4774
6188
|
if (options.web !== false && !webRoot) {
|
|
4775
6189
|
console.error(
|
|
@@ -4784,10 +6198,10 @@ function runServe(options) {
|
|
|
4784
6198
|
const startWatcher = () => {
|
|
4785
6199
|
if (watcher) return null;
|
|
4786
6200
|
try {
|
|
4787
|
-
watcher =
|
|
6201
|
+
watcher = fs8.watch(state.root, { recursive: true }, (_event, filename) => {
|
|
4788
6202
|
if (!filename) return;
|
|
4789
|
-
const
|
|
4790
|
-
if (IGNORED_WATCH.test(
|
|
6203
|
+
const relative2 = filename.toString().replace(/\\/g, "/");
|
|
6204
|
+
if (IGNORED_WATCH.test(relative2)) return;
|
|
4791
6205
|
scheduleRefresh();
|
|
4792
6206
|
});
|
|
4793
6207
|
return null;
|
|
@@ -4847,8 +6261,8 @@ function runServe(options) {
|
|
|
4847
6261
|
broadcast(clients, "watch", { watch: state.watch });
|
|
4848
6262
|
};
|
|
4849
6263
|
const switchRoot = (nextRoot) => {
|
|
4850
|
-
const resolved =
|
|
4851
|
-
const stat =
|
|
6264
|
+
const resolved = path6.resolve(nextRoot);
|
|
6265
|
+
const stat = fs8.statSync(resolved);
|
|
4852
6266
|
if (!stat.isDirectory()) throw new Error("la ruta no es un directorio");
|
|
4853
6267
|
const next = analyzeState(resolved, state.watch);
|
|
4854
6268
|
stopWatcher();
|
|
@@ -4870,7 +6284,7 @@ function runServe(options) {
|
|
|
4870
6284
|
return state;
|
|
4871
6285
|
};
|
|
4872
6286
|
const meta = () => ({
|
|
4873
|
-
root:
|
|
6287
|
+
root: path6.basename(state.root),
|
|
4874
6288
|
rootPath: state.root,
|
|
4875
6289
|
watch: state.watch,
|
|
4876
6290
|
files: state.files,
|
|
@@ -4942,7 +6356,11 @@ function runServe(options) {
|
|
|
4942
6356
|
if (pathname === "/data/report.json") {
|
|
4943
6357
|
const report = readReport(state.root);
|
|
4944
6358
|
if (report === null) {
|
|
4945
|
-
|
|
6359
|
+
response.writeHead(204, {
|
|
6360
|
+
"Cache-Control": "no-store",
|
|
6361
|
+
"X-Content-Type-Options": "nosniff"
|
|
6362
|
+
});
|
|
6363
|
+
response.end();
|
|
4946
6364
|
return;
|
|
4947
6365
|
}
|
|
4948
6366
|
sendJson(response, report);
|
|
@@ -5034,15 +6452,15 @@ function analyzeState(root, watch) {
|
|
|
5034
6452
|
};
|
|
5035
6453
|
}
|
|
5036
6454
|
function browseDirectories(root, requested) {
|
|
5037
|
-
const target = requested?.trim() ?
|
|
5038
|
-
const stat =
|
|
6455
|
+
const target = requested?.trim() ? path6.resolve(requested) : root;
|
|
6456
|
+
const stat = fs8.statSync(target);
|
|
5039
6457
|
if (!stat.isDirectory()) throw new Error("la ruta no es un directorio");
|
|
5040
|
-
const entries =
|
|
6458
|
+
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
6459
|
name: entry.name,
|
|
5042
|
-
path:
|
|
6460
|
+
path: path6.join(target, entry.name),
|
|
5043
6461
|
kind: "directory"
|
|
5044
6462
|
}));
|
|
5045
|
-
const parent =
|
|
6463
|
+
const parent = path6.dirname(target);
|
|
5046
6464
|
return {
|
|
5047
6465
|
path: target,
|
|
5048
6466
|
parent: parent === target ? null : parent,
|
|
@@ -5078,9 +6496,9 @@ function originAllowed(request, options) {
|
|
|
5078
6496
|
function findWebRoot() {
|
|
5079
6497
|
const candidates = [
|
|
5080
6498
|
fileURLToPath(new URL("./web", import.meta.url)),
|
|
5081
|
-
|
|
5082
|
-
|
|
5083
|
-
|
|
6499
|
+
path6.resolve(process.cwd(), "apps", "web", "dist"),
|
|
6500
|
+
path6.resolve(
|
|
6501
|
+
path6.dirname(fileURLToPath(import.meta.url)),
|
|
5084
6502
|
"..",
|
|
5085
6503
|
"..",
|
|
5086
6504
|
"..",
|
|
@@ -5089,14 +6507,14 @@ function findWebRoot() {
|
|
|
5089
6507
|
)
|
|
5090
6508
|
];
|
|
5091
6509
|
for (const candidate of candidates) {
|
|
5092
|
-
if (
|
|
6510
|
+
if (fs8.existsSync(path6.join(candidate, "index.html"))) return candidate;
|
|
5093
6511
|
}
|
|
5094
6512
|
return null;
|
|
5095
6513
|
}
|
|
5096
6514
|
function readReport(root) {
|
|
5097
|
-
const file =
|
|
6515
|
+
const file = path6.join(root, ".codarch", "report.json");
|
|
5098
6516
|
try {
|
|
5099
|
-
return JSON.parse(
|
|
6517
|
+
return JSON.parse(fs8.readFileSync(file, "utf8"));
|
|
5100
6518
|
} catch (error) {
|
|
5101
6519
|
if (error.code !== "ENOENT") {
|
|
5102
6520
|
console.error(`no se pudo leer ${file}: ${error.message}`);
|
|
@@ -5113,17 +6531,17 @@ function serveStatic(webRoot, pathname, headOnly, response) {
|
|
|
5113
6531
|
response.end();
|
|
5114
6532
|
return;
|
|
5115
6533
|
}
|
|
5116
|
-
const
|
|
5117
|
-
let file =
|
|
5118
|
-
if (!isWithin2(webRoot, file) || !
|
|
5119
|
-
file =
|
|
6534
|
+
const relative2 = decoded === "/" ? "index.html" : decoded.replace(/^\/+/, "");
|
|
6535
|
+
let file = path6.resolve(webRoot, relative2);
|
|
6536
|
+
if (!isWithin2(webRoot, file) || !fs8.existsSync(file) || !fs8.statSync(file).isFile()) {
|
|
6537
|
+
file = path6.join(webRoot, "index.html");
|
|
5120
6538
|
}
|
|
5121
6539
|
try {
|
|
5122
|
-
const body =
|
|
6540
|
+
const body = fs8.readFileSync(file);
|
|
5123
6541
|
response.writeHead(200, {
|
|
5124
|
-
"Content-Type": MIME[
|
|
6542
|
+
"Content-Type": MIME[path6.extname(file).toLowerCase()] ?? "application/octet-stream",
|
|
5125
6543
|
"Content-Length": body.length,
|
|
5126
|
-
"Cache-Control": file.includes(`${
|
|
6544
|
+
"Cache-Control": file.includes(`${path6.sep}assets${path6.sep}`) ? "public, max-age=31536000, immutable" : "no-store",
|
|
5127
6545
|
"X-Content-Type-Options": "nosniff"
|
|
5128
6546
|
});
|
|
5129
6547
|
if (headOnly) response.end();
|
|
@@ -5134,8 +6552,8 @@ function serveStatic(webRoot, pathname, headOnly, response) {
|
|
|
5134
6552
|
}
|
|
5135
6553
|
}
|
|
5136
6554
|
function isWithin2(root, candidate) {
|
|
5137
|
-
const
|
|
5138
|
-
return
|
|
6555
|
+
const relative2 = path6.relative(root, candidate);
|
|
6556
|
+
return relative2 === "" || !relative2.startsWith("..") && !path6.isAbsolute(relative2);
|
|
5139
6557
|
}
|
|
5140
6558
|
function sendJson(response, value) {
|
|
5141
6559
|
const body = `${JSON.stringify(value)}
|
|
@@ -5172,8 +6590,170 @@ data: ${JSON.stringify(data)}
|
|
|
5172
6590
|
}
|
|
5173
6591
|
}
|
|
5174
6592
|
|
|
6593
|
+
// apps/cli/src/commands/profiles.ts
|
|
6594
|
+
function runProfiles(json) {
|
|
6595
|
+
if (json) {
|
|
6596
|
+
console.log(
|
|
6597
|
+
JSON.stringify(
|
|
6598
|
+
{
|
|
6599
|
+
profiles: DOMAIN_PROFILES.map((profile) => ({
|
|
6600
|
+
id: profile.id,
|
|
6601
|
+
title: profile.title,
|
|
6602
|
+
description: profile.description,
|
|
6603
|
+
when: profile.when,
|
|
6604
|
+
layers: profile.layers.map((layer) => ({
|
|
6605
|
+
name: layer.name,
|
|
6606
|
+
patterns: layer.patterns
|
|
6607
|
+
})),
|
|
6608
|
+
rules: profile.rules.map((rule) => ({
|
|
6609
|
+
name: rule.name,
|
|
6610
|
+
from: rule.from,
|
|
6611
|
+
to: rule.to,
|
|
6612
|
+
severity: rule.severity,
|
|
6613
|
+
message: rule.message ?? null
|
|
6614
|
+
}))
|
|
6615
|
+
}))
|
|
6616
|
+
},
|
|
6617
|
+
null,
|
|
6618
|
+
2
|
|
6619
|
+
)
|
|
6620
|
+
);
|
|
6621
|
+
return 0;
|
|
6622
|
+
}
|
|
6623
|
+
console.log("");
|
|
6624
|
+
console.log(" CodArch \xB7 perfiles de arquitectura");
|
|
6625
|
+
console.log("");
|
|
6626
|
+
for (const profile of DOMAIN_PROFILES) {
|
|
6627
|
+
console.log(` ${profile.id} \xB7 ${profile.title}`);
|
|
6628
|
+
console.log(` ${profile.description}`);
|
|
6629
|
+
console.log(` para: ${profile.when}`);
|
|
6630
|
+
console.log(
|
|
6631
|
+
` capas: ${profile.layers.map((layer) => layer.name).join(", ")}`
|
|
6632
|
+
);
|
|
6633
|
+
for (const rule of profile.rules) {
|
|
6634
|
+
console.log(
|
|
6635
|
+
` \xB7 ${rule.name}: ${rule.from} no puede depender de ${rule.to} (${rule.severity})`
|
|
6636
|
+
);
|
|
6637
|
+
}
|
|
6638
|
+
console.log("");
|
|
6639
|
+
}
|
|
6640
|
+
console.log(" Para usarlo, en .codarch.yml:");
|
|
6641
|
+
console.log("");
|
|
6642
|
+
console.log(" version: 1");
|
|
6643
|
+
console.log(" profile: capas");
|
|
6644
|
+
console.log("");
|
|
6645
|
+
console.log(' Las reglas del perfil entran como aviso: suben a "error" cuando el c\xF3digo est\xE9 listo.');
|
|
6646
|
+
console.log("");
|
|
6647
|
+
return 0;
|
|
6648
|
+
}
|
|
6649
|
+
|
|
6650
|
+
// apps/cli/src/commands/suggest.ts
|
|
6651
|
+
import { stringify as toYaml } from "yaml";
|
|
6652
|
+
function runSuggest(options) {
|
|
6653
|
+
if (!options.file) {
|
|
6654
|
+
console.error(
|
|
6655
|
+
"falta la ruta del fichero cc.json\n\n codarch suggest <fichero.cc.json[.gz]>"
|
|
6656
|
+
);
|
|
6657
|
+
return 1;
|
|
6658
|
+
}
|
|
6659
|
+
let loaded;
|
|
6660
|
+
try {
|
|
6661
|
+
loaded = readJsonFile2(options.file);
|
|
6662
|
+
} catch (error) {
|
|
6663
|
+
console.error(`no se pudo leer ${options.file}: ${error.message}`);
|
|
6664
|
+
return 1;
|
|
6665
|
+
}
|
|
6666
|
+
let suggestion;
|
|
6667
|
+
try {
|
|
6668
|
+
const cc = parseCcJson(loaded.json);
|
|
6669
|
+
suggestion = suggestLayers(cc);
|
|
6670
|
+
} catch (error) {
|
|
6671
|
+
console.error(`no se pudo leer ${options.file}: ${error.message}`);
|
|
6672
|
+
return 1;
|
|
6673
|
+
}
|
|
6674
|
+
if (options.json) {
|
|
6675
|
+
console.log(
|
|
6676
|
+
JSON.stringify(
|
|
6677
|
+
{
|
|
6678
|
+
detected: suggestion.detected,
|
|
6679
|
+
layers: suggestion.layers,
|
|
6680
|
+
rules: suggestion.rules,
|
|
6681
|
+
proposal: suggestion.proposal,
|
|
6682
|
+
profile: suggestion.profile,
|
|
6683
|
+
ungrouped: suggestion.ungrouped,
|
|
6684
|
+
notes: suggestion.notes,
|
|
6685
|
+
fits: suggestion.fits
|
|
6686
|
+
},
|
|
6687
|
+
null,
|
|
6688
|
+
2
|
|
6689
|
+
)
|
|
6690
|
+
);
|
|
6691
|
+
return 0;
|
|
6692
|
+
}
|
|
6693
|
+
console.log("");
|
|
6694
|
+
console.log(" CodArch \xB7 propuesta de capas");
|
|
6695
|
+
console.log("");
|
|
6696
|
+
if (suggestion.layers.length === 0) {
|
|
6697
|
+
console.log(" No se ha encontrado ninguna carpeta con c\xF3digo.");
|
|
6698
|
+
for (const note of suggestion.notes) console.log(` ${note}`);
|
|
6699
|
+
console.log("");
|
|
6700
|
+
return 0;
|
|
6701
|
+
}
|
|
6702
|
+
for (const layer of suggestion.detected) {
|
|
6703
|
+
console.log(
|
|
6704
|
+
` ${layer.name} \xB7 ${layer.files} ficheros \xB7 nivel ${layer.depth}` + (layer.usedBy.length > 0 ? ` \xB7 de aqu\xED dependen: ${layer.usedBy.join(", ")}` : "")
|
|
6705
|
+
);
|
|
6706
|
+
}
|
|
6707
|
+
console.log("");
|
|
6708
|
+
const yaml = toYaml(
|
|
6709
|
+
{
|
|
6710
|
+
version: 1,
|
|
6711
|
+
layers: suggestion.layers.map((layer) => ({
|
|
6712
|
+
name: layer.name,
|
|
6713
|
+
paths: layer.patterns
|
|
6714
|
+
})),
|
|
6715
|
+
rules: suggestion.rules
|
|
6716
|
+
},
|
|
6717
|
+
{ lineWidth: 0 }
|
|
6718
|
+
);
|
|
6719
|
+
console.log(" Propuesta para .codarch.yml:");
|
|
6720
|
+
console.log("");
|
|
6721
|
+
console.log(
|
|
6722
|
+
suggestion.proposal === "profile" && suggestion.profile ? ` # Propuesta a partir del perfil "${suggestion.profile}" del cat\xE1logo.` : " # Propuesta deducida del grafo de dependencias del repo."
|
|
6723
|
+
);
|
|
6724
|
+
console.log(
|
|
6725
|
+
yaml.split("\n").map((line) => ` ${line}`).join("\n").trimEnd()
|
|
6726
|
+
);
|
|
6727
|
+
console.log("");
|
|
6728
|
+
const best = suggestion.fits[0];
|
|
6729
|
+
if (best && best.matched > 0) {
|
|
6730
|
+
console.log(" Encaje con el cat\xE1logo de perfiles:");
|
|
6731
|
+
console.log("");
|
|
6732
|
+
for (const fit of suggestion.fits.filter((entry) => entry.matched > 0)) {
|
|
6733
|
+
console.log(
|
|
6734
|
+
` ${fit.id.padEnd(12)} ${fit.matched} de ${fit.total} capas \xB7 cubre el ${fit.coverage}% del c\xF3digo`
|
|
6735
|
+
);
|
|
6736
|
+
}
|
|
6737
|
+
console.log("");
|
|
6738
|
+
if (best.broken.length > 0) {
|
|
6739
|
+
console.log(
|
|
6740
|
+
` Con el perfil "${best.id}", el c\xF3digo incumple hoy ${best.broken.length} ${best.broken.length === 1 ? "regla" : "reglas"}:`
|
|
6741
|
+
);
|
|
6742
|
+
for (const message of best.broken) console.log(` \xB7 ${message}`);
|
|
6743
|
+
console.log("");
|
|
6744
|
+
}
|
|
6745
|
+
}
|
|
6746
|
+
for (const note of suggestion.notes) console.log(` ${note}`);
|
|
6747
|
+
console.log("");
|
|
6748
|
+
console.log(
|
|
6749
|
+
' Las reglas salen como aviso: consultan, no bloquean. Rev\xEDsalas y s\xFAbelas a "error" si encajan.'
|
|
6750
|
+
);
|
|
6751
|
+
console.log("");
|
|
6752
|
+
return 0;
|
|
6753
|
+
}
|
|
6754
|
+
|
|
5175
6755
|
// apps/cli/src/index.ts
|
|
5176
|
-
var VERSION = true ? "0.
|
|
6756
|
+
var VERSION = true ? "0.2.0" : "0.0.0";
|
|
5177
6757
|
var USAGE = `
|
|
5178
6758
|
CodArch \xB7 mapa vivo de la arquitectura de tu software
|
|
5179
6759
|
|
|
@@ -5183,6 +6763,8 @@ Uso:
|
|
|
5183
6763
|
codarch render <fichero.cc.json[.gz]> [opciones]
|
|
5184
6764
|
codarch diff <base.cc.json[.gz]> <head.cc.json[.gz]> [opciones]
|
|
5185
6765
|
codarch serve [ruta] [opciones]
|
|
6766
|
+
codarch profiles [--json]
|
|
6767
|
+
codarch suggest <fichero.cc.json[.gz]> [--json]
|
|
5186
6768
|
|
|
5187
6769
|
Opciones de analyze:
|
|
5188
6770
|
-o, --out <dir> directorio de salida (por defecto <ruta>/.codarch)
|
|
@@ -5224,6 +6806,14 @@ Opciones de serve:
|
|
|
5224
6806
|
--no-web modo API sin servir la app (para Vite u otro frontend)
|
|
5225
6807
|
-h, --help esta ayuda
|
|
5226
6808
|
|
|
6809
|
+
Opciones de profiles:
|
|
6810
|
+
--json salida machine-readable
|
|
6811
|
+
-h, --help esta ayuda
|
|
6812
|
+
|
|
6813
|
+
Opciones de suggest:
|
|
6814
|
+
--json salida machine-readable
|
|
6815
|
+
-h, --help esta ayuda
|
|
6816
|
+
|
|
5227
6817
|
Ejemplos:
|
|
5228
6818
|
codarch analyze .
|
|
5229
6819
|
codarch analyze . -o .codarch --title "Mi proyecto"
|
|
@@ -5233,6 +6823,8 @@ Ejemplos:
|
|
|
5233
6823
|
codarch diff base.cc.json head.cc.json -o report.json --fail-on cycles
|
|
5234
6824
|
codarch serve .
|
|
5235
6825
|
codarch serve . --no-watch
|
|
6826
|
+
codarch profiles
|
|
6827
|
+
codarch suggest .codarch/snapshot.cc.json
|
|
5236
6828
|
`;
|
|
5237
6829
|
function parseAnalyze(args) {
|
|
5238
6830
|
const options = { help: false, ignore: [] };
|
|
@@ -5297,6 +6889,30 @@ function parseAnalyze(args) {
|
|
|
5297
6889
|
options.root = positional[0] ?? ".";
|
|
5298
6890
|
return options;
|
|
5299
6891
|
}
|
|
6892
|
+
function parseSuggest(args) {
|
|
6893
|
+
const options = { json: false };
|
|
6894
|
+
const positional = [];
|
|
6895
|
+
for (let index = 0; index < args.length; index++) {
|
|
6896
|
+
const arg = args[index];
|
|
6897
|
+
if (arg === "--json") {
|
|
6898
|
+
options.json = true;
|
|
6899
|
+
} else if (arg === "--help" || arg === "-h") {
|
|
6900
|
+
console.log(USAGE);
|
|
6901
|
+
return 0;
|
|
6902
|
+
} else if (arg.startsWith("-")) {
|
|
6903
|
+
console.error(`opci\xF3n desconocida: ${arg}`);
|
|
6904
|
+
return 1;
|
|
6905
|
+
} else {
|
|
6906
|
+
positional.push(arg);
|
|
6907
|
+
}
|
|
6908
|
+
}
|
|
6909
|
+
if (positional.length > 1) {
|
|
6910
|
+
console.error(`sobra un argumento: ${positional[1]}`);
|
|
6911
|
+
return 1;
|
|
6912
|
+
}
|
|
6913
|
+
options.file = positional[0];
|
|
6914
|
+
return options;
|
|
6915
|
+
}
|
|
5300
6916
|
function parseInspect(args) {
|
|
5301
6917
|
const options = { json: false, top: 5, help: false };
|
|
5302
6918
|
const positional = [];
|
|
@@ -5592,6 +7208,14 @@ function main(argv) {
|
|
|
5592
7208
|
}
|
|
5593
7209
|
return runServe(parsed);
|
|
5594
7210
|
}
|
|
7211
|
+
if (command === "profiles") {
|
|
7212
|
+
return runProfiles(rest.includes("--json"));
|
|
7213
|
+
}
|
|
7214
|
+
if (command === "suggest") {
|
|
7215
|
+
const parsed = parseSuggest(rest);
|
|
7216
|
+
if (typeof parsed === "number") return parsed;
|
|
7217
|
+
return runSuggest(parsed);
|
|
7218
|
+
}
|
|
5595
7219
|
console.error(`comando desconocido: ${command}
|
|
5596
7220
|
${USAGE}`);
|
|
5597
7221
|
return 1;
|