@viberails/scanner 0.1.0 → 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/dist/index.cjs +225 -35
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +54 -5
- package/dist/index.d.ts +54 -5
- package/dist/index.js +222 -35
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1,3 +1,153 @@
|
|
|
1
|
+
// src/aggregate.ts
|
|
2
|
+
import {
|
|
3
|
+
confidenceFromConsistency
|
|
4
|
+
} from "@viberails/types";
|
|
5
|
+
var FRAMEWORK_PRIORITY = [
|
|
6
|
+
"nextjs",
|
|
7
|
+
"sveltekit",
|
|
8
|
+
"astro",
|
|
9
|
+
"expo",
|
|
10
|
+
"react-native",
|
|
11
|
+
"svelte",
|
|
12
|
+
"vue",
|
|
13
|
+
"react"
|
|
14
|
+
];
|
|
15
|
+
function aggregateStacks(packages) {
|
|
16
|
+
if (packages.length === 1) return packages[0].stack;
|
|
17
|
+
const language = packages.some((p) => p.stack.language.name === "typescript") ? packages.find((p) => p.stack.language.name === "typescript").stack.language : packages[0].stack.language;
|
|
18
|
+
const packageManager = packages[0].stack.packageManager;
|
|
19
|
+
const frameworkPackages = packages.filter((p) => p.stack.framework);
|
|
20
|
+
let framework;
|
|
21
|
+
if (frameworkPackages.length > 0) {
|
|
22
|
+
frameworkPackages.sort((a, b) => {
|
|
23
|
+
const aIdx = FRAMEWORK_PRIORITY.indexOf(a.stack.framework.name);
|
|
24
|
+
const bIdx = FRAMEWORK_PRIORITY.indexOf(b.stack.framework.name);
|
|
25
|
+
return (aIdx === -1 ? Infinity : aIdx) - (bIdx === -1 ? Infinity : bIdx);
|
|
26
|
+
});
|
|
27
|
+
framework = frameworkPackages[0].stack.framework;
|
|
28
|
+
}
|
|
29
|
+
const libraryMap = /* @__PURE__ */ new Map();
|
|
30
|
+
for (const pkg of packages) {
|
|
31
|
+
if (pkg.stack.framework && pkg.stack.framework.name !== framework?.name) {
|
|
32
|
+
libraryMap.set(pkg.stack.framework.name, pkg.stack.framework);
|
|
33
|
+
}
|
|
34
|
+
for (const lib of pkg.stack.libraries) {
|
|
35
|
+
if (!libraryMap.has(lib.name)) {
|
|
36
|
+
libraryMap.set(lib.name, lib);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
const styling = packages.find((p) => p.stack.styling)?.stack.styling;
|
|
41
|
+
const backend = packages.find((p) => p.stack.backend)?.stack.backend;
|
|
42
|
+
const linter = packages.find((p) => p.stack.linter)?.stack.linter;
|
|
43
|
+
const testRunner = packages.find((p) => p.stack.testRunner)?.stack.testRunner;
|
|
44
|
+
return {
|
|
45
|
+
language,
|
|
46
|
+
packageManager,
|
|
47
|
+
framework,
|
|
48
|
+
libraries: [...libraryMap.values()],
|
|
49
|
+
styling,
|
|
50
|
+
backend,
|
|
51
|
+
linter,
|
|
52
|
+
testRunner
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
function aggregateStructures(packages) {
|
|
56
|
+
if (packages.length === 1) return packages[0].structure;
|
|
57
|
+
const srcDir = packages.some((p) => p.structure.srcDir) ? "src" : void 0;
|
|
58
|
+
const directories = packages.flatMap(
|
|
59
|
+
(pkg) => pkg.structure.directories.map((dir) => ({
|
|
60
|
+
...dir,
|
|
61
|
+
path: pkg.relativePath ? `${pkg.relativePath}/${dir.path}` : dir.path
|
|
62
|
+
}))
|
|
63
|
+
);
|
|
64
|
+
const testPatterns = packages.map((p) => p.structure.testPattern).filter((t) => t !== void 0);
|
|
65
|
+
let testPattern;
|
|
66
|
+
if (testPatterns.length > 0) {
|
|
67
|
+
const counts = /* @__PURE__ */ new Map();
|
|
68
|
+
for (const tp of testPatterns) {
|
|
69
|
+
const existing = counts.get(tp.value);
|
|
70
|
+
if (existing) {
|
|
71
|
+
existing.count++;
|
|
72
|
+
} else {
|
|
73
|
+
counts.set(tp.value, { count: 1, pattern: tp });
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
let best = { count: 0, pattern: testPatterns[0] };
|
|
77
|
+
for (const entry of counts.values()) {
|
|
78
|
+
if (entry.count > best.count) best = entry;
|
|
79
|
+
}
|
|
80
|
+
testPattern = best.pattern;
|
|
81
|
+
}
|
|
82
|
+
return { srcDir, directories, testPattern };
|
|
83
|
+
}
|
|
84
|
+
function aggregateConventions(packages) {
|
|
85
|
+
if (packages.length === 1) return packages[0].conventions;
|
|
86
|
+
const allKeys = /* @__PURE__ */ new Set();
|
|
87
|
+
for (const pkg of packages) {
|
|
88
|
+
for (const key of Object.keys(pkg.conventions)) {
|
|
89
|
+
allKeys.add(key);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
const result = {};
|
|
93
|
+
for (const key of allKeys) {
|
|
94
|
+
const entries = packages.map((p) => p.conventions[key]).filter((c) => c !== void 0);
|
|
95
|
+
if (entries.length < packages.length / 2) continue;
|
|
96
|
+
const valueCounts = /* @__PURE__ */ new Map();
|
|
97
|
+
for (const entry of entries) {
|
|
98
|
+
const existing = valueCounts.get(entry.value);
|
|
99
|
+
if (existing) {
|
|
100
|
+
existing.count++;
|
|
101
|
+
existing.totalConsistency += entry.consistency;
|
|
102
|
+
existing.totalSamples += entry.sampleSize;
|
|
103
|
+
} else {
|
|
104
|
+
valueCounts.set(entry.value, {
|
|
105
|
+
count: 1,
|
|
106
|
+
totalConsistency: entry.consistency,
|
|
107
|
+
totalSamples: entry.sampleSize
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
let majorityValue = "";
|
|
112
|
+
let majorityData = { count: 0, totalConsistency: 0, totalSamples: 0 };
|
|
113
|
+
for (const [value, data] of valueCounts) {
|
|
114
|
+
if (data.count > majorityData.count) {
|
|
115
|
+
majorityValue = value;
|
|
116
|
+
majorityData = data;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
const agreement = majorityData.count / entries.length;
|
|
120
|
+
const avgConsistency = majorityData.totalConsistency / majorityData.count;
|
|
121
|
+
const consistency = Math.round(avgConsistency * agreement);
|
|
122
|
+
result[key] = {
|
|
123
|
+
value: majorityValue,
|
|
124
|
+
confidence: confidenceFromConsistency(consistency),
|
|
125
|
+
sampleSize: majorityData.totalSamples,
|
|
126
|
+
consistency
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
return result;
|
|
130
|
+
}
|
|
131
|
+
function aggregateStatistics(packages) {
|
|
132
|
+
if (packages.length === 1) return packages[0].statistics;
|
|
133
|
+
const totalFiles = packages.reduce((sum, p) => sum + p.statistics.totalFiles, 0);
|
|
134
|
+
const totalLines = packages.reduce((sum, p) => sum + p.statistics.totalLines, 0);
|
|
135
|
+
const averageFileLines = totalFiles > 0 ? Math.round(totalLines / totalFiles) : 0;
|
|
136
|
+
const largestFiles = packages.flatMap(
|
|
137
|
+
(pkg) => pkg.statistics.largestFiles.map((f) => ({
|
|
138
|
+
path: pkg.relativePath ? `${pkg.relativePath}/${f.path}` : f.path,
|
|
139
|
+
lines: f.lines
|
|
140
|
+
}))
|
|
141
|
+
).sort((a, b) => b.lines - a.lines).slice(0, 5);
|
|
142
|
+
const filesByExtension = {};
|
|
143
|
+
for (const pkg of packages) {
|
|
144
|
+
for (const [ext, count] of Object.entries(pkg.statistics.filesByExtension)) {
|
|
145
|
+
filesByExtension[ext] = (filesByExtension[ext] ?? 0) + count;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return { totalFiles, totalLines, averageFileLines, largestFiles, filesByExtension };
|
|
149
|
+
}
|
|
150
|
+
|
|
1
151
|
// src/compute-statistics.ts
|
|
2
152
|
import { readdir as readdir2, readFile } from "fs/promises";
|
|
3
153
|
import { extname, join as join2 } from "path";
|
|
@@ -167,7 +317,7 @@ async function computeStatistics(projectPath, dirs) {
|
|
|
167
317
|
// src/detect-conventions.ts
|
|
168
318
|
import { readFile as readFile2 } from "fs/promises";
|
|
169
319
|
import { join as join3 } from "path";
|
|
170
|
-
import { confidenceFromConsistency } from "@viberails/types";
|
|
320
|
+
import { confidenceFromConsistency as confidenceFromConsistency2 } from "@viberails/types";
|
|
171
321
|
|
|
172
322
|
// src/utils/classify-filename.ts
|
|
173
323
|
var PATTERNS = [
|
|
@@ -224,7 +374,7 @@ function detectFileNaming(dirs) {
|
|
|
224
374
|
if (sorted.length === 0) return void 0;
|
|
225
375
|
const [dominantConvention, dominantCount] = sorted[0];
|
|
226
376
|
const consistency = Math.round(dominantCount / total * 100);
|
|
227
|
-
const confidence =
|
|
377
|
+
const confidence = confidenceFromConsistency2(consistency);
|
|
228
378
|
if (confidence === "low") return void 0;
|
|
229
379
|
return {
|
|
230
380
|
value: dominantConvention,
|
|
@@ -250,7 +400,7 @@ function detectComponentNaming(dirs, structure) {
|
|
|
250
400
|
const dominantValue = pascalCount >= tsxFiles.length / 2 ? "PascalCase" : "camelCase";
|
|
251
401
|
return {
|
|
252
402
|
value: dominantValue,
|
|
253
|
-
confidence:
|
|
403
|
+
confidence: confidenceFromConsistency2(consistency),
|
|
254
404
|
sampleSize: tsxFiles.length,
|
|
255
405
|
consistency
|
|
256
406
|
};
|
|
@@ -286,7 +436,7 @@ function detectHookNaming(dirs, structure) {
|
|
|
286
436
|
const consistency = Math.round(dominantCount / total * 100);
|
|
287
437
|
return {
|
|
288
438
|
value: isDominantKebab ? "use-*" : "useXxx",
|
|
289
|
-
confidence:
|
|
439
|
+
confidence: confidenceFromConsistency2(consistency),
|
|
290
440
|
sampleSize: total,
|
|
291
441
|
consistency
|
|
292
442
|
};
|
|
@@ -300,7 +450,7 @@ async function detectImportAlias(projectPath) {
|
|
|
300
450
|
const aliases = Object.keys(paths);
|
|
301
451
|
if (aliases.length === 0) return void 0;
|
|
302
452
|
return {
|
|
303
|
-
value: aliases
|
|
453
|
+
value: aliases.join(","),
|
|
304
454
|
confidence: "high",
|
|
305
455
|
sampleSize: aliases.length,
|
|
306
456
|
consistency: 100
|
|
@@ -341,6 +491,8 @@ async function fileExists(filePath) {
|
|
|
341
491
|
}
|
|
342
492
|
var FRAMEWORK_MAPPINGS = [
|
|
343
493
|
{ dep: "next", name: "nextjs" },
|
|
494
|
+
{ dep: "expo", name: "expo" },
|
|
495
|
+
{ dep: "react-native", name: "react-native", excludeDep: "expo" },
|
|
344
496
|
{ dep: "@sveltejs/kit", name: "sveltekit" },
|
|
345
497
|
{ dep: "svelte", name: "svelte" },
|
|
346
498
|
{ dep: "astro", name: "astro" },
|
|
@@ -374,9 +526,10 @@ var LOCK_FILE_MAP = [
|
|
|
374
526
|
{ file: "bun.lockb", name: "bun" },
|
|
375
527
|
{ file: "package-lock.json", name: "npm" }
|
|
376
528
|
];
|
|
377
|
-
async function detectStack(projectPath) {
|
|
529
|
+
async function detectStack(projectPath, additionalDeps) {
|
|
378
530
|
const pkg = await readPackageJson(projectPath);
|
|
379
531
|
const allDeps = {
|
|
532
|
+
...additionalDeps,
|
|
380
533
|
...pkg?.dependencies,
|
|
381
534
|
...pkg?.devDependencies
|
|
382
535
|
};
|
|
@@ -477,7 +630,7 @@ function detectLibraries(allDeps) {
|
|
|
477
630
|
}
|
|
478
631
|
|
|
479
632
|
// src/detect-structure.ts
|
|
480
|
-
import { confidenceFromConsistency as
|
|
633
|
+
import { confidenceFromConsistency as confidenceFromConsistency3 } from "@viberails/types";
|
|
481
634
|
|
|
482
635
|
// src/utils/classify-directory.ts
|
|
483
636
|
var ROLE_PATTERNS = [
|
|
@@ -518,7 +671,9 @@ function classifyDirectory(dir) {
|
|
|
518
671
|
function matchByName(relativePath) {
|
|
519
672
|
for (const { role, pathPatterns } of ROLE_PATTERNS) {
|
|
520
673
|
for (const pattern of pathPatterns) {
|
|
521
|
-
if (relativePath === pattern)
|
|
674
|
+
if (relativePath === pattern || relativePath.endsWith(`/${pattern}`)) {
|
|
675
|
+
return role;
|
|
676
|
+
}
|
|
522
677
|
}
|
|
523
678
|
}
|
|
524
679
|
return null;
|
|
@@ -529,7 +684,7 @@ function inferFromContent(dir) {
|
|
|
529
684
|
const name = f.split(".")[0];
|
|
530
685
|
return name.startsWith("use-") || /^use[A-Z]/.test(name);
|
|
531
686
|
});
|
|
532
|
-
if (hookFiles.length
|
|
687
|
+
if (hookFiles.length >= 2 && hookFiles.length / sourceFileCount >= 0.5) {
|
|
533
688
|
return {
|
|
534
689
|
path: dir.relativePath,
|
|
535
690
|
role: "hooks",
|
|
@@ -538,7 +693,7 @@ function inferFromContent(dir) {
|
|
|
538
693
|
};
|
|
539
694
|
}
|
|
540
695
|
const testFiles = sourceFileNames.filter((f) => f.includes(".test.") || f.includes(".spec."));
|
|
541
|
-
if (testFiles.length
|
|
696
|
+
if (testFiles.length >= 2 && testFiles.length / sourceFileCount >= 0.5) {
|
|
542
697
|
return {
|
|
543
698
|
path: dir.relativePath,
|
|
544
699
|
role: "tests",
|
|
@@ -583,7 +738,7 @@ function detectTestPattern(dirs) {
|
|
|
583
738
|
const sep = isDotTest ? "test" : "spec";
|
|
584
739
|
return {
|
|
585
740
|
value: `*.${sep}${topExt}`,
|
|
586
|
-
confidence:
|
|
741
|
+
confidence: confidenceFromConsistency3(consistency),
|
|
587
742
|
sampleSize: testFiles.length,
|
|
588
743
|
consistency
|
|
589
744
|
};
|
|
@@ -683,20 +838,33 @@ async function resolvePackages(projectRoot, dirs) {
|
|
|
683
838
|
return packages;
|
|
684
839
|
}
|
|
685
840
|
|
|
686
|
-
// src/scan.ts
|
|
687
|
-
import { stat } from "fs/promises";
|
|
841
|
+
// src/scan-package.ts
|
|
688
842
|
import { resolve } from "path";
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
843
|
+
async function scanPackage(packagePath, name, relativePath, rootDeps) {
|
|
844
|
+
const root = resolve(packagePath);
|
|
845
|
+
const dirs = await walkDirectory(root, 4);
|
|
846
|
+
const [stack, structure, statistics] = await Promise.all([
|
|
847
|
+
detectStack(root, rootDeps),
|
|
848
|
+
detectStructure(root, dirs),
|
|
849
|
+
computeStatistics(root, dirs)
|
|
850
|
+
]);
|
|
851
|
+
const conventions = await detectConventions(root, structure, dirs);
|
|
852
|
+
return {
|
|
853
|
+
name,
|
|
854
|
+
root,
|
|
855
|
+
relativePath,
|
|
856
|
+
stack,
|
|
857
|
+
structure,
|
|
858
|
+
conventions,
|
|
859
|
+
statistics
|
|
860
|
+
};
|
|
697
861
|
}
|
|
862
|
+
|
|
863
|
+
// src/scan.ts
|
|
864
|
+
import { stat } from "fs/promises";
|
|
865
|
+
import { basename, resolve as resolve2 } from "path";
|
|
698
866
|
async function scan(projectPath, _options) {
|
|
699
|
-
const root =
|
|
867
|
+
const root = resolve2(projectPath);
|
|
700
868
|
try {
|
|
701
869
|
const st = await stat(root);
|
|
702
870
|
if (!st.isDirectory()) {
|
|
@@ -708,22 +876,36 @@ async function scan(projectPath, _options) {
|
|
|
708
876
|
}
|
|
709
877
|
throw new Error(`Project path does not exist: ${root}`);
|
|
710
878
|
}
|
|
711
|
-
const allDirs = await walkDirectory(root, 4);
|
|
712
|
-
const dirs = filterFixtureDirs(allDirs);
|
|
713
|
-
const [stack, structure, statistics] = await Promise.all([
|
|
714
|
-
detectStack(root),
|
|
715
|
-
detectStructure(root, dirs),
|
|
716
|
-
computeStatistics(root, dirs)
|
|
717
|
-
]);
|
|
718
|
-
const conventions = await detectConventions(root, structure, dirs);
|
|
719
879
|
const workspace = await detectWorkspace(root);
|
|
880
|
+
if (workspace && workspace.packages.length > 0) {
|
|
881
|
+
const rootPkg2 = await readPackageJson(root);
|
|
882
|
+
const rootDeps = {
|
|
883
|
+
...rootPkg2?.dependencies,
|
|
884
|
+
...rootPkg2?.devDependencies
|
|
885
|
+
};
|
|
886
|
+
const packages = await Promise.all(
|
|
887
|
+
workspace.packages.map((wp) => scanPackage(wp.path, wp.name, wp.relativePath, rootDeps))
|
|
888
|
+
);
|
|
889
|
+
return {
|
|
890
|
+
root,
|
|
891
|
+
stack: aggregateStacks(packages),
|
|
892
|
+
structure: aggregateStructures(packages),
|
|
893
|
+
conventions: aggregateConventions(packages),
|
|
894
|
+
statistics: aggregateStatistics(packages),
|
|
895
|
+
workspace,
|
|
896
|
+
packages
|
|
897
|
+
};
|
|
898
|
+
}
|
|
899
|
+
const rootPkg = await readPackageJson(root);
|
|
900
|
+
const name = rootPkg?.name ?? basename(root);
|
|
901
|
+
const pkg = await scanPackage(root, name, "");
|
|
720
902
|
return {
|
|
721
903
|
root,
|
|
722
|
-
stack,
|
|
723
|
-
structure,
|
|
724
|
-
conventions,
|
|
725
|
-
statistics,
|
|
726
|
-
|
|
904
|
+
stack: pkg.stack,
|
|
905
|
+
structure: pkg.structure,
|
|
906
|
+
conventions: pkg.conventions,
|
|
907
|
+
statistics: pkg.statistics,
|
|
908
|
+
packages: [pkg]
|
|
727
909
|
};
|
|
728
910
|
}
|
|
729
911
|
|
|
@@ -731,6 +913,10 @@ async function scan(projectPath, _options) {
|
|
|
731
913
|
var VERSION = "0.1.0";
|
|
732
914
|
export {
|
|
733
915
|
VERSION,
|
|
916
|
+
aggregateConventions,
|
|
917
|
+
aggregateStacks,
|
|
918
|
+
aggregateStatistics,
|
|
919
|
+
aggregateStructures,
|
|
734
920
|
computeStatistics,
|
|
735
921
|
detectConventions,
|
|
736
922
|
detectStack,
|
|
@@ -739,6 +925,7 @@ export {
|
|
|
739
925
|
extractMajorVersion,
|
|
740
926
|
readPackageJson,
|
|
741
927
|
scan,
|
|
928
|
+
scanPackage,
|
|
742
929
|
walkDirectory
|
|
743
930
|
};
|
|
744
931
|
//# sourceMappingURL=index.js.map
|