@ripast/cli 0.2.2 → 0.4.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 +228 -53
  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",
@@ -265,13 +265,13 @@ const unusedCmd = defineCommand({
265
265
  glob: globArg,
266
266
  exports: {
267
267
  type: "string",
268
- description: "Declaration filter: all, exported, or local. Defaults to local."
268
+ description: "Declaration filter: all, exported, or local. Defaults to exported (unused local declarations are already caught by tsc noUnusedLocals)."
269
269
  },
270
270
  tsconfig: { type: "string" },
271
271
  json: jsonArg
272
272
  },
273
273
  run({ args }) {
274
- const exportFilter = args.exports == null ? "local" : resolveExportFilter(args.exports);
274
+ const exportFilter = args.exports == null ? "exported" : resolveExportFilter(args.exports);
275
275
  const unused = buildUnusedDeclarations({
276
276
  glob: args.glob ? args.glob.split(",") : void 0,
277
277
  exports: exportFilter,
@@ -349,6 +349,7 @@ const deleteCmd = defineCommand({
349
349
  });
350
350
  function emitResult(r, apply, verify = false, json = false, agentProfile = false) {
351
351
  const s = summarize(r.changes);
352
+ const warnings = r.warnings ?? [];
352
353
  if (json) {
353
354
  const blockedByRegression = verify && apply && r.regressions.length > 0;
354
355
  const wrote = apply && !blockedByRegression;
@@ -365,12 +366,14 @@ function emitResult(r, apply, verify = false, json = false, agentProfile = false
365
366
  before: c.before,
366
367
  after: c.after
367
368
  })),
368
- regressions: r.regressions
369
+ regressions: r.regressions,
370
+ warnings
369
371
  };
370
372
  process.stdout.write(`${JSON.stringify(payload)}\n`);
371
373
  if (blockedByRegression) process.exit(1);
372
374
  return;
373
375
  }
376
+ for (const w of warnings) process.stderr.write(`warning: ${w}\n`);
374
377
  if (agentProfile) {
375
378
  const blockedByRegression = verify && apply && r.regressions.length > 0;
376
379
  process.stdout.write(`${profileHeader()}\n`);
@@ -681,6 +684,136 @@ const rootOnlyArg = {
681
684
  default: false,
682
685
  description: "Match only template-root elements (direct children of <template>); ignores nested matches."
683
686
  };
687
+ const vueTemplateWrapCmd = defineCommand({
688
+ meta: {
689
+ name: "vue-template-wrap",
690
+ 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\\\"\")."
691
+ },
692
+ args: {
693
+ selector: {
694
+ type: "positional",
695
+ required: true
696
+ },
697
+ wrapper: {
698
+ type: "positional",
699
+ required: true
700
+ },
701
+ glob: globArg,
702
+ scope: scopeArg,
703
+ rootOnly: rootOnlyArg,
704
+ apply: applyArg,
705
+ profile: profileArg,
706
+ json: jsonArg
707
+ },
708
+ async run({ args }) {
709
+ emitResult(await runVueTemplateWrap(args.selector, args.wrapper, {
710
+ glob: args.glob ? args.glob.split(",") : void 0,
711
+ scope: args.scope,
712
+ rootOnly: args.rootOnly
713
+ }), !!args.apply, false, !!args.json, resolveProfile(args.profile).agentProfile);
714
+ }
715
+ });
716
+ const vueTemplateUnwrapCmd = defineCommand({
717
+ meta: {
718
+ name: "vue-template-unwrap",
719
+ description: "Remove every matching element in a Vue <template>, hoisting its children up one level. Inverse of vue-template-wrap."
720
+ },
721
+ args: {
722
+ selector: {
723
+ type: "positional",
724
+ required: true
725
+ },
726
+ glob: globArg,
727
+ scope: scopeArg,
728
+ rootOnly: rootOnlyArg,
729
+ apply: applyArg,
730
+ profile: profileArg,
731
+ json: jsonArg
732
+ },
733
+ async run({ args }) {
734
+ emitResult(await runVueTemplateUnwrap(args.selector, {
735
+ glob: args.glob ? args.glob.split(",") : void 0,
736
+ scope: args.scope,
737
+ rootOnly: args.rootOnly
738
+ }), !!args.apply, false, !!args.json, resolveProfile(args.profile).agentProfile);
739
+ }
740
+ });
741
+ const componentsCmd = defineCommand({
742
+ meta: {
743
+ name: "components",
744
+ description: "Inventory Vue/Nuxt components (manifest-first, glob fallback); flag shadowed entries and duplicate-name groups. Pass a name positional for the focused view."
745
+ },
746
+ args: {
747
+ name: {
748
+ type: "positional",
749
+ required: false,
750
+ description: "Component name. If given, prints file, aliases, usages, and same-name candidates."
751
+ },
752
+ glob: globArg,
753
+ source: {
754
+ type: "string",
755
+ description: "Discovery source: auto, manifest, or filesystem. Default: auto."
756
+ },
757
+ dups: {
758
+ type: "boolean",
759
+ default: false,
760
+ description: "Print only duplicate-name groups."
761
+ },
762
+ profile: profileArg,
763
+ json: jsonArg
764
+ },
765
+ async run({ args }) {
766
+ const { agentProfile } = resolveProfile(args.profile);
767
+ const opts = {
768
+ glob: args.glob ? args.glob.split(",") : void 0,
769
+ source: resolveComponentsSource(args.source)
770
+ };
771
+ if (args.name) {
772
+ const detail = await buildComponentDetail(args.name, opts);
773
+ if (!detail) {
774
+ process.stderr.write(`ripast components: no component named "${args.name}".\n`);
775
+ process.exit(1);
776
+ }
777
+ if (args.json) {
778
+ process.stdout.write(`${JSON.stringify(detail, null, 2)}\n`);
779
+ return;
780
+ }
781
+ process.stdout.write(`${formatDetail(detail)}\n`);
782
+ return;
783
+ }
784
+ const inv = await buildComponentInventory(opts);
785
+ if (args.json) {
786
+ process.stdout.write(`${JSON.stringify(args.dups ? inv.duplicates : inv, null, 2)}\n`);
787
+ return;
788
+ }
789
+ if (args.dups) {
790
+ if (!inv.duplicates.length) {
791
+ process.stdout.write("no duplicate-name groups\n");
792
+ return;
793
+ }
794
+ const lines = [];
795
+ for (const dup of inv.duplicates) {
796
+ lines.push(dup.name);
797
+ for (const e of dup.entries) lines.push(` ${e.rel}${e.shadowed ? " (shadowed)" : ""}`);
798
+ }
799
+ process.stdout.write(`${lines.join("\n")}\n`);
800
+ return;
801
+ }
802
+ if (agentProfile) {
803
+ process.stdout.write(`${profileHeader()}\n${formatAgentInventory(inv)}\n`);
804
+ return;
805
+ }
806
+ process.stdout.write(`${formatInventory(inv)}\n`);
807
+ }
808
+ });
809
+ function resolveComponentsSource(raw) {
810
+ if (raw == null) return void 0;
811
+ if (raw !== "auto" && raw !== "manifest" && raw !== "filesystem") {
812
+ process.stderr.write(`ripast components: --source must be "auto", "manifest", or "filesystem".\n`);
813
+ process.exit(2);
814
+ }
815
+ return raw;
816
+ }
684
817
  runMain(defineCommand({
685
818
  meta: {
686
819
  name: "ripast",
@@ -690,67 +823,109 @@ runMain(defineCommand({
690
823
  "scan": scanCmd,
691
824
  "tree": treeCmd,
692
825
  "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({
826
+ "doctor": defineCommand({
701
827
  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\\\"\")."
828
+ name: "doctor",
829
+ 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
830
  },
705
831
  args: {
706
- selector: {
707
- type: "positional",
708
- required: true
832
+ glob: globArg,
833
+ checks: {
834
+ type: "string",
835
+ description: "Comma-separated subset: dangling-reexport, stale-reexport, stale-import, duplicate-export, orphan-file, orphan-test, inconsistent-import-path, circular-dep, phantom-component, shadowed-component, cross-realm-import, stale-nuxt-config-ref. Default: all (adapter checks run when their framework is detected)."
709
836
  },
710
- wrapper: {
711
- type: "positional",
712
- required: true
837
+ entry: {
838
+ type: "string",
839
+ description: "Comma-separated entry files exempt from orphan check (relative to cwd)."
713
840
  },
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
841
+ fix: {
842
+ type: "boolean",
843
+ default: false,
844
+ description: "Compute safe fixes for inconsistent-import-path and dangling-reexport findings."
738
845
  },
739
- glob: globArg,
740
- scope: scopeArg,
741
- rootOnly: rootOnlyArg,
742
846
  apply: applyArg,
847
+ changed: {
848
+ type: "string",
849
+ 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."
850
+ },
743
851
  profile: profileArg,
744
852
  json: jsonArg
745
853
  },
746
854
  async run({ args }) {
747
- emitResult(await runVueTemplateUnwrap(args.selector, {
855
+ const { agentProfile } = resolveProfile(args.profile);
856
+ const checks = args.checks ? args.checks.split(",") : void 0;
857
+ const entry = args.entry ? args.entry.split(",") : void 0;
858
+ let changedFiles;
859
+ if (args.changed != null) {
860
+ const ref = typeof args.changed === "string" && args.changed !== "" && args.changed !== "true" ? args.changed : void 0;
861
+ changedFiles = getChangedFiles({
862
+ cwd: process.cwd(),
863
+ ref
864
+ });
865
+ if (!changedFiles.length) {
866
+ process.stderr.write(`ripast doctor: --changed${ref ? ` ${ref}` : ""} matched no files; nothing to report.\n`);
867
+ return;
868
+ }
869
+ }
870
+ const report = await runDoctor({
748
871
  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);
872
+ checks,
873
+ entry,
874
+ changedFiles
875
+ });
876
+ if (args.fix) {
877
+ const fix = buildDoctorFixes(report, process.cwd());
878
+ if (args.json) {
879
+ process.stdout.write(`${JSON.stringify({
880
+ findings: report.findings,
881
+ filesScanned: report.filesScanned,
882
+ fix: {
883
+ applied: !!args.apply,
884
+ files: fix.changes.length,
885
+ fixed: fix.fixed,
886
+ skipped: fix.skipped.length
887
+ },
888
+ changes: args.apply ? void 0 : fix.changes.map((c) => ({
889
+ path: c.rel,
890
+ before: c.before,
891
+ after: c.after
892
+ }))
893
+ })}\n`);
894
+ if (args.apply && fix.changes.length) writeChanges(fix.changes);
895
+ if (fix.skipped.length) process.exit(1);
896
+ return;
897
+ }
898
+ const s = summarize(fix.changes);
899
+ process.stdout.write(`doctor --fix: ${fix.fixed.length} fixable / ${fix.skipped.length} non-fixable findings\n`);
900
+ process.stdout.write(`${fix.changes.length} file${fix.changes.length === 1 ? "" : "s"}, +${s.linesAdded} -${s.linesRemoved} lines\n\n`);
901
+ if (!args.apply) {
902
+ printDiffs(fix.changes);
903
+ process.stdout.write(`\n(dry run, pass --apply to write)\n`);
904
+ } else {
905
+ writeChanges(fix.changes);
906
+ for (const c of fix.changes) process.stdout.write(`wrote ${c.rel}\n`);
907
+ }
908
+ if (fix.skipped.length) process.exit(1);
909
+ return;
910
+ }
911
+ if (agentProfile && !args.json) {
912
+ process.stdout.write(`${profileHeader()}\n${formatAgentDoctorReport(report)}\n`);
913
+ return;
914
+ }
915
+ process.stdout.write(formatDoctorReport(report, !!args.json));
916
+ if (report.findings.length) process.exit(1);
752
917
  }
753
- })
918
+ }),
919
+ "rename": renameCmd,
920
+ "replace": replaceCmd,
921
+ "rename-file": renameFileCmd,
922
+ "move": moveCmd,
923
+ "delete": deleteCmd,
924
+ "components": componentsCmd,
925
+ "css-class-rename": cssClassRenameCmd,
926
+ "css-class-scan": cssClassScanCmd,
927
+ "vue-template-wrap": vueTemplateWrapCmd,
928
+ "vue-template-unwrap": vueTemplateUnwrapCmd
754
929
  }
755
930
  }));
756
931
  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.4.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.4.0"
54
54
  },
55
55
  "scripts": {
56
56
  "build": "obuild",