@carbon/upgrade 11.23.0-rc.0 → 11.24.0-rc.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/cli.js CHANGED
@@ -52641,6 +52641,87 @@ var upgrades = [
52641
52641
  });
52642
52642
  }
52643
52643
  },
52644
+ {
52645
+ name: "enable-v12-overflowmenu",
52646
+ description: `
52647
+ Updates OverflowMenu components to v12 with optional FeatureFlags wrapping:
52648
+ 1. Migrates to new API (OverflowMenuItem -> MenuItem)
52649
+ 2. Updates props (itemText -> label, etc)
52650
+ 3. Optional FeatureFlags wrapping (--wrapWithFeatureFlag=false to disable)
52651
+
52652
+ Example:
52653
+ Before: <OverflowMenu aria-label="menu"><OverflowMenuItem itemText="Option" /></OverflowMenu>
52654
+ After: <FeatureFlags enableV12Overflowmenu>
52655
+ <OverflowMenu label="menu"><MenuItem label="Option" /></OverflowMenu>
52656
+ </FeatureFlags>
52657
+ `,
52658
+ migrate: async (options) => {
52659
+ const transform = import_path2.default.join(
52660
+ TRANSFORM_DIR,
52661
+ "enable-v12-overflowmenu.js"
52662
+ );
52663
+ const paths = Array.isArray(options.paths) && options.paths.length > 0 ? options.paths : await (0, import_fast_glob2.default)(["**/*.{js,jsx,ts,tsx}"], {
52664
+ cwd: options.workspaceDir,
52665
+ ignore: [
52666
+ "**/es/**",
52667
+ "**/lib/**",
52668
+ "**/umd/**",
52669
+ "**/node_modules/**",
52670
+ "**/storybook-static/**"
52671
+ ]
52672
+ });
52673
+ await run2({
52674
+ dry: !options.write,
52675
+ transform,
52676
+ paths,
52677
+ verbose: options.verbose,
52678
+ wrapWithFeatureFlag: options.wrapWithFeatureFlag
52679
+ });
52680
+ }
52681
+ },
52682
+ {
52683
+ name: "slug-prop-to-decorator-prop",
52684
+ description: `
52685
+ Replace slug prop with decorator
52686
+
52687
+ Transforms:
52688
+ <Component slug="value">
52689
+ content
52690
+ </Component>
52691
+
52692
+ Into:
52693
+ <Component decorator="value">
52694
+ content
52695
+ </Component>
52696
+ `,
52697
+ migrate: async (options) => {
52698
+ const transform = import_path2.default.join(
52699
+ TRANSFORM_DIR,
52700
+ "slug-prop-to-decorator-prop.js"
52701
+ );
52702
+ const paths = Array.isArray(options.paths) && options.paths.length > 0 ? options.paths : await (0, import_fast_glob2.default)(["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx"], {
52703
+ cwd: options.workspaceDir,
52704
+ ignore: [
52705
+ "**/es/**",
52706
+ "**/lib/**",
52707
+ "**/umd/**",
52708
+ "**/node_modules/**",
52709
+ "**/storybook-static/**",
52710
+ "**/dist/**",
52711
+ "**/build/**",
52712
+ "**/*.d.ts",
52713
+ "**/coverage/**"
52714
+ ]
52715
+ });
52716
+ await run2({
52717
+ dry: !options.write,
52718
+ transform,
52719
+ paths,
52720
+ verbose: options.verbose,
52721
+ parser: "tsx"
52722
+ });
52723
+ }
52724
+ },
52644
52725
  {
52645
52726
  name: "refactor-light-to-layer",
52646
52727
  description: `
@@ -52886,7 +52967,7 @@ var upgrades = [
52886
52967
  var package_default = {
52887
52968
  name: "@carbon/upgrade",
52888
52969
  description: "A tool for upgrading Carbon versions",
52889
- version: "11.23.0-rc.0",
52970
+ version: "11.24.0-rc.0",
52890
52971
  license: "Apache-2.0",
52891
52972
  bin: {
52892
52973
  "carbon-upgrade": "./bin/carbon-upgrade.js"
@@ -52926,7 +53007,7 @@ var package_default = {
52926
53007
  devDependencies: {
52927
53008
  chalk: "^4.1.1",
52928
53009
  "change-case": "^4.1.2",
52929
- esbuild: "^0.24.0",
53010
+ esbuild: "^0.25.0",
52930
53011
  execa: "^5.1.1",
52931
53012
  "fast-glob": "^3.2.11",
52932
53013
  "fs-extra": "^11.0.0",
@@ -52966,6 +53047,10 @@ async function main({ argv, cwd }) {
52966
53047
  default: false,
52967
53048
  describe: "optionally include additional logs, useful for debugging",
52968
53049
  type: "boolean"
53050
+ }).option("wrapWithFeatureFlag", {
53051
+ default: true,
53052
+ describe: "wrap the migration with a feature flag",
53053
+ type: "boolean"
52969
53054
  });
52970
53055
  cli.usage("Usage: $0 [options]").command(
52971
53056
  ["upgrade", "$0"],
@@ -52997,14 +53082,19 @@ async function main({ argv, cwd }) {
52997
53082
  );
52998
53083
  },
52999
53084
  run3(async (args) => {
53000
- const { verbose, migration, write, paths } = args;
53085
+ const { verbose, migration, write, paths, wrapWithFeatureFlag } = args;
53001
53086
  const options = {
53002
53087
  cwd: cwd(),
53003
53088
  verbose,
53004
53089
  write,
53005
53090
  migration,
53006
- paths
53091
+ paths,
53092
+ wrapWithFeatureFlag
53007
53093
  };
53094
+ console.log(
53095
+ "CLI options wrapWithFeatureFlag:",
53096
+ options.wrapWithFeatureFlag
53097
+ );
53008
53098
  await migrate(options, upgrades);
53009
53099
  })
53010
53100
  );
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@carbon/upgrade",
3
3
  "description": "A tool for upgrading Carbon versions",
4
- "version": "11.23.0-rc.0",
4
+ "version": "11.24.0-rc.0",
5
5
  "license": "Apache-2.0",
6
6
  "bin": {
7
7
  "carbon-upgrade": "./bin/carbon-upgrade.js"
@@ -41,7 +41,7 @@
41
41
  "devDependencies": {
42
42
  "chalk": "^4.1.1",
43
43
  "change-case": "^4.1.2",
44
- "esbuild": "^0.24.0",
44
+ "esbuild": "^0.25.0",
45
45
  "execa": "^5.1.1",
46
46
  "fast-glob": "^3.2.11",
47
47
  "fs-extra": "^11.0.0",
@@ -61,5 +61,5 @@
61
61
  "@ibm/telemetry-js": "^1.5.0",
62
62
  "jscodeshift": "^17.0.0"
63
63
  },
64
- "gitHead": "9cf96c5f68ef9216d1c73a5dfb7bdb3bd007b0c1"
64
+ "gitHead": "faf649817d3be3e8e258aba866e14e9378b5c68e"
65
65
  }
@@ -0,0 +1,41 @@
1
+ //prettier-ignore
2
+ import React from 'react';
3
+ import { OverflowMenu, OverflowMenuItem } from '@carbon/react';
4
+
5
+ function TestComponent({ menuProps }) {
6
+ return (
7
+ <div>
8
+ {/* Old API usage - with explicit props */}
9
+ <OverflowMenu
10
+ aria-label="overflow-menu"
11
+ align="bottom"
12
+ flipped={true}
13
+ light={true}
14
+ size="xl">
15
+ <OverflowMenuItem
16
+ className="test-class"
17
+ itemText="Stop app"
18
+ disabled={false}
19
+ onClick={() => {}}
20
+ />
21
+ <OverflowMenuItem itemText="Restart app" />
22
+ <OverflowMenuItem hasDivider isDelete itemText="Delete app" />
23
+ </OverflowMenu>
24
+
25
+ {/* Old API with spread props */}
26
+ <OverflowMenu {...menuProps}>
27
+ <OverflowMenuItem itemText="Dynamic item" />
28
+ <OverflowMenuItem hasDivider isDelete itemText="Remove" />
29
+ </OverflowMenu>
30
+
31
+ {/* Already using new API - should not be transformed */}
32
+ <OverflowMenu label="Already migrated">
33
+ <MenuItem label="Option 1" />
34
+ <MenuItemDivider />
35
+ <MenuItem label="Delete" kind="danger" />
36
+ </OverflowMenu>
37
+ </div>
38
+ );
39
+ }
40
+
41
+ export default TestComponent;
@@ -0,0 +1,37 @@
1
+ //prettier-ignore
2
+ import React from 'react';
3
+ import type { FC } from 'react';
4
+ import { OverflowMenu, OverflowMenuItem } from '@carbon/react';
5
+
6
+ interface MenuItem {
7
+ id: string;
8
+ label: string;
9
+ action?: () => void;
10
+ }
11
+
12
+ interface Props {
13
+ items: MenuItem[];
14
+ }
15
+
16
+ const TestComponent: FC<Props> = ({ items }) => {
17
+ return (
18
+ <div>
19
+ {/* Old API usage - mapped items */}
20
+ <OverflowMenu aria-label="mapped-menu">
21
+ {items.map((item) => (
22
+ <OverflowMenuItem
23
+ key={item.id}
24
+ itemText={item.label}
25
+ onClick={item.action}
26
+ />
27
+ ))}
28
+ </OverflowMenu>
29
+ {/* Old API - explicit props */}
30
+ <OverflowMenu direction="top" size="lg" flipped={true}>
31
+ <OverflowMenuItem hasDivider isDelete itemText="TypeScript Item" />
32
+ </OverflowMenu>
33
+ </div>
34
+ );
35
+ };
36
+
37
+ export default TestComponent;
@@ -0,0 +1,50 @@
1
+ //prettier-ignore
2
+ import React from 'react';
3
+ import { OverflowMenu, OverflowMenuItem, MenuItem, MenuItemDivider, Button } from '@carbon/react';
4
+ import { FeatureFlags } from '@carbon/feature-flags';
5
+
6
+ function TestComponent({ menuProps }) {
7
+ return (
8
+ <div>
9
+ {/* Old API usage with props - transformed */}
10
+ <OverflowMenu
11
+ label="overflow-menu"
12
+ align="bottom"
13
+ flipped={true}
14
+ light={true}
15
+ size="xl"
16
+ >
17
+ <MenuItem
18
+ className="test-class"
19
+ label="Stop app"
20
+ disabled={false}
21
+ onClick={() => {}}
22
+ />
23
+ <MenuItem label="Restart app" />
24
+ <MenuItemDivider />
25
+ <MenuItem label="Delete app" kind="danger" />
26
+ </OverflowMenu>
27
+
28
+ {/* Old API with spread props */}
29
+ <OverflowMenu {...menuProps}>
30
+ <MenuItem label="Dynamic item" />
31
+ <MenuItemDivider />
32
+ <MenuItem label="Remove" kind="danger" />
33
+ </OverflowMenu>
34
+
35
+ {/* Already using new API - should not be transformed */}
36
+ <FeatureFlags enableV12Overflowmenu>
37
+ <OverflowMenu label="Already migrated">
38
+ <MenuItem label="Option 1" />
39
+ <MenuItemDivider />
40
+ <MenuItem label="Delete" kind="danger" />
41
+ </OverflowMenu>
42
+ </FeatureFlags>
43
+
44
+ {/* Other components - unchanged */}
45
+ <Button>Normal button</Button>
46
+ </div>
47
+ );
48
+ }
49
+
50
+ export default TestComponent;
@@ -0,0 +1,42 @@
1
+ //prettier-ignore
2
+ import React from 'react';
3
+ import { FeatureFlags } from '@carbon/feature-flags';
4
+ import type { FC } from 'react';
5
+ import { MenuItem, MenuItemDivider, OverflowMenu, OverflowMenuItem } from '@carbon/react';
6
+
7
+ interface MenuItem {
8
+ id: string;
9
+ label: string;
10
+ action?: () => void;
11
+ }
12
+
13
+ interface Props {
14
+ items: MenuItem[];
15
+ }
16
+
17
+ const TestComponent: FC<Props> = ({ items }) => {
18
+ return (
19
+ (<div>
20
+ {/* Old API usage - mapped items */}
21
+ <FeatureFlags enableV12Overflowmenu>
22
+ <OverflowMenu aria-label="mapped-menu">
23
+ {items.map((item) => (
24
+ <OverflowMenuItem
25
+ key={item.id}
26
+ itemText={item.label}
27
+ onClick={item.action}
28
+ />
29
+ ))}
30
+ </OverflowMenu>
31
+ </FeatureFlags>
32
+ {/* Old API - explicit props */}
33
+ <FeatureFlags enableV12Overflowmenu>
34
+ <OverflowMenu direction="top" size="lg" flipped={true}>
35
+ <MenuItemDivider /><MenuItem kind='danger' label="TypeScript Item" />
36
+ </OverflowMenu>
37
+ </FeatureFlags>
38
+ </div>)
39
+ );
40
+ };
41
+
42
+ export default TestComponent;
@@ -0,0 +1,45 @@
1
+ //prettier-ignore
2
+ import React from 'react';
3
+ import { OverflowMenu, OverflowMenuItem, Button } from '@carbon/react';
4
+
5
+ function TestComponent({ menuProps }) {
6
+ return (
7
+ <div>
8
+ {/* Old API usage - with explicit props */}
9
+ <OverflowMenu
10
+ aria-label="overflow-menu"
11
+ flipped={true}
12
+ light={true}
13
+ size="xl">
14
+ <OverflowMenuItem
15
+ className="test-class"
16
+ itemText="Stop app"
17
+ disabled={false}
18
+ onClick={() => {}}
19
+ />
20
+ <OverflowMenuItem itemText="Restart app" />
21
+ <OverflowMenuItem hasDivider isDelete itemText="Delete app" />
22
+ </OverflowMenu>
23
+
24
+ {/* Old API with spread props */}
25
+ <OverflowMenu {...menuProps}>
26
+ <OverflowMenuItem itemText="Dynamic item" />
27
+ <OverflowMenuItem hasDivider isDelete itemText="Remove" />
28
+ </OverflowMenu>
29
+
30
+ {/* Already using new API - should not be transformed */}
31
+ <FeatureFlags enableV12Overflowmenu>
32
+ <OverflowMenu label="Already migrated">
33
+ <MenuItem label="Option 1" />
34
+ <MenuItemDivider />
35
+ <MenuItem label="Delete" kind="danger" />
36
+ </OverflowMenu>
37
+ </FeatureFlags>
38
+
39
+ {/* Other components - should not be transformed */}
40
+ <Button>Normal button</Button>
41
+ </div>
42
+ );
43
+ }
44
+
45
+ export default TestComponent;
@@ -0,0 +1,37 @@
1
+ //prettier-ignore
2
+ import React from 'react';
3
+ import type { FC } from 'react';
4
+ import { OverflowMenu, OverflowMenuItem } from '@carbon/react';
5
+
6
+ interface MenuItem {
7
+ id: string;
8
+ label: string;
9
+ action?: () => void;
10
+ }
11
+
12
+ interface Props {
13
+ items: MenuItem[];
14
+ }
15
+
16
+ const TestComponent: FC<Props> = ({ items }) => {
17
+ return (
18
+ <div>
19
+ {/* Old API usage - mapped items */}
20
+ <OverflowMenu aria-label="mapped-menu">
21
+ {items.map((item) => (
22
+ <OverflowMenuItem
23
+ key={item.id}
24
+ itemText={item.label}
25
+ onClick={item.action}
26
+ />
27
+ ))}
28
+ </OverflowMenu>
29
+ {/* Old API - explicit props */}
30
+ <OverflowMenu direction="top" size="lg" flipped={true}>
31
+ <OverflowMenuItem hasDivider isDelete itemText="TypeScript Item" />
32
+ </OverflowMenu>
33
+ </div>
34
+ );
35
+ };
36
+
37
+ export default TestComponent;
@@ -0,0 +1,54 @@
1
+ //prettier-ignore
2
+ import React from 'react';
3
+ import { OverflowMenu, OverflowMenuItem, MenuItem, MenuItemDivider, Button } from '@carbon/react';
4
+ import { FeatureFlags } from '@carbon/feature-flags';
5
+
6
+ function TestComponent({ menuProps }) {
7
+ return (
8
+ (<div>
9
+ {/* Old API usage with props - transformed */}
10
+ <FeatureFlags enableV12Overflowmenu>
11
+ <OverflowMenu
12
+ label="overflow-menu"
13
+ align="bottom"
14
+ flipped={true}
15
+ light={true}
16
+ size="xl"
17
+ >
18
+ <MenuItem
19
+ className="test-class"
20
+ label="Stop app"
21
+ disabled={false}
22
+ onClick={() => {}}
23
+ />
24
+ <MenuItem label="Restart app" />
25
+ <MenuItemDivider />
26
+ <MenuItem label="Delete app" kind="danger" />
27
+ </OverflowMenu>
28
+ </FeatureFlags>
29
+
30
+ {/* Old API with spread props */}
31
+ <FeatureFlags enableV12Overflowmenu>
32
+ <OverflowMenu {...menuProps}>
33
+ <MenuItem label="Dynamic item" />
34
+ <MenuItemDivider />
35
+ <MenuItem label="Remove" kind="danger" />
36
+ </OverflowMenu>
37
+ </FeatureFlags>
38
+
39
+ {/* Already using new API - should not be transformed */}
40
+ <FeatureFlags enableV12Overflowmenu>
41
+ <OverflowMenu label="Already migrated">
42
+ <MenuItem label="Option 1" />
43
+ <MenuItemDivider />
44
+ <MenuItem label="Delete" kind="danger" />
45
+ </OverflowMenu>
46
+ </FeatureFlags>
47
+
48
+ {/* Other components - unchanged */}
49
+ <Button>Normal button</Button>
50
+ </div>)
51
+ );
52
+ }
53
+
54
+ export default TestComponent;
@@ -0,0 +1,42 @@
1
+ //prettier-ignore
2
+ import React from 'react';
3
+ import { FeatureFlags } from '@carbon/feature-flags';
4
+ import type { FC } from 'react';
5
+ import { MenuItem, MenuItemDivider, OverflowMenu, OverflowMenuItem } from '@carbon/react';
6
+
7
+ interface MenuItem {
8
+ id: string;
9
+ label: string;
10
+ action?: () => void;
11
+ }
12
+
13
+ interface Props {
14
+ items: MenuItem[];
15
+ }
16
+
17
+ const TestComponent: FC<Props> = ({ items }) => {
18
+ return (
19
+ (<div>
20
+ {/* Old API usage - mapped items */}
21
+ <FeatureFlags enableV12Overflowmenu>
22
+ <OverflowMenu aria-label="mapped-menu">
23
+ {items.map((item) => (
24
+ <OverflowMenuItem
25
+ key={item.id}
26
+ itemText={item.label}
27
+ onClick={item.action}
28
+ />
29
+ ))}
30
+ </OverflowMenu>
31
+ </FeatureFlags>
32
+ {/* Old API - explicit props */}
33
+ <FeatureFlags enableV12Overflowmenu>
34
+ <OverflowMenu direction="top" size="lg" flipped={true}>
35
+ <MenuItemDivider /><MenuItem kind='danger' label="TypeScript Item" />
36
+ </OverflowMenu>
37
+ </FeatureFlags>
38
+ </div>)
39
+ );
40
+ };
41
+
42
+ export default TestComponent;
@@ -0,0 +1,41 @@
1
+ import React from 'react';
2
+ import { Dropdown, Checkbox, Tag } from '@carbon/react';
3
+
4
+ function TestComponent() {
5
+ return (
6
+ //prettier-ignore
7
+ <div>
8
+ {/* Basic Dropdown usage */}
9
+ <Dropdown
10
+ label="Select an option"
11
+ slug="dropdown-1"
12
+ items={['Option 1', 'Option 2']}
13
+ id="dropdown-1"
14
+ titleText="Dropdown"
15
+ />
16
+ {/* Checkbox with expression */}
17
+ <Checkbox
18
+ labelText="Check me"
19
+ slug={'checkbox-1'}
20
+ id="checkbox-1"
21
+ />
22
+ {/* Tag with string literal */}
23
+ <Tag slug={'static-tag'} type="red">
24
+ Important
25
+ </Tag>
26
+ {/* Nested structure */}
27
+ <div>
28
+ <Tag slug="tag-1" type="blue">
29
+ Active
30
+ </Tag>
31
+ <Checkbox
32
+ slug="checkbox-2"
33
+ labelText="Enable feature"
34
+ id="checkbox-2"
35
+ />
36
+ </div>
37
+ </div>
38
+ );
39
+ }
40
+
41
+ export default TestComponent;
@@ -0,0 +1,41 @@
1
+ import React from 'react';
2
+ import { Dropdown, Checkbox, Tag } from '@carbon/react';
3
+
4
+ const TestComponent: React.FC = () => {
5
+ return (
6
+ //prettier-ignore
7
+ <div>
8
+ {/* Basic Dropdown usage */}
9
+ <Dropdown
10
+ label="Select an option"
11
+ slug="dropdown-1"
12
+ items={['Option 1', 'Option 2']}
13
+ id="dropdown-1"
14
+ titleText="Dropdown"
15
+ />
16
+ {/* Checkbox with expression */}
17
+ <Checkbox
18
+ labelText="Check me"
19
+ slug={'checkbox-1'}
20
+ id="checkbox-1"
21
+ />
22
+ {/* Tag with string literal */}
23
+ <Tag slug={'static-tag'} type="red">
24
+ Important
25
+ </Tag>
26
+ {/* Nested structure */}
27
+ <div>
28
+ <Tag slug="tag-1" type="blue">
29
+ Active
30
+ </Tag>
31
+ <Checkbox
32
+ slug="checkbox-2"
33
+ labelText="Enable feature"
34
+ id="checkbox-2"
35
+ />
36
+ </div>
37
+ </div>
38
+ );
39
+ };
40
+
41
+ export default TestComponent;
@@ -0,0 +1,41 @@
1
+ import React from 'react';
2
+ import { Dropdown, Checkbox, Tag } from '@carbon/react';
3
+
4
+ function TestComponent() {
5
+ return (
6
+ //prettier-ignore
7
+ (<div>
8
+ {/* Basic Dropdown usage */}
9
+ <Dropdown
10
+ label="Select an option"
11
+ decorator="dropdown-1"
12
+ items={['Option 1', 'Option 2']}
13
+ id="dropdown-1"
14
+ titleText="Dropdown"
15
+ />
16
+ {/* Checkbox with expression */}
17
+ <Checkbox
18
+ labelText="Check me"
19
+ decorator={'checkbox-1'}
20
+ id="checkbox-1"
21
+ />
22
+ {/* Tag with string literal */}
23
+ <Tag decorator={'static-tag'} type="red">
24
+ Important
25
+ </Tag>
26
+ {/* Nested structure */}
27
+ <div>
28
+ <Tag decorator="tag-1" type="blue">
29
+ Active
30
+ </Tag>
31
+ <Checkbox
32
+ decorator="checkbox-2"
33
+ labelText="Enable feature"
34
+ id="checkbox-2"
35
+ />
36
+ </div>
37
+ </div>)
38
+ );
39
+ }
40
+
41
+ export default TestComponent;
@@ -0,0 +1,41 @@
1
+ import React from 'react';
2
+ import { Dropdown, Checkbox, Tag } from '@carbon/react';
3
+
4
+ const TestComponent: React.FC = () => {
5
+ return (
6
+ //prettier-ignore
7
+ (<div>
8
+ {/* Basic Dropdown usage */}
9
+ <Dropdown
10
+ label="Select an option"
11
+ decorator="dropdown-1"
12
+ items={['Option 1', 'Option 2']}
13
+ id="dropdown-1"
14
+ titleText="Dropdown"
15
+ />
16
+ {/* Checkbox with expression */}
17
+ <Checkbox
18
+ labelText="Check me"
19
+ decorator={'checkbox-1'}
20
+ id="checkbox-1"
21
+ />
22
+ {/* Tag with string literal */}
23
+ <Tag decorator={'static-tag'} type="red">
24
+ Important
25
+ </Tag>
26
+ {/* Nested structure */}
27
+ <div>
28
+ <Tag decorator="tag-1" type="blue">
29
+ Active
30
+ </Tag>
31
+ <Checkbox
32
+ decorator="checkbox-2"
33
+ labelText="Enable feature"
34
+ id="checkbox-2"
35
+ />
36
+ </div>
37
+ </div>)
38
+ );
39
+ };
40
+
41
+ export default TestComponent;
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Copyright IBM Corp. 2025
3
+ *
4
+ * This source code is licensed under the Apache-2.0 license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ */
7
+
8
+ 'use strict';
9
+
10
+ const { defineTest } = require('jscodeshift/dist/testUtils');
11
+
12
+ // Test with wrapping (default)
13
+ defineTest(
14
+ __dirname,
15
+ 'enable-v12-overflowmenu',
16
+ { wrapWithFeatureFlag: 'true' },
17
+ 'enable-v12-overflowmenu'
18
+ );
19
+
20
+ // Test without wrapping
21
+ defineTest(
22
+ __dirname,
23
+ 'enable-v12-overflowmenu',
24
+ { wrapWithFeatureFlag: 'false' },
25
+ 'enable-v12-overflowmenu-nowrap'
26
+ );
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Copyright IBM Corp. 2025
3
+ *
4
+ * This source code is licensed under the Apache-2.0 license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ */
7
+
8
+ 'use strict';
9
+
10
+ const { defineTest } = require('jscodeshift/dist/testUtils');
11
+
12
+ defineTest(__dirname, 'slug-prop-to-decorator-prop');
@@ -0,0 +1,218 @@
1
+ /**
2
+ * Copyright IBM Corp. 2025
3
+ *
4
+ * This source code is licensed under the Apache-2.0 license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * Migrate OverflowMenu components to v12 API
8
+ */
9
+
10
+ 'use strict';
11
+
12
+ const defaultOptions = {
13
+ quote: 'single',
14
+ trailingComma: true,
15
+ };
16
+
17
+ // Props mapping from OverflowMenuItem to MenuItem
18
+ const MENU_ITEM_PROPS_MAP = {
19
+ itemText: 'label',
20
+ href: 'href',
21
+ disabled: 'disabled',
22
+ className: 'className',
23
+ };
24
+
25
+ const EVENT_HANDLERS = new Set(['onClick']);
26
+
27
+ function transform(fileInfo, api, options) {
28
+ const { jscodeshift: j } = api;
29
+ const root = j(fileInfo.source);
30
+ const printOptions = options.printOptions || defaultOptions;
31
+ const shouldWrapWithFlags = options.wrapWithFeatureFlag !== false;
32
+ const overflowMenuElements = root
33
+ .find(j.JSXElement, {
34
+ openingElement: { name: { name: 'OverflowMenu' } },
35
+ })
36
+ .filter((path) => {
37
+ let parent = path.parent;
38
+ while (parent) {
39
+ if (
40
+ parent.node.type === 'JSXElement' &&
41
+ parent.node.openingElement.name.name === 'FeatureFlags'
42
+ ) {
43
+ return false;
44
+ }
45
+ parent = parent.parent;
46
+ }
47
+ return true;
48
+ });
49
+
50
+ if (!overflowMenuElements.length) {
51
+ return null;
52
+ }
53
+
54
+ // Add required imports
55
+ const importsToAdd = ['MenuItem', 'MenuItemDivider'].sort();
56
+ const carbonImport = root.find(j.ImportDeclaration, {
57
+ source: { value: '@carbon/react' },
58
+ });
59
+
60
+ if (carbonImport.length) {
61
+ const importNode = carbonImport.get(0);
62
+ const existingSpecifiers = new Set(
63
+ importNode.node.specifiers
64
+ .filter((spec) => spec.type === 'ImportSpecifier')
65
+ .map((spec) => spec.imported.name)
66
+ );
67
+
68
+ importsToAdd.forEach((importName) => {
69
+ if (!existingSpecifiers.has(importName)) {
70
+ importNode.node.specifiers.push(
71
+ j.importSpecifier(j.identifier(importName))
72
+ );
73
+ }
74
+ });
75
+
76
+ // Sort specifiers alphabetically
77
+ importNode.node.specifiers.sort((a, b) =>
78
+ a.imported.name.localeCompare(b.imported.name)
79
+ );
80
+ }
81
+
82
+ function transformOverflowMenuItems(elements) {
83
+ elements.forEach((path) => {
84
+ path.node.children.forEach((child, index) => {
85
+ if (
86
+ child.type === 'JSXElement' &&
87
+ child.openingElement.name.name === 'OverflowMenuItem'
88
+ ) {
89
+ const itemProps = [];
90
+ let needsDivider = false;
91
+ let classNames = [];
92
+
93
+ child.openingElement.attributes.forEach((attr) => {
94
+ if (attr.type === 'JSXSpreadAttribute') {
95
+ itemProps.push(attr);
96
+ return;
97
+ }
98
+
99
+ const propName = attr.name.name;
100
+
101
+ if (MENU_ITEM_PROPS_MAP[propName]) {
102
+ if (propName === 'className') {
103
+ classNames.push(attr.value.value);
104
+ } else {
105
+ itemProps.push(
106
+ j.jsxAttribute(
107
+ j.jsxIdentifier(MENU_ITEM_PROPS_MAP[propName]),
108
+ attr.value
109
+ )
110
+ );
111
+ }
112
+ } else if (propName === 'wrapperClassName') {
113
+ classNames.push(attr.value.value);
114
+ } else if (propName === 'hasDivider') {
115
+ needsDivider = true;
116
+ } else if (propName === 'isDelete') {
117
+ itemProps.push(
118
+ j.jsxAttribute(
119
+ j.jsxIdentifier('kind'),
120
+ j.stringLiteral('danger')
121
+ )
122
+ );
123
+ } else if (EVENT_HANDLERS.has(propName)) {
124
+ itemProps.push(attr);
125
+ }
126
+ });
127
+
128
+ if (classNames.length > 0) {
129
+ itemProps.push(
130
+ j.jsxAttribute(
131
+ j.jsxIdentifier('className'),
132
+ j.stringLiteral(classNames.join(' '))
133
+ )
134
+ );
135
+ }
136
+
137
+ if (needsDivider) {
138
+ path.node.children.splice(
139
+ index,
140
+ 0,
141
+ j.jsxElement(
142
+ j.jsxOpeningElement(
143
+ j.jsxIdentifier('MenuItemDivider'),
144
+ [],
145
+ true
146
+ ),
147
+ null,
148
+ [],
149
+ true
150
+ )
151
+ );
152
+ }
153
+
154
+ child.openingElement.name = j.jsxIdentifier('MenuItem');
155
+ child.openingElement.attributes = itemProps;
156
+ if (child.closingElement) {
157
+ child.closingElement.name = j.jsxIdentifier('MenuItem');
158
+ }
159
+ }
160
+ });
161
+ });
162
+ }
163
+
164
+ function addFeatureFlagsImport() {
165
+ const hasFeatureFlagsImport = root
166
+ .find(j.ImportDeclaration)
167
+ .some(
168
+ (path) =>
169
+ path.node.source.value === '@carbon/feature-flags' &&
170
+ path.node.specifiers.some(
171
+ (spec) => spec.imported && spec.imported.name === 'FeatureFlags'
172
+ )
173
+ );
174
+
175
+ if (!hasFeatureFlagsImport) {
176
+ const featureFlagsImport = j.importDeclaration(
177
+ [j.importSpecifier(j.identifier('FeatureFlags'))],
178
+ j.literal('@carbon/feature-flags')
179
+ );
180
+
181
+ const firstImport = root.find(j.ImportDeclaration).at(0);
182
+ if (firstImport.length) {
183
+ firstImport.insertAfter(featureFlagsImport);
184
+ } else {
185
+ root.get().node.program.body.unshift(featureFlagsImport);
186
+ }
187
+ }
188
+ }
189
+
190
+ function wrapWithFeatureFlags(elements) {
191
+ elements.forEach((path) => {
192
+ const wrappedElement = j.jsxElement(
193
+ j.jsxOpeningElement(
194
+ j.jsxIdentifier('FeatureFlags'),
195
+ [j.jsxAttribute(j.jsxIdentifier('enableV12Overflowmenu'))],
196
+ false
197
+ ),
198
+ j.jsxClosingElement(j.jsxIdentifier('FeatureFlags')),
199
+ [j.jsxText('\n '), path.node, j.jsxText('\n ')]
200
+ );
201
+
202
+ j(path).replaceWith(wrappedElement);
203
+ });
204
+ }
205
+
206
+ // Transform based on wrap option
207
+ transformOverflowMenuItems(overflowMenuElements);
208
+
209
+ if (shouldWrapWithFlags) {
210
+ addFeatureFlagsImport();
211
+ wrapWithFeatureFlags(overflowMenuElements);
212
+ }
213
+
214
+ return root.toSource(printOptions);
215
+ }
216
+
217
+ module.exports = transform;
218
+ module.exports.parser = 'tsx';
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Copyright IBM Corp. 2025
3
+ *
4
+ * This source code is licensed under the Apache-2.0 license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * Replace slug prop with decorator
8
+ */
9
+
10
+ 'use strict';
11
+
12
+ const defaultOptions = {
13
+ quote: 'single',
14
+ trailingComma: true,
15
+ };
16
+
17
+ function transform(fileInfo, api, options) {
18
+ const j = api.jscodeshift;
19
+ const root = j(fileInfo.source);
20
+ const printOptions = options.printOptions || defaultOptions;
21
+
22
+ // Early return if no JSX elements with slug prop found
23
+ if (!root.find(j.JSXAttribute, { name: { name: 'slug' } }).size()) {
24
+ return null;
25
+ }
26
+
27
+ // Replace slug with decorator
28
+ root
29
+ .find(j.JSXAttribute, {
30
+ name: { name: 'slug' },
31
+ })
32
+ .forEach((path) => {
33
+ // Create new decorator attribute with same value as slug
34
+ const newAttribute = j.jsxAttribute(
35
+ j.jsxIdentifier('decorator'),
36
+ path.node.value
37
+ );
38
+
39
+ // Replace the slug attribute with decorator
40
+ j(path).replaceWith(newAttribute);
41
+ });
42
+
43
+ return root.toSource(printOptions);
44
+ }
45
+
46
+ module.exports = transform;
47
+ module.exports.parser = 'tsx';