Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .lintstagedrc.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ const config = {
"oxlint -c demo/.oxlintrc.json --disable-nested-config --fix",
"!(demo|codemod)/**/*.{js,jsx,ts,tsx}":
"oxlint -c .oxlintrc.json --disable-nested-config --fix",
"*": "oxfmt",
// Exclude codemod test fixtures from oxfmt — they must exactly match the
// transform output, which jscodeshift formats independently of oxfmt.
"!(codemod/tests/fixtures/**)": "oxfmt",
};

export default config;
179 changes: 112 additions & 67 deletions codemod/bin/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,23 @@
*
* @see {@link https://github.com/vercel/next.js/blob/dc9f30c/packages/next-codemod/bin/cli.ts}
*/
import execa from "execa";
import globby from "globby";
import inquirer from "inquirer";
import { execaSync } from "execa";
import { globbySync } from "globby";
import inquirer, { type DistinctQuestion } from "inquirer";
import isGitClean from "is-git-clean";
import meow from "meow";
import path from "path";
import { yellow } from "picocolors";
import meow, { type AnyFlags } from "meow";
import { createRequire } from "node:module";
import path from "node:path";
import { fileURLToPath } from "node:url";
import pc from "picocolors";

export const jscodeshiftExecutable = require.resolve(".bin/jscodeshift");
const { yellow } = pc;

const require = createRequire(import.meta.url);
const __dirname = path.dirname(fileURLToPath(import.meta.url));

export const jscodeshiftExecutable =
require.resolve("jscodeshift/bin/jscodeshift.js");
export const transformerDirectory = path.join(__dirname, "../", "transforms");

export function checkGitStatus(force: boolean) {
Expand All @@ -21,8 +29,12 @@ export function checkGitStatus(force: boolean) {
try {
clean = isGitClean.sync(process.cwd());
errorMessage = "Git directory is not clean";
} catch (err) {
if (err && err.stderr && err.stderr.includes("Not a git repository")) {
} catch (err: unknown) {
const stderr =
err && typeof err === "object" && "stderr" in err
? String((err as { stderr: unknown }).stderr)
: "";
if (stderr.includes("Not a git repository")) {
clean = true;
}
}
Expand All @@ -45,12 +57,26 @@ export function checkGitStatus(force: boolean) {
}
}

export async function runTransform({ files, flags, transformer }) {
const transformerPath = path.join(transformerDirectory, `${transformer}.js`);
interface RunTransformOptions {
files: string[];
transformer: string;
dry?: boolean;
print?: boolean;
runInBand?: boolean;
jscodeshift?: readonly string[];
}

let args = [];
export function runTransform({
files,
transformer,
dry,
print,
runInBand,
jscodeshift,
}: RunTransformOptions) {
const transformerPath = path.join(transformerDirectory, `${transformer}.js`);

const { dry, print, runInBand } = flags;
const args: string[] = [];

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

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

args = args.concat(["--transform", transformerPath]);
args.push("--transform", transformerPath);

if (flags.jscodeshift) {
args = args.concat(flags.jscodeshift);
if (jscodeshift) {
args.push(...jscodeshift);
}

args = args.concat(files);
args.push(...files);

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

const result = execa.sync(jscodeshiftExecutable, args, {
const result = execaSync(jscodeshiftExecutable, args, {
stdio: "inherit",
stripFinalNewline: false,
});
Expand All @@ -96,17 +122,32 @@ const TRANSFORMER_INQUIRER_CHOICES = [
},
];

function expandFilePathsIfNeeded(filesBeforeExpansion) {
function expandFilePathsIfNeeded(filesBeforeExpansion: string[]) {
const shouldExpandFiles = filesBeforeExpansion.some((file) =>
file.includes("*")
);
return shouldExpandFiles
? globby.sync(filesBeforeExpansion)
? globbySync(filesBeforeExpansion)
: filesBeforeExpansion;
}

const flagsSchema = {
force: { type: "boolean" },
dry: { type: "boolean" },
print: { type: "boolean" },
runInBand: { type: "boolean" },
jscodeshift: { type: "string", isMultiple: true },
help: { type: "boolean", shortFlag: "h" },
} as const satisfies AnyFlags;

interface PromptAnswers {
files?: string;
transformer?: string;
}

export function run() {
const cli = meow({
importMeta: import.meta,
description: "Codemods for updating chakra-react-select in applications.",
help: `
Usage
Expand All @@ -119,14 +160,8 @@ export function run() {
--print Print transformed files to your terminal
--jscodeshift (Advanced) Pass options directly to jscodeshift
`,
flags: {
boolean: ["force", "dry", "print", "help"],
string: ["_"],
alias: {
h: "help",
},
},
} as meow.Options<meow.AnyFlags>);
flags: flagsSchema,
});

if (!cli.flags.dry) {
checkGitStatus(!!cli.flags.force);
Expand All @@ -143,44 +178,54 @@ export function run() {
process.exit(1);
}

inquirer
.prompt([
{
type: "input",
name: "files",
message: "On which files or directory should the codemods be applied?",
when: !cli.input[1],
default: ".",
filter: (files) => files.trim(),
},
{
type: "list",
name: "transformer",
message: "Which transform would you like to apply?",
when: !cli.input[0],
pageSize: TRANSFORMER_INQUIRER_CHOICES.length,
choices: TRANSFORMER_INQUIRER_CHOICES,
},
])
.then((answers) => {
const { files, transformer } = answers;

const filesBeforeExpansion = cli.input[1] || files;
const filesExpanded = expandFilePathsIfNeeded([filesBeforeExpansion]);

const selectedTransformer = cli.input[0] || transformer;

if (!filesExpanded.length) {
console.log(
`No files found matching ${filesBeforeExpansion.join(" ")}`
);
return null;
}

return runTransform({
files: filesExpanded,
flags: cli.flags,
transformer: selectedTransformer,
});
const questions: DistinctQuestion<PromptAnswers>[] = [
{
type: "input",
name: "files",
message: "On which files or directory should the codemods be applied?",
when: !cli.input[1],
default: ".",
filter: (files: string) => files.trim(),
},
{
type: "select",
name: "transformer",
message: "Which transform would you like to apply?",
when: !cli.input[0],
pageSize: TRANSFORMER_INQUIRER_CHOICES.length,
choices: TRANSFORMER_INQUIRER_CHOICES,
},
];

inquirer.prompt<PromptAnswers>(questions).then((answers) => {
const { files, transformer } = answers;

const filesBeforeExpansion = cli.input[1] || files;
if (!filesBeforeExpansion) {
console.log("No files or directory provided.");
return null;
}

const filesExpanded = expandFilePathsIfNeeded([filesBeforeExpansion]);

const selectedTransformer = cli.input[0] || transformer;
if (!selectedTransformer) {
console.log("No transformer selected.");
return null;
}

if (!filesExpanded.length) {
console.log(`No files found matching ${filesBeforeExpansion}`);
return null;
}

return runTransform({
files: filesExpanded,
transformer: selectedTransformer,
dry: cli.flags.dry,
print: cli.flags.print,
runInBand: cli.flags.runInBand,
jscodeshift: cli.flags.jscodeshift,
});
});
}
4 changes: 3 additions & 1 deletion codemod/bin/crs-codemod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,6 @@
*
* @see {@link https://github.com/vercel/next.js/blob/dc9f30c1064ea72aef2fd046da2f1d2722b89735/packages/next-codemod/bin/next-codemod.ts}
*/
require("./cli").run();
import { run } from "./cli.js";

run();
30 changes: 17 additions & 13 deletions codemod/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,27 +14,31 @@
"bin/*.js",
"transforms/*.js"
],
"type": "module",
"scripts": {
"prebuild": "rimraf **/*.{d.ts,js}",
"prebuild": "rimraf bin/*.{d.ts,js} transforms/*.{d.ts,js}",
"build": "tsc -d -p tsconfig.json",
"dev": "tsc -d -w -p tsconfig.json",
"test": "vitest run",
"test:watch": "vitest",
"test:update": "vitest run -u",
"prepublishOnly": "pnpm build"
},
"dependencies": {
"execa": "^4.1.0",
"globby": "^11.1.0",
"inquirer": "^7.3.3",
"execa": "^9.6.1",
"globby": "^16.2.0",
"inquirer": "^13.4.3",
"is-git-clean": "^1.1.0",
"jscodeshift": "^0.15.0",
"meow": "^7.1.1",
"picocolors": "^1.0.0"
"jscodeshift": "^17.3.0",
"meow": "^14.1.0",
"picocolors": "^1.1.1"
},
"devDependencies": {
"@types/inquirer": "^7.3.3",
"@types/is-git-clean": "^1.1.0",
"@types/jscodeshift": "^0.11.7",
"@types/node": "^20.8.4",
"rimraf": "^5.0.5",
"typescript": "^5.2.2"
"@types/is-git-clean": "^1.1.2",
"@types/jscodeshift": "^17.3.0",
"@types/node": "^24.12.4",
"rimraf": "^6.1.3",
"typescript": "^6.0.3",
"vitest": "^4.1.6"
}
}
5 changes: 5 additions & 0 deletions codemod/tests/fixtures/v5/aliased-import.input.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { Select as MySelect } from "chakra-react-select";

export const Example = () => (
<MySelect useBasicStyles selectedOptionColor="purple" colorScheme="red" />
);
5 changes: 5 additions & 0 deletions codemod/tests/fixtures/v5/aliased-import.output.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { Select as MySelect } from "chakra-react-select";

export const Example = () => (
<MySelect selectedOptionColorScheme="purple" tagColorScheme="red" />
);
15 changes: 15 additions & 0 deletions codemod/tests/fixtures/v5/all-select-variants.input.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import {
AsyncCreatableSelect,
AsyncSelect,
CreatableSelect,
Select,
} from "chakra-react-select";

export const Example = () => (
<>
<Select useBasicStyles colorScheme="blue" />
<AsyncSelect hasStickyGroupHeaders selectedOptionColor="green" />
<CreatableSelect useBasicStyles selectedOptionColor="red" />
<AsyncCreatableSelect colorScheme="purple" hasStickyGroupHeaders />
</>
);
15 changes: 15 additions & 0 deletions codemod/tests/fixtures/v5/all-select-variants.output.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import {
AsyncCreatableSelect,
AsyncSelect,
CreatableSelect,
Select,
} from "chakra-react-select";

export const Example = () => (
<>
<Select tagColorScheme="blue" />
<AsyncSelect selectedOptionColorScheme="green" />
<CreatableSelect selectedOptionColorScheme="red" />
<AsyncCreatableSelect tagColorScheme="purple" />
</>
);
10 changes: 10 additions & 0 deletions codemod/tests/fixtures/v5/basic.input.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { Select } from "chakra-react-select";

export const Example = () => (
<Select
useBasicStyles
hasStickyGroupHeaders
selectedOptionColor="blue"
colorScheme="green"
/>
);
5 changes: 5 additions & 0 deletions codemod/tests/fixtures/v5/basic.output.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { Select } from "chakra-react-select";

export const Example = () => (
<Select selectedOptionColorScheme="blue" tagColorScheme="green" />
);
12 changes: 12 additions & 0 deletions codemod/tests/fixtures/v5/preserves-other-props.input.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { Select } from "chakra-react-select";

export const Example = () => (
<Select
isMulti
placeholder="Select an option"
useBasicStyles
options={[{ label: "A", value: "a" }]}
selectedOptionColor="blue"
onChange={(value) => console.log(value)}
/>
);
10 changes: 10 additions & 0 deletions codemod/tests/fixtures/v5/preserves-other-props.output.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { Select } from "chakra-react-select";

export const Example = () => (
<Select
isMulti
placeholder="Select an option"
options={[{ label: "A", value: "a" }]}
selectedOptionColorScheme="blue"
onChange={(value) => console.log(value)} />
);
Loading
Loading