@trpc/upgrade 0.0.0-alpha.1 → 0.0.0-alpha.3

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 CHANGED
@@ -10,9 +10,9 @@ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
10
10
 
11
11
  var path__default = /*#__PURE__*/_interopDefault(path);
12
12
 
13
- var version = "0.0.0-alpha.0";
13
+ var version = "0.0.0-alpha.2";
14
14
 
15
- platform.Command.string(platform.Command.make('git', 'status')).pipe(effect.Effect.filterOrFail((status)=>status.includes('nothing to commit'), ()=>'Git tree is not clean, please commit your changes before running the migrator'));
15
+ const assertCleanGitTree = platform.Command.string(platform.Command.make('git', 'status')).pipe(effect.Effect.filterOrFail(effect.String.includes('nothing to commit'), ()=>'Git tree is not clean, please commit your changes and try again, or run with `--force`'));
16
16
  const installPackage = (packageName)=>{
17
17
  const packageManager = effect.Match.value(process.env.npm_config_user_agent ?? 'npm').pipe(effect.Match.when(effect.String.startsWith('pnpm'), ()=>'pnpm'), effect.Match.when(effect.String.startsWith('yarn'), ()=>'yarn'), effect.Match.when(effect.String.startsWith('bun'), ()=>'bun'), effect.Match.orElse(()=>'npm'));
18
18
  return platform.Command.streamLines(platform.Command.make(packageManager, 'install', packageName)).pipe(effect.Stream.mapEffect(effect.Console.log), effect.Stream.runDrain);
@@ -28,50 +28,50 @@ const filterIgnored = (files)=>effect.Effect.gen(function*() {
28
28
  yield* effect.Effect.log('Filtered files:', filteredSourcePaths);
29
29
  return filteredSourcePaths;
30
30
  });
31
- const Program = 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
+ const TSProgram = effect.Effect.succeed(typescript.findConfigFile(process.cwd(), typescript.sys.fileExists)).pipe(effect.Effect.filterOrFail(effect.Predicate.isNotNullable, ()=>'No tsconfig found'), effect.Effect.tap((_)=>effect.Effect.logDebug('Using tsconfig', _)), effect.Effect.map((_)=>typescript.readConfigFile(_, typescript.sys.readFile)), effect.Effect.map((_)=>typescript.parseJsonConfigFileContent(_.config, typescript.sys, process.cwd())), effect.Effect.map((_)=>typescript.createProgram({
32
32
  options: _.options,
33
33
  rootNames: _.fileNames,
34
34
  configFileParsingDiagnostics: _.errors
35
35
  })));
36
+ // FIXME :: hacky
36
37
  const transformPath = (path)=>process.env.DEV ? path : path.replace('../', './').replace('.ts', '.cjs');
37
- const prompts = cli$1.Command.prompt('transforms', cli$1.Prompt.multiSelect({
38
- message: 'Select transforms to run',
39
- choices: [
40
- {
41
- title: 'Migrate Hooks to xxxOptions API',
42
- value: require.resolve(transformPath('../transforms/hooksToOptions.ts'))
43
- },
44
- {
45
- title: 'Migrate context provider setup',
46
- value: require.resolve(transformPath('../transforms/provider.ts'))
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;
47
50
  }
48
- ]
49
- }), (_)=>effect.Effect.gen(function*() {
50
- // yield* assertCleanGitTree;
51
- const program = yield* Program;
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;
52
66
  const sourceFiles = program.getSourceFiles();
53
- // Make sure provider transform runs first if it's selected
54
- _.sort((a, b)=>a.includes('provider.ts') ? -1 : b.includes('provider.ts') ? 1 : 0);
55
- /**
56
- * TODO: Detect these automatically
57
- */ const appRouterImportFile = '~/server/routers/_app';
58
- const appRouterImportName = 'AppRouter';
59
- const trpcFile = '~/lib/trpc';
60
- const trpcImportName = 'trpc';
61
67
  const commitedFiles = yield* filterIgnored(sourceFiles);
62
- yield* effect.Effect.forEach(_, (transform)=>{
63
- 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.replace('file://', ''), commitedFiles, {
64
- appRouterImportFile,
65
- appRouterImportName,
66
- trpcFile,
67
- trpcImportName
68
- })))), 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, args)))), effect.Effect.map((_)=>effect.Effect.log('Transform result', _)));
69
70
  });
70
71
  yield* effect.Effect.log('Installing @trpc/tanstack-react-query');
71
72
  yield* installPackage('@trpc/tanstack-react-query');
72
- // TODO: Format project
73
73
  }));
74
- const cli = cli$1.Command.run(prompts, {
74
+ const cli = cli$1.Command.run(rootComamnd, {
75
75
  name: 'tRPC Upgrade CLI',
76
76
  version: `v${version}`
77
77
  });
@@ -43,59 +43,50 @@ const utilMap = {
43
43
  getInfiniteData: 'getInfiniteQueryData'
44
44
  };
45
45
  function transform(file, api, options) {
46
- const { trpcFile, trpcImportName } = options;
47
- if (!trpcFile || !trpcImportName) {
48
- throw new Error('trpcFile and trpcImportName are required');
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 updateTRPCImport(path) {
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
- const body = path.node.body.body;
96
- body.unshift(variableDeclaration);
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(path) {
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(path).find(j.CallExpression, {
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('trpc');
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 (declarator && queryName) {
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'; // https://go.codemod.com/ddX54TM
219
+ const parser = 'tsx';
219
220
 
220
221
  exports.default = transform;
221
222
  exports.parser = parser;
@@ -1,26 +1,18 @@
1
1
  Object.defineProperty(exports, '__esModule', { value: true });
2
2
 
3
3
  function transform(file, api, options) {
4
- const { trpcFile, trpcImportName, appRouterImportFile, appRouterImportName } = options;
4
+ const { trpcImportName } = options;
5
+ let routerName = undefined;
5
6
  const j = api.jscodeshift;
6
7
  const root = j(file.source);
7
8
  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
9
  // Find the variable declaration for `trpc`
20
10
  root.find(j.VariableDeclaration).forEach((path)=>{
21
11
  const declaration = path.node.declarations[0];
22
12
  if (j.Identifier.check(declaration.id) && declaration.id.name === trpcImportName) {
23
13
  if (j.CallExpression.check(declaration.init) && j.Identifier.check(declaration.init.callee) && declaration.init.callee.name === 'createTRPCReact') {
14
+ // Get router name ( TODO : should probably get this from the TS compiler along with the import path)
15
+ routerName = declaration.init.original?.typeParameters?.params?.[0]?.typeName?.name;
24
16
  // Replace the `createTRPCReact` call with `createTRPCContext`
25
17
  declaration.init.callee.name = 'createTRPCContext';
26
18
  // Destructure the result into `TRPCProvider` and `useTRPC`
@@ -57,7 +49,7 @@ function transform(file, api, options) {
57
49
  });
58
50
  });
59
51
  }
60
- // Replace trpc.createClient with createTRPCClient
52
+ // Replace trpc.createClient with createTRPCClient<TRouter>
61
53
  root.find(j.CallExpression, {
62
54
  callee: {
63
55
  object: {
@@ -69,12 +61,12 @@ function transform(file, api, options) {
69
61
  }
70
62
  }).forEach((path)=>{
71
63
  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
64
  dirtyFlag = true;
65
+ if (routerName) {
66
+ path.node.typeParameters = j.tsTypeParameterInstantiation([
67
+ j.tsTypeReference(j.identifier(routerName))
68
+ ]);
69
+ }
78
70
  });
79
71
  // Replace <trpc.Provider client={...} with <TRPCProvider trpcClient={...}
80
72
  root.find(j.JSXElement, {
@@ -112,14 +104,12 @@ function transform(file, api, options) {
112
104
  path.node.specifiers?.push(createTRPCClientImport);
113
105
  });
114
106
  // Replace trpc import with TRPCProvider
115
- root.find(j.ImportDeclaration, {
116
- source: {
117
- value: trpcFile
107
+ root.find(j.ImportSpecifier, {
108
+ imported: {
109
+ name: trpcImportName
118
110
  }
119
111
  }).forEach((path)=>{
120
- path.node.specifiers = [
121
- j.importSpecifier(j.identifier('TRPCProvider'))
122
- ];
112
+ path.node.name = j.identifier('TRPCProvider');
123
113
  });
124
114
  }
125
115
  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.1",
3
+ "version": "0.0.0-alpha.3",
4
4
  "description": "Upgrade scripts for tRPC",
5
5
  "author": "juliusmarminge",
6
6
  "license": "MIT",
package/src/bin/cli.ts CHANGED
@@ -1,12 +1,14 @@
1
1
  /* eslint-disable @typescript-eslint/unbound-method */
2
2
  import path from 'path';
3
- import { Command as CLICommand, Prompt } from '@effect/cli';
3
+ import { Command as CLICommand, Options, Prompt } from '@effect/cli';
4
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,
@@ -24,9 +26,9 @@ import { version } from '../../package.json';
24
26
 
25
27
  const assertCleanGitTree = Command.string(Command.make('git', 'status')).pipe(
26
28
  Effect.filterOrFail(
27
- (status) => status.includes('nothing to commit'),
29
+ String.includes('nothing to commit'),
28
30
  () =>
29
- 'Git tree is not clean, please commit your changes before running the migrator',
31
+ 'Git tree is not clean, please commit your changes and try again, or run with `--force`',
30
32
  ),
31
33
  );
32
34
 
@@ -71,7 +73,7 @@ const filterIgnored = (files: readonly SourceFile[]) =>
71
73
  return filteredSourcePaths;
72
74
  });
73
75
 
74
- const Program = Effect.succeed(
76
+ const TSProgram = Effect.succeed(
75
77
  findConfigFile(process.cwd(), sys.fileExists),
76
78
  ).pipe(
77
79
  Effect.filterOrFail(Predicate.isNotNullable, () => 'No tsconfig found'),
@@ -87,73 +89,90 @@ const Program = Effect.succeed(
87
89
  ),
88
90
  );
89
91
 
92
+ // FIXME :: hacky
90
93
  const transformPath = (path: string) =>
91
94
  process.env.DEV ? path : path.replace('../', './').replace('.ts', '.cjs');
92
95
 
93
- const prompts = CLICommand.prompt(
94
- 'transforms',
95
- Prompt.multiSelect({
96
- message: 'Select transforms to run',
97
- choices: [
98
- {
99
- title: 'Migrate Hooks to xxxOptions API',
100
- value: require.resolve(
101
- transformPath('../transforms/hooksToOptions.ts'),
102
- ),
103
- },
104
- {
105
- title: 'Migrate context provider setup',
106
- value: require.resolve(transformPath('../transforms/provider.ts')),
107
- },
108
- ],
109
- }),
110
- (_) =>
111
- Effect.gen(function* () {
112
- // yield* assertCleanGitTree;
113
- const program = yield* Program;
114
- const sourceFiles = program.getSourceFiles();
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
+ );
115
116
 
116
- // Make sure provider transform runs first if it's selected
117
- _.sort((a, b) =>
118
- a.includes('provider.ts') ? -1 : b.includes('provider.ts') ? 1 : 0,
117
+ const rootComamnd = CLICommand.make(
118
+ 'upgrade',
119
+ {
120
+ force,
121
+ trpcFile,
122
+ trpcImportName,
123
+ },
124
+ (args) =>
125
+ Effect.gen(function* () {
126
+ if (!args.force) {
127
+ yield* assertCleanGitTree;
128
+ }
129
+
130
+ const transforms = yield* Effect.map(
131
+ Prompt.multiSelect({
132
+ message: 'Select transforms to run',
133
+ choices: [
134
+ {
135
+ title: 'Migrate Hooks to xxxOptions API',
136
+ value: require.resolve(
137
+ transformPath('../transforms/hooksToOptions.ts'),
138
+ ),
139
+ },
140
+ {
141
+ title: 'Migrate context provider setup',
142
+ value: require.resolve(
143
+ transformPath('../transforms/provider.ts'),
144
+ ),
145
+ },
146
+ ],
147
+ }),
148
+ // Make sure provider transform runs first if it's selected
149
+ Array.sortWith((a) => !a.includes('provider.ts'), Order.boolean),
119
150
  );
120
151
 
121
- /**
122
- * TODO: Detect these automatically
123
- */
124
- const appRouterImportFile = '~/server/routers/_app';
125
- const appRouterImportName = 'AppRouter';
126
- const trpcFile = '~/lib/trpc';
127
- const trpcImportName = 'trpc';
152
+ const program = yield* TSProgram;
153
+ const sourceFiles = program.getSourceFiles();
128
154
 
129
155
  const commitedFiles = yield* filterIgnored(sourceFiles);
130
- yield* Effect.forEach(_, (transform) => {
156
+ yield* Effect.forEach(transforms, (transform) => {
131
157
  return pipe(
132
158
  Effect.log('Running transform', transform),
133
159
  Effect.flatMap(() =>
134
160
  Effect.tryPromise(async () =>
135
161
  import('jscodeshift/src/Runner.js').then(({ run }) =>
136
- run(transform.replace('file://', ''), commitedFiles, {
137
- appRouterImportFile,
138
- appRouterImportName,
139
- trpcFile,
140
- trpcImportName,
141
- }),
162
+ run(transform, commitedFiles, args),
142
163
  ),
143
164
  ),
144
165
  ),
145
- Effect.map((res) => Effect.log('Transform result', res)),
166
+ Effect.map((_) => Effect.log('Transform result', _)),
146
167
  );
147
168
  });
148
169
 
149
170
  yield* Effect.log('Installing @trpc/tanstack-react-query');
150
171
  yield* installPackage('@trpc/tanstack-react-query');
151
-
152
- // TODO: Format project
153
172
  }),
154
173
  );
155
174
 
156
- const cli = CLICommand.run(prompts, {
175
+ const cli = CLICommand.run(rootComamnd, {
157
176
  name: 'tRPC Upgrade CLI',
158
177
  version: `v${version}`,
159
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 { trpcFile, trpcImportName } = options;
65
- if (!trpcFile || !trpcImportName) {
66
- throw new Error('trpcFile and trpcImportName are required');
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 updateTRPCImport(
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
- const body = path.node.body.body;
122
- body.unshift(variableDeclaration);
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
- path: ASTPath<FunctionDeclaration | ArrowFunctionExpression>,
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(path)
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('trpc');
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 (declarator && queryName) {
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,13 @@ export default function transform(
10
9
  api: API,
11
10
  options: TransformOptions,
12
11
  ) {
13
- const { trpcFile, trpcImportName, appRouterImportFile, appRouterImportName } =
14
- options;
12
+ const { trpcImportName } = options;
13
+ let routerName: string | undefined = undefined;
15
14
 
16
15
  const j = api.jscodeshift;
17
16
  const root = j(file.source);
18
17
  let dirtyFlag = false;
19
18
 
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
19
  // Find the variable declaration for `trpc`
40
20
  root.find(j.VariableDeclaration).forEach((path) => {
41
21
  const declaration = path.node.declarations[0];
@@ -48,6 +28,11 @@ export default function transform(
48
28
  j.Identifier.check(declaration.init.callee) &&
49
29
  declaration.init.callee.name === 'createTRPCReact'
50
30
  ) {
31
+ // Get router name ( TODO : should probably get this from the TS compiler along with the import path)
32
+ routerName =
33
+ declaration.init.original?.typeParameters?.params?.[0]?.typeName
34
+ ?.name;
35
+
51
36
  // Replace the `createTRPCReact` call with `createTRPCContext`
52
37
  declaration.init.callee.name = 'createTRPCContext';
53
38
 
@@ -89,7 +74,7 @@ export default function transform(
89
74
  });
90
75
  }
91
76
 
92
- // Replace trpc.createClient with createTRPCClient
77
+ // Replace trpc.createClient with createTRPCClient<TRouter>
93
78
  root
94
79
  .find(j.CallExpression, {
95
80
  callee: {
@@ -99,12 +84,13 @@ export default function transform(
99
84
  })
100
85
  .forEach((path) => {
101
86
  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
87
  dirtyFlag = true;
88
+
89
+ if (routerName) {
90
+ (path.node as any).typeParameters = j.tsTypeParameterInstantiation([
91
+ j.tsTypeReference(j.identifier(routerName)),
92
+ ]);
93
+ }
108
94
  });
109
95
 
110
96
  // Replace <trpc.Provider client={...} with <TRPCProvider trpcClient={...}
@@ -146,13 +132,11 @@ export default function transform(
146
132
 
147
133
  // Replace trpc import with TRPCProvider
148
134
  root
149
- .find(j.ImportDeclaration, {
150
- source: { value: trpcFile },
135
+ .find(j.ImportSpecifier, {
136
+ imported: { name: trpcImportName },
151
137
  })
152
138
  .forEach((path) => {
153
- path.node.specifiers = [
154
- j.importSpecifier(j.identifier('TRPCProvider')),
155
- ];
139
+ path.node.name = j.identifier('TRPCProvider');
156
140
  });
157
141
  }
158
142