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