@trpc/upgrade 0.0.0-alpha.0 → 0.0.0-alpha.2
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/cli.cjs +45 -41
- package/dist/transforms/hooksToOptions.cjs +37 -36
- package/dist/transforms/provider.cjs +5 -23
- package/package.json +1 -2
- package/src/bin/cli.ts +83 -61
- package/src/transforms/hooksToOptions.ts +37 -36
- package/src/transforms/provider.ts +4 -32
package/dist/cli.cjs
CHANGED
|
@@ -4,73 +4,77 @@ var cli$1 = require('@effect/cli');
|
|
|
4
4
|
var platform = require('@effect/platform');
|
|
5
5
|
var platformNode = require('@effect/platform-node');
|
|
6
6
|
var effect = require('effect');
|
|
7
|
-
var ignore = require('ignore');
|
|
8
7
|
var typescript = require('typescript');
|
|
9
8
|
|
|
10
9
|
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
11
10
|
|
|
12
11
|
var path__default = /*#__PURE__*/_interopDefault(path);
|
|
13
|
-
var ignore__default = /*#__PURE__*/_interopDefault(ignore);
|
|
14
12
|
|
|
15
|
-
var version = "0.0.0-alpha.
|
|
13
|
+
var version = "0.0.0-alpha.2";
|
|
16
14
|
|
|
17
|
-
platform.Command.string(platform.Command.make('git', 'status')).pipe(effect.Effect.filterOrFail(
|
|
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`'));
|
|
18
16
|
const installPackage = (packageName)=>{
|
|
19
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'));
|
|
20
18
|
return platform.Command.streamLines(platform.Command.make(packageManager, 'install', packageName)).pipe(effect.Stream.mapEffect(effect.Console.log), effect.Stream.runDrain);
|
|
21
19
|
};
|
|
22
20
|
const filterIgnored = (files)=>effect.Effect.gen(function*() {
|
|
23
|
-
const
|
|
24
|
-
|
|
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);
|
|
25
24
|
// Ignore "common files"
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
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;
|
|
29
30
|
});
|
|
30
|
-
const
|
|
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({
|
|
31
32
|
options: _.options,
|
|
32
33
|
rootNames: _.fileNames,
|
|
33
34
|
configFileParsingDiagnostics: _.errors
|
|
34
35
|
})));
|
|
36
|
+
// FIXME :: hacky
|
|
35
37
|
const transformPath = (path)=>process.env.DEV ? path : path.replace('../', './').replace('.ts', '.cjs');
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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;
|
|
46
50
|
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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;
|
|
51
66
|
const sourceFiles = program.getSourceFiles();
|
|
52
|
-
// Make sure provider transform runs first if it's selected
|
|
53
|
-
_.sort((a, b)=>a.includes('provider.ts') ? -1 : b.includes('provider.ts') ? 1 : 0);
|
|
54
|
-
/**
|
|
55
|
-
* TODO: Detect these automatically
|
|
56
|
-
*/ const appRouterImportFile = '~/server/routers/_app';
|
|
57
|
-
const appRouterImportName = 'AppRouter';
|
|
58
|
-
const trpcFile = '~/lib/trpc';
|
|
59
|
-
const trpcImportName = 'trpc';
|
|
60
67
|
const commitedFiles = yield* filterIgnored(sourceFiles);
|
|
61
|
-
yield* effect.Effect.forEach(
|
|
62
|
-
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
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
trpcImportName
|
|
67
|
-
})))), effect.Effect.map((res)=>effect.Effect.log('Transform result', res)));
|
|
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, {
|
|
70
|
+
...args,
|
|
71
|
+
verbose: true
|
|
72
|
+
})))), effect.Effect.map((_)=>effect.Effect.log('Transform result', _)));
|
|
68
73
|
});
|
|
69
74
|
yield* effect.Effect.log('Installing @trpc/tanstack-react-query');
|
|
70
75
|
yield* installPackage('@trpc/tanstack-react-query');
|
|
71
|
-
// TODO: Format project
|
|
72
76
|
}));
|
|
73
|
-
const cli = cli$1.Command.run(
|
|
77
|
+
const cli = cli$1.Command.run(rootComamnd, {
|
|
74
78
|
name: 'tRPC Upgrade CLI',
|
|
75
79
|
version: `v${version}`
|
|
76
80
|
});
|
|
@@ -43,59 +43,50 @@ const utilMap = {
|
|
|
43
43
|
getInfiniteData: 'getInfiniteQueryData'
|
|
44
44
|
};
|
|
45
45
|
function transform(file, api, options) {
|
|
46
|
-
const {
|
|
47
|
-
if (!
|
|
48
|
-
throw new Error('
|
|
46
|
+
const { trpcImportName } = options;
|
|
47
|
+
if (!trpcImportName) {
|
|
48
|
+
throw new Error('trpcImportName is required');
|
|
49
49
|
}
|
|
50
50
|
const j = api.jscodeshift;
|
|
51
51
|
const root = j(file.source);
|
|
52
52
|
let dirtyFlag = false;
|
|
53
53
|
// Traverse all functions, and _do stuff_
|
|
54
54
|
root.find(j.FunctionDeclaration).forEach((path)=>{
|
|
55
|
-
if (j(path).find(j.Identifier, {
|
|
56
|
-
name: trpcImportName
|
|
57
|
-
}).size() > 0) {
|
|
58
|
-
updateTRPCImport(path);
|
|
59
|
-
}
|
|
60
55
|
replaceHooksWithOptions(path);
|
|
61
56
|
removeSuspenseDestructuring(path);
|
|
62
57
|
migrateUseUtils(path);
|
|
63
58
|
});
|
|
64
59
|
root.find(j.ArrowFunctionExpression).forEach((path)=>{
|
|
65
|
-
if (j(path).find(j.Identifier, {
|
|
66
|
-
name: trpcImportName
|
|
67
|
-
}).size() > 0) {
|
|
68
|
-
updateTRPCImport(path);
|
|
69
|
-
}
|
|
70
60
|
replaceHooksWithOptions(path);
|
|
71
61
|
removeSuspenseDestructuring(path);
|
|
72
62
|
migrateUseUtils(path);
|
|
73
63
|
});
|
|
64
|
+
if (dirtyFlag) {
|
|
65
|
+
updateTRPCImport();
|
|
66
|
+
}
|
|
74
67
|
/**
|
|
75
68
|
* === HELPER FUNCTIONS BELOW ===
|
|
76
|
-
*/ function
|
|
77
|
-
const specifier = root.find(j.ImportDeclaration, {
|
|
78
|
-
source: {
|
|
79
|
-
value: trpcFile
|
|
80
|
-
}
|
|
81
|
-
}).find(j.ImportSpecifier, {
|
|
82
|
-
imported: {
|
|
83
|
-
name: trpcImportName
|
|
84
|
-
}
|
|
85
|
-
});
|
|
86
|
-
if (specifier.size() === 0) {
|
|
87
|
-
return;
|
|
88
|
-
}
|
|
89
|
-
specifier.replaceWith(j.importSpecifier(j.identifier('useTRPC')));
|
|
90
|
-
dirtyFlag = true;
|
|
69
|
+
*/ function ensureUseTRPCCall(path) {
|
|
91
70
|
const variableDeclaration = j.variableDeclaration('const', [
|
|
92
71
|
j.variableDeclarator(j.identifier(trpcImportName), j.callExpression(j.identifier('useTRPC'), []))
|
|
93
72
|
]);
|
|
94
73
|
if (j.FunctionDeclaration.check(path.node)) {
|
|
95
|
-
|
|
96
|
-
|
|
74
|
+
path.node.body.body.unshift(variableDeclaration);
|
|
75
|
+
dirtyFlag = true;
|
|
97
76
|
} else if (j.BlockStatement.check(path.node.body)) {
|
|
98
77
|
path.node.body.body.unshift(variableDeclaration);
|
|
78
|
+
dirtyFlag = true;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
function updateTRPCImport() {
|
|
82
|
+
const specifier = root.find(j.ImportSpecifier, {
|
|
83
|
+
imported: {
|
|
84
|
+
name: trpcImportName
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
if (specifier.size() > 0) {
|
|
88
|
+
specifier.replaceWith(j.importSpecifier(j.identifier('useTRPC')));
|
|
89
|
+
dirtyFlag = true;
|
|
99
90
|
}
|
|
100
91
|
}
|
|
101
92
|
function ensureImported(lib, specifier) {
|
|
@@ -114,10 +105,11 @@ function transform(file, api, options) {
|
|
|
114
105
|
dirtyFlag = true;
|
|
115
106
|
}
|
|
116
107
|
}
|
|
117
|
-
function replaceHooksWithOptions(
|
|
108
|
+
function replaceHooksWithOptions(fnPath) {
|
|
118
109
|
// REplace proxy-hooks with useX(options())
|
|
110
|
+
let hasInserted = false;
|
|
119
111
|
for (const [hook, { fn, lib }] of Object.entries(hookToOptions)){
|
|
120
|
-
j(
|
|
112
|
+
j(fnPath).find(j.CallExpression, {
|
|
121
113
|
callee: {
|
|
122
114
|
property: {
|
|
123
115
|
name: hook
|
|
@@ -131,6 +123,10 @@ function transform(file, api, options) {
|
|
|
131
123
|
}
|
|
132
124
|
// Rename the hook to the options function
|
|
133
125
|
memberExpr.property.name = fn;
|
|
126
|
+
if (!hasInserted) {
|
|
127
|
+
ensureUseTRPCCall(fnPath);
|
|
128
|
+
hasInserted = true;
|
|
129
|
+
}
|
|
134
130
|
// Wrap it in the hook call
|
|
135
131
|
j(path).replaceWith(j.callExpression(j.identifier(hook), [
|
|
136
132
|
path.node
|
|
@@ -172,7 +168,7 @@ function transform(file, api, options) {
|
|
|
172
168
|
}
|
|
173
169
|
// Replace util.PATH.proxyMethod() with trpc.PATH.queryFilter()
|
|
174
170
|
const proxyMethod = memberExpr.property.name;
|
|
175
|
-
memberExpr.object.object = j.identifier(
|
|
171
|
+
memberExpr.object.object = j.identifier(trpcImportName);
|
|
176
172
|
memberExpr.property = j.identifier('queryFilter');
|
|
177
173
|
// Wrap it in queryClient.utilMethod()
|
|
178
174
|
j(callExprPath).replaceWith(j.memberExpression(j.identifier('queryClient'), j.callExpression(j.identifier(utilMap[proxyMethod]), [
|
|
@@ -198,11 +194,10 @@ function transform(file, api, options) {
|
|
|
198
194
|
].includes(declarator.init.callee.name)) {
|
|
199
195
|
return;
|
|
200
196
|
}
|
|
201
|
-
console.log(declarator.init.callee.name);
|
|
202
197
|
const tuple = j.ArrayPattern.check(declarator?.id) ? declarator.id : null;
|
|
203
198
|
const dataName = j.Identifier.check(tuple?.elements?.[0]) ? tuple.elements[0].name : null;
|
|
204
199
|
const queryName = j.Identifier.check(tuple?.elements?.[1]) ? tuple.elements[1].name : null;
|
|
205
|
-
if (
|
|
200
|
+
if (queryName) {
|
|
206
201
|
declarator.id = j.identifier(queryName);
|
|
207
202
|
dirtyFlag = true;
|
|
208
203
|
if (dataName) {
|
|
@@ -210,12 +205,18 @@ function transform(file, api, options) {
|
|
|
210
205
|
j.variableDeclarator(j.identifier(dataName), j.memberExpression(declarator.id, j.identifier('data')))
|
|
211
206
|
]));
|
|
212
207
|
}
|
|
208
|
+
} else if (dataName) {
|
|
209
|
+
// const [dataName] = ... => const { data: dataName } = ...
|
|
210
|
+
declarator.id = j.objectPattern([
|
|
211
|
+
j.property('init', j.identifier('data'), j.identifier(dataName))
|
|
212
|
+
]);
|
|
213
|
+
dirtyFlag = true;
|
|
213
214
|
}
|
|
214
215
|
});
|
|
215
216
|
}
|
|
216
217
|
return dirtyFlag ? root.toSource() : undefined;
|
|
217
218
|
}
|
|
218
|
-
const parser = 'tsx';
|
|
219
|
+
const parser = 'tsx';
|
|
219
220
|
|
|
220
221
|
exports.default = transform;
|
|
221
222
|
exports.parser = parser;
|
|
@@ -1,21 +1,10 @@
|
|
|
1
1
|
Object.defineProperty(exports, '__esModule', { value: true });
|
|
2
2
|
|
|
3
3
|
function transform(file, api, options) {
|
|
4
|
-
const {
|
|
4
|
+
const { trpcImportName } = options;
|
|
5
5
|
const j = api.jscodeshift;
|
|
6
6
|
const root = j(file.source);
|
|
7
7
|
let dirtyFlag = false;
|
|
8
|
-
function upsertAppRouterImport() {
|
|
9
|
-
if (root.find(j.ImportDeclaration, {
|
|
10
|
-
source: {
|
|
11
|
-
value: appRouterImportFile
|
|
12
|
-
}
|
|
13
|
-
}).size() === 0) {
|
|
14
|
-
root.find(j.ImportDeclaration).at(-1).insertAfter(j.importDeclaration([
|
|
15
|
-
j.importSpecifier(j.identifier(appRouterImportName))
|
|
16
|
-
], j.literal(appRouterImportFile), 'type'));
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
8
|
// Find the variable declaration for `trpc`
|
|
20
9
|
root.find(j.VariableDeclaration).forEach((path)=>{
|
|
21
10
|
const declaration = path.node.declarations[0];
|
|
@@ -69,11 +58,6 @@ function transform(file, api, options) {
|
|
|
69
58
|
}
|
|
70
59
|
}).forEach((path)=>{
|
|
71
60
|
path.node.callee = j.identifier('createTRPCClient');
|
|
72
|
-
// Add the type parameter `<AppRouter>`
|
|
73
|
-
path.node.typeParameters = j.tsTypeParameterInstantiation([
|
|
74
|
-
j.tsTypeReference(j.identifier(appRouterImportName))
|
|
75
|
-
]);
|
|
76
|
-
upsertAppRouterImport();
|
|
77
61
|
dirtyFlag = true;
|
|
78
62
|
});
|
|
79
63
|
// Replace <trpc.Provider client={...} with <TRPCProvider trpcClient={...}
|
|
@@ -112,14 +96,12 @@ function transform(file, api, options) {
|
|
|
112
96
|
path.node.specifiers?.push(createTRPCClientImport);
|
|
113
97
|
});
|
|
114
98
|
// Replace trpc import with TRPCProvider
|
|
115
|
-
root.find(j.
|
|
116
|
-
|
|
117
|
-
|
|
99
|
+
root.find(j.ImportSpecifier, {
|
|
100
|
+
imported: {
|
|
101
|
+
name: trpcImportName
|
|
118
102
|
}
|
|
119
103
|
}).forEach((path)=>{
|
|
120
|
-
path.node.
|
|
121
|
-
j.importSpecifier(j.identifier('TRPCProvider'))
|
|
122
|
-
];
|
|
104
|
+
path.node.name = j.identifier('TRPCProvider');
|
|
123
105
|
});
|
|
124
106
|
}
|
|
125
107
|
return dirtyFlag ? root.toSource() : undefined;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@trpc/upgrade",
|
|
3
|
-
"version": "0.0.0-alpha.
|
|
3
|
+
"version": "0.0.0-alpha.2",
|
|
4
4
|
"description": "Upgrade scripts for tRPC",
|
|
5
5
|
"author": "juliusmarminge",
|
|
6
6
|
"license": "MIT",
|
|
@@ -32,7 +32,6 @@
|
|
|
32
32
|
"@effect/platform": "0.69.25",
|
|
33
33
|
"@effect/platform-node": "0.64.27",
|
|
34
34
|
"effect": "3.10.16",
|
|
35
|
-
"ignore": "^6.0.2",
|
|
36
35
|
"jscodeshift": "17.1.1",
|
|
37
36
|
"typescript": "^5.6.2"
|
|
38
37
|
},
|
package/src/bin/cli.ts
CHANGED
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
/* eslint-disable @typescript-eslint/unbound-method */
|
|
2
2
|
import path from 'path';
|
|
3
|
-
import { Command as CLICommand, Prompt } from '@effect/cli';
|
|
4
|
-
import { Command
|
|
3
|
+
import { Command as CLICommand, Options, Prompt } from '@effect/cli';
|
|
4
|
+
import { Command } from '@effect/platform';
|
|
5
5
|
import { NodeContext, NodeRuntime } from '@effect/platform-node';
|
|
6
6
|
import {
|
|
7
|
+
Array,
|
|
7
8
|
Console,
|
|
8
9
|
Effect,
|
|
9
10
|
Match,
|
|
11
|
+
Order,
|
|
10
12
|
pipe,
|
|
11
13
|
Predicate,
|
|
12
14
|
Stream,
|
|
13
15
|
String,
|
|
14
16
|
} from 'effect';
|
|
15
|
-
import ignore from 'ignore';
|
|
16
17
|
import type { SourceFile } from 'typescript';
|
|
17
18
|
import {
|
|
18
19
|
createProgram,
|
|
@@ -25,9 +26,9 @@ import { version } from '../../package.json';
|
|
|
25
26
|
|
|
26
27
|
const assertCleanGitTree = Command.string(Command.make('git', 'status')).pipe(
|
|
27
28
|
Effect.filterOrFail(
|
|
28
|
-
|
|
29
|
+
String.includes('nothing to commit'),
|
|
29
30
|
() =>
|
|
30
|
-
'Git tree is not clean, please commit your changes
|
|
31
|
+
'Git tree is not clean, please commit your changes and try again, or run with `--force`',
|
|
31
32
|
),
|
|
32
33
|
);
|
|
33
34
|
|
|
@@ -47,28 +48,32 @@ const installPackage = (packageName: string) => {
|
|
|
47
48
|
|
|
48
49
|
const filterIgnored = (files: readonly SourceFile[]) =>
|
|
49
50
|
Effect.gen(function* () {
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
)
|
|
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);
|
|
57
60
|
|
|
58
61
|
// Ignore "common files"
|
|
59
|
-
const
|
|
62
|
+
const filteredSourcePaths = files
|
|
60
63
|
.filter(
|
|
61
64
|
(source) =>
|
|
62
|
-
|
|
63
|
-
!source.fileName.includes('packages/')
|
|
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
|
|
64
68
|
)
|
|
65
|
-
.map((
|
|
69
|
+
.map((source) => source.fileName);
|
|
66
70
|
|
|
67
|
-
|
|
68
|
-
|
|
71
|
+
yield* Effect.log('Filtered files:', filteredSourcePaths);
|
|
72
|
+
|
|
73
|
+
return filteredSourcePaths;
|
|
69
74
|
});
|
|
70
75
|
|
|
71
|
-
const
|
|
76
|
+
const TSProgram = Effect.succeed(
|
|
72
77
|
findConfigFile(process.cwd(), sys.fileExists),
|
|
73
78
|
).pipe(
|
|
74
79
|
Effect.filterOrFail(Predicate.isNotNullable, () => 'No tsconfig found'),
|
|
@@ -84,73 +89,90 @@ const Program = Effect.succeed(
|
|
|
84
89
|
),
|
|
85
90
|
);
|
|
86
91
|
|
|
92
|
+
// FIXME :: hacky
|
|
87
93
|
const transformPath = (path: string) =>
|
|
88
94
|
process.env.DEV ? path : path.replace('../', './').replace('.ts', '.cjs');
|
|
89
95
|
|
|
90
|
-
const
|
|
91
|
-
'
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
(
|
|
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) =>
|
|
108
125
|
Effect.gen(function* () {
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
126
|
+
if (!args.force) {
|
|
127
|
+
yield* assertCleanGitTree;
|
|
128
|
+
}
|
|
112
129
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
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),
|
|
116
150
|
);
|
|
117
151
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
*/
|
|
121
|
-
const appRouterImportFile = '~/server/routers/_app';
|
|
122
|
-
const appRouterImportName = 'AppRouter';
|
|
123
|
-
const trpcFile = '~/lib/trpc';
|
|
124
|
-
const trpcImportName = 'trpc';
|
|
152
|
+
const program = yield* TSProgram;
|
|
153
|
+
const sourceFiles = program.getSourceFiles();
|
|
125
154
|
|
|
126
155
|
const commitedFiles = yield* filterIgnored(sourceFiles);
|
|
127
|
-
yield* Effect.forEach(
|
|
156
|
+
yield* Effect.forEach(transforms, (transform) => {
|
|
128
157
|
return pipe(
|
|
129
158
|
Effect.log('Running transform', transform),
|
|
130
159
|
Effect.flatMap(() =>
|
|
131
160
|
Effect.tryPromise(async () =>
|
|
132
161
|
import('jscodeshift/src/Runner.js').then(({ run }) =>
|
|
133
|
-
run(transform
|
|
134
|
-
appRouterImportFile,
|
|
135
|
-
appRouterImportName,
|
|
136
|
-
trpcFile,
|
|
137
|
-
trpcImportName,
|
|
138
|
-
}),
|
|
162
|
+
run(transform, commitedFiles, { ...args, verbose: true }),
|
|
139
163
|
),
|
|
140
164
|
),
|
|
141
165
|
),
|
|
142
|
-
Effect.map((
|
|
166
|
+
Effect.map((_) => Effect.log('Transform result', _)),
|
|
143
167
|
);
|
|
144
168
|
});
|
|
145
169
|
|
|
146
170
|
yield* Effect.log('Installing @trpc/tanstack-react-query');
|
|
147
171
|
yield* installPackage('@trpc/tanstack-react-query');
|
|
148
|
-
|
|
149
|
-
// TODO: Format project
|
|
150
172
|
}),
|
|
151
173
|
);
|
|
152
174
|
|
|
153
|
-
const cli = CLICommand.run(
|
|
175
|
+
const cli = CLICommand.run(rootComamnd, {
|
|
154
176
|
name: 'tRPC Upgrade CLI',
|
|
155
177
|
version: `v${version}`,
|
|
156
178
|
});
|
|
@@ -13,7 +13,6 @@ import type {
|
|
|
13
13
|
} from 'jscodeshift';
|
|
14
14
|
|
|
15
15
|
interface TransformOptions extends Options {
|
|
16
|
-
trpcFile?: string;
|
|
17
16
|
trpcImportName?: string;
|
|
18
17
|
}
|
|
19
18
|
|
|
@@ -61,9 +60,9 @@ export default function transform(
|
|
|
61
60
|
api: API,
|
|
62
61
|
options: TransformOptions,
|
|
63
62
|
) {
|
|
64
|
-
const {
|
|
65
|
-
if (!
|
|
66
|
-
throw new Error('
|
|
63
|
+
const { trpcImportName } = options;
|
|
64
|
+
if (!trpcImportName) {
|
|
65
|
+
throw new Error('trpcImportName is required');
|
|
67
66
|
}
|
|
68
67
|
|
|
69
68
|
const j = api.jscodeshift;
|
|
@@ -72,44 +71,27 @@ export default function transform(
|
|
|
72
71
|
|
|
73
72
|
// Traverse all functions, and _do stuff_
|
|
74
73
|
root.find(j.FunctionDeclaration).forEach((path) => {
|
|
75
|
-
if (j(path).find(j.Identifier, { name: trpcImportName }).size() > 0) {
|
|
76
|
-
updateTRPCImport(path);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
74
|
replaceHooksWithOptions(path);
|
|
80
75
|
removeSuspenseDestructuring(path);
|
|
81
76
|
migrateUseUtils(path);
|
|
82
77
|
});
|
|
83
78
|
root.find(j.ArrowFunctionExpression).forEach((path) => {
|
|
84
|
-
if (j(path).find(j.Identifier, { name: trpcImportName }).size() > 0) {
|
|
85
|
-
updateTRPCImport(path);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
79
|
replaceHooksWithOptions(path);
|
|
89
80
|
removeSuspenseDestructuring(path);
|
|
90
81
|
migrateUseUtils(path);
|
|
91
82
|
});
|
|
92
83
|
|
|
84
|
+
if (dirtyFlag) {
|
|
85
|
+
updateTRPCImport();
|
|
86
|
+
}
|
|
87
|
+
|
|
93
88
|
/**
|
|
94
89
|
* === HELPER FUNCTIONS BELOW ===
|
|
95
90
|
*/
|
|
96
91
|
|
|
97
|
-
function
|
|
92
|
+
function ensureUseTRPCCall(
|
|
98
93
|
path: ASTPath<FunctionDeclaration | ArrowFunctionExpression>,
|
|
99
94
|
) {
|
|
100
|
-
const specifier = root
|
|
101
|
-
.find(j.ImportDeclaration, {
|
|
102
|
-
source: { value: trpcFile },
|
|
103
|
-
})
|
|
104
|
-
.find(j.ImportSpecifier, { imported: { name: trpcImportName } });
|
|
105
|
-
|
|
106
|
-
if (specifier.size() === 0) {
|
|
107
|
-
return;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
specifier.replaceWith(j.importSpecifier(j.identifier('useTRPC')));
|
|
111
|
-
dirtyFlag = true;
|
|
112
|
-
|
|
113
95
|
const variableDeclaration = j.variableDeclaration('const', [
|
|
114
96
|
j.variableDeclarator(
|
|
115
97
|
j.identifier(trpcImportName!),
|
|
@@ -118,10 +100,21 @@ export default function transform(
|
|
|
118
100
|
]);
|
|
119
101
|
|
|
120
102
|
if (j.FunctionDeclaration.check(path.node)) {
|
|
121
|
-
|
|
122
|
-
|
|
103
|
+
path.node.body.body.unshift(variableDeclaration);
|
|
104
|
+
dirtyFlag = true;
|
|
123
105
|
} else if (j.BlockStatement.check(path.node.body)) {
|
|
124
106
|
path.node.body.body.unshift(variableDeclaration);
|
|
107
|
+
dirtyFlag = true;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function updateTRPCImport() {
|
|
112
|
+
const specifier = root.find(j.ImportSpecifier, {
|
|
113
|
+
imported: { name: trpcImportName },
|
|
114
|
+
});
|
|
115
|
+
if (specifier.size() > 0) {
|
|
116
|
+
specifier.replaceWith(j.importSpecifier(j.identifier('useTRPC')));
|
|
117
|
+
dirtyFlag = true;
|
|
125
118
|
}
|
|
126
119
|
}
|
|
127
120
|
|
|
@@ -148,11 +141,12 @@ export default function transform(
|
|
|
148
141
|
}
|
|
149
142
|
|
|
150
143
|
function replaceHooksWithOptions(
|
|
151
|
-
|
|
144
|
+
fnPath: ASTPath<FunctionDeclaration | ArrowFunctionExpression>,
|
|
152
145
|
) {
|
|
153
146
|
// REplace proxy-hooks with useX(options())
|
|
147
|
+
let hasInserted = false;
|
|
154
148
|
for (const [hook, { fn, lib }] of Object.entries(hookToOptions)) {
|
|
155
|
-
j(
|
|
149
|
+
j(fnPath)
|
|
156
150
|
.find(j.CallExpression, {
|
|
157
151
|
callee: {
|
|
158
152
|
property: { name: hook },
|
|
@@ -171,6 +165,11 @@ export default function transform(
|
|
|
171
165
|
// Rename the hook to the options function
|
|
172
166
|
memberExpr.property.name = fn;
|
|
173
167
|
|
|
168
|
+
if (!hasInserted) {
|
|
169
|
+
ensureUseTRPCCall(fnPath);
|
|
170
|
+
hasInserted = true;
|
|
171
|
+
}
|
|
172
|
+
|
|
174
173
|
// Wrap it in the hook call
|
|
175
174
|
j(path).replaceWith(
|
|
176
175
|
j.callExpression(j.identifier(hook), [path.node]),
|
|
@@ -237,7 +236,7 @@ export default function transform(
|
|
|
237
236
|
|
|
238
237
|
// Replace util.PATH.proxyMethod() with trpc.PATH.queryFilter()
|
|
239
238
|
const proxyMethod = memberExpr.property.name as ProxyMethod;
|
|
240
|
-
memberExpr.object.object = j.identifier(
|
|
239
|
+
memberExpr.object.object = j.identifier(trpcImportName!);
|
|
241
240
|
memberExpr.property = j.identifier('queryFilter');
|
|
242
241
|
|
|
243
242
|
// Wrap it in queryClient.utilMethod()
|
|
@@ -285,8 +284,6 @@ export default function transform(
|
|
|
285
284
|
return;
|
|
286
285
|
}
|
|
287
286
|
|
|
288
|
-
console.log(declarator.init.callee.name);
|
|
289
|
-
|
|
290
287
|
const tuple = j.ArrayPattern.check(declarator?.id)
|
|
291
288
|
? declarator.id
|
|
292
289
|
: null;
|
|
@@ -297,7 +294,7 @@ export default function transform(
|
|
|
297
294
|
? tuple.elements[1].name
|
|
298
295
|
: null;
|
|
299
296
|
|
|
300
|
-
if (
|
|
297
|
+
if (queryName) {
|
|
301
298
|
declarator.id = j.identifier(queryName);
|
|
302
299
|
dirtyFlag = true;
|
|
303
300
|
|
|
@@ -311,6 +308,12 @@ export default function transform(
|
|
|
311
308
|
]),
|
|
312
309
|
);
|
|
313
310
|
}
|
|
311
|
+
} else if (dataName) {
|
|
312
|
+
// const [dataName] = ... => const { data: dataName } = ...
|
|
313
|
+
declarator.id = j.objectPattern([
|
|
314
|
+
j.property('init', j.identifier('data'), j.identifier(dataName)),
|
|
315
|
+
]);
|
|
316
|
+
dirtyFlag = true;
|
|
314
317
|
}
|
|
315
318
|
});
|
|
316
319
|
}
|
|
@@ -319,5 +322,3 @@ export default function transform(
|
|
|
319
322
|
}
|
|
320
323
|
|
|
321
324
|
export const parser = 'tsx';
|
|
322
|
-
|
|
323
|
-
// https://go.codemod.com/ddX54TM
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import type { API, FileInfo, Options } from 'jscodeshift';
|
|
2
2
|
|
|
3
3
|
interface TransformOptions extends Options {
|
|
4
|
-
trpcFile?: string;
|
|
5
4
|
trpcImportName?: string;
|
|
6
5
|
}
|
|
7
6
|
|
|
@@ -10,32 +9,12 @@ export default function transform(
|
|
|
10
9
|
api: API,
|
|
11
10
|
options: TransformOptions,
|
|
12
11
|
) {
|
|
13
|
-
const {
|
|
14
|
-
options;
|
|
12
|
+
const { trpcImportName } = options;
|
|
15
13
|
|
|
16
14
|
const j = api.jscodeshift;
|
|
17
15
|
const root = j(file.source);
|
|
18
16
|
let dirtyFlag = false;
|
|
19
17
|
|
|
20
|
-
function upsertAppRouterImport() {
|
|
21
|
-
if (
|
|
22
|
-
root
|
|
23
|
-
.find(j.ImportDeclaration, { source: { value: appRouterImportFile } })
|
|
24
|
-
.size() === 0
|
|
25
|
-
) {
|
|
26
|
-
root
|
|
27
|
-
.find(j.ImportDeclaration)
|
|
28
|
-
.at(-1)
|
|
29
|
-
.insertAfter(
|
|
30
|
-
j.importDeclaration(
|
|
31
|
-
[j.importSpecifier(j.identifier(appRouterImportName))],
|
|
32
|
-
j.literal(appRouterImportFile),
|
|
33
|
-
'type',
|
|
34
|
-
),
|
|
35
|
-
);
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
18
|
// Find the variable declaration for `trpc`
|
|
40
19
|
root.find(j.VariableDeclaration).forEach((path) => {
|
|
41
20
|
const declaration = path.node.declarations[0];
|
|
@@ -99,11 +78,6 @@ export default function transform(
|
|
|
99
78
|
})
|
|
100
79
|
.forEach((path) => {
|
|
101
80
|
path.node.callee = j.identifier('createTRPCClient');
|
|
102
|
-
// Add the type parameter `<AppRouter>`
|
|
103
|
-
path.node.typeParameters = j.tsTypeParameterInstantiation([
|
|
104
|
-
j.tsTypeReference(j.identifier(appRouterImportName)),
|
|
105
|
-
]);
|
|
106
|
-
upsertAppRouterImport();
|
|
107
81
|
dirtyFlag = true;
|
|
108
82
|
});
|
|
109
83
|
|
|
@@ -146,13 +120,11 @@ export default function transform(
|
|
|
146
120
|
|
|
147
121
|
// Replace trpc import with TRPCProvider
|
|
148
122
|
root
|
|
149
|
-
.find(j.
|
|
150
|
-
|
|
123
|
+
.find(j.ImportSpecifier, {
|
|
124
|
+
imported: { name: trpcImportName },
|
|
151
125
|
})
|
|
152
126
|
.forEach((path) => {
|
|
153
|
-
path.node.
|
|
154
|
-
j.importSpecifier(j.identifier('TRPCProvider')),
|
|
155
|
-
];
|
|
127
|
+
path.node.name = j.identifier('TRPCProvider');
|
|
156
128
|
});
|
|
157
129
|
}
|
|
158
130
|
|