@trpc/upgrade 0.0.0-alpha.26 → 0.0.0-alpha.28
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/bin.js +240 -0
- package/dist/transforms/{hooksToOptions.cjs → hooksToOptions.js} +5 -13
- package/dist/transforms/{provider.cjs → provider.js} +1 -4
- package/package.json +9 -10
- package/src/bin/index.ts +98 -0
- package/src/lib/ast/modifiers.ts +5 -5
- package/src/lib/ast/scanners.ts +129 -0
- package/src/lib/ast/walkers.ts +4 -5
- package/src/lib/execa.ts +4 -0
- package/src/lib/git.ts +44 -0
- package/src/lib/pkgmgr.ts +34 -0
- package/src/transforms/hooksToOptions.ts +8 -2
- package/dist/cli.cjs +0 -105
- package/src/bin/cli.ts +0 -238
package/dist/bin.js
ADDED
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import * as CP from 'node:child_process';
|
|
3
|
+
import * as Path from 'node:path';
|
|
4
|
+
import * as Util from 'node:util';
|
|
5
|
+
import * as Args from '@bomb.sh/args';
|
|
6
|
+
import * as p from '@clack/prompts';
|
|
7
|
+
import * as ts from 'typescript';
|
|
8
|
+
import { forEachChild, isImportDeclaration, isStringLiteral, resolveModuleName, sys, isVariableStatement, isVariableDeclaration, isCallExpression, isIdentifier } from 'typescript';
|
|
9
|
+
import __node_cjsModule from 'node:module';
|
|
10
|
+
|
|
11
|
+
var version = "0.0.0-alpha.27";
|
|
12
|
+
|
|
13
|
+
function findSourceAndImportName(program) {
|
|
14
|
+
const files = program.getSourceFiles().filter((sourceFile)=>{
|
|
15
|
+
if (sourceFile.isDeclarationFile) return false;
|
|
16
|
+
let found = false;
|
|
17
|
+
forEachChild(sourceFile, (node)=>{
|
|
18
|
+
if (!found && isImportDeclaration(node)) {
|
|
19
|
+
const { moduleSpecifier } = node;
|
|
20
|
+
if (isStringLiteral(moduleSpecifier) && moduleSpecifier.text.includes('@trpc/react-query')) {
|
|
21
|
+
found = true;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
return found;
|
|
26
|
+
});
|
|
27
|
+
let importName = 'trpc';
|
|
28
|
+
files.forEach((sourceFile)=>{
|
|
29
|
+
forEachChild(sourceFile, (node)=>{
|
|
30
|
+
if (isVariableStatement(node) && node.modifiers?.some((mod)=>mod.getText(sourceFile) === 'export')) {
|
|
31
|
+
node.declarationList.declarations.forEach((declaration)=>{
|
|
32
|
+
if (isVariableDeclaration(declaration) && declaration.initializer && isCallExpression(declaration.initializer) && isIdentifier(declaration.initializer.expression) && declaration.initializer.expression.getText(sourceFile) === 'createTRPCReact') {
|
|
33
|
+
importName = declaration.name.getText(sourceFile);
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
return {
|
|
40
|
+
files: files.map((d)=>d.fileName),
|
|
41
|
+
importName
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
function findTRPCImportReferences(program) {
|
|
45
|
+
const { files: filesImportingTRPC, importName } = findSourceAndImportName(program);
|
|
46
|
+
const trpcReferenceSpecifiers = new Map();
|
|
47
|
+
program.getSourceFiles().forEach((sourceFile)=>{
|
|
48
|
+
if (sourceFile.isDeclarationFile) return;
|
|
49
|
+
forEachChild(sourceFile, (node)=>{
|
|
50
|
+
if (isImportDeclaration(node) && isStringLiteral(node.moduleSpecifier)) {
|
|
51
|
+
const resolved = resolveModuleName(node.moduleSpecifier.text, sourceFile.fileName, program.getCompilerOptions(), sys);
|
|
52
|
+
if (resolved.resolvedModule && filesImportingTRPC.includes(resolved.resolvedModule.resolvedFileName)) {
|
|
53
|
+
trpcReferenceSpecifiers.set(resolved.resolvedModule.resolvedFileName, node.moduleSpecifier.text);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
const counts = {};
|
|
59
|
+
let currentMax = 0;
|
|
60
|
+
const mostUsed = {
|
|
61
|
+
file: ''
|
|
62
|
+
};
|
|
63
|
+
[
|
|
64
|
+
...trpcReferenceSpecifiers.values()
|
|
65
|
+
].forEach((specifier)=>{
|
|
66
|
+
counts[specifier] = (counts[specifier] || 0) + 1;
|
|
67
|
+
if (counts[specifier] > currentMax) {
|
|
68
|
+
currentMax = counts[specifier];
|
|
69
|
+
mostUsed.file = specifier;
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
return {
|
|
73
|
+
importName,
|
|
74
|
+
mostUsed,
|
|
75
|
+
all: Object.fromEntries(trpcReferenceSpecifiers.entries())
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const require = __node_cjsModule.createRequire(import.meta.url);
|
|
80
|
+
|
|
81
|
+
const execa = Util.promisify(CP.exec);
|
|
82
|
+
function exitOnCancel(value) {
|
|
83
|
+
if (p.isCancel(value)) {
|
|
84
|
+
process.exit(0);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
async function getPackageManager() {
|
|
88
|
+
const userAgent = process.env.npm_config_user_agent;
|
|
89
|
+
if (userAgent?.startsWith('pnpm')) return 'pnpm';
|
|
90
|
+
if (userAgent?.startsWith('yarn')) return 'yarn';
|
|
91
|
+
if (userAgent?.startsWith('bun')) return 'bun';
|
|
92
|
+
return 'npm';
|
|
93
|
+
}
|
|
94
|
+
async function assertCleanGitTree() {
|
|
95
|
+
const { stdout } = await execa('git status');
|
|
96
|
+
if (!stdout.includes('nothing to commit')) {
|
|
97
|
+
throw new Error('Git tree is not clean, please commit your changes and try again, or run with `--force`');
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
async function installPackage(packageName) {
|
|
101
|
+
const packageManager = await getPackageManager();
|
|
102
|
+
const { stdout } = await execa(`${packageManager} install ${packageName}`);
|
|
103
|
+
console.log(stdout);
|
|
104
|
+
}
|
|
105
|
+
async function uninstallPackage(packageName) {
|
|
106
|
+
const packageManager = await getPackageManager();
|
|
107
|
+
const uninstallCmd = packageManager === 'yarn' ? 'remove' : 'uninstall';
|
|
108
|
+
const { stdout } = await execa(`${packageManager} ${uninstallCmd} ${packageName}`);
|
|
109
|
+
console.log(stdout);
|
|
110
|
+
}
|
|
111
|
+
async function filterIgnored(files) {
|
|
112
|
+
const { stdout } = await execa('git check-ignore **/*');
|
|
113
|
+
const ignores = stdout.split('\n');
|
|
114
|
+
if (process.env.VERBOSE) {
|
|
115
|
+
console.debug('cwd:', process.cwd());
|
|
116
|
+
console.debug('All files in program:', files.map((file)=>file.fileName));
|
|
117
|
+
console.debug('Ignored files:', ignores);
|
|
118
|
+
}
|
|
119
|
+
// Ignore "common files"
|
|
120
|
+
const filteredSourcePaths = files.filter((source)=>source.fileName.startsWith(Path.resolve()) && // only look ahead of current directory
|
|
121
|
+
!source.fileName.includes('/trpc/packages/') && // relative paths when running codemod locally
|
|
122
|
+
!source.fileName.includes('/node_modules/') && // always ignore node_modules
|
|
123
|
+
!ignores.includes(source.fileName)).map((source)=>source.fileName);
|
|
124
|
+
if (process.env.VERBOSE) {
|
|
125
|
+
console.debug('Filtered files:', filteredSourcePaths);
|
|
126
|
+
}
|
|
127
|
+
return filteredSourcePaths;
|
|
128
|
+
}
|
|
129
|
+
// FIXME :: hacky
|
|
130
|
+
const transformPath = (path)=>process.env.DEV ? path : path.replace('../', './').replace('.ts', '.cjs');
|
|
131
|
+
async function main() {
|
|
132
|
+
const args = Args.parse(process.argv.slice(2), {
|
|
133
|
+
default: {
|
|
134
|
+
force: false,
|
|
135
|
+
skipTanstackQuery: false,
|
|
136
|
+
verbose: false,
|
|
137
|
+
version: false
|
|
138
|
+
},
|
|
139
|
+
alias: {
|
|
140
|
+
f: 'force',
|
|
141
|
+
h: 'help',
|
|
142
|
+
v: 'verbose',
|
|
143
|
+
q: 'skipTanstackQuery'
|
|
144
|
+
},
|
|
145
|
+
boolean: true
|
|
146
|
+
});
|
|
147
|
+
if (args.version) {
|
|
148
|
+
console.log(`v${version}`);
|
|
149
|
+
process.exit(0);
|
|
150
|
+
}
|
|
151
|
+
if (args.help) {
|
|
152
|
+
console.log(`
|
|
153
|
+
Usage: upgrade [options]
|
|
154
|
+
|
|
155
|
+
Options:
|
|
156
|
+
-f, --force Skip git status check, use with caution
|
|
157
|
+
-q, --skipTanstackQuery Skip installing @trpc/tanstack-react-query package
|
|
158
|
+
-v, --verbose Enable verbose logging
|
|
159
|
+
-h, --help Show help
|
|
160
|
+
`.trim());
|
|
161
|
+
process.exit(0);
|
|
162
|
+
}
|
|
163
|
+
if (args.verbose) {
|
|
164
|
+
console.log('Running upgrade with args:', args);
|
|
165
|
+
}
|
|
166
|
+
p.intro('tRPC Upgrade CLI');
|
|
167
|
+
if (!args.force) {
|
|
168
|
+
try {
|
|
169
|
+
await assertCleanGitTree();
|
|
170
|
+
} catch (error) {
|
|
171
|
+
console.error(error);
|
|
172
|
+
process.exit(1);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
const transforms = await p.multiselect({
|
|
176
|
+
message: 'Select transforms to run',
|
|
177
|
+
options: [
|
|
178
|
+
{
|
|
179
|
+
value: require.resolve(transformPath('../transforms/hooksToOptions.ts')),
|
|
180
|
+
label: 'Migrate Hooks to xxxOptions API'
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
value: require.resolve(transformPath('../transforms/provider.ts')),
|
|
184
|
+
label: 'Migrate context provider setup'
|
|
185
|
+
}
|
|
186
|
+
]
|
|
187
|
+
});
|
|
188
|
+
exitOnCancel(transforms);
|
|
189
|
+
if (!transforms || transforms.length === 0) {
|
|
190
|
+
console.error('Please select at least one transform to run');
|
|
191
|
+
process.exit(1);
|
|
192
|
+
}
|
|
193
|
+
// Make sure provider transform runs first if it's selected
|
|
194
|
+
const sortedTransforms = transforms.sort((a)=>a.includes('provider.ts') ? -1 : 1);
|
|
195
|
+
const configFile = ts.findConfigFile(process.cwd(), ts.sys.fileExists);
|
|
196
|
+
if (!configFile) {
|
|
197
|
+
console.error('No tsconfig found');
|
|
198
|
+
process.exit(1);
|
|
199
|
+
}
|
|
200
|
+
if (process.env.VERBOSE) {
|
|
201
|
+
console.debug('Using tsconfig', configFile);
|
|
202
|
+
}
|
|
203
|
+
const { config } = ts.readConfigFile(configFile, ts.sys.readFile);
|
|
204
|
+
const parsedConfig = ts.parseJsonConfigFileContent(config, ts.sys, process.cwd());
|
|
205
|
+
const program = ts.createProgram({
|
|
206
|
+
options: parsedConfig.options,
|
|
207
|
+
rootNames: parsedConfig.fileNames,
|
|
208
|
+
configFileParsingDiagnostics: parsedConfig.errors
|
|
209
|
+
});
|
|
210
|
+
const sourceFiles = program.getSourceFiles();
|
|
211
|
+
const possibleReferences = findTRPCImportReferences(program);
|
|
212
|
+
const trpcFile = possibleReferences.mostUsed.file;
|
|
213
|
+
const trpcImportName = possibleReferences.importName;
|
|
214
|
+
const commitedFiles = await filterIgnored(sourceFiles);
|
|
215
|
+
for (const transform of sortedTransforms){
|
|
216
|
+
console.log('Running transform', transform);
|
|
217
|
+
const { run } = await import('jscodeshift/src/Runner.js');
|
|
218
|
+
const result = await run(transform, commitedFiles, {
|
|
219
|
+
...args,
|
|
220
|
+
trpcFile,
|
|
221
|
+
trpcImportName
|
|
222
|
+
});
|
|
223
|
+
console.log('Transform result', result);
|
|
224
|
+
}
|
|
225
|
+
if (!args.skipTanstackQuery) {
|
|
226
|
+
console.log('Installing @trpc/tanstack-react-query');
|
|
227
|
+
await installPackage('@trpc/tanstack-react-query');
|
|
228
|
+
console.log('Uninstalling @trpc/react-query');
|
|
229
|
+
await uninstallPackage('@trpc/react-query');
|
|
230
|
+
}
|
|
231
|
+
p.outro('Upgrade complete! 🎉');
|
|
232
|
+
}
|
|
233
|
+
main().catch((error)=>{
|
|
234
|
+
if (error instanceof Error) {
|
|
235
|
+
console.error(error.message);
|
|
236
|
+
} else {
|
|
237
|
+
console.error('An unknown error occurred:', error);
|
|
238
|
+
}
|
|
239
|
+
process.exit(1);
|
|
240
|
+
});
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
Object.defineProperty(exports, '__esModule', { value: true });
|
|
2
|
-
|
|
3
1
|
/**
|
|
4
2
|
* Replaces the identifier for the root path key
|
|
5
3
|
* of a member expression
|
|
@@ -16,23 +14,17 @@ Object.defineProperty(exports, '__esModule', { value: true });
|
|
|
16
14
|
expr.object = id;
|
|
17
15
|
return true;
|
|
18
16
|
}
|
|
19
|
-
|
|
20
|
-
return replaceMemberExpressionRootIndentifier(j, expr.object, id);
|
|
21
|
-
}
|
|
22
|
-
return false;
|
|
17
|
+
return !j.MemberExpression.check(expr.object) ? false : replaceMemberExpressionRootIndentifier(j, expr.object, id);
|
|
23
18
|
}
|
|
24
19
|
|
|
25
20
|
/**
|
|
26
21
|
* Walks the path upwards to look for the closest parent
|
|
27
22
|
* of the mentioned type
|
|
28
23
|
*/ function findParentOfType(path, type) {
|
|
29
|
-
if (!type.check(path.node)) {
|
|
30
|
-
return findParentOfType(path.parentPath, type);
|
|
31
|
-
}
|
|
32
24
|
if (!path.parent) {
|
|
33
25
|
return false;
|
|
34
26
|
}
|
|
35
|
-
return path;
|
|
27
|
+
return type.check(path.node) ? path : findParentOfType(path.parentPath, type);
|
|
36
28
|
}
|
|
37
29
|
|
|
38
30
|
const hookToOptions = {
|
|
@@ -183,7 +175,8 @@ function transform(file, api, options) {
|
|
|
183
175
|
}
|
|
184
176
|
}
|
|
185
177
|
}).forEach((path)=>{
|
|
186
|
-
|
|
178
|
+
const isTRPCContextUtil = j.MemberExpression.check(path.value.callee) && j.Identifier.check(path.value.callee.object) && path.value.callee.object.name == trpcImportName;
|
|
179
|
+
if (isTRPCContextUtil && j.VariableDeclarator.check(path.parentPath.node) && j.Identifier.check(path.parentPath.node.id)) {
|
|
187
180
|
const oldIdentifier = path.parentPath.node.id;
|
|
188
181
|
// Find all the references to `utils` and replace with `queryClient[helperMap](trpc.PATH.queryFilter())`
|
|
189
182
|
root.find(j.Identifier, {
|
|
@@ -266,5 +259,4 @@ function transform(file, api, options) {
|
|
|
266
259
|
}
|
|
267
260
|
const parser = 'tsx';
|
|
268
261
|
|
|
269
|
-
|
|
270
|
-
exports.parser = parser;
|
|
262
|
+
export { transform as default, parser };
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
Object.defineProperty(exports, '__esModule', { value: true });
|
|
2
|
-
|
|
3
1
|
function transform(file, api, options) {
|
|
4
2
|
const { trpcImportName } = options;
|
|
5
3
|
let routerName = undefined;
|
|
@@ -116,5 +114,4 @@ function transform(file, api, options) {
|
|
|
116
114
|
}
|
|
117
115
|
const parser = 'tsx';
|
|
118
116
|
|
|
119
|
-
|
|
120
|
-
exports.parser = parser;
|
|
117
|
+
export { transform as default, parser };
|
package/package.json
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@trpc/upgrade",
|
|
3
|
-
"version": "0.0.0-alpha.
|
|
3
|
+
"version": "0.0.0-alpha.28",
|
|
4
4
|
"description": "Upgrade scripts for tRPC",
|
|
5
5
|
"author": "juliusmarminge",
|
|
6
6
|
"license": "MIT",
|
|
7
|
-
"
|
|
7
|
+
"type": "module",
|
|
8
|
+
"bin": "./dist/bin.js",
|
|
8
9
|
"homepage": "https://trpc.io",
|
|
9
10
|
"repository": {
|
|
10
11
|
"type": "git",
|
|
@@ -13,10 +14,10 @@
|
|
|
13
14
|
},
|
|
14
15
|
"exports": {
|
|
15
16
|
"./transforms/hooksToOptions": {
|
|
16
|
-
"
|
|
17
|
+
"default": "./dist/transforms/hooksToOptions.js"
|
|
17
18
|
},
|
|
18
19
|
"./transforms/provider": {
|
|
19
|
-
"
|
|
20
|
+
"default": "./dist/transforms/provider.js"
|
|
20
21
|
}
|
|
21
22
|
},
|
|
22
23
|
"files": [
|
|
@@ -28,18 +29,16 @@
|
|
|
28
29
|
"!**/__tests__"
|
|
29
30
|
],
|
|
30
31
|
"dependencies": {
|
|
31
|
-
"@
|
|
32
|
-
"@
|
|
33
|
-
"@effect/platform-node": "0.72.0",
|
|
34
|
-
"effect": "3.12.11",
|
|
32
|
+
"@bomb.sh/args": "0.3.0",
|
|
33
|
+
"@clack/prompts": "0.10.0",
|
|
35
34
|
"jscodeshift": "17.1.1",
|
|
36
35
|
"typescript": "^5.6.2"
|
|
37
36
|
},
|
|
38
37
|
"devDependencies": {
|
|
39
38
|
"@types/jscodeshift": "0.12.0",
|
|
40
39
|
"@types/node": "^22.9.0",
|
|
41
|
-
"bunchee": "
|
|
42
|
-
"esbuild": "0.
|
|
40
|
+
"bunchee": "6.4.0",
|
|
41
|
+
"esbuild": "0.25.0",
|
|
43
42
|
"tsx": "^4.0.0"
|
|
44
43
|
},
|
|
45
44
|
"publishConfig": {
|
package/src/bin/index.ts
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { parse } from '@bomb.sh/args';
|
|
3
|
+
import { intro, isCancel, log, multiselect, outro } from '@clack/prompts';
|
|
4
|
+
import { version } from '../../package.json';
|
|
5
|
+
import { findTRPCImportReferences, getProgram } from '../lib/ast/scanners';
|
|
6
|
+
import { assertCleanGitTree, filterIgnored } from '../lib/git';
|
|
7
|
+
import { installPackage, uninstallPackage } from '../lib/pkgmgr';
|
|
8
|
+
|
|
9
|
+
const args = parse(process.argv.slice(2), {
|
|
10
|
+
default: {
|
|
11
|
+
force: false,
|
|
12
|
+
skipTanstackQuery: false,
|
|
13
|
+
verbose: false,
|
|
14
|
+
},
|
|
15
|
+
alias: {
|
|
16
|
+
f: 'force',
|
|
17
|
+
h: 'help',
|
|
18
|
+
v: 'verbose',
|
|
19
|
+
q: 'skipTanstackQuery',
|
|
20
|
+
},
|
|
21
|
+
boolean: true,
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
intro(`tRPC Upgrade CLI v${version}`);
|
|
25
|
+
|
|
26
|
+
if (args.help) {
|
|
27
|
+
log.info(
|
|
28
|
+
`
|
|
29
|
+
Usage: upgrade [options]
|
|
30
|
+
|
|
31
|
+
Options:
|
|
32
|
+
-f, --force Skip git status check, use with caution
|
|
33
|
+
-q, --skipTanstackQuery Skip installing @trpc/tanstack-react-query package
|
|
34
|
+
-v, --verbose Enable verbose logging
|
|
35
|
+
-h, --help Show help
|
|
36
|
+
`.trim(),
|
|
37
|
+
);
|
|
38
|
+
process.exit(0);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (args.verbose) {
|
|
42
|
+
log.info(`Running upgrade with args: ${JSON.stringify(args, null, 2)}`);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (!args.force) {
|
|
46
|
+
await assertCleanGitTree();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const transforms = await multiselect({
|
|
50
|
+
message: 'Select transforms to run',
|
|
51
|
+
options: [
|
|
52
|
+
{
|
|
53
|
+
value: require.resolve('@trpc/upgrade/transforms/hooksToOptions'),
|
|
54
|
+
label: 'Migrate Hooks to xxxOptions API',
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
value: require.resolve('@trpc/upgrade/transforms/provider'),
|
|
58
|
+
label: 'Migrate context provider setup',
|
|
59
|
+
},
|
|
60
|
+
],
|
|
61
|
+
});
|
|
62
|
+
if (isCancel(transforms)) process.exit(0);
|
|
63
|
+
|
|
64
|
+
// Make sure provider transform runs first if it's selected
|
|
65
|
+
const sortedTransforms = transforms.sort((a) =>
|
|
66
|
+
a.includes('provider') ? -1 : 1,
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
const program = getProgram(args);
|
|
70
|
+
const sourceFiles = program.getSourceFiles();
|
|
71
|
+
const possibleReferences = findTRPCImportReferences(program);
|
|
72
|
+
const trpcFile = possibleReferences.mostUsed.file;
|
|
73
|
+
const trpcImportName = possibleReferences.importName;
|
|
74
|
+
|
|
75
|
+
const commitedFiles = await filterIgnored(sourceFiles);
|
|
76
|
+
|
|
77
|
+
for (const transform of sortedTransforms) {
|
|
78
|
+
log.info(`Running transform: ${transform}`);
|
|
79
|
+
const { run } = await import('jscodeshift/src/Runner.js');
|
|
80
|
+
await run(transform, commitedFiles, {
|
|
81
|
+
...args,
|
|
82
|
+
trpcFile,
|
|
83
|
+
trpcImportName,
|
|
84
|
+
});
|
|
85
|
+
log.info(`Transform ${transform} completed`);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (!args.skipTanstackQuery) {
|
|
89
|
+
log.info('Installing @trpc/tanstack-react-query');
|
|
90
|
+
await installPackage('@trpc/tanstack-react-query');
|
|
91
|
+
log.success('@trpc/tanstack-react-query installed');
|
|
92
|
+
|
|
93
|
+
log.info('Uninstalling @trpc/react-query');
|
|
94
|
+
await uninstallPackage('@trpc/react-query');
|
|
95
|
+
log.success('@trpc/react-query uninstalled');
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
outro('Upgrade complete! 🎉');
|
package/src/lib/ast/modifiers.ts
CHANGED
|
@@ -16,13 +16,13 @@ export function replaceMemberExpressionRootIndentifier(
|
|
|
16
16
|
j: JSCodeshift,
|
|
17
17
|
expr: MemberExpression,
|
|
18
18
|
id: Identifier,
|
|
19
|
-
) {
|
|
19
|
+
): boolean {
|
|
20
20
|
if (j.Identifier.check(expr.object)) {
|
|
21
21
|
expr.object = id;
|
|
22
22
|
return true;
|
|
23
23
|
}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
24
|
+
|
|
25
|
+
return !j.MemberExpression.check(expr.object)
|
|
26
|
+
? false
|
|
27
|
+
: replaceMemberExpressionRootIndentifier(j, expr.object, id);
|
|
28
28
|
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import * as p from '@clack/prompts';
|
|
2
|
+
import * as ts from 'typescript';
|
|
3
|
+
|
|
4
|
+
export function getProgram(args: Record<string, unknown>) {
|
|
5
|
+
const configFile = ts.findConfigFile(process.cwd(), (filepath) =>
|
|
6
|
+
ts.sys.fileExists(filepath),
|
|
7
|
+
);
|
|
8
|
+
if (!configFile) {
|
|
9
|
+
p.log.error('No tsconfig found');
|
|
10
|
+
process.exit(1);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
if (args.verbose) {
|
|
14
|
+
p.log.info(`Using tsconfig: ${configFile}`);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const { config } = ts.readConfigFile(configFile, (filepath) =>
|
|
18
|
+
ts.sys.readFile(filepath),
|
|
19
|
+
);
|
|
20
|
+
const parsedConfig = ts.parseJsonConfigFileContent(
|
|
21
|
+
config,
|
|
22
|
+
ts.sys,
|
|
23
|
+
process.cwd(),
|
|
24
|
+
);
|
|
25
|
+
const program = ts.createProgram({
|
|
26
|
+
options: parsedConfig.options,
|
|
27
|
+
rootNames: parsedConfig.fileNames,
|
|
28
|
+
configFileParsingDiagnostics: parsedConfig.errors,
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
return program;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function findSourceAndImportName(program: ts.Program) {
|
|
35
|
+
const files = program.getSourceFiles().filter((sourceFile) => {
|
|
36
|
+
if (sourceFile.isDeclarationFile) return false;
|
|
37
|
+
let found = false;
|
|
38
|
+
ts.forEachChild(sourceFile, (node) => {
|
|
39
|
+
if (!found && ts.isImportDeclaration(node)) {
|
|
40
|
+
const { moduleSpecifier } = node;
|
|
41
|
+
if (
|
|
42
|
+
ts.isStringLiteral(moduleSpecifier) &&
|
|
43
|
+
moduleSpecifier.text.includes('@trpc/react-query')
|
|
44
|
+
) {
|
|
45
|
+
found = true;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
return found;
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
let importName = 'trpc';
|
|
53
|
+
files.forEach((sourceFile) => {
|
|
54
|
+
ts.forEachChild(sourceFile, (node) => {
|
|
55
|
+
if (
|
|
56
|
+
ts.isVariableStatement(node) &&
|
|
57
|
+
node.modifiers?.some((mod) => mod.getText(sourceFile) === 'export')
|
|
58
|
+
) {
|
|
59
|
+
node.declarationList.declarations.forEach((declaration) => {
|
|
60
|
+
if (
|
|
61
|
+
ts.isVariableDeclaration(declaration) &&
|
|
62
|
+
declaration.initializer &&
|
|
63
|
+
ts.isCallExpression(declaration.initializer) &&
|
|
64
|
+
ts.isIdentifier(declaration.initializer.expression) &&
|
|
65
|
+
declaration.initializer.expression.getText(sourceFile) ===
|
|
66
|
+
'createTRPCReact'
|
|
67
|
+
) {
|
|
68
|
+
importName = declaration.name.getText(sourceFile);
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
return {
|
|
76
|
+
files: files.map((d) => d.fileName),
|
|
77
|
+
importName,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function findTRPCImportReferences(program: ts.Program) {
|
|
82
|
+
const { files: filesImportingTRPC, importName } =
|
|
83
|
+
findSourceAndImportName(program);
|
|
84
|
+
const trpcReferenceSpecifiers = new Map<string, string>();
|
|
85
|
+
|
|
86
|
+
program.getSourceFiles().forEach((sourceFile) => {
|
|
87
|
+
if (sourceFile.isDeclarationFile) return;
|
|
88
|
+
ts.forEachChild(sourceFile, (node) => {
|
|
89
|
+
if (
|
|
90
|
+
ts.isImportDeclaration(node) &&
|
|
91
|
+
ts.isStringLiteral(node.moduleSpecifier)
|
|
92
|
+
) {
|
|
93
|
+
const resolved = ts.resolveModuleName(
|
|
94
|
+
node.moduleSpecifier.text,
|
|
95
|
+
sourceFile.fileName,
|
|
96
|
+
program.getCompilerOptions(),
|
|
97
|
+
ts.sys,
|
|
98
|
+
);
|
|
99
|
+
if (
|
|
100
|
+
resolved.resolvedModule &&
|
|
101
|
+
filesImportingTRPC.includes(resolved.resolvedModule.resolvedFileName)
|
|
102
|
+
) {
|
|
103
|
+
trpcReferenceSpecifiers.set(
|
|
104
|
+
resolved.resolvedModule.resolvedFileName,
|
|
105
|
+
node.moduleSpecifier.text,
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
const counts: Record<string, number> = {};
|
|
113
|
+
let currentMax = 0;
|
|
114
|
+
const mostUsed = { file: '' };
|
|
115
|
+
|
|
116
|
+
[...trpcReferenceSpecifiers.values()].forEach((specifier) => {
|
|
117
|
+
counts[specifier] = (counts[specifier] || 0) + 1;
|
|
118
|
+
if (counts[specifier] > currentMax) {
|
|
119
|
+
currentMax = counts[specifier];
|
|
120
|
+
mostUsed.file = specifier;
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
return {
|
|
125
|
+
importName,
|
|
126
|
+
mostUsed,
|
|
127
|
+
all: Object.fromEntries(trpcReferenceSpecifiers.entries()),
|
|
128
|
+
};
|
|
129
|
+
}
|
package/src/lib/ast/walkers.ts
CHANGED
|
@@ -7,12 +7,11 @@ import type { ASTPath, JSCodeshift } from 'jscodeshift';
|
|
|
7
7
|
export function findParentOfType<TPath>(
|
|
8
8
|
path: ASTPath<unknown>,
|
|
9
9
|
type: JSCodeshift['AnyType'],
|
|
10
|
-
): ASTPath<
|
|
11
|
-
if (!type.check(path.node)) {
|
|
12
|
-
return findParentOfType(path.parentPath, type);
|
|
13
|
-
}
|
|
10
|
+
): ASTPath<TPath> | false {
|
|
14
11
|
if (!path.parent) {
|
|
15
12
|
return false;
|
|
16
13
|
}
|
|
17
|
-
return path
|
|
14
|
+
return type.check(path.node)
|
|
15
|
+
? (path as ASTPath<TPath>)
|
|
16
|
+
: findParentOfType(path.parentPath, type);
|
|
18
17
|
}
|
package/src/lib/execa.ts
ADDED
package/src/lib/git.ts
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import * as Path from 'path';
|
|
2
|
+
import * as p from '@clack/prompts';
|
|
3
|
+
import type { SourceFile } from 'typescript';
|
|
4
|
+
import { execa } from './execa';
|
|
5
|
+
|
|
6
|
+
export async function assertCleanGitTree() {
|
|
7
|
+
const { stdout } = await execa('git status');
|
|
8
|
+
if (!stdout.includes('nothing to commit')) {
|
|
9
|
+
p.cancel(
|
|
10
|
+
'Git tree is not clean, please commit your changes and try again, or run with `--force`',
|
|
11
|
+
);
|
|
12
|
+
process.exit(1);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export async function filterIgnored(files: readonly SourceFile[]) {
|
|
17
|
+
const { stdout } = await execa('git check-ignore **/*');
|
|
18
|
+
const ignores = stdout.split('\n');
|
|
19
|
+
|
|
20
|
+
if (process.env.VERBOSE) {
|
|
21
|
+
p.log.info(`cwd: ${process.cwd()}`);
|
|
22
|
+
p.log.info(
|
|
23
|
+
`All files in program: ${files.map((file) => file.fileName).join(', ')}`,
|
|
24
|
+
);
|
|
25
|
+
p.log.info(`Ignored files: ${ignores.join(', ')}`);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Ignore "common files"
|
|
29
|
+
const filteredSourcePaths = files
|
|
30
|
+
.filter(
|
|
31
|
+
(source) =>
|
|
32
|
+
source.fileName.startsWith(Path.resolve()) && // only look ahead of current directory
|
|
33
|
+
!source.fileName.includes('/trpc/packages/') && // relative paths when running codemod locally
|
|
34
|
+
!source.fileName.includes('/node_modules/') && // always ignore node_modules
|
|
35
|
+
!ignores.includes(source.fileName), // ignored files
|
|
36
|
+
)
|
|
37
|
+
.map((source) => source.fileName);
|
|
38
|
+
|
|
39
|
+
if (process.env.VERBOSE) {
|
|
40
|
+
p.log.info(`Filtered files: ${filteredSourcePaths.join(', ')}`);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return filteredSourcePaths;
|
|
44
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import * as p from '@clack/prompts';
|
|
2
|
+
import { execa } from './execa';
|
|
3
|
+
|
|
4
|
+
function getPackageManager() {
|
|
5
|
+
const userAgent = process.env.npm_config_user_agent;
|
|
6
|
+
if (userAgent?.startsWith('pnpm')) return 'pnpm';
|
|
7
|
+
if (userAgent?.startsWith('yarn')) return 'yarn';
|
|
8
|
+
if (userAgent?.startsWith('bun')) return 'bun';
|
|
9
|
+
return 'npm';
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export async function installPackage(packageName: string) {
|
|
13
|
+
const packageManager = getPackageManager();
|
|
14
|
+
const installCmd = packageManager === 'yarn' ? 'add' : 'install';
|
|
15
|
+
const { stdout, stderr } = await execa(
|
|
16
|
+
`${packageManager} ${installCmd} ${packageName}`,
|
|
17
|
+
);
|
|
18
|
+
if (stderr) {
|
|
19
|
+
p.log.error(stderr);
|
|
20
|
+
}
|
|
21
|
+
p.log.info(stdout);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export async function uninstallPackage(packageName: string) {
|
|
25
|
+
const packageManager = getPackageManager();
|
|
26
|
+
const uninstallCmd = packageManager === 'yarn' ? 'remove' : 'uninstall';
|
|
27
|
+
const { stdout, stderr } = await execa(
|
|
28
|
+
`${packageManager} ${uninstallCmd} ${packageName}`,
|
|
29
|
+
);
|
|
30
|
+
if (stderr) {
|
|
31
|
+
p.log.error(stderr);
|
|
32
|
+
}
|
|
33
|
+
p.log.info(stdout);
|
|
34
|
+
}
|
|
@@ -196,7 +196,13 @@ export default function transform(
|
|
|
196
196
|
},
|
|
197
197
|
})
|
|
198
198
|
.forEach((path) => {
|
|
199
|
+
const isTRPCContextUtil =
|
|
200
|
+
j.MemberExpression.check(path.value.callee) &&
|
|
201
|
+
j.Identifier.check(path.value.callee.object) &&
|
|
202
|
+
path.value.callee.object.name == trpcImportName;
|
|
203
|
+
|
|
199
204
|
if (
|
|
205
|
+
isTRPCContextUtil &&
|
|
200
206
|
j.VariableDeclarator.check(path.parentPath.node) &&
|
|
201
207
|
j.Identifier.check(path.parentPath.node.id)
|
|
202
208
|
) {
|
|
@@ -219,7 +225,7 @@ export default function transform(
|
|
|
219
225
|
);
|
|
220
226
|
return;
|
|
221
227
|
}
|
|
222
|
-
const callExpr = callExprPath.node
|
|
228
|
+
const callExpr = callExprPath.node;
|
|
223
229
|
const memberExpr = callExpr.callee as MemberExpression;
|
|
224
230
|
if (
|
|
225
231
|
!j.CallExpression.check(callExpr) ||
|
|
@@ -252,7 +258,7 @@ export default function transform(
|
|
|
252
258
|
const replacedPath = replaceMemberExpressionRootIndentifier(
|
|
253
259
|
j,
|
|
254
260
|
memberExpr,
|
|
255
|
-
j.identifier(trpcImportName
|
|
261
|
+
j.identifier(trpcImportName),
|
|
256
262
|
);
|
|
257
263
|
if (!replacedPath) {
|
|
258
264
|
console.warn(
|
package/dist/cli.cjs
DELETED
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
var path = require('node:path');
|
|
3
|
-
var cli$1 = require('@effect/cli');
|
|
4
|
-
var platform = require('@effect/platform');
|
|
5
|
-
var platformNode = require('@effect/platform-node');
|
|
6
|
-
var effect = require('effect');
|
|
7
|
-
var typescript = require('typescript');
|
|
8
|
-
|
|
9
|
-
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
10
|
-
|
|
11
|
-
var path__default = /*#__PURE__*/_interopDefault(path);
|
|
12
|
-
|
|
13
|
-
var version = "0.0.0-alpha.25";
|
|
14
|
-
|
|
15
|
-
const MakeCommand = (command, ...args)=>{
|
|
16
|
-
return platform.Command.make(command, ...args).pipe(platform.Command.workingDirectory(process.cwd()), platform.Command.runInShell(true));
|
|
17
|
-
};
|
|
18
|
-
const assertCleanGitTree = platform.Command.string(MakeCommand('git', 'status')).pipe().pipe(effect.Effect.filterOrFail(effect.String.includes('nothing to commit'), ()=>'Git tree is not clean, please commit your changes and try again, or run with `--force`'));
|
|
19
|
-
const getPackageManager = ()=>effect.Match.value(process.env.npm_config_user_agent ?? 'npm').pipe(effect.Match.when(effect.String.startsWith('pnpm'), ()=>'pnpm'), effect.Match.when(effect.String.startsWith('yarn'), ()=>'yarn'), effect.Match.when(effect.String.startsWith('bun'), ()=>'bun'), effect.Match.orElse(()=>'npm'));
|
|
20
|
-
const installPackage = (packageName)=>{
|
|
21
|
-
const packageManager = getPackageManager();
|
|
22
|
-
return platform.Command.streamLines(MakeCommand(packageManager, 'install', packageName)).pipe(effect.Stream.mapEffect(effect.Console.log), effect.Stream.runDrain);
|
|
23
|
-
};
|
|
24
|
-
const uninstallPackage = (packageName)=>{
|
|
25
|
-
const packageManager = getPackageManager();
|
|
26
|
-
const uninstallCmd = packageManager === 'yarn' ? 'remove' : 'uninstall';
|
|
27
|
-
return platform.Command.streamLines(MakeCommand(packageManager, uninstallCmd, packageName)).pipe(effect.Stream.mapEffect(effect.Console.log), effect.Stream.runDrain);
|
|
28
|
-
};
|
|
29
|
-
const filterIgnored = (files)=>effect.Effect.gen(function*() {
|
|
30
|
-
const ignores = yield* platform.Command.string(MakeCommand('git', 'check-ignore', '**/*').pipe(platform.Command.runInShell(true))).pipe(effect.Effect.tap((_)=>effect.Effect.logDebug('Ignored files output:', _)), effect.Effect.map((_)=>_.split('\n')));
|
|
31
|
-
yield* effect.Effect.logDebug('cwd:', process.cwd());
|
|
32
|
-
yield* effect.Effect.logDebug('All files in program:', files.map((_)=>_.fileName));
|
|
33
|
-
yield* effect.Effect.logDebug('Ignored files:', ignores);
|
|
34
|
-
// Ignore "common files"
|
|
35
|
-
const filteredSourcePaths = files.filter((source)=>source.fileName.startsWith(path__default.default.resolve()) && // only look ahead of current directory
|
|
36
|
-
!source.fileName.includes('/trpc/packages/') && // relative paths when running codemod locally
|
|
37
|
-
!source.fileName.includes('/node_modules/') && // always ignore node_modules
|
|
38
|
-
!ignores.includes(source.fileName)).map((source)=>source.fileName);
|
|
39
|
-
yield* effect.Effect.logDebug('Filtered files:', filteredSourcePaths);
|
|
40
|
-
return filteredSourcePaths;
|
|
41
|
-
});
|
|
42
|
-
const TSProgram = effect.Effect.succeed(typescript.findConfigFile(process.cwd(), typescript.sys.fileExists)).pipe(effect.Effect.filterOrFail(effect.Predicate.isNotNullable, ()=>'No tsconfig found'), effect.Effect.tap((_)=>effect.Effect.logDebug('Using tsconfig', _)), effect.Effect.map((_)=>typescript.readConfigFile(_, typescript.sys.readFile)), effect.Effect.map((_)=>typescript.parseJsonConfigFileContent(_.config, typescript.sys, process.cwd())), effect.Effect.map((_)=>typescript.createProgram({
|
|
43
|
-
options: _.options,
|
|
44
|
-
rootNames: _.fileNames,
|
|
45
|
-
configFileParsingDiagnostics: _.errors
|
|
46
|
-
})));
|
|
47
|
-
// FIXME :: hacky
|
|
48
|
-
const transformPath = (path)=>process.env.DEV ? path : path.replace('../', './').replace('.ts', '.cjs');
|
|
49
|
-
const force = cli$1.Options.boolean('force').pipe(cli$1.Options.withAlias('f'), cli$1.Options.withDefault(false), cli$1.Options.withDescription('Skip git status check, use with caution'));
|
|
50
|
-
/**
|
|
51
|
-
* TODO: Instead of default values these should be detected automatically from the TS program
|
|
52
|
-
*/ const trpcFile = cli$1.Options.text('trpcFile').pipe(cli$1.Options.withAlias('f'), cli$1.Options.withDefault('~/trpc'), cli$1.Options.withDescription('Path to the trpc import file'));
|
|
53
|
-
const trpcImportName = cli$1.Options.text('trpcImportName').pipe(cli$1.Options.withAlias('i'), cli$1.Options.withDefault('trpc'), cli$1.Options.withDescription('Name of the trpc import'));
|
|
54
|
-
const skipTanstackQuery = cli$1.Options.boolean('skipTanstackQuery').pipe(cli$1.Options.withAlias('q'), cli$1.Options.withDefault(false), cli$1.Options.withDescription('Skip installing @trpc/tanstack-react-query package'));
|
|
55
|
-
const verbose = cli$1.Options.boolean('verbose').pipe(cli$1.Options.withAlias('v'), cli$1.Options.withDefault(false), cli$1.Options.withDescription('Enable verbose logging'));
|
|
56
|
-
const rootComamnd = cli$1.Command.make('upgrade', {
|
|
57
|
-
force,
|
|
58
|
-
trpcFile,
|
|
59
|
-
trpcImportName,
|
|
60
|
-
skipTanstackQuery,
|
|
61
|
-
verbose
|
|
62
|
-
}, (args)=>effect.Effect.gen(function*() {
|
|
63
|
-
if (args.verbose) {
|
|
64
|
-
yield* effect.Effect.log('Running upgrade with args:', args);
|
|
65
|
-
}
|
|
66
|
-
if (!args.force) {
|
|
67
|
-
yield* assertCleanGitTree;
|
|
68
|
-
}
|
|
69
|
-
const transforms = yield* effect.pipe(cli$1.Prompt.multiSelect({
|
|
70
|
-
message: 'Select transforms to run',
|
|
71
|
-
choices: [
|
|
72
|
-
{
|
|
73
|
-
title: 'Migrate Hooks to xxxOptions API',
|
|
74
|
-
value: require.resolve(transformPath('../transforms/hooksToOptions.ts'))
|
|
75
|
-
},
|
|
76
|
-
{
|
|
77
|
-
title: 'Migrate context provider setup',
|
|
78
|
-
value: require.resolve(transformPath('../transforms/provider.ts'))
|
|
79
|
-
}
|
|
80
|
-
]
|
|
81
|
-
}), effect.Effect.flatMap((selected)=>{
|
|
82
|
-
if (selected.length === 0) {
|
|
83
|
-
return effect.Effect.fail(new Error('Please select at least one transform to run'));
|
|
84
|
-
}
|
|
85
|
-
return effect.Effect.succeed(selected);
|
|
86
|
-
}), effect.Effect.map(// Make sure provider transform runs first if it's selected
|
|
87
|
-
effect.Array.sortWith((a)=>!a.includes('provider.ts'), effect.Order.boolean)));
|
|
88
|
-
const program = yield* TSProgram;
|
|
89
|
-
const sourceFiles = program.getSourceFiles();
|
|
90
|
-
const commitedFiles = yield* filterIgnored(sourceFiles);
|
|
91
|
-
yield* effect.Effect.forEach(transforms, (transform)=>{
|
|
92
|
-
return effect.pipe(effect.Effect.log('Running transform', transform), effect.Effect.flatMap(()=>effect.Effect.tryPromise(async ()=>import('jscodeshift/src/Runner.js').then(({ run })=>run(transform, commitedFiles, args)))), effect.Effect.map((_)=>effect.Effect.log('Transform result', _)));
|
|
93
|
-
});
|
|
94
|
-
if (!args.skipTanstackQuery) {
|
|
95
|
-
yield* effect.Effect.log('Installing @trpc/tanstack-react-query');
|
|
96
|
-
yield* installPackage('@trpc/tanstack-react-query');
|
|
97
|
-
yield* effect.Effect.log('Uninstalling @trpc/react-query');
|
|
98
|
-
yield* uninstallPackage('@trpc/react-query');
|
|
99
|
-
}
|
|
100
|
-
}).pipe(effect.Logger.withMinimumLogLevel(args.verbose ? effect.LogLevel.Debug : effect.LogLevel.Info)));
|
|
101
|
-
const cli = cli$1.Command.run(rootComamnd, {
|
|
102
|
-
name: 'tRPC Upgrade CLI',
|
|
103
|
-
version: `v${version}`
|
|
104
|
-
});
|
|
105
|
-
cli(process.argv).pipe(effect.Effect.provide(platformNode.NodeContext.layer), platformNode.NodeRuntime.runMain);
|
package/src/bin/cli.ts
DELETED
|
@@ -1,238 +0,0 @@
|
|
|
1
|
-
/* eslint-disable @typescript-eslint/unbound-method */
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
import { Command as CLICommand, Options, Prompt } from '@effect/cli';
|
|
4
|
-
import { Command } from '@effect/platform';
|
|
5
|
-
import { NodeContext, NodeRuntime } from '@effect/platform-node';
|
|
6
|
-
import {
|
|
7
|
-
Array,
|
|
8
|
-
Console,
|
|
9
|
-
Effect,
|
|
10
|
-
Logger,
|
|
11
|
-
LogLevel,
|
|
12
|
-
Match,
|
|
13
|
-
Order,
|
|
14
|
-
pipe,
|
|
15
|
-
Predicate,
|
|
16
|
-
Stream,
|
|
17
|
-
String,
|
|
18
|
-
} from 'effect';
|
|
19
|
-
import type { SourceFile } from 'typescript';
|
|
20
|
-
import {
|
|
21
|
-
createProgram,
|
|
22
|
-
findConfigFile,
|
|
23
|
-
parseJsonConfigFileContent,
|
|
24
|
-
readConfigFile,
|
|
25
|
-
sys,
|
|
26
|
-
} from 'typescript';
|
|
27
|
-
import { version } from '../../package.json';
|
|
28
|
-
|
|
29
|
-
const MakeCommand = (command: string, ...args: string[]) => {
|
|
30
|
-
return Command.make(command, ...args).pipe(
|
|
31
|
-
Command.workingDirectory(process.cwd()),
|
|
32
|
-
Command.runInShell(true),
|
|
33
|
-
);
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
const assertCleanGitTree = Command.string(MakeCommand('git', 'status'))
|
|
37
|
-
.pipe()
|
|
38
|
-
.pipe(
|
|
39
|
-
Effect.filterOrFail(
|
|
40
|
-
String.includes('nothing to commit'),
|
|
41
|
-
() =>
|
|
42
|
-
'Git tree is not clean, please commit your changes and try again, or run with `--force`',
|
|
43
|
-
),
|
|
44
|
-
);
|
|
45
|
-
const getPackageManager = () =>
|
|
46
|
-
Match.value(process.env.npm_config_user_agent ?? 'npm').pipe(
|
|
47
|
-
Match.when(String.startsWith('pnpm'), () => 'pnpm'),
|
|
48
|
-
Match.when(String.startsWith('yarn'), () => 'yarn'),
|
|
49
|
-
Match.when(String.startsWith('bun'), () => 'bun'),
|
|
50
|
-
Match.orElse(() => 'npm'),
|
|
51
|
-
);
|
|
52
|
-
|
|
53
|
-
const installPackage = (packageName: string) => {
|
|
54
|
-
const packageManager = getPackageManager();
|
|
55
|
-
return Command.streamLines(
|
|
56
|
-
MakeCommand(packageManager, 'install', packageName),
|
|
57
|
-
).pipe(Stream.mapEffect(Console.log), Stream.runDrain);
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
const uninstallPackage = (packageName: string) => {
|
|
61
|
-
const packageManager = getPackageManager();
|
|
62
|
-
const uninstallCmd = packageManager === 'yarn' ? 'remove' : 'uninstall';
|
|
63
|
-
return Command.streamLines(
|
|
64
|
-
MakeCommand(packageManager, uninstallCmd, packageName),
|
|
65
|
-
).pipe(Stream.mapEffect(Console.log), Stream.runDrain);
|
|
66
|
-
};
|
|
67
|
-
|
|
68
|
-
const filterIgnored = (files: readonly SourceFile[]) =>
|
|
69
|
-
Effect.gen(function* () {
|
|
70
|
-
const ignores = yield* Command.string(
|
|
71
|
-
MakeCommand('git', 'check-ignore', '**/*').pipe(Command.runInShell(true)),
|
|
72
|
-
).pipe(
|
|
73
|
-
Effect.tap((_) => Effect.logDebug('Ignored files output:', _)),
|
|
74
|
-
Effect.map((_) => _.split('\n')),
|
|
75
|
-
);
|
|
76
|
-
|
|
77
|
-
yield* Effect.logDebug('cwd:', process.cwd());
|
|
78
|
-
|
|
79
|
-
yield* Effect.logDebug(
|
|
80
|
-
'All files in program:',
|
|
81
|
-
files.map((_) => _.fileName),
|
|
82
|
-
);
|
|
83
|
-
yield* Effect.logDebug('Ignored files:', ignores);
|
|
84
|
-
|
|
85
|
-
// Ignore "common files"
|
|
86
|
-
const filteredSourcePaths = files
|
|
87
|
-
.filter(
|
|
88
|
-
(source) =>
|
|
89
|
-
source.fileName.startsWith(path.resolve()) && // only look ahead of current directory
|
|
90
|
-
!source.fileName.includes('/trpc/packages/') && // relative paths when running codemod locally
|
|
91
|
-
!source.fileName.includes('/node_modules/') && // always ignore node_modules
|
|
92
|
-
!ignores.includes(source.fileName), // ignored files
|
|
93
|
-
)
|
|
94
|
-
.map((source) => source.fileName);
|
|
95
|
-
|
|
96
|
-
yield* Effect.logDebug('Filtered files:', filteredSourcePaths);
|
|
97
|
-
|
|
98
|
-
return filteredSourcePaths;
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
const TSProgram = Effect.succeed(
|
|
102
|
-
findConfigFile(process.cwd(), sys.fileExists),
|
|
103
|
-
).pipe(
|
|
104
|
-
Effect.filterOrFail(Predicate.isNotNullable, () => 'No tsconfig found'),
|
|
105
|
-
Effect.tap((_) => Effect.logDebug('Using tsconfig', _)),
|
|
106
|
-
Effect.map((_) => readConfigFile(_, sys.readFile)),
|
|
107
|
-
Effect.map((_) => parseJsonConfigFileContent(_.config, sys, process.cwd())),
|
|
108
|
-
Effect.map((_) =>
|
|
109
|
-
createProgram({
|
|
110
|
-
options: _.options,
|
|
111
|
-
rootNames: _.fileNames,
|
|
112
|
-
configFileParsingDiagnostics: _.errors,
|
|
113
|
-
}),
|
|
114
|
-
),
|
|
115
|
-
);
|
|
116
|
-
|
|
117
|
-
// FIXME :: hacky
|
|
118
|
-
const transformPath = (path: string) =>
|
|
119
|
-
process.env.DEV ? path : path.replace('../', './').replace('.ts', '.cjs');
|
|
120
|
-
|
|
121
|
-
const force = Options.boolean('force').pipe(
|
|
122
|
-
Options.withAlias('f'),
|
|
123
|
-
Options.withDefault(false),
|
|
124
|
-
Options.withDescription('Skip git status check, use with caution'),
|
|
125
|
-
);
|
|
126
|
-
|
|
127
|
-
/**
|
|
128
|
-
* TODO: Instead of default values these should be detected automatically from the TS program
|
|
129
|
-
*/
|
|
130
|
-
const trpcFile = Options.text('trpcFile').pipe(
|
|
131
|
-
Options.withAlias('f'),
|
|
132
|
-
Options.withDefault('~/trpc'),
|
|
133
|
-
Options.withDescription('Path to the trpc import file'),
|
|
134
|
-
);
|
|
135
|
-
|
|
136
|
-
const trpcImportName = Options.text('trpcImportName').pipe(
|
|
137
|
-
Options.withAlias('i'),
|
|
138
|
-
Options.withDefault('trpc'),
|
|
139
|
-
Options.withDescription('Name of the trpc import'),
|
|
140
|
-
);
|
|
141
|
-
|
|
142
|
-
const skipTanstackQuery = Options.boolean('skipTanstackQuery').pipe(
|
|
143
|
-
Options.withAlias('q'),
|
|
144
|
-
Options.withDefault(false),
|
|
145
|
-
Options.withDescription('Skip installing @trpc/tanstack-react-query package'),
|
|
146
|
-
);
|
|
147
|
-
|
|
148
|
-
const verbose = Options.boolean('verbose').pipe(
|
|
149
|
-
Options.withAlias('v'),
|
|
150
|
-
Options.withDefault(false),
|
|
151
|
-
Options.withDescription('Enable verbose logging'),
|
|
152
|
-
);
|
|
153
|
-
|
|
154
|
-
const rootComamnd = CLICommand.make(
|
|
155
|
-
'upgrade',
|
|
156
|
-
{
|
|
157
|
-
force,
|
|
158
|
-
trpcFile,
|
|
159
|
-
trpcImportName,
|
|
160
|
-
skipTanstackQuery,
|
|
161
|
-
verbose,
|
|
162
|
-
},
|
|
163
|
-
(args) =>
|
|
164
|
-
Effect.gen(function* () {
|
|
165
|
-
if (args.verbose) {
|
|
166
|
-
yield* Effect.log('Running upgrade with args:', args);
|
|
167
|
-
}
|
|
168
|
-
if (!args.force) {
|
|
169
|
-
yield* assertCleanGitTree;
|
|
170
|
-
}
|
|
171
|
-
const transforms = yield* pipe(
|
|
172
|
-
Prompt.multiSelect({
|
|
173
|
-
message: 'Select transforms to run',
|
|
174
|
-
choices: [
|
|
175
|
-
{
|
|
176
|
-
title: 'Migrate Hooks to xxxOptions API',
|
|
177
|
-
value: require.resolve(
|
|
178
|
-
transformPath('../transforms/hooksToOptions.ts'),
|
|
179
|
-
),
|
|
180
|
-
},
|
|
181
|
-
{
|
|
182
|
-
title: 'Migrate context provider setup',
|
|
183
|
-
value: require.resolve(
|
|
184
|
-
transformPath('../transforms/provider.ts'),
|
|
185
|
-
),
|
|
186
|
-
},
|
|
187
|
-
],
|
|
188
|
-
}),
|
|
189
|
-
Effect.flatMap((selected) => {
|
|
190
|
-
if (selected.length === 0) {
|
|
191
|
-
return Effect.fail(
|
|
192
|
-
new Error('Please select at least one transform to run'),
|
|
193
|
-
);
|
|
194
|
-
}
|
|
195
|
-
return Effect.succeed(selected);
|
|
196
|
-
}),
|
|
197
|
-
Effect.map(
|
|
198
|
-
// Make sure provider transform runs first if it's selected
|
|
199
|
-
Array.sortWith((a) => !a.includes('provider.ts'), Order.boolean),
|
|
200
|
-
),
|
|
201
|
-
);
|
|
202
|
-
|
|
203
|
-
const program = yield* TSProgram;
|
|
204
|
-
const sourceFiles = program.getSourceFiles();
|
|
205
|
-
|
|
206
|
-
const commitedFiles = yield* filterIgnored(sourceFiles);
|
|
207
|
-
yield* Effect.forEach(transforms, (transform) => {
|
|
208
|
-
return pipe(
|
|
209
|
-
Effect.log('Running transform', transform),
|
|
210
|
-
Effect.flatMap(() =>
|
|
211
|
-
Effect.tryPromise(async () =>
|
|
212
|
-
import('jscodeshift/src/Runner.js').then(({ run }) =>
|
|
213
|
-
run(transform, commitedFiles, args),
|
|
214
|
-
),
|
|
215
|
-
),
|
|
216
|
-
),
|
|
217
|
-
Effect.map((_) => Effect.log('Transform result', _)),
|
|
218
|
-
);
|
|
219
|
-
});
|
|
220
|
-
|
|
221
|
-
if (!args.skipTanstackQuery) {
|
|
222
|
-
yield* Effect.log('Installing @trpc/tanstack-react-query');
|
|
223
|
-
yield* installPackage('@trpc/tanstack-react-query');
|
|
224
|
-
|
|
225
|
-
yield* Effect.log('Uninstalling @trpc/react-query');
|
|
226
|
-
yield* uninstallPackage('@trpc/react-query');
|
|
227
|
-
}
|
|
228
|
-
}).pipe(
|
|
229
|
-
Logger.withMinimumLogLevel(args.verbose ? LogLevel.Debug : LogLevel.Info),
|
|
230
|
-
),
|
|
231
|
-
);
|
|
232
|
-
|
|
233
|
-
const cli = CLICommand.run(rootComamnd, {
|
|
234
|
-
name: 'tRPC Upgrade CLI',
|
|
235
|
-
version: `v${version}`,
|
|
236
|
-
});
|
|
237
|
-
|
|
238
|
-
cli(process.argv).pipe(Effect.provide(NodeContext.layer), NodeRuntime.runMain);
|