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