Skip to content

Commit

Permalink
feat: Optional codemod for migrating enums (#2543)
Browse files Browse the repository at this point in the history
  • Loading branch information
rivka-ungar authored Nov 6, 2024
1 parent 601b44d commit 3bf7563
Show file tree
Hide file tree
Showing 6 changed files with 845 additions and 33 deletions.
91 changes: 60 additions & 31 deletions packages/codemod/bin/vibe-codemod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ import yargs from "yargs";
import { hideBin } from "yargs/helpers";

const mapMigrationType: { [key: string]: string } = {
v3: "v2-to-v3"
v3: "v2-to-v3",
enums: "v2-to-v3/enums"
};

const migrations = Object.keys(mapMigrationType);
Expand All @@ -21,7 +22,7 @@ const argv = yargs(hideBin(process.argv))
.option("migration", {
alias: "m",
type: "string",
description: "Migration type to run (e.g., v3)",
description: "Migration type to run (e.g., v3, enums)",
choices: migrations
})
.option("target", {
Expand Down Expand Up @@ -112,6 +113,15 @@ async function main() {
}
}

const isVibeCoreInstalled = checkIfPackageExists("@vibe/core");
if (migrationType === "v3" && !isVibeCoreInstalled) {
console.log(chalk.yellow("Warning: You need to install @vibe/core package to fully apply the v3 migration."));
}
if (migrationType === "enums" && !isVibeCoreInstalled) {
console.log(chalk.red("Error: Please install @vibe/core to run the enum migration."));
process.exit(1);
}

if (!isGitClean()) {
console.warn(
chalk.yellow(
Expand All @@ -126,6 +136,34 @@ async function main() {
}
}

async function processTransformations(transformationFiles: string[], type = "Transformation") {
for (let index = 0; index < transformationFiles.length; index++) {
const transform = transformationFiles[index];
const transformName = path.basename(transform, path.extname(transform));

spinner.text = `Processing ${type.toLowerCase()} (${index + 1}/${transformationFiles.length}): ${transformName}`;

try {
const result = await runSingleTransformation(transform);

if (result) {
successCount++;
spinner.succeed(chalk.green(`${type} completed: ${transformName}`));
} else {
failureCount++;
spinner.fail(chalk.red(`${type} finished with errors: ${transformName}`));
}
} catch (error) {
failureCount++;
spinner.fail(chalk.red(`${type} failed: ${transformName}`));
}

if (index < transformationFiles.length - 1) {
spinner.start();
}
}
}

const verbosityLevel: number = verbose ? 2 : 0;
const outputTarget: string = verbose ? `>> "${logFile}"` : "";

Expand Down Expand Up @@ -204,40 +242,19 @@ async function main() {
process.exit(1);
}

const filesToProcessLast = [
resolve(transformationsDir, "type-imports-migration.js"),
resolve(transformationsDir, "packages-rename-migration.js")
];
const filesToProcessLast =
migrationType === "v3"
? [
resolve(transformationsDir, "type-imports-migration.js"),
resolve(transformationsDir, "packages-rename-migration.js")
]
: [];
const orderedTransformationFiles = [
...transformationFiles.filter(file => !filesToProcessLast.includes(file)),
...filesToProcessLast
];

for (let index = 0; index < orderedTransformationFiles.length; index++) {
const transform = orderedTransformationFiles[index];
const transformName = path.basename(transform, path.extname(transform));

spinner.text = `Processing transformation (${index + 1}/${orderedTransformationFiles.length}): ${transformName}`;

try {
const result = await runSingleTransformation(transform);

if (result) {
successCount++;
spinner.succeed(chalk.green(`Transformation completed: ${transformName}`));
} else {
failureCount++;
spinner.fail(chalk.red(`Transformation finished with errors: ${transformName}`));
}
} catch (error) {
failureCount++;
spinner.fail(chalk.red(`Transformation failed: ${transformName}`));
}

if (index < orderedTransformationFiles.length - 1) {
spinner.start();
}
}
await processTransformations(orderedTransformationFiles);

spinner.stop();

Expand All @@ -250,4 +267,16 @@ async function main() {
}
}

function checkIfPackageExists(packageName: string): boolean {
const packageJsonPath = path.resolve(process.cwd(), "package.json");
if (!fs.existsSync(packageJsonPath)) {
console.error(chalk.red(`Error: package.json not found at ${packageJsonPath}`));
process.exit(1);
}
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
const dependencies = packageJson.dependencies || {};
const version = dependencies[packageName];
return !!version;
}

main();
4 changes: 2 additions & 2 deletions packages/codemod/src/utils/import-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ export function getImports(root: Collection, path: string): Collection<ImportDec
/**
* Retrieves all "monday-ui-react-core" import declarations for a root.
*/
export function getCoreImportsForFile(root: Collection): Collection<ImportDeclaration> {
return getImports(root, CORE_IMPORT_PATH);
export function getCoreImportsForFile(root: Collection, path = CORE_IMPORT_PATH): Collection<ImportDeclaration> {
return getImports(root, path);
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { TransformationContext } from "../../../../types";
import { getCoreImportsForFile, getPropValue, setPropValue, wrap } from "../../../../src/utils";
import enumToStringMapping from "./enumMappings.json";
import { NEW_CORE_IMPORT_PATH } from "../../../../src/consts";

const enumToString: Record<string, string> = enumToStringMapping;

/**
* Replace enums with string equivalent
*/
function transform({ j, root }: TransformationContext) {
// Since it runs after the imports are updated need to get the new import path
const coreImports = getCoreImportsForFile(root, NEW_CORE_IMPORT_PATH);

const importedComponents = coreImports
.find(j.ImportSpecifier)
.nodes()
.map(importSpecifier => {
if (importSpecifier.local && importSpecifier.local.name) {
return importSpecifier.local.name;
}
return null;
})
.filter(Boolean);

const allElements = root.find(j.JSXElement).nodes();

const elements = allElements.filter(path => {
const openingElement = path.openingElement;
const elementName = openingElement.name.type === "JSXIdentifier" ? openingElement.name.name : null;
return elementName && importedComponents.includes(elementName);
});

if (!elements.length) return;
elements.forEach(elementPath => {
const props = j(elementPath).find(j.JSXOpeningElement).find(j.JSXAttribute);
props.forEach(prop => {
const propValue = getPropValue(j, prop.node) as string;
if (enumToString[propValue]) {
setPropValue(j, prop, {
value: enumToString[propValue],
type: "Literal"
});
}
});
});
}

export default wrap(transform);
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import transform from "../Enums-migration";
import { defineInlineTest } from "jscodeshift/src/testUtils";

function prependImport(source: string): string {
return `
import { Button } from "@vibe/core";
${source}
`;
}

describe("Enums migration", () => {
defineInlineTest(
transform,
{},
prependImport(`<Button kind={Button.kinds.PRIMARY}/>`),
prependImport(`<Button kind="primary"/>`),
"should update enum"
);

defineInlineTest(
transform,
{},
prependImport(`<Button kind={Button.kinds.SECONDARY} size={Button.sizes.SMALL} />`),
prependImport(`<Button kind="secondary" size="small" />`),
"should update enum"
);

defineInlineTest(
transform,
{},
`
import { Button } from "another-library";
<Button size={Button.sizes.SMALL} />
`,
`
import { Button } from "another-library";
<Button size={Button.sizes.SMALL} />
`,
"should not change component is not imported from vibe"
);
});
Loading

0 comments on commit 3bf7563

Please sign in to comment.