@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.cjs
CHANGED
|
@@ -21,6 +21,10 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
23
|
VERSION: () => VERSION,
|
|
24
|
+
aggregateConventions: () => aggregateConventions,
|
|
25
|
+
aggregateStacks: () => aggregateStacks,
|
|
26
|
+
aggregateStatistics: () => aggregateStatistics,
|
|
27
|
+
aggregateStructures: () => aggregateStructures,
|
|
24
28
|
computeStatistics: () => computeStatistics,
|
|
25
29
|
detectConventions: () => detectConventions,
|
|
26
30
|
detectStack: () => detectStack,
|
|
@@ -29,10 +33,159 @@ __export(index_exports, {
|
|
|
29
33
|
extractMajorVersion: () => extractMajorVersion,
|
|
30
34
|
readPackageJson: () => readPackageJson,
|
|
31
35
|
scan: () => scan,
|
|
36
|
+
scanPackage: () => scanPackage,
|
|
32
37
|
walkDirectory: () => walkDirectory
|
|
33
38
|
});
|
|
34
39
|
module.exports = __toCommonJS(index_exports);
|
|
35
40
|
|
|
41
|
+
// src/aggregate.ts
|
|
42
|
+
var import_types = require("@viberails/types");
|
|
43
|
+
var FRAMEWORK_PRIORITY = [
|
|
44
|
+
"nextjs",
|
|
45
|
+
"sveltekit",
|
|
46
|
+
"astro",
|
|
47
|
+
"expo",
|
|
48
|
+
"react-native",
|
|
49
|
+
"svelte",
|
|
50
|
+
"vue",
|
|
51
|
+
"react"
|
|
52
|
+
];
|
|
53
|
+
function aggregateStacks(packages) {
|
|
54
|
+
if (packages.length === 1) return packages[0].stack;
|
|
55
|
+
const language = packages.some((p) => p.stack.language.name === "typescript") ? packages.find((p) => p.stack.language.name === "typescript").stack.language : packages[0].stack.language;
|
|
56
|
+
const packageManager = packages[0].stack.packageManager;
|
|
57
|
+
const frameworkPackages = packages.filter((p) => p.stack.framework);
|
|
58
|
+
let framework;
|
|
59
|
+
if (frameworkPackages.length > 0) {
|
|
60
|
+
frameworkPackages.sort((a, b) => {
|
|
61
|
+
const aIdx = FRAMEWORK_PRIORITY.indexOf(a.stack.framework.name);
|
|
62
|
+
const bIdx = FRAMEWORK_PRIORITY.indexOf(b.stack.framework.name);
|
|
63
|
+
return (aIdx === -1 ? Infinity : aIdx) - (bIdx === -1 ? Infinity : bIdx);
|
|
64
|
+
});
|
|
65
|
+
framework = frameworkPackages[0].stack.framework;
|
|
66
|
+
}
|
|
67
|
+
const libraryMap = /* @__PURE__ */ new Map();
|
|
68
|
+
for (const pkg of packages) {
|
|
69
|
+
if (pkg.stack.framework && pkg.stack.framework.name !== framework?.name) {
|
|
70
|
+
libraryMap.set(pkg.stack.framework.name, pkg.stack.framework);
|
|
71
|
+
}
|
|
72
|
+
for (const lib of pkg.stack.libraries) {
|
|
73
|
+
if (!libraryMap.has(lib.name)) {
|
|
74
|
+
libraryMap.set(lib.name, lib);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
const styling = packages.find((p) => p.stack.styling)?.stack.styling;
|
|
79
|
+
const backend = packages.find((p) => p.stack.backend)?.stack.backend;
|
|
80
|
+
const linter = packages.find((p) => p.stack.linter)?.stack.linter;
|
|
81
|
+
const testRunner = packages.find((p) => p.stack.testRunner)?.stack.testRunner;
|
|
82
|
+
return {
|
|
83
|
+
language,
|
|
84
|
+
packageManager,
|
|
85
|
+
framework,
|
|
86
|
+
libraries: [...libraryMap.values()],
|
|
87
|
+
styling,
|
|
88
|
+
backend,
|
|
89
|
+
linter,
|
|
90
|
+
testRunner
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
function aggregateStructures(packages) {
|
|
94
|
+
if (packages.length === 1) return packages[0].structure;
|
|
95
|
+
const srcDir = packages.some((p) => p.structure.srcDir) ? "src" : void 0;
|
|
96
|
+
const directories = packages.flatMap(
|
|
97
|
+
(pkg) => pkg.structure.directories.map((dir) => ({
|
|
98
|
+
...dir,
|
|
99
|
+
path: pkg.relativePath ? `${pkg.relativePath}/${dir.path}` : dir.path
|
|
100
|
+
}))
|
|
101
|
+
);
|
|
102
|
+
const testPatterns = packages.map((p) => p.structure.testPattern).filter((t) => t !== void 0);
|
|
103
|
+
let testPattern;
|
|
104
|
+
if (testPatterns.length > 0) {
|
|
105
|
+
const counts = /* @__PURE__ */ new Map();
|
|
106
|
+
for (const tp of testPatterns) {
|
|
107
|
+
const existing = counts.get(tp.value);
|
|
108
|
+
if (existing) {
|
|
109
|
+
existing.count++;
|
|
110
|
+
} else {
|
|
111
|
+
counts.set(tp.value, { count: 1, pattern: tp });
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
let best = { count: 0, pattern: testPatterns[0] };
|
|
115
|
+
for (const entry of counts.values()) {
|
|
116
|
+
if (entry.count > best.count) best = entry;
|
|
117
|
+
}
|
|
118
|
+
testPattern = best.pattern;
|
|
119
|
+
}
|
|
120
|
+
return { srcDir, directories, testPattern };
|
|
121
|
+
}
|
|
122
|
+
function aggregateConventions(packages) {
|
|
123
|
+
if (packages.length === 1) return packages[0].conventions;
|
|
124
|
+
const allKeys = /* @__PURE__ */ new Set();
|
|
125
|
+
for (const pkg of packages) {
|
|
126
|
+
for (const key of Object.keys(pkg.conventions)) {
|
|
127
|
+
allKeys.add(key);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
const result = {};
|
|
131
|
+
for (const key of allKeys) {
|
|
132
|
+
const entries = packages.map((p) => p.conventions[key]).filter((c) => c !== void 0);
|
|
133
|
+
if (entries.length < packages.length / 2) continue;
|
|
134
|
+
const valueCounts = /* @__PURE__ */ new Map();
|
|
135
|
+
for (const entry of entries) {
|
|
136
|
+
const existing = valueCounts.get(entry.value);
|
|
137
|
+
if (existing) {
|
|
138
|
+
existing.count++;
|
|
139
|
+
existing.totalConsistency += entry.consistency;
|
|
140
|
+
existing.totalSamples += entry.sampleSize;
|
|
141
|
+
} else {
|
|
142
|
+
valueCounts.set(entry.value, {
|
|
143
|
+
count: 1,
|
|
144
|
+
totalConsistency: entry.consistency,
|
|
145
|
+
totalSamples: entry.sampleSize
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
let majorityValue = "";
|
|
150
|
+
let majorityData = { count: 0, totalConsistency: 0, totalSamples: 0 };
|
|
151
|
+
for (const [value, data] of valueCounts) {
|
|
152
|
+
if (data.count > majorityData.count) {
|
|
153
|
+
majorityValue = value;
|
|
154
|
+
majorityData = data;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
const agreement = majorityData.count / entries.length;
|
|
158
|
+
const avgConsistency = majorityData.totalConsistency / majorityData.count;
|
|
159
|
+
const consistency = Math.round(avgConsistency * agreement);
|
|
160
|
+
result[key] = {
|
|
161
|
+
value: majorityValue,
|
|
162
|
+
confidence: (0, import_types.confidenceFromConsistency)(consistency),
|
|
163
|
+
sampleSize: majorityData.totalSamples,
|
|
164
|
+
consistency
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
return result;
|
|
168
|
+
}
|
|
169
|
+
function aggregateStatistics(packages) {
|
|
170
|
+
if (packages.length === 1) return packages[0].statistics;
|
|
171
|
+
const totalFiles = packages.reduce((sum, p) => sum + p.statistics.totalFiles, 0);
|
|
172
|
+
const totalLines = packages.reduce((sum, p) => sum + p.statistics.totalLines, 0);
|
|
173
|
+
const averageFileLines = totalFiles > 0 ? Math.round(totalLines / totalFiles) : 0;
|
|
174
|
+
const largestFiles = packages.flatMap(
|
|
175
|
+
(pkg) => pkg.statistics.largestFiles.map((f) => ({
|
|
176
|
+
path: pkg.relativePath ? `${pkg.relativePath}/${f.path}` : f.path,
|
|
177
|
+
lines: f.lines
|
|
178
|
+
}))
|
|
179
|
+
).sort((a, b) => b.lines - a.lines).slice(0, 5);
|
|
180
|
+
const filesByExtension = {};
|
|
181
|
+
for (const pkg of packages) {
|
|
182
|
+
for (const [ext, count] of Object.entries(pkg.statistics.filesByExtension)) {
|
|
183
|
+
filesByExtension[ext] = (filesByExtension[ext] ?? 0) + count;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return { totalFiles, totalLines, averageFileLines, largestFiles, filesByExtension };
|
|
187
|
+
}
|
|
188
|
+
|
|
36
189
|
// src/compute-statistics.ts
|
|
37
190
|
var import_promises2 = require("fs/promises");
|
|
38
191
|
var import_node_path2 = require("path");
|
|
@@ -202,7 +355,7 @@ async function computeStatistics(projectPath, dirs) {
|
|
|
202
355
|
// src/detect-conventions.ts
|
|
203
356
|
var import_promises3 = require("fs/promises");
|
|
204
357
|
var import_node_path3 = require("path");
|
|
205
|
-
var
|
|
358
|
+
var import_types2 = require("@viberails/types");
|
|
206
359
|
|
|
207
360
|
// src/utils/classify-filename.ts
|
|
208
361
|
var PATTERNS = [
|
|
@@ -259,7 +412,7 @@ function detectFileNaming(dirs) {
|
|
|
259
412
|
if (sorted.length === 0) return void 0;
|
|
260
413
|
const [dominantConvention, dominantCount] = sorted[0];
|
|
261
414
|
const consistency = Math.round(dominantCount / total * 100);
|
|
262
|
-
const confidence = (0,
|
|
415
|
+
const confidence = (0, import_types2.confidenceFromConsistency)(consistency);
|
|
263
416
|
if (confidence === "low") return void 0;
|
|
264
417
|
return {
|
|
265
418
|
value: dominantConvention,
|
|
@@ -285,7 +438,7 @@ function detectComponentNaming(dirs, structure) {
|
|
|
285
438
|
const dominantValue = pascalCount >= tsxFiles.length / 2 ? "PascalCase" : "camelCase";
|
|
286
439
|
return {
|
|
287
440
|
value: dominantValue,
|
|
288
|
-
confidence: (0,
|
|
441
|
+
confidence: (0, import_types2.confidenceFromConsistency)(consistency),
|
|
289
442
|
sampleSize: tsxFiles.length,
|
|
290
443
|
consistency
|
|
291
444
|
};
|
|
@@ -321,7 +474,7 @@ function detectHookNaming(dirs, structure) {
|
|
|
321
474
|
const consistency = Math.round(dominantCount / total * 100);
|
|
322
475
|
return {
|
|
323
476
|
value: isDominantKebab ? "use-*" : "useXxx",
|
|
324
|
-
confidence: (0,
|
|
477
|
+
confidence: (0, import_types2.confidenceFromConsistency)(consistency),
|
|
325
478
|
sampleSize: total,
|
|
326
479
|
consistency
|
|
327
480
|
};
|
|
@@ -335,7 +488,7 @@ async function detectImportAlias(projectPath) {
|
|
|
335
488
|
const aliases = Object.keys(paths);
|
|
336
489
|
if (aliases.length === 0) return void 0;
|
|
337
490
|
return {
|
|
338
|
-
value: aliases
|
|
491
|
+
value: aliases.join(","),
|
|
339
492
|
confidence: "high",
|
|
340
493
|
sampleSize: aliases.length,
|
|
341
494
|
consistency: 100
|
|
@@ -376,6 +529,8 @@ async function fileExists(filePath) {
|
|
|
376
529
|
}
|
|
377
530
|
var FRAMEWORK_MAPPINGS = [
|
|
378
531
|
{ dep: "next", name: "nextjs" },
|
|
532
|
+
{ dep: "expo", name: "expo" },
|
|
533
|
+
{ dep: "react-native", name: "react-native", excludeDep: "expo" },
|
|
379
534
|
{ dep: "@sveltejs/kit", name: "sveltekit" },
|
|
380
535
|
{ dep: "svelte", name: "svelte" },
|
|
381
536
|
{ dep: "astro", name: "astro" },
|
|
@@ -409,9 +564,10 @@ var LOCK_FILE_MAP = [
|
|
|
409
564
|
{ file: "bun.lockb", name: "bun" },
|
|
410
565
|
{ file: "package-lock.json", name: "npm" }
|
|
411
566
|
];
|
|
412
|
-
async function detectStack(projectPath) {
|
|
567
|
+
async function detectStack(projectPath, additionalDeps) {
|
|
413
568
|
const pkg = await readPackageJson(projectPath);
|
|
414
569
|
const allDeps = {
|
|
570
|
+
...additionalDeps,
|
|
415
571
|
...pkg?.dependencies,
|
|
416
572
|
...pkg?.devDependencies
|
|
417
573
|
};
|
|
@@ -512,7 +668,7 @@ function detectLibraries(allDeps) {
|
|
|
512
668
|
}
|
|
513
669
|
|
|
514
670
|
// src/detect-structure.ts
|
|
515
|
-
var
|
|
671
|
+
var import_types3 = require("@viberails/types");
|
|
516
672
|
|
|
517
673
|
// src/utils/classify-directory.ts
|
|
518
674
|
var ROLE_PATTERNS = [
|
|
@@ -553,7 +709,9 @@ function classifyDirectory(dir) {
|
|
|
553
709
|
function matchByName(relativePath) {
|
|
554
710
|
for (const { role, pathPatterns } of ROLE_PATTERNS) {
|
|
555
711
|
for (const pattern of pathPatterns) {
|
|
556
|
-
if (relativePath === pattern)
|
|
712
|
+
if (relativePath === pattern || relativePath.endsWith(`/${pattern}`)) {
|
|
713
|
+
return role;
|
|
714
|
+
}
|
|
557
715
|
}
|
|
558
716
|
}
|
|
559
717
|
return null;
|
|
@@ -564,7 +722,7 @@ function inferFromContent(dir) {
|
|
|
564
722
|
const name = f.split(".")[0];
|
|
565
723
|
return name.startsWith("use-") || /^use[A-Z]/.test(name);
|
|
566
724
|
});
|
|
567
|
-
if (hookFiles.length
|
|
725
|
+
if (hookFiles.length >= 2 && hookFiles.length / sourceFileCount >= 0.5) {
|
|
568
726
|
return {
|
|
569
727
|
path: dir.relativePath,
|
|
570
728
|
role: "hooks",
|
|
@@ -573,7 +731,7 @@ function inferFromContent(dir) {
|
|
|
573
731
|
};
|
|
574
732
|
}
|
|
575
733
|
const testFiles = sourceFileNames.filter((f) => f.includes(".test.") || f.includes(".spec."));
|
|
576
|
-
if (testFiles.length
|
|
734
|
+
if (testFiles.length >= 2 && testFiles.length / sourceFileCount >= 0.5) {
|
|
577
735
|
return {
|
|
578
736
|
path: dir.relativePath,
|
|
579
737
|
role: "tests",
|
|
@@ -618,7 +776,7 @@ function detectTestPattern(dirs) {
|
|
|
618
776
|
const sep = isDotTest ? "test" : "spec";
|
|
619
777
|
return {
|
|
620
778
|
value: `*.${sep}${topExt}`,
|
|
621
|
-
confidence: (0,
|
|
779
|
+
confidence: (0, import_types3.confidenceFromConsistency)(consistency),
|
|
622
780
|
sampleSize: testFiles.length,
|
|
623
781
|
consistency
|
|
624
782
|
};
|
|
@@ -718,20 +876,33 @@ async function resolvePackages(projectRoot, dirs) {
|
|
|
718
876
|
return packages;
|
|
719
877
|
}
|
|
720
878
|
|
|
721
|
-
// src/scan.ts
|
|
722
|
-
var import_promises7 = require("fs/promises");
|
|
879
|
+
// src/scan-package.ts
|
|
723
880
|
var import_node_path7 = require("path");
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
881
|
+
async function scanPackage(packagePath, name, relativePath, rootDeps) {
|
|
882
|
+
const root = (0, import_node_path7.resolve)(packagePath);
|
|
883
|
+
const dirs = await walkDirectory(root, 4);
|
|
884
|
+
const [stack, structure, statistics] = await Promise.all([
|
|
885
|
+
detectStack(root, rootDeps),
|
|
886
|
+
detectStructure(root, dirs),
|
|
887
|
+
computeStatistics(root, dirs)
|
|
888
|
+
]);
|
|
889
|
+
const conventions = await detectConventions(root, structure, dirs);
|
|
890
|
+
return {
|
|
891
|
+
name,
|
|
892
|
+
root,
|
|
893
|
+
relativePath,
|
|
894
|
+
stack,
|
|
895
|
+
structure,
|
|
896
|
+
conventions,
|
|
897
|
+
statistics
|
|
898
|
+
};
|
|
732
899
|
}
|
|
900
|
+
|
|
901
|
+
// src/scan.ts
|
|
902
|
+
var import_promises7 = require("fs/promises");
|
|
903
|
+
var import_node_path8 = require("path");
|
|
733
904
|
async function scan(projectPath, _options) {
|
|
734
|
-
const root = (0,
|
|
905
|
+
const root = (0, import_node_path8.resolve)(projectPath);
|
|
735
906
|
try {
|
|
736
907
|
const st = await (0, import_promises7.stat)(root);
|
|
737
908
|
if (!st.isDirectory()) {
|
|
@@ -743,22 +914,36 @@ async function scan(projectPath, _options) {
|
|
|
743
914
|
}
|
|
744
915
|
throw new Error(`Project path does not exist: ${root}`);
|
|
745
916
|
}
|
|
746
|
-
const allDirs = await walkDirectory(root, 4);
|
|
747
|
-
const dirs = filterFixtureDirs(allDirs);
|
|
748
|
-
const [stack, structure, statistics] = await Promise.all([
|
|
749
|
-
detectStack(root),
|
|
750
|
-
detectStructure(root, dirs),
|
|
751
|
-
computeStatistics(root, dirs)
|
|
752
|
-
]);
|
|
753
|
-
const conventions = await detectConventions(root, structure, dirs);
|
|
754
917
|
const workspace = await detectWorkspace(root);
|
|
918
|
+
if (workspace && workspace.packages.length > 0) {
|
|
919
|
+
const rootPkg2 = await readPackageJson(root);
|
|
920
|
+
const rootDeps = {
|
|
921
|
+
...rootPkg2?.dependencies,
|
|
922
|
+
...rootPkg2?.devDependencies
|
|
923
|
+
};
|
|
924
|
+
const packages = await Promise.all(
|
|
925
|
+
workspace.packages.map((wp) => scanPackage(wp.path, wp.name, wp.relativePath, rootDeps))
|
|
926
|
+
);
|
|
927
|
+
return {
|
|
928
|
+
root,
|
|
929
|
+
stack: aggregateStacks(packages),
|
|
930
|
+
structure: aggregateStructures(packages),
|
|
931
|
+
conventions: aggregateConventions(packages),
|
|
932
|
+
statistics: aggregateStatistics(packages),
|
|
933
|
+
workspace,
|
|
934
|
+
packages
|
|
935
|
+
};
|
|
936
|
+
}
|
|
937
|
+
const rootPkg = await readPackageJson(root);
|
|
938
|
+
const name = rootPkg?.name ?? (0, import_node_path8.basename)(root);
|
|
939
|
+
const pkg = await scanPackage(root, name, "");
|
|
755
940
|
return {
|
|
756
941
|
root,
|
|
757
|
-
stack,
|
|
758
|
-
structure,
|
|
759
|
-
conventions,
|
|
760
|
-
statistics,
|
|
761
|
-
|
|
942
|
+
stack: pkg.stack,
|
|
943
|
+
structure: pkg.structure,
|
|
944
|
+
conventions: pkg.conventions,
|
|
945
|
+
statistics: pkg.statistics,
|
|
946
|
+
packages: [pkg]
|
|
762
947
|
};
|
|
763
948
|
}
|
|
764
949
|
|
|
@@ -767,6 +952,10 @@ var VERSION = "0.1.0";
|
|
|
767
952
|
// Annotate the CommonJS export names for ESM import in node:
|
|
768
953
|
0 && (module.exports = {
|
|
769
954
|
VERSION,
|
|
955
|
+
aggregateConventions,
|
|
956
|
+
aggregateStacks,
|
|
957
|
+
aggregateStatistics,
|
|
958
|
+
aggregateStructures,
|
|
770
959
|
computeStatistics,
|
|
771
960
|
detectConventions,
|
|
772
961
|
detectStack,
|
|
@@ -775,6 +964,7 @@ var VERSION = "0.1.0";
|
|
|
775
964
|
extractMajorVersion,
|
|
776
965
|
readPackageJson,
|
|
777
966
|
scan,
|
|
967
|
+
scanPackage,
|
|
778
968
|
walkDirectory
|
|
779
969
|
});
|
|
780
970
|
//# sourceMappingURL=index.cjs.map
|