@ripast/cli 0.2.2 → 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.
Files changed (2) hide show
  1. package/dist/cli.mjs +222 -50
  2. package/package.json +2 -2
package/dist/cli.mjs CHANGED
@@ -1,12 +1,12 @@
1
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, runVueTemplateUnwrap, runVueTemplateWrap, 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",
@@ -681,6 +681,136 @@ const rootOnlyArg = {
681
681
  default: false,
682
682
  description: "Match only template-root elements (direct children of <template>); ignores nested matches."
683
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
+ }
684
814
  runMain(defineCommand({
685
815
  meta: {
686
816
  name: "ripast",
@@ -690,67 +820,109 @@ runMain(defineCommand({
690
820
  "scan": scanCmd,
691
821
  "tree": treeCmd,
692
822
  "unused": unusedCmd,
693
- "rename": renameCmd,
694
- "replace": replaceCmd,
695
- "rename-file": renameFileCmd,
696
- "move": moveCmd,
697
- "delete": deleteCmd,
698
- "css-class-rename": cssClassRenameCmd,
699
- "css-class-scan": cssClassScanCmd,
700
- "vue-template-wrap": defineCommand({
823
+ "doctor": defineCommand({
701
824
  meta: {
702
- name: "vue-template-wrap",
703
- 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\\\"\")."
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]`."
704
827
  },
705
828
  args: {
706
- selector: {
707
- type: "positional",
708
- required: true
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."
709
833
  },
710
- wrapper: {
711
- type: "positional",
712
- required: true
834
+ entry: {
835
+ type: "string",
836
+ description: "Comma-separated entry files exempt from orphan check (relative to cwd)."
713
837
  },
714
- glob: globArg,
715
- scope: scopeArg,
716
- rootOnly: rootOnlyArg,
717
- apply: applyArg,
718
- profile: profileArg,
719
- json: jsonArg
720
- },
721
- async run({ args }) {
722
- emitResult(await runVueTemplateWrap(args.selector, args.wrapper, {
723
- glob: args.glob ? args.glob.split(",") : void 0,
724
- scope: args.scope,
725
- rootOnly: args.rootOnly
726
- }), !!args.apply, false, !!args.json, resolveProfile(args.profile).agentProfile);
727
- }
728
- }),
729
- "vue-template-unwrap": defineCommand({
730
- meta: {
731
- name: "vue-template-unwrap",
732
- description: "Remove every matching element in a Vue <template>, hoisting its children up one level. Inverse of vue-template-wrap."
733
- },
734
- args: {
735
- selector: {
736
- type: "positional",
737
- required: true
838
+ fix: {
839
+ type: "boolean",
840
+ default: false,
841
+ description: "Compute safe fixes for inconsistent-import-path and dangling-reexport findings."
738
842
  },
739
- glob: globArg,
740
- scope: scopeArg,
741
- rootOnly: rootOnlyArg,
742
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
+ },
743
848
  profile: profileArg,
744
849
  json: jsonArg
745
850
  },
746
851
  async run({ args }) {
747
- emitResult(await runVueTemplateUnwrap(args.selector, {
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({
748
868
  glob: args.glob ? args.glob.split(",") : void 0,
749
- scope: args.scope,
750
- rootOnly: args.rootOnly
751
- }), !!args.apply, false, !!args.json, resolveProfile(args.profile).agentProfile);
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);
752
914
  }
753
- })
915
+ }),
916
+ "rename": renameCmd,
917
+ "replace": replaceCmd,
918
+ "rename-file": renameFileCmd,
919
+ "move": moveCmd,
920
+ "delete": deleteCmd,
921
+ "components": componentsCmd,
922
+ "css-class-rename": cssClassRenameCmd,
923
+ "css-class-scan": cssClassScanCmd,
924
+ "vue-template-wrap": vueTemplateWrapCmd,
925
+ "vue-template-unwrap": vueTemplateUnwrapCmd
754
926
  }
755
927
  }));
756
928
  export {};
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@ripast/cli",
3
3
  "type": "module",
4
- "version": "0.2.2",
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.2.2"
53
+ "@ripast/core": "0.3.0"
54
54
  },
55
55
  "scripts": {
56
56
  "build": "obuild",