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

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trpc/upgrade",
3
- "version": "0.0.0-alpha.27",
3
+ "version": "0.0.0-alpha.28",
4
4
  "description": "Upgrade scripts for tRPC",
5
5
  "author": "juliusmarminge",
6
6
  "license": "MIT",
package/src/bin/index.ts CHANGED
@@ -1,225 +1,98 @@
1
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';
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
- 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
- }
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
- 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
- }
24
+ intro(`tRPC Upgrade CLI v${version}`);
37
25
 
38
- async function installPackage(packageName: string) {
39
- const packageManager = await getPackageManager();
40
- const { stdout } = await execa(`${packageManager} install ${packageName}`);
41
- console.log(stdout);
42
- }
26
+ if (args.help) {
27
+ log.info(
28
+ `
29
+ Usage: upgrade [options]
43
30
 
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}`,
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
- console.log(stdout);
38
+ process.exit(0);
51
39
  }
52
40
 
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;
41
+ if (args.verbose) {
42
+ log.info(`Running upgrade with args: ${JSON.stringify(args, null, 2)}`);
82
43
  }
83
44
 
84
- // FIXME :: hacky
85
- const transformPath = (path: string) =>
86
- process.env.DEV ? path : path.replace('../', './').replace('.ts', '.cjs');
45
+ if (!args.force) {
46
+ await assertCleanGitTree();
47
+ }
87
48
 
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,
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
- alias: {
97
- f: 'force',
98
- h: 'help',
99
- v: 'verbose',
100
- q: 'skipTanstackQuery',
56
+ {
57
+ value: require.resolve('@trpc/upgrade/transforms/provider'),
58
+ label: 'Migrate context provider setup',
101
59
  },
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,
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
- 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
- }
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
- p.outro('Upgrade complete! 🎉');
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
- 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
- });
98
+ outro('Upgrade complete! 🎉');
@@ -1,25 +1,45 @@
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';
1
+ import * as p from '@clack/prompts';
2
+ import * as ts from 'typescript';
13
3
 
14
- export function findSourceAndImportName(program: Program) {
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 (isImportDeclaration(node) && isStringLiteral(node.moduleSpecifier)) {
70
- const resolved = resolveModuleName(
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 &&
@@ -0,0 +1,4 @@
1
+ import * as CP from 'node:child_process';
2
+ import * as Util from 'node:util';
3
+
4
+ export const execa = Util.promisify(CP.exec);
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
+ }