@trpc/upgrade 0.0.0-alpha.26 → 0.0.0-alpha.27

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