Skip to content

Commit b3c4c04

Browse files
csandmanclaude
andauthored
chore(codemod): Modernize to ESM + bump all deps + add test suite (#418)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 5ee3313 commit b3c4c04

18 files changed

Lines changed: 1057 additions & 877 deletions

.lintstagedrc.mjs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ const config = {
33
"oxlint -c demo/.oxlintrc.json --disable-nested-config --fix",
44
"!(demo|codemod)/**/*.{js,jsx,ts,tsx}":
55
"oxlint -c .oxlintrc.json --disable-nested-config --fix",
6-
"*": "oxfmt",
6+
// Exclude codemod test fixtures from oxfmt — they must exactly match the
7+
// transform output, which jscodeshift formats independently of oxfmt.
8+
"!(codemod/tests/fixtures/**)": "oxfmt",
79
};
810

911
export default config;

codemod/bin/cli.ts

Lines changed: 112 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,23 @@
44
*
55
* @see {@link https://github.com/vercel/next.js/blob/dc9f30c/packages/next-codemod/bin/cli.ts}
66
*/
7-
import execa from "execa";
8-
import globby from "globby";
9-
import inquirer from "inquirer";
7+
import { execaSync } from "execa";
8+
import { globbySync } from "globby";
9+
import inquirer, { type DistinctQuestion } from "inquirer";
1010
import isGitClean from "is-git-clean";
11-
import meow from "meow";
12-
import path from "path";
13-
import { yellow } from "picocolors";
11+
import meow, { type AnyFlags } from "meow";
12+
import { createRequire } from "node:module";
13+
import path from "node:path";
14+
import { fileURLToPath } from "node:url";
15+
import pc from "picocolors";
1416

15-
export const jscodeshiftExecutable = require.resolve(".bin/jscodeshift");
17+
const { yellow } = pc;
18+
19+
const require = createRequire(import.meta.url);
20+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
21+
22+
export const jscodeshiftExecutable =
23+
require.resolve("jscodeshift/bin/jscodeshift.js");
1624
export const transformerDirectory = path.join(__dirname, "../", "transforms");
1725

1826
export function checkGitStatus(force: boolean) {
@@ -21,8 +29,12 @@ export function checkGitStatus(force: boolean) {
2129
try {
2230
clean = isGitClean.sync(process.cwd());
2331
errorMessage = "Git directory is not clean";
24-
} catch (err) {
25-
if (err && err.stderr && err.stderr.includes("Not a git repository")) {
32+
} catch (err: unknown) {
33+
const stderr =
34+
err && typeof err === "object" && "stderr" in err
35+
? String((err as { stderr: unknown }).stderr)
36+
: "";
37+
if (stderr.includes("Not a git repository")) {
2638
clean = true;
2739
}
2840
}
@@ -45,12 +57,26 @@ export function checkGitStatus(force: boolean) {
4557
}
4658
}
4759

48-
export async function runTransform({ files, flags, transformer }) {
49-
const transformerPath = path.join(transformerDirectory, `${transformer}.js`);
60+
interface RunTransformOptions {
61+
files: string[];
62+
transformer: string;
63+
dry?: boolean;
64+
print?: boolean;
65+
runInBand?: boolean;
66+
jscodeshift?: readonly string[];
67+
}
5068

51-
let args = [];
69+
export function runTransform({
70+
files,
71+
transformer,
72+
dry,
73+
print,
74+
runInBand,
75+
jscodeshift,
76+
}: RunTransformOptions) {
77+
const transformerPath = path.join(transformerDirectory, `${transformer}.js`);
5278

53-
const { dry, print, runInBand } = flags;
79+
const args: string[] = [];
5480

5581
if (dry) {
5682
args.push("--dry");
@@ -69,17 +95,17 @@ export async function runTransform({ files, flags, transformer }) {
6995

7096
args.push("--extensions=tsx,ts,jsx,js");
7197

72-
args = args.concat(["--transform", transformerPath]);
98+
args.push("--transform", transformerPath);
7399

74-
if (flags.jscodeshift) {
75-
args = args.concat(flags.jscodeshift);
100+
if (jscodeshift) {
101+
args.push(...jscodeshift);
76102
}
77103

78-
args = args.concat(files);
104+
args.push(...files);
79105

80106
console.log(`Executing command: jscodeshift ${args.join(" ")}`);
81107

82-
const result = execa.sync(jscodeshiftExecutable, args, {
108+
const result = execaSync(jscodeshiftExecutable, args, {
83109
stdio: "inherit",
84110
stripFinalNewline: false,
85111
});
@@ -96,17 +122,32 @@ const TRANSFORMER_INQUIRER_CHOICES = [
96122
},
97123
];
98124

99-
function expandFilePathsIfNeeded(filesBeforeExpansion) {
125+
function expandFilePathsIfNeeded(filesBeforeExpansion: string[]) {
100126
const shouldExpandFiles = filesBeforeExpansion.some((file) =>
101127
file.includes("*")
102128
);
103129
return shouldExpandFiles
104-
? globby.sync(filesBeforeExpansion)
130+
? globbySync(filesBeforeExpansion)
105131
: filesBeforeExpansion;
106132
}
107133

134+
const flagsSchema = {
135+
force: { type: "boolean" },
136+
dry: { type: "boolean" },
137+
print: { type: "boolean" },
138+
runInBand: { type: "boolean" },
139+
jscodeshift: { type: "string", isMultiple: true },
140+
help: { type: "boolean", shortFlag: "h" },
141+
} as const satisfies AnyFlags;
142+
143+
interface PromptAnswers {
144+
files?: string;
145+
transformer?: string;
146+
}
147+
108148
export function run() {
109149
const cli = meow({
150+
importMeta: import.meta,
110151
description: "Codemods for updating chakra-react-select in applications.",
111152
help: `
112153
Usage
@@ -119,14 +160,8 @@ export function run() {
119160
--print Print transformed files to your terminal
120161
--jscodeshift (Advanced) Pass options directly to jscodeshift
121162
`,
122-
flags: {
123-
boolean: ["force", "dry", "print", "help"],
124-
string: ["_"],
125-
alias: {
126-
h: "help",
127-
},
128-
},
129-
} as meow.Options<meow.AnyFlags>);
163+
flags: flagsSchema,
164+
});
130165

131166
if (!cli.flags.dry) {
132167
checkGitStatus(!!cli.flags.force);
@@ -143,44 +178,54 @@ export function run() {
143178
process.exit(1);
144179
}
145180

146-
inquirer
147-
.prompt([
148-
{
149-
type: "input",
150-
name: "files",
151-
message: "On which files or directory should the codemods be applied?",
152-
when: !cli.input[1],
153-
default: ".",
154-
filter: (files) => files.trim(),
155-
},
156-
{
157-
type: "list",
158-
name: "transformer",
159-
message: "Which transform would you like to apply?",
160-
when: !cli.input[0],
161-
pageSize: TRANSFORMER_INQUIRER_CHOICES.length,
162-
choices: TRANSFORMER_INQUIRER_CHOICES,
163-
},
164-
])
165-
.then((answers) => {
166-
const { files, transformer } = answers;
167-
168-
const filesBeforeExpansion = cli.input[1] || files;
169-
const filesExpanded = expandFilePathsIfNeeded([filesBeforeExpansion]);
170-
171-
const selectedTransformer = cli.input[0] || transformer;
172-
173-
if (!filesExpanded.length) {
174-
console.log(
175-
`No files found matching ${filesBeforeExpansion.join(" ")}`
176-
);
177-
return null;
178-
}
179-
180-
return runTransform({
181-
files: filesExpanded,
182-
flags: cli.flags,
183-
transformer: selectedTransformer,
184-
});
181+
const questions: DistinctQuestion<PromptAnswers>[] = [
182+
{
183+
type: "input",
184+
name: "files",
185+
message: "On which files or directory should the codemods be applied?",
186+
when: !cli.input[1],
187+
default: ".",
188+
filter: (files: string) => files.trim(),
189+
},
190+
{
191+
type: "select",
192+
name: "transformer",
193+
message: "Which transform would you like to apply?",
194+
when: !cli.input[0],
195+
pageSize: TRANSFORMER_INQUIRER_CHOICES.length,
196+
choices: TRANSFORMER_INQUIRER_CHOICES,
197+
},
198+
];
199+
200+
inquirer.prompt<PromptAnswers>(questions).then((answers) => {
201+
const { files, transformer } = answers;
202+
203+
const filesBeforeExpansion = cli.input[1] || files;
204+
if (!filesBeforeExpansion) {
205+
console.log("No files or directory provided.");
206+
return null;
207+
}
208+
209+
const filesExpanded = expandFilePathsIfNeeded([filesBeforeExpansion]);
210+
211+
const selectedTransformer = cli.input[0] || transformer;
212+
if (!selectedTransformer) {
213+
console.log("No transformer selected.");
214+
return null;
215+
}
216+
217+
if (!filesExpanded.length) {
218+
console.log(`No files found matching ${filesBeforeExpansion}`);
219+
return null;
220+
}
221+
222+
return runTransform({
223+
files: filesExpanded,
224+
transformer: selectedTransformer,
225+
dry: cli.flags.dry,
226+
print: cli.flags.print,
227+
runInBand: cli.flags.runInBand,
228+
jscodeshift: cli.flags.jscodeshift,
185229
});
230+
});
186231
}

codemod/bin/crs-codemod.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,6 @@
66
*
77
* @see {@link https://github.com/vercel/next.js/blob/dc9f30c1064ea72aef2fd046da2f1d2722b89735/packages/next-codemod/bin/next-codemod.ts}
88
*/
9-
require("./cli").run();
9+
import { run } from "./cli.js";
10+
11+
run();

codemod/package.json

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -14,27 +14,31 @@
1414
"bin/*.js",
1515
"transforms/*.js"
1616
],
17+
"type": "module",
1718
"scripts": {
18-
"prebuild": "rimraf **/*.{d.ts,js}",
19+
"prebuild": "rimraf bin/*.{d.ts,js} transforms/*.{d.ts,js}",
1920
"build": "tsc -d -p tsconfig.json",
2021
"dev": "tsc -d -w -p tsconfig.json",
22+
"test": "vitest run",
23+
"test:watch": "vitest",
24+
"test:update": "vitest run -u",
2125
"prepublishOnly": "pnpm build"
2226
},
2327
"dependencies": {
24-
"execa": "^4.1.0",
25-
"globby": "^11.1.0",
26-
"inquirer": "^7.3.3",
28+
"execa": "^9.6.1",
29+
"globby": "^16.2.0",
30+
"inquirer": "^13.4.3",
2731
"is-git-clean": "^1.1.0",
28-
"jscodeshift": "^0.15.0",
29-
"meow": "^7.1.1",
30-
"picocolors": "^1.0.0"
32+
"jscodeshift": "^17.3.0",
33+
"meow": "^14.1.0",
34+
"picocolors": "^1.1.1"
3135
},
3236
"devDependencies": {
33-
"@types/inquirer": "^7.3.3",
34-
"@types/is-git-clean": "^1.1.0",
35-
"@types/jscodeshift": "^0.11.7",
36-
"@types/node": "^20.8.4",
37-
"rimraf": "^5.0.5",
38-
"typescript": "^5.2.2"
37+
"@types/is-git-clean": "^1.1.2",
38+
"@types/jscodeshift": "^17.3.0",
39+
"@types/node": "^24.12.4",
40+
"rimraf": "^6.1.3",
41+
"typescript": "^6.0.3",
42+
"vitest": "^4.1.6"
3943
}
4044
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { Select as MySelect } from "chakra-react-select";
2+
3+
export const Example = () => (
4+
<MySelect useBasicStyles selectedOptionColor="purple" colorScheme="red" />
5+
);
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { Select as MySelect } from "chakra-react-select";
2+
3+
export const Example = () => (
4+
<MySelect selectedOptionColorScheme="purple" tagColorScheme="red" />
5+
);
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import {
2+
AsyncCreatableSelect,
3+
AsyncSelect,
4+
CreatableSelect,
5+
Select,
6+
} from "chakra-react-select";
7+
8+
export const Example = () => (
9+
<>
10+
<Select useBasicStyles colorScheme="blue" />
11+
<AsyncSelect hasStickyGroupHeaders selectedOptionColor="green" />
12+
<CreatableSelect useBasicStyles selectedOptionColor="red" />
13+
<AsyncCreatableSelect colorScheme="purple" hasStickyGroupHeaders />
14+
</>
15+
);
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import {
2+
AsyncCreatableSelect,
3+
AsyncSelect,
4+
CreatableSelect,
5+
Select,
6+
} from "chakra-react-select";
7+
8+
export const Example = () => (
9+
<>
10+
<Select tagColorScheme="blue" />
11+
<AsyncSelect selectedOptionColorScheme="green" />
12+
<CreatableSelect selectedOptionColorScheme="red" />
13+
<AsyncCreatableSelect tagColorScheme="purple" />
14+
</>
15+
);
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { Select } from "chakra-react-select";
2+
3+
export const Example = () => (
4+
<Select
5+
useBasicStyles
6+
hasStickyGroupHeaders
7+
selectedOptionColor="blue"
8+
colorScheme="green"
9+
/>
10+
);
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { Select } from "chakra-react-select";
2+
3+
export const Example = () => (
4+
<Select selectedOptionColorScheme="blue" tagColorScheme="green" />
5+
);

0 commit comments

Comments
 (0)