viberails 0.2.0 → 0.2.2
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 +318 -231
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +316 -229
- package/dist/index.js.map +1 -1
- package/package.json +6 -6
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
|
-
import
|
|
4
|
+
import chalk10 from "chalk";
|
|
5
5
|
import { Command } from "commander";
|
|
6
6
|
|
|
7
7
|
// src/commands/boundaries.ts
|
|
@@ -223,6 +223,19 @@ function resolveIgnoreForFile(relPath, config) {
|
|
|
223
223
|
import { execSync } from "child_process";
|
|
224
224
|
import * as fs4 from "fs";
|
|
225
225
|
import * as path4 from "path";
|
|
226
|
+
var ALWAYS_SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
227
|
+
"node_modules",
|
|
228
|
+
".git",
|
|
229
|
+
"dist",
|
|
230
|
+
"build",
|
|
231
|
+
".next",
|
|
232
|
+
".expo",
|
|
233
|
+
".output",
|
|
234
|
+
".svelte-kit",
|
|
235
|
+
".turbo",
|
|
236
|
+
"coverage",
|
|
237
|
+
".viberails"
|
|
238
|
+
]);
|
|
226
239
|
var SOURCE_EXTS = /* @__PURE__ */ new Set([
|
|
227
240
|
".ts",
|
|
228
241
|
".tsx",
|
|
@@ -271,7 +284,7 @@ function checkNaming(relPath, conventions) {
|
|
|
271
284
|
const filename = path4.basename(relPath);
|
|
272
285
|
const ext = path4.extname(filename);
|
|
273
286
|
if (!SOURCE_EXTS.has(ext)) return void 0;
|
|
274
|
-
if (filename.startsWith("index.") || filename.includes(".config.") || filename.includes(".test.") || filename.includes(".spec.") || filename.startsWith(".")) {
|
|
287
|
+
if (filename.startsWith("index.") || filename.includes(".config.") || filename.includes(".test.") || filename.includes(".spec.") || filename.startsWith(".") || filename.startsWith("_") || filename.startsWith("+") || filename.startsWith("$") || filename.startsWith("[")) {
|
|
275
288
|
return void 0;
|
|
276
289
|
}
|
|
277
290
|
const bare = filename.slice(0, filename.indexOf("."));
|
|
@@ -304,7 +317,7 @@ function getAllSourceFiles(projectRoot, config) {
|
|
|
304
317
|
for (const entry of entries) {
|
|
305
318
|
const rel = path4.relative(projectRoot, path4.join(dir, entry.name));
|
|
306
319
|
if (entry.isDirectory()) {
|
|
307
|
-
if (
|
|
320
|
+
if (ALWAYS_SKIP_DIRS.has(entry.name)) {
|
|
308
321
|
continue;
|
|
309
322
|
}
|
|
310
323
|
if (isIgnored(rel, config.ignore)) continue;
|
|
@@ -499,12 +512,56 @@ ${violations.length} ${word} found.`);
|
|
|
499
512
|
}
|
|
500
513
|
|
|
501
514
|
// src/commands/fix.ts
|
|
502
|
-
import { execSync as execSync2 } from "child_process";
|
|
503
515
|
import * as fs9 from "fs";
|
|
504
516
|
import * as path10 from "path";
|
|
505
|
-
import { createInterface as createInterface2 } from "readline";
|
|
506
517
|
import { loadConfig as loadConfig3 } from "@viberails/config";
|
|
518
|
+
import chalk4 from "chalk";
|
|
519
|
+
|
|
520
|
+
// src/commands/fix-helpers.ts
|
|
521
|
+
import { execSync as execSync2 } from "child_process";
|
|
522
|
+
import { createInterface as createInterface2 } from "readline";
|
|
507
523
|
import chalk3 from "chalk";
|
|
524
|
+
function printPlan(renames, stubs) {
|
|
525
|
+
if (renames.length > 0) {
|
|
526
|
+
console.log(chalk3.bold("\nFile renames:"));
|
|
527
|
+
for (const r of renames) {
|
|
528
|
+
console.log(` ${chalk3.red(r.oldPath)} \u2192 ${chalk3.green(r.newPath)}`);
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
if (stubs.length > 0) {
|
|
532
|
+
console.log(chalk3.bold("\nTest stubs to create:"));
|
|
533
|
+
for (const s of stubs) {
|
|
534
|
+
console.log(` ${chalk3.green("+")} ${s.path}`);
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
function checkGitDirty(projectRoot) {
|
|
539
|
+
try {
|
|
540
|
+
const output = execSync2("git status --porcelain", {
|
|
541
|
+
cwd: projectRoot,
|
|
542
|
+
encoding: "utf-8"
|
|
543
|
+
});
|
|
544
|
+
return output.trim().length > 0;
|
|
545
|
+
} catch {
|
|
546
|
+
return false;
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
function getConventionValue(convention) {
|
|
550
|
+
if (typeof convention === "string") return convention;
|
|
551
|
+
if (convention && typeof convention === "object" && "value" in convention) {
|
|
552
|
+
return convention.value;
|
|
553
|
+
}
|
|
554
|
+
return void 0;
|
|
555
|
+
}
|
|
556
|
+
function promptConfirm(question) {
|
|
557
|
+
const rl = createInterface2({ input: process.stdin, output: process.stdout });
|
|
558
|
+
return new Promise((resolve4) => {
|
|
559
|
+
rl.question(`${question} (y/N) `, (answer) => {
|
|
560
|
+
rl.close();
|
|
561
|
+
resolve4(answer.toLowerCase() === "y" || answer.toLowerCase() === "yes");
|
|
562
|
+
});
|
|
563
|
+
});
|
|
564
|
+
}
|
|
508
565
|
|
|
509
566
|
// src/commands/fix-imports.ts
|
|
510
567
|
import * as path7 from "path";
|
|
@@ -724,22 +781,22 @@ async function fixCommand(options, cwd) {
|
|
|
724
781
|
const startDir = cwd ?? process.cwd();
|
|
725
782
|
const projectRoot = findProjectRoot(startDir);
|
|
726
783
|
if (!projectRoot) {
|
|
727
|
-
console.error(`${
|
|
784
|
+
console.error(`${chalk4.red("Error:")} No package.json found. Are you in a JS/TS project?`);
|
|
728
785
|
return 1;
|
|
729
786
|
}
|
|
730
787
|
const configPath = path10.join(projectRoot, CONFIG_FILE3);
|
|
731
788
|
if (!fs9.existsSync(configPath)) {
|
|
732
789
|
console.error(
|
|
733
|
-
`${
|
|
790
|
+
`${chalk4.red("Error:")} No viberails.config.json found. Run \`viberails init\` first.`
|
|
734
791
|
);
|
|
735
792
|
return 1;
|
|
736
793
|
}
|
|
737
794
|
const config = await loadConfig3(configPath);
|
|
738
|
-
if (!options.
|
|
795
|
+
if (!options.dryRun) {
|
|
739
796
|
const isDirty = checkGitDirty(projectRoot);
|
|
740
797
|
if (isDirty) {
|
|
741
798
|
console.log(
|
|
742
|
-
|
|
799
|
+
chalk4.yellow("Warning: You have uncommitted changes. Consider committing first.")
|
|
743
800
|
);
|
|
744
801
|
}
|
|
745
802
|
}
|
|
@@ -769,12 +826,12 @@ async function fixCommand(options, cwd) {
|
|
|
769
826
|
}
|
|
770
827
|
}
|
|
771
828
|
if (dedupedRenames.length === 0 && testStubs.length === 0) {
|
|
772
|
-
console.log(`${
|
|
829
|
+
console.log(`${chalk4.green("\u2713")} No fixable violations found.`);
|
|
773
830
|
return 0;
|
|
774
831
|
}
|
|
775
832
|
printPlan(dedupedRenames, testStubs);
|
|
776
833
|
if (options.dryRun) {
|
|
777
|
-
console.log(
|
|
834
|
+
console.log(chalk4.dim("\nDry run \u2014 no changes applied."));
|
|
778
835
|
return 0;
|
|
779
836
|
}
|
|
780
837
|
if (!options.yes) {
|
|
@@ -805,87 +862,82 @@ async function fixCommand(options, cwd) {
|
|
|
805
862
|
}
|
|
806
863
|
console.log("");
|
|
807
864
|
if (renameCount > 0) {
|
|
808
|
-
console.log(`${
|
|
865
|
+
console.log(`${chalk4.green("\u2713")} Renamed ${renameCount} file${renameCount > 1 ? "s" : ""}`);
|
|
809
866
|
}
|
|
810
867
|
if (importUpdateCount > 0) {
|
|
811
868
|
console.log(
|
|
812
|
-
`${
|
|
869
|
+
`${chalk4.green("\u2713")} Updated ${importUpdateCount} import${importUpdateCount > 1 ? "s" : ""}`
|
|
813
870
|
);
|
|
814
871
|
}
|
|
815
872
|
if (stubCount > 0) {
|
|
816
|
-
console.log(`${
|
|
873
|
+
console.log(`${chalk4.green("\u2713")} Generated ${stubCount} test stub${stubCount > 1 ? "s" : ""}`);
|
|
817
874
|
}
|
|
818
875
|
return 0;
|
|
819
876
|
}
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
877
|
+
|
|
878
|
+
// src/commands/init.ts
|
|
879
|
+
import * as fs12 from "fs";
|
|
880
|
+
import * as path13 from "path";
|
|
881
|
+
import { generateConfig } from "@viberails/config";
|
|
882
|
+
import { scan } from "@viberails/scanner";
|
|
883
|
+
import chalk8 from "chalk";
|
|
884
|
+
|
|
885
|
+
// src/display.ts
|
|
886
|
+
import { FRAMEWORK_NAMES as FRAMEWORK_NAMES2, LIBRARY_NAMES, STYLING_NAMES as STYLING_NAMES2 } from "@viberails/types";
|
|
887
|
+
import chalk6 from "chalk";
|
|
888
|
+
|
|
889
|
+
// src/display-helpers.ts
|
|
890
|
+
import { ROLE_DESCRIPTIONS } from "@viberails/types";
|
|
891
|
+
function groupByRole(directories) {
|
|
892
|
+
const map = /* @__PURE__ */ new Map();
|
|
893
|
+
for (const dir of directories) {
|
|
894
|
+
if (dir.role === "unknown") continue;
|
|
895
|
+
const existing = map.get(dir.role);
|
|
896
|
+
if (existing) {
|
|
897
|
+
existing.dirs.push(dir);
|
|
898
|
+
} else {
|
|
899
|
+
map.set(dir.role, { dirs: [dir] });
|
|
831
900
|
}
|
|
832
901
|
}
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
const
|
|
837
|
-
|
|
838
|
-
|
|
902
|
+
const groups = [];
|
|
903
|
+
for (const [role, { dirs }] of map) {
|
|
904
|
+
const label = ROLE_DESCRIPTIONS[role] ?? role;
|
|
905
|
+
const totalFiles = dirs.reduce((sum, d) => sum + d.fileCount, 0);
|
|
906
|
+
groups.push({
|
|
907
|
+
role,
|
|
908
|
+
label,
|
|
909
|
+
dirCount: dirs.length,
|
|
910
|
+
totalFiles,
|
|
911
|
+
singlePath: dirs.length === 1 ? dirs[0].path : void 0
|
|
839
912
|
});
|
|
840
|
-
return output.trim().length > 0;
|
|
841
|
-
} catch {
|
|
842
|
-
return false;
|
|
843
913
|
}
|
|
914
|
+
return groups;
|
|
844
915
|
}
|
|
845
|
-
function
|
|
846
|
-
|
|
847
|
-
if (
|
|
848
|
-
|
|
916
|
+
function formatSummary(stats, packageCount) {
|
|
917
|
+
const parts = [];
|
|
918
|
+
if (packageCount && packageCount > 1) {
|
|
919
|
+
parts.push(`${packageCount} packages`);
|
|
849
920
|
}
|
|
850
|
-
|
|
921
|
+
parts.push(`${stats.totalFiles.toLocaleString()} source files`);
|
|
922
|
+
parts.push(`${stats.totalLines.toLocaleString()} lines`);
|
|
923
|
+
parts.push(`avg ${Math.round(stats.averageFileLines)} lines/file`);
|
|
924
|
+
return parts.join(" \xB7 ");
|
|
851
925
|
}
|
|
852
|
-
function
|
|
853
|
-
|
|
854
|
-
return new Promise((resolve4) => {
|
|
855
|
-
rl.question(`${question} (y/N) `, (answer) => {
|
|
856
|
-
rl.close();
|
|
857
|
-
resolve4(answer.toLowerCase() === "y" || answer.toLowerCase() === "yes");
|
|
858
|
-
});
|
|
859
|
-
});
|
|
926
|
+
function formatExtensions(filesByExtension, maxEntries = 4) {
|
|
927
|
+
return Object.entries(filesByExtension).sort(([, a], [, b]) => b - a).slice(0, maxEntries).map(([ext, count]) => `${ext} ${count}`).join(" \xB7 ");
|
|
860
928
|
}
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
import { generateConfig } from "@viberails/config";
|
|
866
|
-
import { scan } from "@viberails/scanner";
|
|
867
|
-
import chalk5 from "chalk";
|
|
868
|
-
|
|
869
|
-
// src/display.ts
|
|
870
|
-
import { FRAMEWORK_NAMES, LIBRARY_NAMES, ROLE_DESCRIPTIONS, STYLING_NAMES } from "@viberails/types";
|
|
871
|
-
import chalk4 from "chalk";
|
|
872
|
-
var CONVENTION_LABELS = {
|
|
873
|
-
fileNaming: "File naming",
|
|
874
|
-
componentNaming: "Component naming",
|
|
875
|
-
hookNaming: "Hook naming",
|
|
876
|
-
importAlias: "Import alias"
|
|
877
|
-
};
|
|
878
|
-
function formatItem(item, nameMap) {
|
|
879
|
-
const name = nameMap?.[item.name] ?? item.name;
|
|
880
|
-
return item.version ? `${name} ${item.version}` : name;
|
|
881
|
-
}
|
|
882
|
-
function confidenceLabel(convention) {
|
|
883
|
-
const pct = Math.round(convention.consistency);
|
|
884
|
-
if (convention.confidence === "high") {
|
|
885
|
-
return `${pct}% \u2014 high confidence, will enforce`;
|
|
929
|
+
function formatRoleGroup(group) {
|
|
930
|
+
const files = group.totalFiles === 1 ? "1 file" : `${group.totalFiles} files`;
|
|
931
|
+
if (group.singlePath) {
|
|
932
|
+
return `${group.label} \u2014 ${group.singlePath} (${files})`;
|
|
886
933
|
}
|
|
887
|
-
|
|
934
|
+
const dirs = group.dirCount === 1 ? "1 dir" : `${group.dirCount} dirs`;
|
|
935
|
+
return `${group.label} \u2014 ${dirs} (${files})`;
|
|
888
936
|
}
|
|
937
|
+
|
|
938
|
+
// src/display-monorepo.ts
|
|
939
|
+
import { FRAMEWORK_NAMES, STYLING_NAMES } from "@viberails/types";
|
|
940
|
+
import chalk5 from "chalk";
|
|
889
941
|
function formatPackageSummary(pkg) {
|
|
890
942
|
const parts = [];
|
|
891
943
|
if (pkg.stack.framework) {
|
|
@@ -901,16 +953,19 @@ function formatPackageSummary(pkg) {
|
|
|
901
953
|
function displayMonorepoResults(scanResult) {
|
|
902
954
|
const { stack, packages } = scanResult;
|
|
903
955
|
console.log(`
|
|
904
|
-
${
|
|
905
|
-
console.log(` ${
|
|
956
|
+
${chalk5.bold(`Detected: (monorepo, ${packages.length} packages)`)}`);
|
|
957
|
+
console.log(` ${chalk5.green("\u2713")} ${formatItem(stack.language)}`);
|
|
906
958
|
if (stack.packageManager) {
|
|
907
|
-
console.log(` ${
|
|
959
|
+
console.log(` ${chalk5.green("\u2713")} ${formatItem(stack.packageManager)}`);
|
|
908
960
|
}
|
|
909
961
|
if (stack.linter) {
|
|
910
|
-
console.log(` ${
|
|
962
|
+
console.log(` ${chalk5.green("\u2713")} ${formatItem(stack.linter)}`);
|
|
963
|
+
}
|
|
964
|
+
if (stack.formatter) {
|
|
965
|
+
console.log(` ${chalk5.green("\u2713")} ${formatItem(stack.formatter)}`);
|
|
911
966
|
}
|
|
912
967
|
if (stack.testRunner) {
|
|
913
|
-
console.log(` ${
|
|
968
|
+
console.log(` ${chalk5.green("\u2713")} ${formatItem(stack.testRunner)}`);
|
|
914
969
|
}
|
|
915
970
|
console.log("");
|
|
916
971
|
for (const pkg of packages) {
|
|
@@ -921,96 +976,123 @@ ${chalk4.bold(`Detected: (monorepo, ${packages.length} packages)`)}`);
|
|
|
921
976
|
);
|
|
922
977
|
if (packagesWithDirs.length > 0) {
|
|
923
978
|
console.log(`
|
|
924
|
-
${
|
|
979
|
+
${chalk5.bold("Structure:")}`);
|
|
925
980
|
for (const pkg of packagesWithDirs) {
|
|
926
|
-
const
|
|
927
|
-
if (
|
|
981
|
+
const groups = groupByRole(pkg.structure.directories);
|
|
982
|
+
if (groups.length === 0) continue;
|
|
928
983
|
console.log(` ${pkg.relativePath}:`);
|
|
929
|
-
for (const
|
|
930
|
-
|
|
931
|
-
const files = dir.fileCount === 1 ? "1 file" : `${dir.fileCount} files`;
|
|
932
|
-
console.log(` ${chalk4.green("\u2713")} ${dir.path} \u2014 ${label} (${files})`);
|
|
984
|
+
for (const group of groups) {
|
|
985
|
+
console.log(` ${chalk5.green("\u2713")} ${formatRoleGroup(group)}`);
|
|
933
986
|
}
|
|
934
987
|
}
|
|
935
988
|
}
|
|
989
|
+
displayConventions(scanResult);
|
|
990
|
+
displaySummarySection(scanResult);
|
|
991
|
+
console.log("");
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
// src/display.ts
|
|
995
|
+
var CONVENTION_LABELS = {
|
|
996
|
+
fileNaming: "File naming",
|
|
997
|
+
componentNaming: "Component naming",
|
|
998
|
+
hookNaming: "Hook naming",
|
|
999
|
+
importAlias: "Import alias"
|
|
1000
|
+
};
|
|
1001
|
+
function formatItem(item, nameMap) {
|
|
1002
|
+
const name = nameMap?.[item.name] ?? item.name;
|
|
1003
|
+
return item.version ? `${name} ${item.version}` : name;
|
|
1004
|
+
}
|
|
1005
|
+
function confidenceLabel(convention) {
|
|
1006
|
+
const pct = Math.round(convention.consistency);
|
|
1007
|
+
if (convention.confidence === "high") {
|
|
1008
|
+
return `${pct}% \u2014 high confidence, will enforce`;
|
|
1009
|
+
}
|
|
1010
|
+
return `${pct}% \u2014 medium confidence, suggested only`;
|
|
1011
|
+
}
|
|
1012
|
+
function displayConventions(scanResult) {
|
|
936
1013
|
const conventionEntries = Object.entries(scanResult.conventions);
|
|
937
|
-
if (conventionEntries.length
|
|
938
|
-
|
|
939
|
-
${
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
1014
|
+
if (conventionEntries.length === 0) return;
|
|
1015
|
+
console.log(`
|
|
1016
|
+
${chalk6.bold("Conventions:")}`);
|
|
1017
|
+
for (const [key, convention] of conventionEntries) {
|
|
1018
|
+
if (convention.confidence === "low") continue;
|
|
1019
|
+
const label = CONVENTION_LABELS[key] ?? key;
|
|
1020
|
+
if (scanResult.packages.length > 1) {
|
|
1021
|
+
const pkgValues = scanResult.packages.filter((pkg) => pkg.conventions[key] && pkg.conventions[key].confidence !== "low").map((pkg) => ({ relativePath: pkg.relativePath, convention: pkg.conventions[key] }));
|
|
944
1022
|
const allSame = pkgValues.every((pv) => pv.convention.value === convention.value);
|
|
945
1023
|
if (allSame || pkgValues.length <= 1) {
|
|
946
|
-
const ind = convention.confidence === "high" ?
|
|
947
|
-
const detail =
|
|
1024
|
+
const ind = convention.confidence === "high" ? chalk6.green("\u2713") : chalk6.yellow("~");
|
|
1025
|
+
const detail = chalk6.dim(`(${confidenceLabel(convention)})`);
|
|
948
1026
|
console.log(` ${ind} ${label}: ${convention.value} ${detail}`);
|
|
949
1027
|
} else {
|
|
950
|
-
console.log(` ${
|
|
1028
|
+
console.log(` ${chalk6.yellow("~")} ${label}: varies by package`);
|
|
951
1029
|
for (const pv of pkgValues) {
|
|
952
1030
|
const pct = Math.round(pv.convention.consistency);
|
|
953
1031
|
console.log(` ${pv.relativePath}: ${pv.convention.value} (${pct}%)`);
|
|
954
1032
|
}
|
|
955
1033
|
}
|
|
1034
|
+
} else {
|
|
1035
|
+
const ind = convention.confidence === "high" ? chalk6.green("\u2713") : chalk6.yellow("~");
|
|
1036
|
+
const detail = chalk6.dim(`(${confidenceLabel(convention)})`);
|
|
1037
|
+
console.log(` ${ind} ${label}: ${convention.value} ${detail}`);
|
|
956
1038
|
}
|
|
957
1039
|
}
|
|
958
|
-
|
|
1040
|
+
}
|
|
1041
|
+
function displaySummarySection(scanResult) {
|
|
1042
|
+
const pkgCount = scanResult.packages.length > 1 ? scanResult.packages.length : void 0;
|
|
1043
|
+
console.log(`
|
|
1044
|
+
${chalk6.bold("Summary:")}`);
|
|
1045
|
+
console.log(` ${formatSummary(scanResult.statistics, pkgCount)}`);
|
|
1046
|
+
const ext = formatExtensions(scanResult.statistics.filesByExtension);
|
|
1047
|
+
if (ext) {
|
|
1048
|
+
console.log(` ${ext}`);
|
|
1049
|
+
}
|
|
959
1050
|
}
|
|
960
1051
|
function displayScanResults(scanResult) {
|
|
961
1052
|
if (scanResult.packages.length > 1) {
|
|
962
1053
|
displayMonorepoResults(scanResult);
|
|
963
1054
|
return;
|
|
964
1055
|
}
|
|
965
|
-
const { stack
|
|
1056
|
+
const { stack } = scanResult;
|
|
966
1057
|
console.log(`
|
|
967
|
-
${
|
|
1058
|
+
${chalk6.bold("Detected:")}`);
|
|
968
1059
|
if (stack.framework) {
|
|
969
|
-
console.log(` ${
|
|
1060
|
+
console.log(` ${chalk6.green("\u2713")} ${formatItem(stack.framework, FRAMEWORK_NAMES2)}`);
|
|
970
1061
|
}
|
|
971
|
-
console.log(` ${
|
|
1062
|
+
console.log(` ${chalk6.green("\u2713")} ${formatItem(stack.language)}`);
|
|
972
1063
|
if (stack.styling) {
|
|
973
|
-
console.log(` ${
|
|
1064
|
+
console.log(` ${chalk6.green("\u2713")} ${formatItem(stack.styling, STYLING_NAMES2)}`);
|
|
974
1065
|
}
|
|
975
1066
|
if (stack.backend) {
|
|
976
|
-
console.log(` ${
|
|
1067
|
+
console.log(` ${chalk6.green("\u2713")} ${formatItem(stack.backend, FRAMEWORK_NAMES2)}`);
|
|
977
1068
|
}
|
|
978
1069
|
if (stack.linter) {
|
|
979
|
-
console.log(` ${
|
|
1070
|
+
console.log(` ${chalk6.green("\u2713")} ${formatItem(stack.linter)}`);
|
|
1071
|
+
}
|
|
1072
|
+
if (stack.formatter) {
|
|
1073
|
+
console.log(` ${chalk6.green("\u2713")} ${formatItem(stack.formatter)}`);
|
|
980
1074
|
}
|
|
981
1075
|
if (stack.testRunner) {
|
|
982
|
-
console.log(` ${
|
|
1076
|
+
console.log(` ${chalk6.green("\u2713")} ${formatItem(stack.testRunner)}`);
|
|
983
1077
|
}
|
|
984
1078
|
if (stack.packageManager) {
|
|
985
|
-
console.log(` ${
|
|
1079
|
+
console.log(` ${chalk6.green("\u2713")} ${formatItem(stack.packageManager)}`);
|
|
986
1080
|
}
|
|
987
1081
|
if (stack.libraries.length > 0) {
|
|
988
1082
|
for (const lib of stack.libraries) {
|
|
989
|
-
console.log(` ${
|
|
990
|
-
}
|
|
991
|
-
}
|
|
992
|
-
const meaningfulDirs = scanResult.structure.directories.filter((d) => d.role !== "unknown");
|
|
993
|
-
if (meaningfulDirs.length > 0) {
|
|
994
|
-
console.log(`
|
|
995
|
-
${chalk4.bold("Structure:")}`);
|
|
996
|
-
for (const dir of meaningfulDirs) {
|
|
997
|
-
const label = ROLE_DESCRIPTIONS[dir.role] ?? dir.role;
|
|
998
|
-
const files = dir.fileCount === 1 ? "1 file" : `${dir.fileCount} files`;
|
|
999
|
-
console.log(` ${chalk4.green("\u2713")} ${dir.path} \u2014 ${label} (${files})`);
|
|
1083
|
+
console.log(` ${chalk6.green("\u2713")} ${formatItem(lib, LIBRARY_NAMES)}`);
|
|
1000
1084
|
}
|
|
1001
1085
|
}
|
|
1002
|
-
const
|
|
1003
|
-
if (
|
|
1086
|
+
const groups = groupByRole(scanResult.structure.directories);
|
|
1087
|
+
if (groups.length > 0) {
|
|
1004
1088
|
console.log(`
|
|
1005
|
-
${
|
|
1006
|
-
for (const
|
|
1007
|
-
|
|
1008
|
-
const label = CONVENTION_LABELS[key] ?? key;
|
|
1009
|
-
const ind = convention.confidence === "high" ? chalk4.green("\u2713") : chalk4.yellow("~");
|
|
1010
|
-
const detail = chalk4.dim(`(${confidenceLabel(convention)})`);
|
|
1011
|
-
console.log(` ${ind} ${label}: ${convention.value} ${detail}`);
|
|
1089
|
+
${chalk6.bold("Structure:")}`);
|
|
1090
|
+
for (const group of groups) {
|
|
1091
|
+
console.log(` ${chalk6.green("\u2713")} ${formatRoleGroup(group)}`);
|
|
1012
1092
|
}
|
|
1013
1093
|
}
|
|
1094
|
+
displayConventions(scanResult);
|
|
1095
|
+
displaySummarySection(scanResult);
|
|
1014
1096
|
console.log("");
|
|
1015
1097
|
}
|
|
1016
1098
|
|
|
@@ -1040,6 +1122,79 @@ function writeGeneratedFiles(projectRoot, config, scanResult) {
|
|
|
1040
1122
|
}
|
|
1041
1123
|
}
|
|
1042
1124
|
|
|
1125
|
+
// src/commands/init-hooks.ts
|
|
1126
|
+
import * as fs11 from "fs";
|
|
1127
|
+
import * as path12 from "path";
|
|
1128
|
+
import chalk7 from "chalk";
|
|
1129
|
+
function setupPreCommitHook(projectRoot) {
|
|
1130
|
+
const lefthookPath = path12.join(projectRoot, "lefthook.yml");
|
|
1131
|
+
if (fs11.existsSync(lefthookPath)) {
|
|
1132
|
+
addLefthookPreCommit(lefthookPath);
|
|
1133
|
+
console.log(` ${chalk7.green("\u2713")} lefthook.yml \u2014 added viberails pre-commit`);
|
|
1134
|
+
return;
|
|
1135
|
+
}
|
|
1136
|
+
const huskyDir = path12.join(projectRoot, ".husky");
|
|
1137
|
+
if (fs11.existsSync(huskyDir)) {
|
|
1138
|
+
writeHuskyPreCommit(huskyDir);
|
|
1139
|
+
console.log(` ${chalk7.green("\u2713")} .husky/pre-commit \u2014 added viberails check`);
|
|
1140
|
+
return;
|
|
1141
|
+
}
|
|
1142
|
+
const gitDir = path12.join(projectRoot, ".git");
|
|
1143
|
+
if (fs11.existsSync(gitDir)) {
|
|
1144
|
+
const hooksDir = path12.join(gitDir, "hooks");
|
|
1145
|
+
if (!fs11.existsSync(hooksDir)) {
|
|
1146
|
+
fs11.mkdirSync(hooksDir, { recursive: true });
|
|
1147
|
+
}
|
|
1148
|
+
writeGitHookPreCommit(hooksDir);
|
|
1149
|
+
console.log(` ${chalk7.green("\u2713")} .git/hooks/pre-commit`);
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
1152
|
+
function writeGitHookPreCommit(hooksDir) {
|
|
1153
|
+
const hookPath = path12.join(hooksDir, "pre-commit");
|
|
1154
|
+
if (fs11.existsSync(hookPath)) {
|
|
1155
|
+
const existing = fs11.readFileSync(hookPath, "utf-8");
|
|
1156
|
+
if (existing.includes("viberails")) return;
|
|
1157
|
+
fs11.writeFileSync(
|
|
1158
|
+
hookPath,
|
|
1159
|
+
`${existing.trimEnd()}
|
|
1160
|
+
|
|
1161
|
+
# viberails check
|
|
1162
|
+
npx viberails check --staged
|
|
1163
|
+
`
|
|
1164
|
+
);
|
|
1165
|
+
return;
|
|
1166
|
+
}
|
|
1167
|
+
const script = [
|
|
1168
|
+
"#!/bin/sh",
|
|
1169
|
+
"# Generated by viberails \u2014 https://viberails.sh",
|
|
1170
|
+
"",
|
|
1171
|
+
"npx viberails check --staged",
|
|
1172
|
+
""
|
|
1173
|
+
].join("\n");
|
|
1174
|
+
fs11.writeFileSync(hookPath, script, { mode: 493 });
|
|
1175
|
+
}
|
|
1176
|
+
function addLefthookPreCommit(lefthookPath) {
|
|
1177
|
+
const content = fs11.readFileSync(lefthookPath, "utf-8");
|
|
1178
|
+
if (content.includes("viberails")) return;
|
|
1179
|
+
const addition = ["", " viberails:", " run: npx viberails check --staged"].join("\n");
|
|
1180
|
+
fs11.writeFileSync(lefthookPath, `${content.trimEnd()}
|
|
1181
|
+
${addition}
|
|
1182
|
+
`);
|
|
1183
|
+
}
|
|
1184
|
+
function writeHuskyPreCommit(huskyDir) {
|
|
1185
|
+
const hookPath = path12.join(huskyDir, "pre-commit");
|
|
1186
|
+
if (fs11.existsSync(hookPath)) {
|
|
1187
|
+
const existing = fs11.readFileSync(hookPath, "utf-8");
|
|
1188
|
+
if (!existing.includes("viberails")) {
|
|
1189
|
+
fs11.writeFileSync(hookPath, `${existing.trimEnd()}
|
|
1190
|
+
npx viberails check --staged
|
|
1191
|
+
`);
|
|
1192
|
+
}
|
|
1193
|
+
return;
|
|
1194
|
+
}
|
|
1195
|
+
fs11.writeFileSync(hookPath, "#!/bin/sh\nnpx viberails check --staged\n", { mode: 493 });
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1043
1198
|
// src/commands/init.ts
|
|
1044
1199
|
var CONFIG_FILE4 = "viberails.config.json";
|
|
1045
1200
|
function filterHighConfidence(conventions) {
|
|
@@ -1062,19 +1217,19 @@ async function initCommand(options, cwd) {
|
|
|
1062
1217
|
"No package.json found in this directory or any parent.\n\nMake sure you are inside a JavaScript or TypeScript project, then run:\n npx viberails"
|
|
1063
1218
|
);
|
|
1064
1219
|
}
|
|
1065
|
-
const configPath =
|
|
1066
|
-
if (
|
|
1220
|
+
const configPath = path13.join(projectRoot, CONFIG_FILE4);
|
|
1221
|
+
if (fs12.existsSync(configPath)) {
|
|
1067
1222
|
console.log(
|
|
1068
|
-
|
|
1223
|
+
chalk8.yellow("!") + " viberails is already initialized in this project.\n Run " + chalk8.cyan("viberails sync") + " to update the generated files."
|
|
1069
1224
|
);
|
|
1070
1225
|
return;
|
|
1071
1226
|
}
|
|
1072
|
-
console.log(
|
|
1227
|
+
console.log(chalk8.dim("Scanning project..."));
|
|
1073
1228
|
const scanResult = await scan(projectRoot);
|
|
1074
1229
|
displayScanResults(scanResult);
|
|
1075
1230
|
if (scanResult.statistics.totalFiles === 0) {
|
|
1076
1231
|
console.log(
|
|
1077
|
-
|
|
1232
|
+
chalk8.yellow("!") + " No source files detected. viberails will generate context with minimal content.\n Run " + chalk8.cyan("viberails sync") + " after adding source files.\n"
|
|
1078
1233
|
);
|
|
1079
1234
|
}
|
|
1080
1235
|
if (!options.yes) {
|
|
@@ -1094,7 +1249,7 @@ async function initCommand(options, cwd) {
|
|
|
1094
1249
|
shouldInfer = await confirm("Infer boundary rules from import patterns?");
|
|
1095
1250
|
}
|
|
1096
1251
|
if (shouldInfer) {
|
|
1097
|
-
console.log(
|
|
1252
|
+
console.log(chalk8.dim("Building import graph..."));
|
|
1098
1253
|
const { buildImportGraph, inferBoundaries } = await import("@viberails/graph");
|
|
1099
1254
|
const packages = resolveWorkspacePackages(projectRoot, config.workspace);
|
|
1100
1255
|
const graph = await buildImportGraph(projectRoot, { packages, ignore: config.ignore });
|
|
@@ -1102,115 +1257,47 @@ async function initCommand(options, cwd) {
|
|
|
1102
1257
|
if (inferred.length > 0) {
|
|
1103
1258
|
config.boundaries = inferred;
|
|
1104
1259
|
config.rules.enforceBoundaries = true;
|
|
1105
|
-
console.log(` ${
|
|
1260
|
+
console.log(` ${chalk8.green("\u2713")} Inferred ${inferred.length} boundary rules`);
|
|
1106
1261
|
}
|
|
1107
1262
|
}
|
|
1108
1263
|
}
|
|
1109
|
-
|
|
1264
|
+
fs12.writeFileSync(configPath, `${JSON.stringify(config, null, 2)}
|
|
1110
1265
|
`);
|
|
1111
1266
|
writeGeneratedFiles(projectRoot, config, scanResult);
|
|
1112
1267
|
updateGitignore(projectRoot);
|
|
1113
1268
|
setupPreCommitHook(projectRoot);
|
|
1114
1269
|
console.log(`
|
|
1115
|
-
${
|
|
1116
|
-
console.log(` ${
|
|
1117
|
-
console.log(` ${
|
|
1118
|
-
console.log(` ${
|
|
1270
|
+
${chalk8.bold("Created:")}`);
|
|
1271
|
+
console.log(` ${chalk8.green("\u2713")} ${CONFIG_FILE4}`);
|
|
1272
|
+
console.log(` ${chalk8.green("\u2713")} .viberails/context.md`);
|
|
1273
|
+
console.log(` ${chalk8.green("\u2713")} .viberails/scan-result.json`);
|
|
1119
1274
|
console.log(`
|
|
1120
|
-
${
|
|
1121
|
-
console.log(` 1. Review ${
|
|
1275
|
+
${chalk8.bold("Next steps:")}`);
|
|
1276
|
+
console.log(` 1. Review ${chalk8.cyan("viberails.config.json")} and adjust rules`);
|
|
1122
1277
|
console.log(
|
|
1123
|
-
` 2. Commit ${
|
|
1278
|
+
` 2. Commit ${chalk8.cyan("viberails.config.json")} and ${chalk8.cyan(".viberails/context.md")}`
|
|
1124
1279
|
);
|
|
1125
|
-
console.log(` 3. Run ${
|
|
1280
|
+
console.log(` 3. Run ${chalk8.cyan("viberails check")} to verify your project passes`);
|
|
1126
1281
|
}
|
|
1127
1282
|
function updateGitignore(projectRoot) {
|
|
1128
|
-
const gitignorePath =
|
|
1283
|
+
const gitignorePath = path13.join(projectRoot, ".gitignore");
|
|
1129
1284
|
let content = "";
|
|
1130
|
-
if (
|
|
1131
|
-
content =
|
|
1285
|
+
if (fs12.existsSync(gitignorePath)) {
|
|
1286
|
+
content = fs12.readFileSync(gitignorePath, "utf-8");
|
|
1132
1287
|
}
|
|
1133
1288
|
if (!content.includes(".viberails/scan-result.json")) {
|
|
1134
1289
|
const block = "\n# viberails\n.viberails/scan-result.json\n";
|
|
1135
|
-
|
|
1290
|
+
fs12.writeFileSync(gitignorePath, `${content.trimEnd()}
|
|
1136
1291
|
${block}`);
|
|
1137
1292
|
}
|
|
1138
1293
|
}
|
|
1139
|
-
function setupPreCommitHook(projectRoot) {
|
|
1140
|
-
const lefthookPath = path12.join(projectRoot, "lefthook.yml");
|
|
1141
|
-
if (fs11.existsSync(lefthookPath)) {
|
|
1142
|
-
addLefthookPreCommit(lefthookPath);
|
|
1143
|
-
console.log(` ${chalk5.green("\u2713")} lefthook.yml \u2014 added viberails pre-commit`);
|
|
1144
|
-
return;
|
|
1145
|
-
}
|
|
1146
|
-
const huskyDir = path12.join(projectRoot, ".husky");
|
|
1147
|
-
if (fs11.existsSync(huskyDir)) {
|
|
1148
|
-
writeHuskyPreCommit(huskyDir);
|
|
1149
|
-
console.log(` ${chalk5.green("\u2713")} .husky/pre-commit \u2014 added viberails check`);
|
|
1150
|
-
return;
|
|
1151
|
-
}
|
|
1152
|
-
const gitDir = path12.join(projectRoot, ".git");
|
|
1153
|
-
if (fs11.existsSync(gitDir)) {
|
|
1154
|
-
const hooksDir = path12.join(gitDir, "hooks");
|
|
1155
|
-
if (!fs11.existsSync(hooksDir)) {
|
|
1156
|
-
fs11.mkdirSync(hooksDir, { recursive: true });
|
|
1157
|
-
}
|
|
1158
|
-
writeGitHookPreCommit(hooksDir);
|
|
1159
|
-
console.log(` ${chalk5.green("\u2713")} .git/hooks/pre-commit`);
|
|
1160
|
-
}
|
|
1161
|
-
}
|
|
1162
|
-
function writeGitHookPreCommit(hooksDir) {
|
|
1163
|
-
const hookPath = path12.join(hooksDir, "pre-commit");
|
|
1164
|
-
if (fs11.existsSync(hookPath)) {
|
|
1165
|
-
const existing = fs11.readFileSync(hookPath, "utf-8");
|
|
1166
|
-
if (existing.includes("viberails")) return;
|
|
1167
|
-
fs11.writeFileSync(
|
|
1168
|
-
hookPath,
|
|
1169
|
-
`${existing.trimEnd()}
|
|
1170
|
-
|
|
1171
|
-
# viberails check
|
|
1172
|
-
npx viberails check --staged
|
|
1173
|
-
`
|
|
1174
|
-
);
|
|
1175
|
-
return;
|
|
1176
|
-
}
|
|
1177
|
-
const script = [
|
|
1178
|
-
"#!/bin/sh",
|
|
1179
|
-
"# Generated by viberails \u2014 https://viberails.sh",
|
|
1180
|
-
"",
|
|
1181
|
-
"npx viberails check --staged",
|
|
1182
|
-
""
|
|
1183
|
-
].join("\n");
|
|
1184
|
-
fs11.writeFileSync(hookPath, script, { mode: 493 });
|
|
1185
|
-
}
|
|
1186
|
-
function addLefthookPreCommit(lefthookPath) {
|
|
1187
|
-
const content = fs11.readFileSync(lefthookPath, "utf-8");
|
|
1188
|
-
if (content.includes("viberails")) return;
|
|
1189
|
-
const addition = ["", " viberails:", " run: npx viberails check --staged"].join("\n");
|
|
1190
|
-
fs11.writeFileSync(lefthookPath, `${content.trimEnd()}
|
|
1191
|
-
${addition}
|
|
1192
|
-
`);
|
|
1193
|
-
}
|
|
1194
|
-
function writeHuskyPreCommit(huskyDir) {
|
|
1195
|
-
const hookPath = path12.join(huskyDir, "pre-commit");
|
|
1196
|
-
if (fs11.existsSync(hookPath)) {
|
|
1197
|
-
const existing = fs11.readFileSync(hookPath, "utf-8");
|
|
1198
|
-
if (!existing.includes("viberails")) {
|
|
1199
|
-
fs11.writeFileSync(hookPath, `${existing.trimEnd()}
|
|
1200
|
-
npx viberails check --staged
|
|
1201
|
-
`);
|
|
1202
|
-
}
|
|
1203
|
-
return;
|
|
1204
|
-
}
|
|
1205
|
-
fs11.writeFileSync(hookPath, "#!/bin/sh\nnpx viberails check --staged\n", { mode: 493 });
|
|
1206
|
-
}
|
|
1207
1294
|
|
|
1208
1295
|
// src/commands/sync.ts
|
|
1209
|
-
import * as
|
|
1210
|
-
import * as
|
|
1296
|
+
import * as fs13 from "fs";
|
|
1297
|
+
import * as path14 from "path";
|
|
1211
1298
|
import { loadConfig as loadConfig4, mergeConfig } from "@viberails/config";
|
|
1212
1299
|
import { scan as scan2 } from "@viberails/scanner";
|
|
1213
|
-
import
|
|
1300
|
+
import chalk9 from "chalk";
|
|
1214
1301
|
var CONFIG_FILE5 = "viberails.config.json";
|
|
1215
1302
|
async function syncCommand(cwd) {
|
|
1216
1303
|
const startDir = cwd ?? process.cwd();
|
|
@@ -1220,23 +1307,23 @@ async function syncCommand(cwd) {
|
|
|
1220
1307
|
"No package.json found in this directory or any parent.\n\nMake sure you are inside a JavaScript or TypeScript project, then run:\n npx viberails"
|
|
1221
1308
|
);
|
|
1222
1309
|
}
|
|
1223
|
-
const configPath =
|
|
1310
|
+
const configPath = path14.join(projectRoot, CONFIG_FILE5);
|
|
1224
1311
|
const existing = await loadConfig4(configPath);
|
|
1225
|
-
console.log(
|
|
1312
|
+
console.log(chalk9.dim("Scanning project..."));
|
|
1226
1313
|
const scanResult = await scan2(projectRoot);
|
|
1227
1314
|
const merged = mergeConfig(existing, scanResult);
|
|
1228
|
-
|
|
1315
|
+
fs13.writeFileSync(configPath, `${JSON.stringify(merged, null, 2)}
|
|
1229
1316
|
`);
|
|
1230
1317
|
writeGeneratedFiles(projectRoot, merged, scanResult);
|
|
1231
1318
|
console.log(`
|
|
1232
|
-
${
|
|
1233
|
-
console.log(` ${
|
|
1234
|
-
console.log(` ${
|
|
1235
|
-
console.log(` ${
|
|
1319
|
+
${chalk9.bold("Synced:")}`);
|
|
1320
|
+
console.log(` ${chalk9.green("\u2713")} ${CONFIG_FILE5} \u2014 updated`);
|
|
1321
|
+
console.log(` ${chalk9.green("\u2713")} .viberails/context.md \u2014 regenerated`);
|
|
1322
|
+
console.log(` ${chalk9.green("\u2713")} .viberails/scan-result.json \u2014 updated`);
|
|
1236
1323
|
}
|
|
1237
1324
|
|
|
1238
1325
|
// src/index.ts
|
|
1239
|
-
var VERSION = "0.2.
|
|
1326
|
+
var VERSION = "0.2.2";
|
|
1240
1327
|
var program = new Command();
|
|
1241
1328
|
program.name("viberails").description("Guardrails for vibe coding").version(VERSION);
|
|
1242
1329
|
program.command("init", { isDefault: true }).description("Scan your project and set up enforcement guardrails").option("-y, --yes", "Non-interactive mode (use defaults, high-confidence only)").action(async (options) => {
|
|
@@ -1244,7 +1331,7 @@ program.command("init", { isDefault: true }).description("Scan your project and
|
|
|
1244
1331
|
await initCommand(options);
|
|
1245
1332
|
} catch (err) {
|
|
1246
1333
|
const message = err instanceof Error ? err.message : String(err);
|
|
1247
|
-
console.error(`${
|
|
1334
|
+
console.error(`${chalk10.red("Error:")} ${message}`);
|
|
1248
1335
|
process.exit(1);
|
|
1249
1336
|
}
|
|
1250
1337
|
});
|
|
@@ -1253,7 +1340,7 @@ program.command("sync").description("Re-scan and update generated files").action
|
|
|
1253
1340
|
await syncCommand();
|
|
1254
1341
|
} catch (err) {
|
|
1255
1342
|
const message = err instanceof Error ? err.message : String(err);
|
|
1256
|
-
console.error(`${
|
|
1343
|
+
console.error(`${chalk10.red("Error:")} ${message}`);
|
|
1257
1344
|
process.exit(1);
|
|
1258
1345
|
}
|
|
1259
1346
|
});
|
|
@@ -1266,7 +1353,7 @@ program.command("check").description("Check files against enforced rules").optio
|
|
|
1266
1353
|
process.exit(exitCode);
|
|
1267
1354
|
} catch (err) {
|
|
1268
1355
|
const message = err instanceof Error ? err.message : String(err);
|
|
1269
|
-
console.error(`${
|
|
1356
|
+
console.error(`${chalk10.red("Error:")} ${message}`);
|
|
1270
1357
|
process.exit(1);
|
|
1271
1358
|
}
|
|
1272
1359
|
});
|
|
@@ -1276,7 +1363,7 @@ program.command("fix").description("Auto-fix file naming violations and generate
|
|
|
1276
1363
|
process.exit(exitCode);
|
|
1277
1364
|
} catch (err) {
|
|
1278
1365
|
const message = err instanceof Error ? err.message : String(err);
|
|
1279
|
-
console.error(`${
|
|
1366
|
+
console.error(`${chalk10.red("Error:")} ${message}`);
|
|
1280
1367
|
process.exit(1);
|
|
1281
1368
|
}
|
|
1282
1369
|
});
|
|
@@ -1285,7 +1372,7 @@ program.command("boundaries").description("Display, infer, or inspect import bou
|
|
|
1285
1372
|
await boundariesCommand(options);
|
|
1286
1373
|
} catch (err) {
|
|
1287
1374
|
const message = err instanceof Error ? err.message : String(err);
|
|
1288
|
-
console.error(`${
|
|
1375
|
+
console.error(`${chalk10.red("Error:")} ${message}`);
|
|
1289
1376
|
process.exit(1);
|
|
1290
1377
|
}
|
|
1291
1378
|
});
|