🌳
pt0/devpconlyF/movementsF/ptmMainAI.mts
1import { relative } from 'path'
17type Section = 'movements' | 'info'
18const cmd = (run: () => Promise<unknown>, section: Section, args: string, desc?: string) => Object.assign(run, { section, args, desc })
20const subcommandH = {
21 mv: cmd(() => import('./ptmvMainF.mts').then(m => m.ptmvMain()),
22 'movements', '<from> <to>', 'mv/rn & update imports & importers (auto-commits)'),
23 commit: cmd(() => import('./ptmCommitMainF.mts').then(m => m.ptmCommitMain()),
24 'movements', '--msg= [--files=...] [--amend]', 'pt_commit CLI equivalent (for worktree delegation)'),
25 extract: cmd(() => import('./ptextractMainF.mts').then(m => m.ptextractMain()),
26 'movements', '<exports...> [dest]', 'extract exports to dest or new file'),
27 rntok: cmd(() => import('./ptrntokMainF.mts').then(m => m.ptrntokMain()),
28 'movements', '<from> <to>', 'rn word codebase-wide (auto-commits)'),
29 subst: cmd(() => import('./ptsubstMainAI.mts').then(m => m.ptsubstMain()),
30 'movements', '<pattern> <name>', 'replace pattern, auto-add import if nec'),
31 resolveimport: cmd(async () => {
32 const [fromFile, toFile] = process.argv.slice(2, 4)
33 if (!fromFile || !toFile) throPtErr('usage: ptm resolveimport <fromFile> <toFile>')
34 const { resolveImportPath } = await import('../../deployF/jsImportsF/resolveImportPathF.mts')
35 console.log(resolveImportPath(fromFile, toFile))
36 }, 'info', '<fromFile> <toFile>', 'compute correct relative import path (avoids depth/extension errors)'),
37 importers: cmd(async () => {
38 captureSaveExampleMeta({name: 'ptm'})
39 const positionalArgs = process.argv.slice(2).filter(a => !a.startsWith('--'))
40 const [importeePath, importerPath] = positionalArgs
41 const epDir = cliArg('--epDir')
42 const result = await codegraphImporters(importeePath, {importerPath})
43 if (!result) return console.log('No importers found')
44 const lines = result.split('\n')
45 const filtered = epDir ? lines.filter(l => l.startsWith(epDir)) : lines
46 console.log(filtered.join('\n') || 'No importers found')
47 }, 'info', '<file> [<importer>] [--epDir]', 'ls files that directly import tgt'),
48 howimports: cmd(async () => {
49 captureSaveExampleMeta({name: 'ptm'})
50 const epDirArg = process.argv.find(a => a.startsWith('--epDir='))
51 const epDir = epDirArg?.split('=')[1]
52 const srcPath = process.argv[3] && !process.argv[3].startsWith('--') ? process.argv[3] : undefined
53 console.log(await codegraphHowimports(process.argv[2], { epDir, srcPath }) || 'No import path found')
54 }, 'info', '<file> [<srcFile|package>] [--epDir]', 'ls eps that transitively import tgt (or chain from srcFile/pkg)'),
55 lseps: cmd(async () => {
56 const epDirArg = process.argv.find(a => a.startsWith('--epDir='))
57 const epDir = epDirArg?.split('=')[1]
58 const inclLoc = true
59 const inclTokens = process.argv.includes('--inclTokens')
60 const inclNpm = process.argv.includes('--inclNpm')
62 let epPathsA = epDir ? filterEpsByDir(getAllRegisteredEps(), epDir) : getAllRegisteredEps()
64 if (!inclLoc) {
65 const maxPathLen = Math.max(...epPathsA.map(ep => ep.length))
66 for (const ep of epPathsA) console.log(`${ep.padEnd(maxPathLen + 2)}${getEpType(ep) || '?'}`)
67 return
68 }
70 const countTokensFn = inclTokens ? (await import('../../sharedF/countTokensF.mts')).countTokens : undefined
71 const epTypesA = epPathsA.map((p: string) => getEpType(p))
72 const fmtN = (n: number) => n >= 1000 ? (n / 1000).toFixed(1) + 'k' : String(n)
73 const statsA = await getEpStatsForAll(epPathsA, epTypesA, {countTokensFn})
75 for (const s of statsA) {
76 const epTypeStr = s.epType ? ` # ${s.epType}` : ''
77 const epTypeLoc = s.epTypeLocCnt != null ? ` (${fmtN(s.epTypeLocCnt)} epType)` : ''
78 let tokStr = ''
79 if (inclTokens) {
80 tokStr = ` ${fmtN(s.appTokenCnt!)} tok`
81 if (s.epTypeTokenCnt != null) tokStr += ` (${fmtN(s.epTypeTokenCnt!)} epType tok)`
82 }
83 console.log(`\n${s.epPath}${epTypeStr} ${fmtN(s.appLocCnt)} loc${epTypeLoc}${tokStr}`)
84 if (inclNpm && s.appPkgNamesA.length) console.log(` ${jsObjStringify({deps: s.appPkgNamesA})}`)
85 if (inclNpm && s.devPkgNamesA.length) console.log(` ${jsObjStringify({devDeps: s.devPkgNamesA})}`)
86 }
87 if (!inclNpm) console.log('\n' + chalkGray('--inclNpm'))
88 }, 'info', '[--epDir=<dir>] [--inclTokens] [--inclNpm]', 'eps & stats'),
89 bisect: cmd(() => import('./ptmBisectAI.mts').then(m => m.ptmBisect()),
90 'info', '<ep> --good=<sha> [--autorun]', 'bisect importtree in isolated worktree'),
91 wt: cmd(() => import('./ptmWorktreeAI.mts').then(m => m.ptmWt()),
92 'info', 'start <name>|--sha=<sha> | done <name> [--discard]', 'isolated worktree (to dev or checkout SHA)'),
93 check: cmd(async () => {
94 const args = process.argv.slice(2)
95 if (!args.length) throPtErr('usage: ptm check <file|dir> [file2|dir2 ...]')
96 const filesA = (await allPromCalls(args, lsFilePathsRec)).flat()
97 console.log(formatPtcheckResult(await refactorPtcheck(filesA)))
98 }, 'info', '<files|dirs...>', 'check syntax + TS types + boundaries (componentsF can\'t import serverF etc)'),
99 epsearch: cmd(() => import('./ptmEpsearchAI.mts').then(m => m.ptmEpsearchMain()),
100 'info', '<ep> <pattern> [rg-flags...]', 'search within ep import tree (uses rg)'),
101 gitlog: cmd(() => import('./ptmGitlogAI.mts').then(m => m.ptmGitlog()),
102 'info', '<file> [git-log-flags...]', 'git log limited to file import tree'),
103 ocsearch: cmd(() => import('./ptmOcsearchAI.mts').then(m => m.ptmOcsearch()),
104 'info', '<query> [--since=Nd]', 'search past oc sessions & commits by keyword'),
105 exceptions: cmd(() => import('./ptmExceptionsAI.mts').then(m => m.ptmExceptions()),
106 'info', '[--show=<errHash>] [--test]', 'exceptions across all eps from IMAP'),
109const mvEmojiA = ['🙏', '💎', '🤲', '👐', '🫶']
111const showHelp = () => {
112 const entries = Object.entries(subcommandH)
113 const printSection = (section: Section, header: string) => {
114 console.log(` # ${header}`)
115 const sectionEntries = entries.filter(([_, h]) => h.section === section)
116 if (section === 'movements') {
117 const mvCount = sectionEntries.length
118 throwIf(() => mvCount !== mvEmojiA.length, {mvCount, mvEmojiA})
119 }
120 sectionEntries.forEach(([name, h], i) => {
121 const emoji = section === 'movements' ? `${mvEmojiA[i]} ` : ' '
122 const desc = h.desc ? ` ${chalkGray(h.desc)}` : ''
123 console.log(` ${name.padEnd(11)}${emoji}${h.args.padEnd(30)}${desc}`)
124 })
125 }
126 console.log(`$ ${ptmBin} <action> [args]`)
127 printSection('movements', 'movements')
128 console.log()
129 printSection('info', 'info/debug')
132export const ptmMain = async () => {
133 const [subcommand, ...restArgs] = process.argv.slice(2)
135 if (!subcommand || subcommand === 'help') {
136 showHelp()
137 return
138 }
140 const handler = subcommandH[subcommand as keyof typeof subcommandH]
141 if (!handler) {
142 console.error(`Unknown command: ${subcommand}\n`)
143 showHelp()
144 process.exit(1)
145 }
147 process.argv = [process.argv[0], process.argv[1], ...restArgs]
149 if (handler.section === 'movements' && relative(ptDir, process.cwd()).startsWith('..')) {
150 console.error(`ERROR: ptm ${subcommand} operates on ptDir (${ptDir}) but you're in a worktree (${process.cwd()}).\nUse: ${process.cwd()}/pt0/path_bin/ptm ${subcommand} ...`)
151 process.exit(1)
152 }
154 await handler()