@trpc/upgrade 0.0.0-alpha.27 → 0.0.0-alpha.29
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 +137 -151
- package/package.json +1 -1
- package/src/bin/index.ts +80 -207
- package/src/lib/ast/scanners.ts +49 -26
- package/src/lib/execa.ts +4 -0
- package/src/lib/git.ts +44 -0
- package/src/lib/pkgmgr.ts +34 -0
package/dist/bin.js
CHANGED
|
@@ -1,23 +1,41 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import
|
|
3
|
-
import * as Path from 'node:path';
|
|
4
|
-
import * as Util from 'node:util';
|
|
5
|
-
import * as Args from '@bomb.sh/args';
|
|
2
|
+
import { parse } from '@bomb.sh/args';
|
|
6
3
|
import * as p from '@clack/prompts';
|
|
4
|
+
import { intro, log, multiselect, isCancel, outro } from '@clack/prompts';
|
|
7
5
|
import * as ts from 'typescript';
|
|
8
|
-
import
|
|
6
|
+
import * as Path from 'path';
|
|
7
|
+
import * as CP from 'node:child_process';
|
|
8
|
+
import * as Util from 'node:util';
|
|
9
9
|
import __node_cjsModule from 'node:module';
|
|
10
10
|
|
|
11
|
-
var version = "0.0.0-alpha.
|
|
11
|
+
var version = "0.0.0-alpha.28";
|
|
12
12
|
|
|
13
|
+
function getProgram(args) {
|
|
14
|
+
const configFile = ts.findConfigFile(process.cwd(), (filepath)=>ts.sys.fileExists(filepath));
|
|
15
|
+
if (!configFile) {
|
|
16
|
+
p.log.error('No tsconfig found');
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
if (args.verbose) {
|
|
20
|
+
p.log.info(`Using tsconfig: ${configFile}`);
|
|
21
|
+
}
|
|
22
|
+
const { config } = ts.readConfigFile(configFile, (filepath)=>ts.sys.readFile(filepath));
|
|
23
|
+
const parsedConfig = ts.parseJsonConfigFileContent(config, ts.sys, process.cwd());
|
|
24
|
+
const program = ts.createProgram({
|
|
25
|
+
options: parsedConfig.options,
|
|
26
|
+
rootNames: parsedConfig.fileNames,
|
|
27
|
+
configFileParsingDiagnostics: parsedConfig.errors
|
|
28
|
+
});
|
|
29
|
+
return program;
|
|
30
|
+
}
|
|
13
31
|
function findSourceAndImportName(program) {
|
|
14
32
|
const files = program.getSourceFiles().filter((sourceFile)=>{
|
|
15
33
|
if (sourceFile.isDeclarationFile) return false;
|
|
16
34
|
let found = false;
|
|
17
|
-
forEachChild(sourceFile, (node)=>{
|
|
18
|
-
if (!found && isImportDeclaration(node)) {
|
|
35
|
+
ts.forEachChild(sourceFile, (node)=>{
|
|
36
|
+
if (!found && ts.isImportDeclaration(node)) {
|
|
19
37
|
const { moduleSpecifier } = node;
|
|
20
|
-
if (isStringLiteral(moduleSpecifier) && moduleSpecifier.text.includes('@trpc/react-query')) {
|
|
38
|
+
if (ts.isStringLiteral(moduleSpecifier) && moduleSpecifier.text.includes('@trpc/react-query')) {
|
|
21
39
|
found = true;
|
|
22
40
|
}
|
|
23
41
|
}
|
|
@@ -26,10 +44,10 @@ function findSourceAndImportName(program) {
|
|
|
26
44
|
});
|
|
27
45
|
let importName = 'trpc';
|
|
28
46
|
files.forEach((sourceFile)=>{
|
|
29
|
-
forEachChild(sourceFile, (node)=>{
|
|
30
|
-
if (isVariableStatement(node) && node.modifiers?.some((mod)=>mod.getText(sourceFile) === 'export')) {
|
|
47
|
+
ts.forEachChild(sourceFile, (node)=>{
|
|
48
|
+
if (ts.isVariableStatement(node) && node.modifiers?.some((mod)=>mod.getText(sourceFile) === 'export')) {
|
|
31
49
|
node.declarationList.declarations.forEach((declaration)=>{
|
|
32
|
-
if (isVariableDeclaration(declaration) && declaration.initializer && isCallExpression(declaration.initializer) && isIdentifier(declaration.initializer.expression) && declaration.initializer.expression.getText(sourceFile) === 'createTRPCReact') {
|
|
50
|
+
if (ts.isVariableDeclaration(declaration) && declaration.initializer && ts.isCallExpression(declaration.initializer) && ts.isIdentifier(declaration.initializer.expression) && declaration.initializer.expression.getText(sourceFile) === 'createTRPCReact') {
|
|
33
51
|
importName = declaration.name.getText(sourceFile);
|
|
34
52
|
}
|
|
35
53
|
});
|
|
@@ -46,9 +64,9 @@ function findTRPCImportReferences(program) {
|
|
|
46
64
|
const trpcReferenceSpecifiers = new Map();
|
|
47
65
|
program.getSourceFiles().forEach((sourceFile)=>{
|
|
48
66
|
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);
|
|
67
|
+
ts.forEachChild(sourceFile, (node)=>{
|
|
68
|
+
if (ts.isImportDeclaration(node) && ts.isStringLiteral(node.moduleSpecifier)) {
|
|
69
|
+
const resolved = ts.resolveModuleName(node.moduleSpecifier.text, sourceFile.fileName, program.getCompilerOptions(), ts.sys);
|
|
52
70
|
if (resolved.resolvedModule && filesImportingTRPC.includes(resolved.resolvedModule.resolvedFileName)) {
|
|
53
71
|
trpcReferenceSpecifiers.set(resolved.resolvedModule.resolvedFileName, node.moduleSpecifier.text);
|
|
54
72
|
}
|
|
@@ -76,45 +94,22 @@ function findTRPCImportReferences(program) {
|
|
|
76
94
|
};
|
|
77
95
|
}
|
|
78
96
|
|
|
79
|
-
const require = __node_cjsModule.createRequire(import.meta.url);
|
|
80
|
-
|
|
81
97
|
const execa = Util.promisify(CP.exec);
|
|
82
|
-
|
|
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
|
-
}
|
|
98
|
+
|
|
94
99
|
async function assertCleanGitTree() {
|
|
95
100
|
const { stdout } = await execa('git status');
|
|
96
101
|
if (!stdout.includes('nothing to commit')) {
|
|
97
|
-
|
|
102
|
+
p.cancel('Git tree is not clean, please commit your changes and try again, or run with `--force`');
|
|
103
|
+
process.exit(1);
|
|
98
104
|
}
|
|
99
105
|
}
|
|
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
106
|
async function filterIgnored(files) {
|
|
112
107
|
const { stdout } = await execa('git check-ignore **/*');
|
|
113
108
|
const ignores = stdout.split('\n');
|
|
114
109
|
if (process.env.VERBOSE) {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
110
|
+
p.log.info(`cwd: ${process.cwd()}`);
|
|
111
|
+
p.log.info(`All files in program: ${files.map((file)=>file.fileName).join(', ')}`);
|
|
112
|
+
p.log.info(`Ignored files: ${ignores.join(', ')}`);
|
|
118
113
|
}
|
|
119
114
|
// Ignore "common files"
|
|
120
115
|
const filteredSourcePaths = files.filter((source)=>source.fileName.startsWith(Path.resolve()) && // only look ahead of current directory
|
|
@@ -122,119 +117,110 @@ async function filterIgnored(files) {
|
|
|
122
117
|
!source.fileName.includes('/node_modules/') && // always ignore node_modules
|
|
123
118
|
!ignores.includes(source.fileName)).map((source)=>source.fileName);
|
|
124
119
|
if (process.env.VERBOSE) {
|
|
125
|
-
|
|
120
|
+
p.log.info(`Filtered files: ${filteredSourcePaths.join(', ')}`);
|
|
126
121
|
}
|
|
127
122
|
return filteredSourcePaths;
|
|
128
123
|
}
|
|
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
124
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
125
|
+
function getPackageManager() {
|
|
126
|
+
const userAgent = process.env.npm_config_user_agent;
|
|
127
|
+
if (userAgent?.startsWith('pnpm')) return 'pnpm';
|
|
128
|
+
if (userAgent?.startsWith('yarn')) return 'yarn';
|
|
129
|
+
if (userAgent?.startsWith('bun')) return 'bun';
|
|
130
|
+
return 'npm';
|
|
131
|
+
}
|
|
132
|
+
async function installPackage(packageName) {
|
|
133
|
+
const packageManager = getPackageManager();
|
|
134
|
+
const installCmd = packageManager === 'yarn' ? 'add' : 'install';
|
|
135
|
+
const { stdout, stderr } = await execa(`${packageManager} ${installCmd} ${packageName}`);
|
|
136
|
+
if (stderr) {
|
|
137
|
+
p.log.error(stderr);
|
|
162
138
|
}
|
|
163
|
-
|
|
164
|
-
|
|
139
|
+
p.log.info(stdout);
|
|
140
|
+
}
|
|
141
|
+
async function uninstallPackage(packageName) {
|
|
142
|
+
const packageManager = getPackageManager();
|
|
143
|
+
const uninstallCmd = packageManager === 'yarn' ? 'remove' : 'uninstall';
|
|
144
|
+
const { stdout, stderr } = await execa(`${packageManager} ${uninstallCmd} ${packageName}`);
|
|
145
|
+
if (stderr) {
|
|
146
|
+
p.log.error(stderr);
|
|
165
147
|
}
|
|
166
|
-
p.
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
148
|
+
p.log.info(stdout);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const require = __node_cjsModule.createRequire(import.meta.url);
|
|
152
|
+
|
|
153
|
+
const args = parse(process.argv.slice(2), {
|
|
154
|
+
default: {
|
|
155
|
+
force: false,
|
|
156
|
+
skipTanstackQuery: false,
|
|
157
|
+
verbose: false
|
|
158
|
+
},
|
|
159
|
+
alias: {
|
|
160
|
+
f: 'force',
|
|
161
|
+
h: 'help',
|
|
162
|
+
v: 'verbose',
|
|
163
|
+
q: 'skipTanstackQuery'
|
|
164
|
+
},
|
|
165
|
+
boolean: true
|
|
166
|
+
});
|
|
167
|
+
intro(`tRPC Upgrade CLI v${version}`);
|
|
168
|
+
if (args.help) {
|
|
169
|
+
log.info(`
|
|
170
|
+
Usage: upgrade [options]
|
|
171
|
+
|
|
172
|
+
Options:
|
|
173
|
+
-f, --force Skip git status check, use with caution
|
|
174
|
+
-q, --skipTanstackQuery Skip installing @trpc/tanstack-react-query package
|
|
175
|
+
-v, --verbose Enable verbose logging
|
|
176
|
+
-h, --help Show help
|
|
177
|
+
`.trim());
|
|
178
|
+
process.exit(0);
|
|
179
|
+
}
|
|
180
|
+
if (args.verbose) {
|
|
181
|
+
log.info(`Running upgrade with args: ${JSON.stringify(args, null, 2)}`);
|
|
182
|
+
}
|
|
183
|
+
if (!args.force) {
|
|
184
|
+
await assertCleanGitTree();
|
|
185
|
+
}
|
|
186
|
+
const transforms = await multiselect({
|
|
187
|
+
message: 'Select transforms to run',
|
|
188
|
+
options: [
|
|
189
|
+
{
|
|
190
|
+
value: require.resolve('@trpc/upgrade/transforms/hooksToOptions'),
|
|
191
|
+
label: 'Migrate Hooks to xxxOptions API'
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
value: require.resolve('@trpc/upgrade/transforms/provider'),
|
|
195
|
+
label: 'Migrate context provider setup'
|
|
173
196
|
}
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
});
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
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
|
|
197
|
+
]
|
|
198
|
+
});
|
|
199
|
+
if (isCancel(transforms)) process.exit(0);
|
|
200
|
+
// Make sure provider transform runs first if it's selected
|
|
201
|
+
const sortedTransforms = transforms.sort((a)=>a.includes('provider') ? -1 : 1);
|
|
202
|
+
const program = getProgram(args);
|
|
203
|
+
const sourceFiles = program.getSourceFiles();
|
|
204
|
+
const possibleReferences = findTRPCImportReferences(program);
|
|
205
|
+
const trpcFile = possibleReferences.mostUsed.file;
|
|
206
|
+
const trpcImportName = possibleReferences.importName;
|
|
207
|
+
const commitedFiles = await filterIgnored(sourceFiles);
|
|
208
|
+
for (const transform of sortedTransforms){
|
|
209
|
+
log.info(`Running transform: ${transform}`);
|
|
210
|
+
const { run } = await import('jscodeshift/src/Runner.js');
|
|
211
|
+
await run(transform, commitedFiles, {
|
|
212
|
+
...args,
|
|
213
|
+
trpcFile,
|
|
214
|
+
trpcImportName
|
|
209
215
|
});
|
|
210
|
-
|
|
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! 🎉');
|
|
216
|
+
log.info(`Transform ${transform} completed`);
|
|
232
217
|
}
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
}
|
|
218
|
+
if (!args.skipTanstackQuery) {
|
|
219
|
+
log.info('Installing @trpc/tanstack-react-query');
|
|
220
|
+
await installPackage('@trpc/tanstack-react-query');
|
|
221
|
+
log.success('@trpc/tanstack-react-query installed');
|
|
222
|
+
log.info('Uninstalling @trpc/react-query');
|
|
223
|
+
await uninstallPackage('@trpc/react-query');
|
|
224
|
+
log.success('@trpc/react-query uninstalled');
|
|
225
|
+
}
|
|
226
|
+
outro('Upgrade complete! 🎉');
|
package/package.json
CHANGED
package/src/bin/index.ts
CHANGED
|
@@ -1,225 +1,98 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
import * as CP from 'node:child_process';
|
|
5
|
-
import * as Path from 'node:path';
|
|
6
|
-
import * as Util from 'node:util';
|
|
7
|
-
import * as Args from '@bomb.sh/args';
|
|
8
|
-
import * as p from '@clack/prompts';
|
|
9
|
-
import * as ts from 'typescript';
|
|
2
|
+
import { parse } from '@bomb.sh/args';
|
|
3
|
+
import { intro, isCancel, log, multiselect, outro } from '@clack/prompts';
|
|
10
4
|
import { version } from '../../package.json';
|
|
11
|
-
import { findTRPCImportReferences } from '../lib/ast/scanners';
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
+
});
|
|
28
23
|
|
|
29
|
-
|
|
30
|
-
const { stdout } = await execa('git status');
|
|
31
|
-
if (!stdout.includes('nothing to commit')) {
|
|
32
|
-
throw new Error(
|
|
33
|
-
'Git tree is not clean, please commit your changes and try again, or run with `--force`',
|
|
34
|
-
);
|
|
35
|
-
}
|
|
36
|
-
}
|
|
24
|
+
intro(`tRPC Upgrade CLI v${version}`);
|
|
37
25
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
}
|
|
26
|
+
if (args.help) {
|
|
27
|
+
log.info(
|
|
28
|
+
`
|
|
29
|
+
Usage: upgrade [options]
|
|
43
30
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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(),
|
|
49
37
|
);
|
|
50
|
-
|
|
38
|
+
process.exit(0);
|
|
51
39
|
}
|
|
52
40
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
const ignores = stdout.split('\n');
|
|
56
|
-
|
|
57
|
-
if (process.env.VERBOSE) {
|
|
58
|
-
console.debug('cwd:', process.cwd());
|
|
59
|
-
console.debug(
|
|
60
|
-
'All files in program:',
|
|
61
|
-
files.map((file) => file.fileName),
|
|
62
|
-
);
|
|
63
|
-
console.debug('Ignored files:', ignores);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
// Ignore "common files"
|
|
67
|
-
const filteredSourcePaths = files
|
|
68
|
-
.filter(
|
|
69
|
-
(source) =>
|
|
70
|
-
source.fileName.startsWith(Path.resolve()) && // only look ahead of current directory
|
|
71
|
-
!source.fileName.includes('/trpc/packages/') && // relative paths when running codemod locally
|
|
72
|
-
!source.fileName.includes('/node_modules/') && // always ignore node_modules
|
|
73
|
-
!ignores.includes(source.fileName), // ignored files
|
|
74
|
-
)
|
|
75
|
-
.map((source) => source.fileName);
|
|
76
|
-
|
|
77
|
-
if (process.env.VERBOSE) {
|
|
78
|
-
console.debug('Filtered files:', filteredSourcePaths);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
return filteredSourcePaths;
|
|
41
|
+
if (args.verbose) {
|
|
42
|
+
log.info(`Running upgrade with args: ${JSON.stringify(args, null, 2)}`);
|
|
82
43
|
}
|
|
83
44
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
45
|
+
if (!args.force) {
|
|
46
|
+
await assertCleanGitTree();
|
|
47
|
+
}
|
|
87
48
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
version: false,
|
|
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',
|
|
95
55
|
},
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
v: 'verbose',
|
|
100
|
-
q: 'skipTanstackQuery',
|
|
56
|
+
{
|
|
57
|
+
value: require.resolve('@trpc/upgrade/transforms/provider'),
|
|
58
|
+
label: 'Migrate context provider setup',
|
|
101
59
|
},
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
console.log('Running upgrade with args:', args);
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
p.intro('tRPC Upgrade CLI');
|
|
130
|
-
|
|
131
|
-
if (!args.force) {
|
|
132
|
-
try {
|
|
133
|
-
await assertCleanGitTree();
|
|
134
|
-
} catch (error) {
|
|
135
|
-
console.error(error);
|
|
136
|
-
process.exit(1);
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
const transforms = await p.multiselect({
|
|
141
|
-
message: 'Select transforms to run',
|
|
142
|
-
options: [
|
|
143
|
-
{
|
|
144
|
-
value: require.resolve(
|
|
145
|
-
transformPath('../transforms/hooksToOptions.ts'),
|
|
146
|
-
),
|
|
147
|
-
label: 'Migrate Hooks to xxxOptions API',
|
|
148
|
-
},
|
|
149
|
-
{
|
|
150
|
-
value: require.resolve(transformPath('../transforms/provider.ts')),
|
|
151
|
-
label: 'Migrate context provider setup',
|
|
152
|
-
},
|
|
153
|
-
],
|
|
154
|
-
});
|
|
155
|
-
exitOnCancel(transforms);
|
|
156
|
-
|
|
157
|
-
if (!transforms || transforms.length === 0) {
|
|
158
|
-
console.error('Please select at least one transform to run');
|
|
159
|
-
process.exit(1);
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
// Make sure provider transform runs first if it's selected
|
|
163
|
-
const sortedTransforms = transforms.sort((a) =>
|
|
164
|
-
a.includes('provider.ts') ? -1 : 1,
|
|
165
|
-
);
|
|
166
|
-
|
|
167
|
-
const configFile = ts.findConfigFile(process.cwd(), ts.sys.fileExists);
|
|
168
|
-
if (!configFile) {
|
|
169
|
-
console.error('No tsconfig found');
|
|
170
|
-
process.exit(1);
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
if (process.env.VERBOSE) {
|
|
174
|
-
console.debug('Using tsconfig', configFile);
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
const { config } = ts.readConfigFile(configFile, ts.sys.readFile);
|
|
178
|
-
const parsedConfig = ts.parseJsonConfigFileContent(
|
|
179
|
-
config,
|
|
180
|
-
ts.sys,
|
|
181
|
-
process.cwd(),
|
|
182
|
-
);
|
|
183
|
-
const program = ts.createProgram({
|
|
184
|
-
options: parsedConfig.options,
|
|
185
|
-
rootNames: parsedConfig.fileNames,
|
|
186
|
-
configFileParsingDiagnostics: parsedConfig.errors,
|
|
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,
|
|
187
84
|
});
|
|
85
|
+
log.info(`Transform ${transform} completed`);
|
|
86
|
+
}
|
|
188
87
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
const commitedFiles = await filterIgnored(sourceFiles);
|
|
195
|
-
|
|
196
|
-
for (const transform of sortedTransforms) {
|
|
197
|
-
console.log('Running transform', transform);
|
|
198
|
-
const { run } = await import('jscodeshift/src/Runner.js');
|
|
199
|
-
const result = await run(transform, commitedFiles, {
|
|
200
|
-
...args,
|
|
201
|
-
trpcFile,
|
|
202
|
-
trpcImportName,
|
|
203
|
-
});
|
|
204
|
-
console.log('Transform result', result);
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
if (!args.skipTanstackQuery) {
|
|
208
|
-
console.log('Installing @trpc/tanstack-react-query');
|
|
209
|
-
await installPackage('@trpc/tanstack-react-query');
|
|
210
|
-
|
|
211
|
-
console.log('Uninstalling @trpc/react-query');
|
|
212
|
-
await uninstallPackage('@trpc/react-query');
|
|
213
|
-
}
|
|
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');
|
|
214
92
|
|
|
215
|
-
|
|
93
|
+
log.info('Uninstalling @trpc/react-query');
|
|
94
|
+
await uninstallPackage('@trpc/react-query');
|
|
95
|
+
log.success('@trpc/react-query uninstalled');
|
|
216
96
|
}
|
|
217
97
|
|
|
218
|
-
|
|
219
|
-
if (error instanceof Error) {
|
|
220
|
-
console.error(error.message);
|
|
221
|
-
} else {
|
|
222
|
-
console.error('An unknown error occurred:', error);
|
|
223
|
-
}
|
|
224
|
-
process.exit(1);
|
|
225
|
-
});
|
|
98
|
+
outro('Upgrade complete! 🎉');
|
package/src/lib/ast/scanners.ts
CHANGED
|
@@ -1,25 +1,45 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
isCallExpression,
|
|
4
|
-
isIdentifier,
|
|
5
|
-
isImportDeclaration,
|
|
6
|
-
isStringLiteral,
|
|
7
|
-
isVariableDeclaration,
|
|
8
|
-
isVariableStatement,
|
|
9
|
-
resolveModuleName,
|
|
10
|
-
sys,
|
|
11
|
-
type Program,
|
|
12
|
-
} from 'typescript';
|
|
1
|
+
import * as p from '@clack/prompts';
|
|
2
|
+
import * as ts from 'typescript';
|
|
13
3
|
|
|
14
|
-
export function
|
|
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) {
|
|
15
35
|
const files = program.getSourceFiles().filter((sourceFile) => {
|
|
16
36
|
if (sourceFile.isDeclarationFile) return false;
|
|
17
37
|
let found = false;
|
|
18
|
-
forEachChild(sourceFile, (node) => {
|
|
19
|
-
if (!found && isImportDeclaration(node)) {
|
|
38
|
+
ts.forEachChild(sourceFile, (node) => {
|
|
39
|
+
if (!found && ts.isImportDeclaration(node)) {
|
|
20
40
|
const { moduleSpecifier } = node;
|
|
21
41
|
if (
|
|
22
|
-
isStringLiteral(moduleSpecifier) &&
|
|
42
|
+
ts.isStringLiteral(moduleSpecifier) &&
|
|
23
43
|
moduleSpecifier.text.includes('@trpc/react-query')
|
|
24
44
|
) {
|
|
25
45
|
found = true;
|
|
@@ -31,17 +51,17 @@ export function findSourceAndImportName(program: Program) {
|
|
|
31
51
|
|
|
32
52
|
let importName = 'trpc';
|
|
33
53
|
files.forEach((sourceFile) => {
|
|
34
|
-
forEachChild(sourceFile, (node) => {
|
|
54
|
+
ts.forEachChild(sourceFile, (node) => {
|
|
35
55
|
if (
|
|
36
|
-
isVariableStatement(node) &&
|
|
56
|
+
ts.isVariableStatement(node) &&
|
|
37
57
|
node.modifiers?.some((mod) => mod.getText(sourceFile) === 'export')
|
|
38
58
|
) {
|
|
39
59
|
node.declarationList.declarations.forEach((declaration) => {
|
|
40
60
|
if (
|
|
41
|
-
isVariableDeclaration(declaration) &&
|
|
61
|
+
ts.isVariableDeclaration(declaration) &&
|
|
42
62
|
declaration.initializer &&
|
|
43
|
-
isCallExpression(declaration.initializer) &&
|
|
44
|
-
isIdentifier(declaration.initializer.expression) &&
|
|
63
|
+
ts.isCallExpression(declaration.initializer) &&
|
|
64
|
+
ts.isIdentifier(declaration.initializer.expression) &&
|
|
45
65
|
declaration.initializer.expression.getText(sourceFile) ===
|
|
46
66
|
'createTRPCReact'
|
|
47
67
|
) {
|
|
@@ -58,20 +78,23 @@ export function findSourceAndImportName(program: Program) {
|
|
|
58
78
|
};
|
|
59
79
|
}
|
|
60
80
|
|
|
61
|
-
export function findTRPCImportReferences(program: Program) {
|
|
81
|
+
export function findTRPCImportReferences(program: ts.Program) {
|
|
62
82
|
const { files: filesImportingTRPC, importName } =
|
|
63
83
|
findSourceAndImportName(program);
|
|
64
84
|
const trpcReferenceSpecifiers = new Map<string, string>();
|
|
65
85
|
|
|
66
86
|
program.getSourceFiles().forEach((sourceFile) => {
|
|
67
87
|
if (sourceFile.isDeclarationFile) return;
|
|
68
|
-
forEachChild(sourceFile, (node) => {
|
|
69
|
-
if (
|
|
70
|
-
|
|
88
|
+
ts.forEachChild(sourceFile, (node) => {
|
|
89
|
+
if (
|
|
90
|
+
ts.isImportDeclaration(node) &&
|
|
91
|
+
ts.isStringLiteral(node.moduleSpecifier)
|
|
92
|
+
) {
|
|
93
|
+
const resolved = ts.resolveModuleName(
|
|
71
94
|
node.moduleSpecifier.text,
|
|
72
95
|
sourceFile.fileName,
|
|
73
96
|
program.getCompilerOptions(),
|
|
74
|
-
sys,
|
|
97
|
+
ts.sys,
|
|
75
98
|
);
|
|
76
99
|
if (
|
|
77
100
|
resolved.resolvedModule &&
|
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
|
+
}
|