@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 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 import_types = require("@viberails/types");
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, import_types.confidenceFromConsistency)(consistency);
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, import_types.confidenceFromConsistency)(consistency),
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, import_types.confidenceFromConsistency)(consistency),
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[0],
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 import_types2 = require("@viberails/types");
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) return role;
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 > 0 && hookFiles.length / sourceFileCount >= 0.5) {
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 > 0 && testFiles.length / sourceFileCount >= 0.5) {
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, import_types2.confidenceFromConsistency)(consistency),
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
- var FIXTURE_PATTERNS = [
725
- /^tests\/fixtures(\/|$)/,
726
- /^test\/fixtures(\/|$)/,
727
- /^__tests__\/fixtures(\/|$)/,
728
- /^fixtures(\/|$)/
729
- ];
730
- function filterFixtureDirs(dirs) {
731
- return dirs.filter((d) => !FIXTURE_PATTERNS.some((pattern) => pattern.test(d.relativePath)));
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, import_node_path7.resolve)(projectPath);
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
- workspace
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