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'
+ });
+}