@ripast/cli 0.2.1 → 0.3.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/cli.mjs +272 -6
- package/package.json +2 -2
package/dist/cli.mjs
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { mkdirSync, readFileSync, renameSync } from "node:fs";
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, renameSync } from "node:fs";
|
|
2
2
|
import { dirname, relative, resolve } from "node:path";
|
|
3
3
|
import process from "node:process";
|
|
4
|
-
import { buildDeclarationTree, buildScanGraph, buildUnusedDeclarations, formatAgentDeclarationTree, formatAgentFileScanHits, formatAgentHits, formatAgentScanHits, formatDeclarationTree, formatFileScanHits, formatHits, formatRegressions, formatScanGraph, formatScanHits, formatUnusedDeclarations, printDiffs, resolveVerifyMode, runCssClassFileScan, runCssClassRename, runCssClassScan, runDelete, runMove, runRename, runRenameFile, runReplace, scan, summarize, writeChanges } from "@ripast/core";
|
|
4
|
+
import { buildComponentDetail, buildComponentInventory, buildDeclarationTree, buildDoctorFixes, buildScanGraph, buildUnusedDeclarations, formatAgentDeclarationTree, formatAgentDoctorReport, formatAgentFileScanHits, formatAgentHits, formatAgentInventory, formatAgentScanHits, formatDeclarationTree, formatDetail, formatDoctorReport, formatFileScanHits, formatHits, formatInventory, formatRegressions, formatScanGraph, formatScanHits, formatUnusedDeclarations, getChangedFiles, printDiffs, resolveVerifyMode, runCssClassFileScan, runCssClassRename, runCssClassScan, runDelete, runDoctor, runMove, runRename, runRenameFile, runReplace, runVueTemplateUnwrap, runVueTemplateWrap, scan, summarize, writeChanges } from "@ripast/core";
|
|
5
5
|
import { defineCommand, runMain } from "citty";
|
|
6
6
|
import { agent, isAgent } from "std-env";
|
|
7
7
|
const globArg = {
|
|
8
8
|
type: "string",
|
|
9
|
-
description: "File glob(s), comma-separated. Defaults to *.ts,*.tsx,*.vue,... Respects .gitignore."
|
|
9
|
+
description: "File glob(s), comma-separated. Prefix with ! to exclude (e.g. \"*.ts,!.nuxt/**,!**/*.d.ts\"). Defaults to *.ts,*.tsx,*.vue,... Respects .gitignore."
|
|
10
10
|
};
|
|
11
11
|
const applyArg = {
|
|
12
12
|
type: "boolean",
|
|
@@ -67,6 +67,35 @@ function resolveCliVerifyMode(verify, verifyMode) {
|
|
|
67
67
|
}
|
|
68
68
|
return resolveVerifyMode(verify);
|
|
69
69
|
}
|
|
70
|
+
function recoverSmushedPair(oldArg, newArg) {
|
|
71
|
+
const cwd = process.cwd();
|
|
72
|
+
if (!!oldArg && existsSync(resolve(cwd, oldArg)) && newArg) return {
|
|
73
|
+
old: oldArg,
|
|
74
|
+
new: newArg
|
|
75
|
+
};
|
|
76
|
+
if (!oldArg) return {
|
|
77
|
+
old: oldArg,
|
|
78
|
+
new: newArg
|
|
79
|
+
};
|
|
80
|
+
const candidates = [];
|
|
81
|
+
for (const sep of [
|
|
82
|
+
/\s+/,
|
|
83
|
+
/\s*,\s*/,
|
|
84
|
+
/\s*:\s*/
|
|
85
|
+
]) {
|
|
86
|
+
const parts = oldArg.split(sep).filter(Boolean);
|
|
87
|
+
if (parts.length === 2) candidates.push([parts[0], parts[1]]);
|
|
88
|
+
}
|
|
89
|
+
for (const [a, b] of candidates) if (existsSync(resolve(cwd, a)) && !existsSync(resolve(cwd, b))) return {
|
|
90
|
+
old: a,
|
|
91
|
+
new: b,
|
|
92
|
+
warning: `recovered smushed positional args: treating "${oldArg}" as old="${a}" new="${b}". Quote each path separately next time.`
|
|
93
|
+
};
|
|
94
|
+
return {
|
|
95
|
+
old: oldArg,
|
|
96
|
+
new: newArg
|
|
97
|
+
};
|
|
98
|
+
}
|
|
70
99
|
function resolveExportFilter(raw) {
|
|
71
100
|
if (raw !== "all" && raw !== "exported" && raw !== "local") {
|
|
72
101
|
process.stderr.write(`ripast: --exports must be "all", "exported", or "local".\n`);
|
|
@@ -407,7 +436,9 @@ const renameFileCmd = defineCommand({
|
|
|
407
436
|
},
|
|
408
437
|
async run({ args }) {
|
|
409
438
|
const verifyMode = resolveCliVerifyMode(args.verify, args.verifyMode);
|
|
410
|
-
const
|
|
439
|
+
const recovered = recoverSmushedPair(args.old, args.new);
|
|
440
|
+
if (recovered.warning) process.stderr.write(`warning: ${recovered.warning}\n`);
|
|
441
|
+
const r = await runRenameFile(recovered.old, recovered.new, {
|
|
411
442
|
tsconfig: args.tsconfig,
|
|
412
443
|
verify: verifyMode
|
|
413
444
|
});
|
|
@@ -455,7 +486,7 @@ const renameFileCmd = defineCommand({
|
|
|
455
486
|
for (const w of r.warnings) process.stderr.write(`warning: ${w}\n`);
|
|
456
487
|
if (agentProfile) {
|
|
457
488
|
process.stdout.write(`${profileHeader()}\n`);
|
|
458
|
-
process.stdout.write(`rename-file: ${
|
|
489
|
+
process.stdout.write(`rename-file: ${recovered.old} -> ${recovered.new}\n`);
|
|
459
490
|
process.stdout.write(`consumers: ${r.changes.length}/${r.scanned}, +${s.linesAdded} -${s.linesRemoved} lines\n`);
|
|
460
491
|
if (selfChangeDisplay) process.stdout.write(`self: rewrote moved file's own relative imports\n`);
|
|
461
492
|
process.stdout.write(`mode: ${apply ? blockedByRegression ? "blocked" : "applied" : "dry-run"}\n`);
|
|
@@ -641,6 +672,145 @@ function resolveCssClassFileScanSort(raw) {
|
|
|
641
672
|
}
|
|
642
673
|
return raw;
|
|
643
674
|
}
|
|
675
|
+
const scopeArg = {
|
|
676
|
+
type: "string",
|
|
677
|
+
description: "Restrict to a single .vue file (skips glob/rg)."
|
|
678
|
+
};
|
|
679
|
+
const rootOnlyArg = {
|
|
680
|
+
type: "boolean",
|
|
681
|
+
default: false,
|
|
682
|
+
description: "Match only template-root elements (direct children of <template>); ignores nested matches."
|
|
683
|
+
};
|
|
684
|
+
const vueTemplateWrapCmd = defineCommand({
|
|
685
|
+
meta: {
|
|
686
|
+
name: "vue-template-wrap",
|
|
687
|
+
description: "Wrap every matching element in a Vue <template> with a parent component. Selector: \"Tag\" or \"Tag[attr]\" / \"Tag[attr=value]\". Wrapper: tag name with optional inline attributes (e.g. \"ProPageStates name=\\\"lh\\\"\")."
|
|
688
|
+
},
|
|
689
|
+
args: {
|
|
690
|
+
selector: {
|
|
691
|
+
type: "positional",
|
|
692
|
+
required: true
|
|
693
|
+
},
|
|
694
|
+
wrapper: {
|
|
695
|
+
type: "positional",
|
|
696
|
+
required: true
|
|
697
|
+
},
|
|
698
|
+
glob: globArg,
|
|
699
|
+
scope: scopeArg,
|
|
700
|
+
rootOnly: rootOnlyArg,
|
|
701
|
+
apply: applyArg,
|
|
702
|
+
profile: profileArg,
|
|
703
|
+
json: jsonArg
|
|
704
|
+
},
|
|
705
|
+
async run({ args }) {
|
|
706
|
+
emitResult(await runVueTemplateWrap(args.selector, args.wrapper, {
|
|
707
|
+
glob: args.glob ? args.glob.split(",") : void 0,
|
|
708
|
+
scope: args.scope,
|
|
709
|
+
rootOnly: args.rootOnly
|
|
710
|
+
}), !!args.apply, false, !!args.json, resolveProfile(args.profile).agentProfile);
|
|
711
|
+
}
|
|
712
|
+
});
|
|
713
|
+
const vueTemplateUnwrapCmd = defineCommand({
|
|
714
|
+
meta: {
|
|
715
|
+
name: "vue-template-unwrap",
|
|
716
|
+
description: "Remove every matching element in a Vue <template>, hoisting its children up one level. Inverse of vue-template-wrap."
|
|
717
|
+
},
|
|
718
|
+
args: {
|
|
719
|
+
selector: {
|
|
720
|
+
type: "positional",
|
|
721
|
+
required: true
|
|
722
|
+
},
|
|
723
|
+
glob: globArg,
|
|
724
|
+
scope: scopeArg,
|
|
725
|
+
rootOnly: rootOnlyArg,
|
|
726
|
+
apply: applyArg,
|
|
727
|
+
profile: profileArg,
|
|
728
|
+
json: jsonArg
|
|
729
|
+
},
|
|
730
|
+
async run({ args }) {
|
|
731
|
+
emitResult(await runVueTemplateUnwrap(args.selector, {
|
|
732
|
+
glob: args.glob ? args.glob.split(",") : void 0,
|
|
733
|
+
scope: args.scope,
|
|
734
|
+
rootOnly: args.rootOnly
|
|
735
|
+
}), !!args.apply, false, !!args.json, resolveProfile(args.profile).agentProfile);
|
|
736
|
+
}
|
|
737
|
+
});
|
|
738
|
+
const componentsCmd = defineCommand({
|
|
739
|
+
meta: {
|
|
740
|
+
name: "components",
|
|
741
|
+
description: "Inventory Vue/Nuxt components (manifest-first, glob fallback); flag shadowed entries and duplicate-name groups. Pass a name positional for the focused view."
|
|
742
|
+
},
|
|
743
|
+
args: {
|
|
744
|
+
name: {
|
|
745
|
+
type: "positional",
|
|
746
|
+
required: false,
|
|
747
|
+
description: "Component name. If given, prints file, aliases, usages, and same-name candidates."
|
|
748
|
+
},
|
|
749
|
+
glob: globArg,
|
|
750
|
+
source: {
|
|
751
|
+
type: "string",
|
|
752
|
+
description: "Discovery source: auto, manifest, or filesystem. Default: auto."
|
|
753
|
+
},
|
|
754
|
+
dups: {
|
|
755
|
+
type: "boolean",
|
|
756
|
+
default: false,
|
|
757
|
+
description: "Print only duplicate-name groups."
|
|
758
|
+
},
|
|
759
|
+
profile: profileArg,
|
|
760
|
+
json: jsonArg
|
|
761
|
+
},
|
|
762
|
+
async run({ args }) {
|
|
763
|
+
const { agentProfile } = resolveProfile(args.profile);
|
|
764
|
+
const opts = {
|
|
765
|
+
glob: args.glob ? args.glob.split(",") : void 0,
|
|
766
|
+
source: resolveComponentsSource(args.source)
|
|
767
|
+
};
|
|
768
|
+
if (args.name) {
|
|
769
|
+
const detail = await buildComponentDetail(args.name, opts);
|
|
770
|
+
if (!detail) {
|
|
771
|
+
process.stderr.write(`ripast components: no component named "${args.name}".\n`);
|
|
772
|
+
process.exit(1);
|
|
773
|
+
}
|
|
774
|
+
if (args.json) {
|
|
775
|
+
process.stdout.write(`${JSON.stringify(detail, null, 2)}\n`);
|
|
776
|
+
return;
|
|
777
|
+
}
|
|
778
|
+
process.stdout.write(`${formatDetail(detail)}\n`);
|
|
779
|
+
return;
|
|
780
|
+
}
|
|
781
|
+
const inv = await buildComponentInventory(opts);
|
|
782
|
+
if (args.json) {
|
|
783
|
+
process.stdout.write(`${JSON.stringify(args.dups ? inv.duplicates : inv, null, 2)}\n`);
|
|
784
|
+
return;
|
|
785
|
+
}
|
|
786
|
+
if (args.dups) {
|
|
787
|
+
if (!inv.duplicates.length) {
|
|
788
|
+
process.stdout.write("no duplicate-name groups\n");
|
|
789
|
+
return;
|
|
790
|
+
}
|
|
791
|
+
const lines = [];
|
|
792
|
+
for (const dup of inv.duplicates) {
|
|
793
|
+
lines.push(dup.name);
|
|
794
|
+
for (const e of dup.entries) lines.push(` ${e.rel}${e.shadowed ? " (shadowed)" : ""}`);
|
|
795
|
+
}
|
|
796
|
+
process.stdout.write(`${lines.join("\n")}\n`);
|
|
797
|
+
return;
|
|
798
|
+
}
|
|
799
|
+
if (agentProfile) {
|
|
800
|
+
process.stdout.write(`${profileHeader()}\n${formatAgentInventory(inv)}\n`);
|
|
801
|
+
return;
|
|
802
|
+
}
|
|
803
|
+
process.stdout.write(`${formatInventory(inv)}\n`);
|
|
804
|
+
}
|
|
805
|
+
});
|
|
806
|
+
function resolveComponentsSource(raw) {
|
|
807
|
+
if (raw == null) return void 0;
|
|
808
|
+
if (raw !== "auto" && raw !== "manifest" && raw !== "filesystem") {
|
|
809
|
+
process.stderr.write(`ripast components: --source must be "auto", "manifest", or "filesystem".\n`);
|
|
810
|
+
process.exit(2);
|
|
811
|
+
}
|
|
812
|
+
return raw;
|
|
813
|
+
}
|
|
644
814
|
runMain(defineCommand({
|
|
645
815
|
meta: {
|
|
646
816
|
name: "ripast",
|
|
@@ -650,13 +820,109 @@ runMain(defineCommand({
|
|
|
650
820
|
"scan": scanCmd,
|
|
651
821
|
"tree": treeCmd,
|
|
652
822
|
"unused": unusedCmd,
|
|
823
|
+
"doctor": defineCommand({
|
|
824
|
+
meta: {
|
|
825
|
+
name: "doctor",
|
|
826
|
+
description: "Health checks over the AST graph. Suppress per-file with `// ripast-doctor-ignore-file[: c1,c2]` or per-line with `// ripast-doctor-ignore-next-line[: c1,c2]`."
|
|
827
|
+
},
|
|
828
|
+
args: {
|
|
829
|
+
glob: globArg,
|
|
830
|
+
checks: {
|
|
831
|
+
type: "string",
|
|
832
|
+
description: "Comma-separated subset: dangling-reexport, stale-reexport, stale-import, duplicate-export, orphan-file, orphan-test, inconsistent-import-path, circular-dep. Default: all."
|
|
833
|
+
},
|
|
834
|
+
entry: {
|
|
835
|
+
type: "string",
|
|
836
|
+
description: "Comma-separated entry files exempt from orphan check (relative to cwd)."
|
|
837
|
+
},
|
|
838
|
+
fix: {
|
|
839
|
+
type: "boolean",
|
|
840
|
+
default: false,
|
|
841
|
+
description: "Compute safe fixes for inconsistent-import-path and dangling-reexport findings."
|
|
842
|
+
},
|
|
843
|
+
apply: applyArg,
|
|
844
|
+
changed: {
|
|
845
|
+
type: "string",
|
|
846
|
+
description: "Report only findings on files changed vs git. No value = dirty working tree (staged+unstaged+untracked). With value (e.g. main, HEAD~1) = diff against that ref. Full project is still scanned so cross-file checks stay accurate."
|
|
847
|
+
},
|
|
848
|
+
profile: profileArg,
|
|
849
|
+
json: jsonArg
|
|
850
|
+
},
|
|
851
|
+
async run({ args }) {
|
|
852
|
+
const { agentProfile } = resolveProfile(args.profile);
|
|
853
|
+
const checks = args.checks ? args.checks.split(",") : void 0;
|
|
854
|
+
const entry = args.entry ? args.entry.split(",") : void 0;
|
|
855
|
+
let changedFiles;
|
|
856
|
+
if (args.changed != null) {
|
|
857
|
+
const ref = typeof args.changed === "string" && args.changed !== "" && args.changed !== "true" ? args.changed : void 0;
|
|
858
|
+
changedFiles = getChangedFiles({
|
|
859
|
+
cwd: process.cwd(),
|
|
860
|
+
ref
|
|
861
|
+
});
|
|
862
|
+
if (!changedFiles.length) {
|
|
863
|
+
process.stderr.write(`ripast doctor: --changed${ref ? ` ${ref}` : ""} matched no files; nothing to report.\n`);
|
|
864
|
+
return;
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
const report = await runDoctor({
|
|
868
|
+
glob: args.glob ? args.glob.split(",") : void 0,
|
|
869
|
+
checks,
|
|
870
|
+
entry,
|
|
871
|
+
changedFiles
|
|
872
|
+
});
|
|
873
|
+
if (args.fix) {
|
|
874
|
+
const fix = buildDoctorFixes(report, process.cwd());
|
|
875
|
+
if (args.json) {
|
|
876
|
+
process.stdout.write(`${JSON.stringify({
|
|
877
|
+
findings: report.findings,
|
|
878
|
+
filesScanned: report.filesScanned,
|
|
879
|
+
fix: {
|
|
880
|
+
applied: !!args.apply,
|
|
881
|
+
files: fix.changes.length,
|
|
882
|
+
fixed: fix.fixed,
|
|
883
|
+
skipped: fix.skipped.length
|
|
884
|
+
},
|
|
885
|
+
changes: args.apply ? void 0 : fix.changes.map((c) => ({
|
|
886
|
+
path: c.rel,
|
|
887
|
+
before: c.before,
|
|
888
|
+
after: c.after
|
|
889
|
+
}))
|
|
890
|
+
})}\n`);
|
|
891
|
+
if (args.apply && fix.changes.length) writeChanges(fix.changes);
|
|
892
|
+
if (fix.skipped.length) process.exit(1);
|
|
893
|
+
return;
|
|
894
|
+
}
|
|
895
|
+
const s = summarize(fix.changes);
|
|
896
|
+
process.stdout.write(`doctor --fix: ${fix.fixed.length} fixable / ${fix.skipped.length} non-fixable findings\n`);
|
|
897
|
+
process.stdout.write(`${fix.changes.length} file${fix.changes.length === 1 ? "" : "s"}, +${s.linesAdded} -${s.linesRemoved} lines\n\n`);
|
|
898
|
+
if (!args.apply) {
|
|
899
|
+
printDiffs(fix.changes);
|
|
900
|
+
process.stdout.write(`\n(dry run, pass --apply to write)\n`);
|
|
901
|
+
} else {
|
|
902
|
+
writeChanges(fix.changes);
|
|
903
|
+
for (const c of fix.changes) process.stdout.write(`wrote ${c.rel}\n`);
|
|
904
|
+
}
|
|
905
|
+
if (fix.skipped.length) process.exit(1);
|
|
906
|
+
return;
|
|
907
|
+
}
|
|
908
|
+
if (agentProfile && !args.json) {
|
|
909
|
+
process.stdout.write(`${profileHeader()}\n${formatAgentDoctorReport(report)}\n`);
|
|
910
|
+
return;
|
|
911
|
+
}
|
|
912
|
+
process.stdout.write(formatDoctorReport(report, !!args.json));
|
|
913
|
+
if (report.findings.length) process.exit(1);
|
|
914
|
+
}
|
|
915
|
+
}),
|
|
653
916
|
"rename": renameCmd,
|
|
654
917
|
"replace": replaceCmd,
|
|
655
918
|
"rename-file": renameFileCmd,
|
|
656
919
|
"move": moveCmd,
|
|
657
920
|
"delete": deleteCmd,
|
|
921
|
+
"components": componentsCmd,
|
|
658
922
|
"css-class-rename": cssClassRenameCmd,
|
|
659
|
-
"css-class-scan": cssClassScanCmd
|
|
923
|
+
"css-class-scan": cssClassScanCmd,
|
|
924
|
+
"vue-template-wrap": vueTemplateWrapCmd,
|
|
925
|
+
"vue-template-unwrap": vueTemplateUnwrapCmd
|
|
660
926
|
}
|
|
661
927
|
}));
|
|
662
928
|
export {};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ripast/cli",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.3.0",
|
|
5
5
|
"description": "CLI for ripast. Deterministic AST refactors (rename, move, find-usages, tailwind/CSS class migration) for TypeScript and frontend frameworks. Auto-installs framework adapters as needed.",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Harlan Wilton",
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
"dependencies": {
|
|
51
51
|
"citty": "^0.2.2",
|
|
52
52
|
"std-env": "^4.1.0",
|
|
53
|
-
"@ripast/core": "0.
|
|
53
|
+
"@ripast/core": "0.3.0"
|
|
54
54
|
},
|
|
55
55
|
"scripts": {
|
|
56
56
|
"build": "obuild",
|