diff --git a/docs/MigrationGuide.mdx b/docs/MigrationGuide.mdx index 2b1f289c859..1a11226faae 100644 --- a/docs/MigrationGuide.mdx +++ b/docs/MigrationGuide.mdx @@ -1,6 +1,7 @@ +import './MigrationGuide.css'; import { Footer, TableOfContent } from '@sb/components'; import { Meta } from '@storybook/blocks'; -import './MigrationGuide.css'; +import { MessageStrip } from '@ui5/webcomponents-react'; @@ -14,10 +15,35 @@ or the [changelog](?path=/docs/change-log--page)._ > This migration guide only covers breaking changes when updating from v1 to v2. > For migration guides for older releases, please refer to our [Migration Guide Archive](https://github.com/SAP/ui5-webcomponents-react/blob/main/docs/MigrationGuide.archive.md). -
- +## Codemod + +To make the migration to UI5 Web Components (for React) v2 easier, +we provide a codemod which tries to transform most of the breaking changes. + + + The codemod is a best efforts attempt to help you migrate the breaking change. Please review the generated code + thoroughly! +
+ + Applying the codemod might break your code formatting, so please don't forget to run prettier and/or eslint + after you've applied the codemod! + + + } +/> + +```shell +npx @ui5/webcomponents-react-cli codemod --transform v2 \ + --src ./path/to/src \ + --typescript # only if you use TypeScript in your project, omit if you use JavaScript +``` + ## General changes ### Removed `react-jss` diff --git a/packages/cli/src/bin/index.ts b/packages/cli/src/bin/index.ts index c985c807d2d..5ebf37b955a 100755 --- a/packages/cli/src/bin/index.ts +++ b/packages/cli/src/bin/index.ts @@ -3,26 +3,25 @@ import { resolve } from 'node:path'; import { parseArgs } from 'node:util'; import * as process from 'process'; -const options = { - packageName: { - type: 'string' as const - }, - out: { - type: 'string' as const, - short: 'o' - }, - additionalComponentNote: { - type: 'string' as const - } -}; -const { values, positionals } = parseArgs({ options, allowPositionals: true }); +const { positionals } = parseArgs({ allowPositionals: true, strict: false }); const [command] = positionals; -console.log(command); - switch (command) { case 'create-wrappers': { + const wrapperOptions = { + packageName: { + type: 'string' as const + }, + out: { + type: 'string' as const, + short: 'o' + }, + additionalComponentNote: { + type: 'string' as const + } + }; + const { values } = parseArgs({ options: wrapperOptions, allowPositionals: true }); const { packageName, out, additionalComponentNote } = values; const missingParameters = []; if (!packageName) { @@ -51,9 +50,74 @@ switch (command) { } case 'resolve-cem': { + const cemOptions = { + packageName: { + type: 'string' as const + }, + out: { + type: 'string' as const, + short: 'o' + } + }; + const { values } = parseArgs({ options: cemOptions, allowPositionals: true }); + const { packageName, out } = values; + const missingParameters = []; + if (!packageName) { + missingParameters.push('--packageName'); + } + if (!out) { + missingParameters.push('--out'); + } + + if (missingParameters.length > 0) { + console.error(` + Missing parameters: ${missingParameters.join(', ')} + Example: ui5-wcr resolve-cem --packageName @ui5/webcomponents --out ./src/components + + Please add the missing parameters and try again. + `); + process.exit(1); + } + const resolveCEM = await import('../scripts/resolve-cem/main.js'); - const outPath = resolve(process.cwd(), values.out!); - await resolveCEM.default(values.packageName!, outPath); + const outPath = resolve(process.cwd(), out!); + await resolveCEM.default(packageName!, outPath); + break; + } + + case 'codemod': { + const codemodOptions = { + transform: { + type: 'string' as const + }, + src: { + type: 'string' as const + }, + typescript: { + type: 'boolean' as const, + default: false + } + }; + const { values } = parseArgs({ options: codemodOptions, allowPositionals: true }); + const missingParameters = []; + if (!values.src) { + missingParameters.push('--src'); + } + if (!values.transform) { + missingParameters.push('--transform'); + } + + if (missingParameters.length > 0) { + console.error(` + Missing parameters: ${missingParameters.join(', ')} + Example: ui5-wcr codemod --src ./src --transform v2 (--typescript) + + Please add the missing parameters and try again. + `); + process.exit(1); + } + const codemod = await import('../scripts/codemod/main.js'); + await codemod.default(values.transform!, values.src!, values.typescript!); break; } default: diff --git a/packages/cli/src/scripts/codemod/main.ts b/packages/cli/src/scripts/codemod/main.ts new file mode 100644 index 00000000000..e2430371022 --- /dev/null +++ b/packages/cli/src/scripts/codemod/main.ts @@ -0,0 +1,40 @@ +import childProcess from 'node:child_process'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const SUPPORTED_TRANSFORMERS = ['v2']; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +const transformerDir = path.resolve(__dirname, 'transforms'); + +export default async function runCodemod(transform: string, inputDir: string, useTypeScript: boolean) { + if (!SUPPORTED_TRANSFORMERS.includes(transform)) { + // eslint-disable-next-line no-console + console.error('Invalid transform choice, pick one of:'); + // eslint-disable-next-line no-console + console.error(SUPPORTED_TRANSFORMERS.map((x) => '- ' + x).join('\n')); + process.exit(1); + } + + const jscodeshiftOptions = []; + + const transformerPath = path.join(transformerDir, transform, `main.cjs`); + jscodeshiftOptions.push(`--transform`, transformerPath); + + if (useTypeScript) { + jscodeshiftOptions.push('--extensions=tsx,ts,jsx,js'); + jscodeshiftOptions.push('--parser', 'tsx'); + } else { + jscodeshiftOptions.push('--extensions=jsx,js'); + } + + jscodeshiftOptions.push('--ignore-pattern=**/node_modules/**'); + + // eslint-disable-next-line no-console + console.log(`Executing 'npx jscodeshift ${jscodeshiftOptions.join(' ')} ${inputDir}'`); + childProcess.spawnSync('npx', ['jscodeshift', ...jscodeshiftOptions, inputDir], { + stdio: 'inherit' + }); +}