@ripast/cli 0.0.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/LICENSE.md ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Harlan Wilton <harlan@harlanzw.com>
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/bin/ripast.mjs ADDED
@@ -0,0 +1,64 @@
1
+ #!/usr/bin/env node
2
+ import { spawnSync } from 'node:child_process'
3
+ import { existsSync, readFileSync } from 'node:fs'
4
+ import { createRequire } from 'node:module'
5
+ import { dirname, join } from 'node:path'
6
+ import process from 'node:process'
7
+
8
+ const FRAMEWORK_MARKERS = {
9
+ vue: ['vue', 'nuxt', '@nuxt/kit'],
10
+ // svelte: ['svelte', '@sveltejs/kit'], // adapter not yet published
11
+ }
12
+
13
+ function detectFrameworks(cwd) {
14
+ const out = []
15
+ let dir = cwd
16
+ for (let i = 0; i < 6; i++) {
17
+ const pkgPath = join(dir, 'package.json')
18
+ if (existsSync(pkgPath)) {
19
+ try {
20
+ const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'))
21
+ const allDeps = { ...pkg.dependencies, ...pkg.devDependencies, ...pkg.peerDependencies }
22
+ for (const [name, markers] of Object.entries(FRAMEWORK_MARKERS)) {
23
+ if (out.includes(name))
24
+ continue
25
+ if (markers.some(m => allDeps[m]))
26
+ out.push(name)
27
+ }
28
+ }
29
+ catch {}
30
+ }
31
+ const parent = dirname(dir)
32
+ if (parent === dir)
33
+ break
34
+ dir = parent
35
+ }
36
+ return out
37
+ }
38
+
39
+ function ensureAdapters(needed) {
40
+ if (process.env.RIPAST_REEXEC)
41
+ return
42
+ if (!needed.length)
43
+ return
44
+
45
+ const require = createRequire(import.meta.url)
46
+ const missing = []
47
+ for (const name of needed) {
48
+ try { require.resolve(`@ripast/${name}`) }
49
+ catch { missing.push(`@ripast/${name}`) }
50
+ }
51
+ if (!missing.length)
52
+ return
53
+
54
+ const args = ['--yes', '--package=@ripast/cli', ...missing.map(p => `--package=${p}`), '--', 'ripast', ...process.argv.slice(2)]
55
+ const res = spawnSync('npx', args, {
56
+ stdio: 'inherit',
57
+ env: { ...process.env, RIPAST_REEXEC: '1' },
58
+ })
59
+ process.exit(res.status ?? 1)
60
+ }
61
+
62
+ ensureAdapters(detectFrameworks(process.cwd()))
63
+
64
+ await import('../dist/cli.mjs')
package/dist/cli.d.mts ADDED
@@ -0,0 +1 @@
1
+ export { };
package/dist/cli.mjs ADDED
@@ -0,0 +1,502 @@
1
+ import { mkdirSync, readFileSync, renameSync } from "node:fs";
2
+ import { dirname, resolve } from "node:path";
3
+ import process from "node:process";
4
+ import { buildDeclarationTree, buildScanGraph, formatAgentDeclarationTree, formatAgentHits, formatAgentScanHits, formatDeclarationTree, formatHits, formatRegressions, formatScanGraph, formatScanHits, printDiffs, resolveVerifyMode, runCssClassRename, runCssClassScan, runMove, runRename, runRenameFile, scan, summarize, writeChanges } from "@ripast/core";
5
+ import { defineCommand, runMain } from "citty";
6
+ import { agent, isAgent } from "std-env";
7
+ const globArg = {
8
+ type: "string",
9
+ description: "File glob(s), comma-separated. Defaults to *.ts,*.tsx,*.vue,... Respects .gitignore."
10
+ };
11
+ const applyArg = {
12
+ type: "boolean",
13
+ default: false,
14
+ description: "Write changes. Default prints a unified diff."
15
+ };
16
+ const verifyArg = {
17
+ type: "boolean",
18
+ default: true,
19
+ description: "Typecheck post-transform; refuse --apply on regression. Disable with --no-verify."
20
+ };
21
+ const verifyModeArg = {
22
+ type: "string",
23
+ description: "Verification mode: touched, project, or none. Defaults to touched; --no-verify maps to none."
24
+ };
25
+ const vueArg = {
26
+ type: "boolean",
27
+ default: true,
28
+ description: "Enable Volar pass for .vue files. Disable with --no-vue to force pure ts-morph behavior."
29
+ };
30
+ const jsonArg = {
31
+ type: "boolean",
32
+ default: false,
33
+ description: "Emit machine-readable JSON (suppresses diff/summary text)."
34
+ };
35
+ const profileArg = {
36
+ type: "string",
37
+ description: "Output profile: auto, agent, or full. Auto uses std-env isAgent."
38
+ };
39
+ function resolveProfile(raw) {
40
+ const profile = raw ?? "auto";
41
+ if (profile !== "auto" && profile !== "agent" && profile !== "full") {
42
+ process.stderr.write(`ripast: --profile must be "auto", "agent", or "full".\n`);
43
+ process.exit(2);
44
+ }
45
+ return {
46
+ profile,
47
+ agentProfile: profile === "agent" || profile === "auto" && isAgent
48
+ };
49
+ }
50
+ function resolveGraphFormat(raw) {
51
+ if (raw !== "mermaid" && raw !== "dot") {
52
+ process.stderr.write(`ripast scan: --graph must be "mermaid" or "dot".\n`);
53
+ process.exit(2);
54
+ }
55
+ return raw;
56
+ }
57
+ function profileHeader() {
58
+ return `# profile: agent${agent ? ` (${agent})` : ""}`;
59
+ }
60
+ function resolveCliVerifyMode(verify, verifyMode) {
61
+ if (verifyMode != null) {
62
+ if (verifyMode !== "none" && verifyMode !== "touched" && verifyMode !== "project") {
63
+ process.stderr.write(`ripast: --verify-mode must be "none", "touched", or "project".\n`);
64
+ process.exit(2);
65
+ }
66
+ return verifyMode;
67
+ }
68
+ return resolveVerifyMode(verify);
69
+ }
70
+ function resolveExportFilter(raw) {
71
+ if (raw !== "all" && raw !== "exported" && raw !== "local") {
72
+ process.stderr.write(`ripast tree: --exports must be "all", "exported", or "local".\n`);
73
+ process.exit(2);
74
+ }
75
+ return raw;
76
+ }
77
+ const scanCmd = defineCommand({
78
+ meta: {
79
+ name: "scan",
80
+ description: "rg-prefilter + AST-classify occurrences of an identifier."
81
+ },
82
+ args: {
83
+ pattern: {
84
+ type: "positional",
85
+ required: true
86
+ },
87
+ glob: globArg,
88
+ kind: {
89
+ type: "string",
90
+ description: "Filter to kind(s), comma-separated. Kinds: identifier-reference, identifier-binding, import-specifier, member-access, property, jsx, string-literal."
91
+ },
92
+ graph: {
93
+ type: "string",
94
+ description: "Emit a dependency graph for hit files: mermaid or dot."
95
+ },
96
+ profile: profileArg,
97
+ json: {
98
+ type: "boolean",
99
+ default: false
100
+ }
101
+ },
102
+ run({ args }) {
103
+ const { agentProfile } = resolveProfile(args.profile);
104
+ const opts = {
105
+ glob: args.glob ? args.glob.split(",") : void 0,
106
+ kinds: args.kind ? args.kind.split(",") : void 0
107
+ };
108
+ if (args.graph) {
109
+ const graphFormat = resolveGraphFormat(args.graph);
110
+ process.stdout.write(`${formatScanGraph(buildScanGraph(args.pattern, opts), graphFormat)}\n`);
111
+ return;
112
+ }
113
+ const hits = scan(args.pattern, opts);
114
+ if (agentProfile && !args.json) {
115
+ process.stdout.write(`${profileHeader()}\n${formatAgentHits(hits)}\n`);
116
+ return;
117
+ }
118
+ process.stdout.write(`${formatHits(hits, args.json)}\n`);
119
+ }
120
+ });
121
+ const renameCmd = defineCommand({
122
+ meta: {
123
+ name: "rename",
124
+ description: "Scope-aware symbol rename via ts-morph (handles type-only imports, shadowing, JSX)."
125
+ },
126
+ args: {
127
+ from: {
128
+ type: "positional",
129
+ required: true
130
+ },
131
+ to: {
132
+ type: "positional",
133
+ required: true
134
+ },
135
+ tsconfig: { type: "string" },
136
+ glob: globArg,
137
+ apply: applyArg,
138
+ verify: verifyArg,
139
+ verifyMode: verifyModeArg,
140
+ scope: {
141
+ type: "string",
142
+ description: "Restrict to a single file when multiple files declare the same name."
143
+ },
144
+ all: {
145
+ type: "boolean",
146
+ default: false,
147
+ description: "Rename declarations in every file that defines the name (bypasses ambiguity check)."
148
+ },
149
+ vue: vueArg,
150
+ profile: profileArg,
151
+ json: jsonArg
152
+ },
153
+ async run({ args }) {
154
+ const verifyMode = resolveCliVerifyMode(args.verify, args.verifyMode);
155
+ emitResult(await runRename(args.from, args.to, {
156
+ tsconfig: args.tsconfig,
157
+ glob: args.glob ? args.glob.split(",") : void 0,
158
+ verify: verifyMode,
159
+ scope: args.scope,
160
+ allowMultiple: args.all,
161
+ vue: args.vue
162
+ }), !!args.apply, verifyMode !== "none", !!args.json, resolveProfile(args.profile).agentProfile);
163
+ }
164
+ });
165
+ const treeCmd = defineCommand({
166
+ meta: {
167
+ name: "tree",
168
+ description: "Print a project declaration tree from top-level AST declarations, grouped by file."
169
+ },
170
+ args: {
171
+ glob: globArg,
172
+ exports: {
173
+ type: "string",
174
+ description: "Declaration filter: all, exported, or local. Defaults to exported for detected agents, all otherwise."
175
+ },
176
+ profile: profileArg,
177
+ json: jsonArg
178
+ },
179
+ run({ args }) {
180
+ const { agentProfile } = resolveProfile(args.profile);
181
+ const exportFilter = args.exports == null ? agentProfile ? "exported" : "all" : resolveExportFilter(args.exports);
182
+ const tree = buildDeclarationTree({
183
+ glob: args.glob ? args.glob.split(",") : void 0,
184
+ exports: agentProfile ? "all" : exportFilter
185
+ });
186
+ if (agentProfile && !args.json) {
187
+ process.stdout.write(`${profileHeader()}\n`);
188
+ process.stdout.write(`${formatAgentDeclarationTree(tree, exportFilter)}\n`);
189
+ return;
190
+ }
191
+ process.stdout.write(`${formatDeclarationTree(tree, !!args.json)}\n`);
192
+ }
193
+ });
194
+ const moveCmd = defineCommand({
195
+ meta: {
196
+ name: "move",
197
+ description: "Move a top-level exported symbol between files; rewrites import sites project-wide."
198
+ },
199
+ args: {
200
+ symbol: {
201
+ type: "positional",
202
+ required: true
203
+ },
204
+ from: {
205
+ type: "string",
206
+ required: true,
207
+ description: "Source file path."
208
+ },
209
+ to: {
210
+ type: "string",
211
+ required: true,
212
+ description: "Target file path (created if missing)."
213
+ },
214
+ tsconfig: { type: "string" },
215
+ apply: applyArg,
216
+ verify: verifyArg,
217
+ verifyMode: verifyModeArg,
218
+ vue: vueArg,
219
+ profile: profileArg,
220
+ json: jsonArg
221
+ },
222
+ async run({ args }) {
223
+ const verifyMode = resolveCliVerifyMode(args.verify, args.verifyMode);
224
+ emitResult(await runMove(args.symbol, args.from, args.to, {
225
+ tsconfig: args.tsconfig,
226
+ verify: verifyMode,
227
+ vue: args.vue
228
+ }), !!args.apply, verifyMode !== "none", !!args.json, resolveProfile(args.profile).agentProfile);
229
+ }
230
+ });
231
+ function emitResult(r, apply, verify = false, json = false, agentProfile = false) {
232
+ const s = summarize(r.changes);
233
+ if (json) {
234
+ const blockedByRegression = verify && apply && r.regressions.length > 0;
235
+ const wrote = apply && !blockedByRegression;
236
+ if (wrote) writeChanges(r.changes);
237
+ const payload = {
238
+ applied: wrote,
239
+ dryRun: !apply,
240
+ blockedByRegression,
241
+ scanned: r.scanned,
242
+ summary: s,
243
+ changes: r.changes.map((c) => ({
244
+ path: c.rel,
245
+ absolutePath: c.path,
246
+ before: c.before,
247
+ after: c.after
248
+ })),
249
+ regressions: r.regressions
250
+ };
251
+ process.stdout.write(`${JSON.stringify(payload)}\n`);
252
+ if (blockedByRegression) process.exit(1);
253
+ return;
254
+ }
255
+ if (agentProfile) {
256
+ const blockedByRegression = verify && apply && r.regressions.length > 0;
257
+ process.stdout.write(`${profileHeader()}\n`);
258
+ process.stdout.write(`changes: ${r.changes.length}/${r.scanned} files, +${s.linesAdded} -${s.linesRemoved} lines\n`);
259
+ process.stdout.write(`mode: ${apply ? blockedByRegression ? "blocked" : "applied" : "dry-run"}\n`);
260
+ if (r.changes.length) {
261
+ process.stdout.write(`files:\n`);
262
+ for (const c of r.changes) process.stdout.write(` ${c.rel}\n`);
263
+ }
264
+ if (verify && r.regressions.length) {
265
+ process.stdout.write(`regressions: ${r.regressions.length}\n`);
266
+ for (const regression of r.regressions.slice(0, 20)) process.stdout.write(` ${regression.file}:${regression.line}:${regression.col} TS${regression.code} ${regression.message}\n`);
267
+ if (r.regressions.length > 20) process.stdout.write(` ... ${r.regressions.length - 20} more\n`);
268
+ if (apply) {
269
+ process.stderr.write(`\nripast: refusing to --apply; --no-verify to override.\n`);
270
+ process.exit(1);
271
+ }
272
+ }
273
+ if (apply) {
274
+ writeChanges(r.changes);
275
+ process.stdout.write(`applied: true\n`);
276
+ } else process.stdout.write(`apply: pass --apply to write, or --profile full to see diff\n`);
277
+ return;
278
+ }
279
+ if (!apply) {
280
+ process.stdout.write(`${s.files} file${s.files === 1 ? "" : "s"}, +${s.linesAdded} -${s.linesRemoved} lines\n\n`);
281
+ printDiffs(r.changes);
282
+ }
283
+ if (verify && r.regressions.length) {
284
+ process.stderr.write(`\n${formatRegressions(r.regressions, process.cwd())}\n`);
285
+ if (apply) {
286
+ process.stderr.write(`\nripast: refusing to --apply; --no-verify to override.\n`);
287
+ process.exit(1);
288
+ }
289
+ }
290
+ if (apply) {
291
+ writeChanges(r.changes);
292
+ for (const c of r.changes) process.stdout.write(`wrote ${c.rel}\n`);
293
+ }
294
+ const suffix = apply ? "" : " (dry run, pass --apply to write)";
295
+ process.stdout.write(`\n${r.changes.length}/${r.scanned} files changed${suffix}\n`);
296
+ }
297
+ const renameFileCmd = defineCommand({
298
+ meta: {
299
+ name: "rename-file",
300
+ description: "Rename a file and rewrite every import site (including .vue consumers and component-name refs)."
301
+ },
302
+ args: {
303
+ old: {
304
+ type: "positional",
305
+ required: true
306
+ },
307
+ new: {
308
+ type: "positional",
309
+ required: true
310
+ },
311
+ tsconfig: { type: "string" },
312
+ apply: applyArg,
313
+ verify: verifyArg,
314
+ profile: profileArg,
315
+ json: jsonArg
316
+ },
317
+ async run({ args }) {
318
+ const r = await runRenameFile(args.old, args.new, {
319
+ tsconfig: args.tsconfig,
320
+ verify: args.verify
321
+ });
322
+ const apply = !!args.apply;
323
+ const verify = !!args.verify;
324
+ const json = !!args.json;
325
+ const { agentProfile } = resolveProfile(args.profile);
326
+ const s = summarize(r.changes);
327
+ const blockedByRegression = verify && apply && r.regressions.length > 0;
328
+ const wrote = apply && !blockedByRegression;
329
+ if (wrote) {
330
+ writeChanges(r.changes);
331
+ mkdirSync(dirname(r.fileMove.to), { recursive: true });
332
+ renameSync(r.fileMove.from, r.fileMove.to);
333
+ }
334
+ if (json) {
335
+ process.stdout.write(`${JSON.stringify({
336
+ applied: wrote,
337
+ dryRun: !apply,
338
+ blockedByRegression,
339
+ scanned: r.scanned,
340
+ summary: s,
341
+ fileMove: r.fileMove,
342
+ changes: r.changes.map((c) => ({
343
+ path: c.rel,
344
+ absolutePath: c.path,
345
+ before: c.before,
346
+ after: c.after
347
+ })),
348
+ regressions: r.regressions
349
+ })}\n`);
350
+ if (blockedByRegression) process.exit(1);
351
+ return;
352
+ }
353
+ if (agentProfile) {
354
+ process.stdout.write(`${profileHeader()}\n`);
355
+ process.stdout.write(`rename-file: ${args.old} -> ${args.new}\n`);
356
+ process.stdout.write(`consumers: ${r.changes.length}/${r.scanned}, +${s.linesAdded} -${s.linesRemoved} lines\n`);
357
+ process.stdout.write(`mode: ${apply ? blockedByRegression ? "blocked" : "applied" : "dry-run"}\n`);
358
+ if (r.changes.length) {
359
+ process.stdout.write(`files:\n`);
360
+ for (const c of r.changes) process.stdout.write(` ${c.rel}\n`);
361
+ }
362
+ if (verify && r.regressions.length) {
363
+ process.stdout.write(`regressions: ${r.regressions.length}\n`);
364
+ if (apply) {
365
+ process.stderr.write(`\nripast: refusing to --apply; --no-verify to override.\n`);
366
+ process.exit(1);
367
+ }
368
+ }
369
+ if (!apply) process.stdout.write(`apply: pass --apply to write, or --profile full to see diff\n`);
370
+ return;
371
+ }
372
+ if (!apply) {
373
+ process.stdout.write(`rename ${args.old} -> ${args.new}\n`);
374
+ process.stdout.write(`${s.files} consumer file${s.files === 1 ? "" : "s"}, +${s.linesAdded} -${s.linesRemoved} lines\n\n`);
375
+ printDiffs(r.changes);
376
+ }
377
+ if (verify && r.regressions.length) {
378
+ process.stderr.write(`\n${formatRegressions(r.regressions, process.cwd())}\n`);
379
+ if (apply) {
380
+ process.stderr.write(`\nripast: refusing to --apply; --no-verify to override.\n`);
381
+ process.exit(1);
382
+ }
383
+ }
384
+ if (wrote) {
385
+ for (const c of r.changes) process.stdout.write(`wrote ${c.rel}\n`);
386
+ process.stdout.write(`renamed ${args.old} -> ${args.new}\n`);
387
+ }
388
+ const suffix = apply ? "" : " (dry run, pass --apply to write)";
389
+ process.stdout.write(`\n${r.changes.length} consumer file(s) updated${suffix}\n`);
390
+ }
391
+ });
392
+ const cssClassRenameCmd = defineCommand({
393
+ meta: {
394
+ name: "css-class-rename",
395
+ description: "Rename CSS utility class token(s) across strings, Vue templates, and @apply. Pass a single \"from to\" pair, or --map <file.json> for bulk. No typecheck verify."
396
+ },
397
+ args: {
398
+ from: {
399
+ type: "positional",
400
+ required: false
401
+ },
402
+ to: {
403
+ type: "positional",
404
+ required: false
405
+ },
406
+ map: {
407
+ type: "string",
408
+ description: "Path to a JSON file with { \"from\": \"to\", ... } flat object. Mutually exclusive with from/to positionals."
409
+ },
410
+ glob: globArg,
411
+ apply: applyArg,
412
+ profile: profileArg,
413
+ json: jsonArg
414
+ },
415
+ async run({ args }) {
416
+ emitResult(await runCssClassRename(buildRenameMap(args.from, args.to, args.map), { glob: args.glob ? args.glob.split(",") : void 0 }), !!args.apply, false, !!args.json, resolveProfile(args.profile).agentProfile);
417
+ }
418
+ });
419
+ function buildRenameMap(from, to, mapPath) {
420
+ const hasPair = from != null && to != null;
421
+ const hasMap = !!mapPath;
422
+ if (hasPair && hasMap) {
423
+ process.stderr.write(`ripast css-class-rename: pass either "from to" positionals OR --map, not both.\n`);
424
+ process.exit(2);
425
+ }
426
+ if (!hasPair && !hasMap) {
427
+ process.stderr.write(`ripast css-class-rename: missing input. Pass "from to" positionals or --map <file.json>.\n`);
428
+ process.exit(2);
429
+ }
430
+ if (hasPair) return new Map([[from, to]]);
431
+ const raw = readFileSync(resolve(process.cwd(), mapPath), "utf8");
432
+ let parsed;
433
+ try {
434
+ parsed = JSON.parse(raw);
435
+ } catch (err) {
436
+ process.stderr.write(`ripast css-class-rename: --map file is not valid JSON (${err.message}).\n`);
437
+ process.exit(2);
438
+ }
439
+ if (parsed == null || typeof parsed !== "object" || Array.isArray(parsed)) {
440
+ process.stderr.write(`ripast css-class-rename: --map must be a flat JSON object, got ${Array.isArray(parsed) ? "array" : typeof parsed}.\n`);
441
+ process.exit(2);
442
+ }
443
+ const entries = [];
444
+ for (const [k, v] of Object.entries(parsed)) {
445
+ if (typeof v !== "string") {
446
+ process.stderr.write(`ripast css-class-rename: --map value for "${k}" is not a string.\n`);
447
+ process.exit(2);
448
+ }
449
+ if (!k) {
450
+ process.stderr.write(`ripast css-class-rename: --map has an empty key.\n`);
451
+ process.exit(2);
452
+ }
453
+ entries.push([k, String(v)]);
454
+ }
455
+ if (!entries.length) {
456
+ process.stderr.write(`ripast css-class-rename: --map is empty.\n`);
457
+ process.exit(2);
458
+ }
459
+ return new Map(entries);
460
+ }
461
+ runMain(defineCommand({
462
+ meta: {
463
+ name: "ripast",
464
+ description: "AST-aware refactor primitives. ripgrep-prefiltered, dry-run by default."
465
+ },
466
+ subCommands: {
467
+ "scan": scanCmd,
468
+ "tree": treeCmd,
469
+ "rename": renameCmd,
470
+ "rename-file": renameFileCmd,
471
+ "move": moveCmd,
472
+ "css-class-rename": cssClassRenameCmd,
473
+ "css-class-scan": defineCommand({
474
+ meta: {
475
+ name: "css-class-scan",
476
+ description: "Tokenize every class site (strings, Vue class attrs, @apply) and emit a frequency-sorted list. Feeds --map for css-class-rename."
477
+ },
478
+ args: {
479
+ pattern: {
480
+ type: "string",
481
+ description: "Comma-separated globs matched against the bare token (e.g. \"bg-*,text-*\"). Default: all tokens."
482
+ },
483
+ glob: globArg,
484
+ profile: profileArg,
485
+ json: jsonArg
486
+ },
487
+ run({ args }) {
488
+ const { agentProfile } = resolveProfile(args.profile);
489
+ const hits = runCssClassScan({
490
+ glob: args.glob ? args.glob.split(",") : void 0,
491
+ pattern: args.pattern ? args.pattern.split(",") : void 0
492
+ });
493
+ if (agentProfile && !args.json) {
494
+ process.stdout.write(`${profileHeader()}\n${formatAgentScanHits(hits)}\n`);
495
+ return;
496
+ }
497
+ process.stdout.write(`${formatScanHits(hits, !!args.json)}\n`);
498
+ }
499
+ })
500
+ }
501
+ }));
502
+ export {};
package/package.json ADDED
@@ -0,0 +1,59 @@
1
+ {
2
+ "name": "@ripast/cli",
3
+ "type": "module",
4
+ "version": "0.0.0",
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
+ "author": {
7
+ "name": "Harlan Wilton",
8
+ "email": "harlan@harlanzw.com",
9
+ "url": "https://harlanzw.com/"
10
+ },
11
+ "license": "MIT",
12
+ "homepage": "https://github.com/harlan-zw/ripast#readme",
13
+ "repository": {
14
+ "type": "git",
15
+ "url": "git+https://github.com/harlan-zw/ripast.git",
16
+ "directory": "packages/cli"
17
+ },
18
+ "bugs": {
19
+ "url": "https://github.com/harlan-zw/ripast/issues"
20
+ },
21
+ "keywords": [
22
+ "ripast",
23
+ "cli",
24
+ "ast",
25
+ "refactor",
26
+ "rename",
27
+ "codemod"
28
+ ],
29
+ "exports": {
30
+ ".": {
31
+ "types": "./dist/cli.d.ts",
32
+ "import": "./dist/cli.mjs"
33
+ }
34
+ },
35
+ "main": "./dist/cli.mjs",
36
+ "types": "./dist/cli.d.ts",
37
+ "bin": {
38
+ "ripast": "./bin/ripast.mjs"
39
+ },
40
+ "files": [
41
+ "bin",
42
+ "dist"
43
+ ],
44
+ "engines": {
45
+ "node": ">=20.11"
46
+ },
47
+ "peerDependencies": {
48
+ "typescript": ">=5.4"
49
+ },
50
+ "dependencies": {
51
+ "citty": "^0.2.2",
52
+ "std-env": "^4.1.0",
53
+ "@ripast/core": "0.0.0"
54
+ },
55
+ "scripts": {
56
+ "build": "obuild",
57
+ "dev:prepare": "obuild --stub"
58
+ }
59
+ }