@carbon/upgrade 11.42.0 → 11.43.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/README.md CHANGED
@@ -81,6 +81,44 @@ The following codemods help you adopt changes introduced by feature flags in
81
81
  preparation for Carbon v12. Each codemod transforms your code to work with
82
82
  specific feature flags enabled.
83
83
 
84
+ ### Enable v12 release
85
+
86
+ Enables the complete Carbon v12 experience for a React application.
87
+
88
+ **Usage:**
89
+
90
+ ```bash
91
+ npx @carbon/upgrade migrate enable-v12-release --write
92
+ ```
93
+
94
+ This codemod wraps the application passed to a React root with
95
+ `<FeatureFlags enableV12Release>` and adds or updates the necessary
96
+ `@carbon/react` import. It supports modern `createRoot` and `hydrateRoot` entry
97
+ points as well as legacy `ReactDOM.render`.
98
+
99
+ **Example:**
100
+
101
+ ```jsx
102
+ // Before
103
+ import { createRoot } from 'react-dom/client';
104
+ import App from './App';
105
+
106
+ const root = createRoot(document.getElementById('root'));
107
+ root.render(<App />);
108
+
109
+ // After
110
+ import { createRoot } from 'react-dom/client';
111
+ import { FeatureFlags } from '@carbon/react';
112
+ import App from './App';
113
+
114
+ const root = createRoot(document.getElementById('root'));
115
+ root.render(
116
+ <FeatureFlags enableV12Release>
117
+ <App />
118
+ </FeatureFlags>
119
+ );
120
+ ```
121
+
84
122
  ### Enable v12 tile default icons
85
123
 
86
124
  Enables default icons for Tile components.
package/cli.js CHANGED
@@ -44083,7 +44083,7 @@ async function run$1(options) {
44083
44083
  //#endregion
44084
44084
  //#region src/upgrades.js
44085
44085
  /**
44086
- * Copyright IBM Corp. 2019, 2023
44086
+ * Copyright IBM Corp. 2019, 2026
44087
44087
  *
44088
44088
  * This source code is licensed under the Apache-2.0 license found in the
44089
44089
  * LICENSE file in the root directory of this source tree.
@@ -44364,6 +44364,46 @@ const upgrades = [
44364
44364
  });
44365
44365
  }
44366
44366
  },
44367
+ {
44368
+ name: "enable-v12-release",
44369
+ description: `
44370
+ Wrap the application rendered by ReactDOM with FeatureFlags enableV12Release.
44371
+
44372
+ Transforms:
44373
+ root.render(<App />);
44374
+
44375
+ Into:
44376
+ root.render(
44377
+ <FeatureFlags enableV12Release>
44378
+ <App />
44379
+ </FeatureFlags>
44380
+ );
44381
+ `,
44382
+ migrate: async (options) => {
44383
+ const transform = path.default.join(TRANSFORM_DIR, "enable-v12-release.js");
44384
+ const paths = Array.isArray(options.paths) && options.paths.length > 0 ? options.paths : await (0, import_out.default)(["**/*.{js,jsx,ts,tsx}"], {
44385
+ cwd: options.workspaceDir,
44386
+ ignore: [
44387
+ "**/es/**",
44388
+ "**/lib/**",
44389
+ "**/umd/**",
44390
+ "**/node_modules/**",
44391
+ "**/storybook-static/**",
44392
+ "**/dist/**",
44393
+ "**/build/**",
44394
+ "**/*.d.ts",
44395
+ "**/coverage/**"
44396
+ ]
44397
+ });
44398
+ await runCodemod(options, {
44399
+ dry: !options.write,
44400
+ transform,
44401
+ paths,
44402
+ verbose: options.verbose,
44403
+ parser: "tsx"
44404
+ });
44405
+ }
44406
+ },
44367
44407
  {
44368
44408
  name: "enable-v12-overflowmenu",
44369
44409
  description: `
@@ -44964,7 +45004,7 @@ const upgrades = [
44964
45004
  //#endregion
44965
45005
  //#region package.json
44966
45006
  var name = "@carbon/upgrade";
44967
- var version = "11.42.0";
45007
+ var version = "11.43.0-rc.0";
44968
45008
  //#endregion
44969
45009
  //#region ../../node_modules/y18n/build/index.cjs
44970
45010
  var require_build$3 = /* @__PURE__ */ __commonJSMin(((exports, module) => {
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.42.0",
4
+ "version": "11.43.0-rc.0",
5
5
  "license": "Apache-2.0",
6
6
  "bin": {
7
7
  "carbon-upgrade": "./bin/carbon-upgrade.js"
@@ -60,5 +60,5 @@
60
60
  "@ibm/telemetry-js": "^1.5.0",
61
61
  "jscodeshift": "^17.0.0"
62
62
  },
63
- "gitHead": "a57cf8a896fe3fe09c5ab27648cd58947f37360a"
63
+ "gitHead": "82f04fbfdb385323883f4f8e3ce97a14865a744a"
64
64
  }
@@ -0,0 +1,20 @@
1
+ import * as Carbon from '@carbon/react';
2
+ import { createRoot as mountRoot } from 'react-dom/client';
3
+ import App from './App';
4
+
5
+ const root = mountRoot(document.getElementById('root')!);
6
+ root.render(
7
+ <Carbon.FeatureFlags enableV12Release={false} {...featureFlags}>
8
+ <App />
9
+ </Carbon.FeatureFlags>
10
+ );
11
+
12
+ const unwrappedRoot = mountRoot(document.getElementById('unwrapped-root')!);
13
+ unwrappedRoot.render(<App />);
14
+
15
+ const wrappedRoot = mountRoot(document.getElementById('wrapped-root')!);
16
+ wrappedRoot.render(
17
+ <Carbon.FeatureFlags>
18
+ <App />
19
+ </Carbon.FeatureFlags>
20
+ );
@@ -0,0 +1,22 @@
1
+ import * as Carbon from '@carbon/react';
2
+ import { createRoot as mountRoot } from 'react-dom/client';
3
+ import App from './App';
4
+
5
+ const root = mountRoot(document.getElementById('root')!);
6
+ root.render(
7
+ <Carbon.FeatureFlags {...featureFlags} enableV12Release>
8
+ <App />
9
+ </Carbon.FeatureFlags>
10
+ );
11
+
12
+ const unwrappedRoot = mountRoot(document.getElementById('unwrapped-root')!);
13
+ unwrappedRoot.render(<Carbon.FeatureFlags enableV12Release>
14
+ <App />
15
+ </Carbon.FeatureFlags>);
16
+
17
+ const wrappedRoot = mountRoot(document.getElementById('wrapped-root')!);
18
+ wrappedRoot.render(
19
+ <Carbon.FeatureFlags enableV12Release>
20
+ <App />
21
+ </Carbon.FeatureFlags>
22
+ );
@@ -0,0 +1,22 @@
1
+ import React from 'react';
2
+ import ReactDOM from 'react-dom';
3
+ import ReactDOMClient, { createRoot, hydrateRoot } from 'react-dom/client';
4
+ import { Button } from '@carbon/react';
5
+ import App from './App';
6
+
7
+ const root = createRoot(document.getElementById('root'));
8
+ root.render(
9
+ <React.StrictMode>
10
+ <App />
11
+ </React.StrictMode>
12
+ );
13
+
14
+ ReactDOM.render(<App />, document.getElementById('legacy-root'));
15
+
16
+ hydrateRoot(document.getElementById('hydrated-root'), <App />);
17
+
18
+ ReactDOMClient.createRoot(document.getElementById('chained-root')).render(
19
+ <App />
20
+ );
21
+
22
+ const preview = <App />;
@@ -0,0 +1,30 @@
1
+ import React from 'react';
2
+ import ReactDOM from 'react-dom';
3
+ import ReactDOMClient, { createRoot, hydrateRoot } from 'react-dom/client';
4
+ import { Button, FeatureFlags } from '@carbon/react';
5
+ import App from './App';
6
+
7
+ const root = createRoot(document.getElementById('root'));
8
+ root.render(
9
+ <FeatureFlags enableV12Release>
10
+ <React.StrictMode>
11
+ <App />
12
+ </React.StrictMode>
13
+ </FeatureFlags>
14
+ );
15
+
16
+ ReactDOM.render(<FeatureFlags enableV12Release>
17
+ <App />
18
+ </FeatureFlags>, document.getElementById('legacy-root'));
19
+
20
+ hydrateRoot(document.getElementById('hydrated-root'), <FeatureFlags enableV12Release>
21
+ <App />
22
+ </FeatureFlags>);
23
+
24
+ ReactDOMClient.createRoot(document.getElementById('chained-root')).render(
25
+ <FeatureFlags enableV12Release>
26
+ <App />
27
+ </FeatureFlags>
28
+ );
29
+
30
+ const preview = <App />;
@@ -0,0 +1,35 @@
1
+ /**
2
+ * @jest-environment node
3
+ */
4
+
5
+ /**
6
+ * Copyright IBM Corp. 2026
7
+ *
8
+ * This source code is licensed under the Apache-2.0 license found in the
9
+ * LICENSE file in the root directory of this source tree.
10
+ */
11
+
12
+ 'use strict';
13
+
14
+ const { defineTest, applyTransform } = require('jscodeshift/dist/testUtils');
15
+ const transform = require('../enable-v12-release');
16
+
17
+ defineTest(__dirname, 'enable-v12-release');
18
+ defineTest(__dirname, 'enable-v12-release', null, 'enable-v12-release-aliased');
19
+
20
+ it('does not change an application with the v12 release flag enabled', () => {
21
+ const source = `
22
+ import { FeatureFlags } from '@carbon/react';
23
+ import { createRoot } from 'react-dom/client';
24
+ import App from './App';
25
+
26
+ const root = createRoot(document.getElementById('root'));
27
+ root.render(
28
+ <FeatureFlags enableV12Release>
29
+ <App />
30
+ </FeatureFlags>
31
+ );
32
+ `;
33
+
34
+ expect(applyTransform(transform, {}, { source })).toBe('');
35
+ });
@@ -0,0 +1,406 @@
1
+ /**
2
+ * Copyright IBM Corp. 2026
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
+ /**
9
+ * Wrap the application passed to a React root with the v12 release feature flag.
10
+ */
11
+
12
+ 'use strict';
13
+
14
+ const defaultOptions = {
15
+ quote: 'single',
16
+ trailingComma: true,
17
+ };
18
+
19
+ function transform(fileInfo, api, options) {
20
+ const j = api.jscodeshift;
21
+ const root = j(fileInfo.source);
22
+ const printOptions = options.printOptions || defaultOptions;
23
+ const reactDomBindings = getReactDomBindings(root, j);
24
+ const reactRootBindings = getReactRootBindings(root, j, reactDomBindings);
25
+ const renderArguments = findRenderArguments(
26
+ root,
27
+ j,
28
+ reactDomBindings,
29
+ reactRootBindings
30
+ );
31
+
32
+ if (renderArguments.length === 0) {
33
+ return null;
34
+ }
35
+
36
+ const featureFlags = getFeatureFlagsReference(root, j);
37
+ let didChange = false;
38
+
39
+ for (const argumentPath of renderArguments) {
40
+ const application = argumentPath.node;
41
+
42
+ if (isNamedJSXElement(application, featureFlags.jsxName)) {
43
+ didChange = enableV12Release(application.openingElement, j) || didChange;
44
+ continue;
45
+ }
46
+
47
+ argumentPath.replace(
48
+ createFeatureFlagsWrapper(application, featureFlags, j)
49
+ );
50
+ didChange = true;
51
+ }
52
+
53
+ if (!didChange) {
54
+ return null;
55
+ }
56
+
57
+ if (featureFlags.needsImport) {
58
+ addFeatureFlagsImport(root, featureFlags, j);
59
+ }
60
+
61
+ return root.toSource(printOptions);
62
+ }
63
+
64
+ function getReactDomBindings(root, j) {
65
+ const bindings = {
66
+ createRoot: new Set(),
67
+ hydrateRoot: new Set(),
68
+ render: new Set(),
69
+ reactDom: new Set(),
70
+ };
71
+
72
+ root
73
+ .find(j.ImportDeclaration)
74
+ .filter(
75
+ (path) =>
76
+ path.node.source.value === 'react-dom' ||
77
+ path.node.source.value === 'react-dom/client'
78
+ )
79
+ .forEach((path) => {
80
+ const source = path.node.source.value;
81
+
82
+ for (const specifier of path.node.specifiers) {
83
+ if (
84
+ specifier.type === 'ImportDefaultSpecifier' ||
85
+ specifier.type === 'ImportNamespaceSpecifier'
86
+ ) {
87
+ bindings.reactDom.add(specifier.local.name);
88
+ continue;
89
+ }
90
+
91
+ if (
92
+ specifier.type !== 'ImportSpecifier' ||
93
+ path.node.importKind === 'type' ||
94
+ specifier.importKind === 'type'
95
+ ) {
96
+ continue;
97
+ }
98
+
99
+ const importedName = specifier.imported.name;
100
+ if (
101
+ source === 'react-dom/client' &&
102
+ (importedName === 'createRoot' || importedName === 'hydrateRoot')
103
+ ) {
104
+ bindings[importedName].add(specifier.local.name);
105
+ } else if (source === 'react-dom' && importedName === 'render') {
106
+ bindings.render.add(specifier.local.name);
107
+ }
108
+ }
109
+ });
110
+
111
+ return bindings;
112
+ }
113
+
114
+ function getReactRootBindings(root, j, reactDomBindings) {
115
+ const rootBindings = new Set();
116
+
117
+ root.find(j.VariableDeclarator).forEach((path) => {
118
+ if (
119
+ path.node.id.type === 'Identifier' &&
120
+ isCreateRootCall(path.node.init, reactDomBindings)
121
+ ) {
122
+ rootBindings.add(path.node.id.name);
123
+ }
124
+ });
125
+
126
+ root.find(j.AssignmentExpression).forEach((path) => {
127
+ if (
128
+ path.node.left.type === 'Identifier' &&
129
+ isCreateRootCall(path.node.right, reactDomBindings)
130
+ ) {
131
+ rootBindings.add(path.node.left.name);
132
+ }
133
+ });
134
+
135
+ return rootBindings;
136
+ }
137
+
138
+ function findRenderArguments(root, j, reactDomBindings, reactRootBindings) {
139
+ const argumentsToWrap = [];
140
+
141
+ root.find(j.CallExpression).forEach((path) => {
142
+ const { callee } = path.node;
143
+
144
+ if (isHydrateRootCall(path.node, reactDomBindings)) {
145
+ addJSXArgument(path, 1, argumentsToWrap);
146
+ return;
147
+ }
148
+
149
+ if (
150
+ callee.type === 'Identifier' &&
151
+ reactDomBindings.render.has(callee.name)
152
+ ) {
153
+ addJSXArgument(path, 0, argumentsToWrap);
154
+ return;
155
+ }
156
+
157
+ if (!isMemberCall(callee, 'render') && !isMemberCall(callee, 'hydrate')) {
158
+ return;
159
+ }
160
+
161
+ if (
162
+ (callee.object.type === 'Identifier' &&
163
+ (reactRootBindings.has(callee.object.name) ||
164
+ reactDomBindings.reactDom.has(callee.object.name))) ||
165
+ isCreateRootCall(callee.object, reactDomBindings)
166
+ ) {
167
+ addJSXArgument(path, 0, argumentsToWrap);
168
+ }
169
+ });
170
+
171
+ return argumentsToWrap;
172
+ }
173
+
174
+ function addJSXArgument(callPath, argumentIndex, argumentsToWrap) {
175
+ const argument = callPath.node.arguments[argumentIndex];
176
+ if (argument?.type === 'JSXElement' || argument?.type === 'JSXFragment') {
177
+ argumentsToWrap.push(callPath.get('arguments', argumentIndex));
178
+ }
179
+ }
180
+
181
+ function isCreateRootCall(node, reactDomBindings) {
182
+ if (!node || node.type !== 'CallExpression') {
183
+ return false;
184
+ }
185
+
186
+ if (
187
+ node.callee.type === 'Identifier' &&
188
+ reactDomBindings.createRoot.has(node.callee.name)
189
+ ) {
190
+ return true;
191
+ }
192
+
193
+ return (
194
+ isMemberCall(node.callee, 'createRoot') &&
195
+ node.callee.object.type === 'Identifier' &&
196
+ reactDomBindings.reactDom.has(node.callee.object.name)
197
+ );
198
+ }
199
+
200
+ function isHydrateRootCall(node, reactDomBindings) {
201
+ if (node.callee.type === 'Identifier') {
202
+ return reactDomBindings.hydrateRoot.has(node.callee.name);
203
+ }
204
+
205
+ return (
206
+ isMemberCall(node.callee, 'hydrateRoot') &&
207
+ node.callee.object.type === 'Identifier' &&
208
+ reactDomBindings.reactDom.has(node.callee.object.name)
209
+ );
210
+ }
211
+
212
+ function isMemberCall(callee, propertyName) {
213
+ if (
214
+ callee.type !== 'MemberExpression' &&
215
+ callee.type !== 'OptionalMemberExpression'
216
+ ) {
217
+ return false;
218
+ }
219
+
220
+ if (callee.computed) {
221
+ return (
222
+ (callee.property.type === 'Literal' ||
223
+ callee.property.type === 'StringLiteral') &&
224
+ callee.property.value === propertyName
225
+ );
226
+ }
227
+
228
+ return (
229
+ callee.property.type === 'Identifier' &&
230
+ callee.property.name === propertyName
231
+ );
232
+ }
233
+
234
+ function getFeatureFlagsReference(root, j) {
235
+ const carbonImports = root
236
+ .find(j.ImportDeclaration, {
237
+ source: { value: '@carbon/react' },
238
+ })
239
+ .paths();
240
+
241
+ for (const path of carbonImports) {
242
+ if (path.node.importKind === 'type') {
243
+ continue;
244
+ }
245
+
246
+ for (const specifier of path.node.specifiers) {
247
+ if (
248
+ specifier.type === 'ImportSpecifier' &&
249
+ specifier.importKind !== 'type' &&
250
+ specifier.imported.name === 'FeatureFlags'
251
+ ) {
252
+ return {
253
+ importName: specifier.local.name,
254
+ jsxName: j.jsxIdentifier(specifier.local.name),
255
+ needsImport: false,
256
+ };
257
+ }
258
+
259
+ if (specifier.type === 'ImportNamespaceSpecifier') {
260
+ return {
261
+ importName: null,
262
+ jsxName: j.jsxMemberExpression(
263
+ j.jsxIdentifier(specifier.local.name),
264
+ j.jsxIdentifier('FeatureFlags')
265
+ ),
266
+ needsImport: false,
267
+ };
268
+ }
269
+ }
270
+ }
271
+
272
+ const importName = getAvailableImportName(root, j);
273
+ return {
274
+ importName,
275
+ jsxName: j.jsxIdentifier(importName),
276
+ needsImport: true,
277
+ };
278
+ }
279
+
280
+ function getAvailableImportName(root, j) {
281
+ let importName = 'FeatureFlags';
282
+ let suffix = 2;
283
+
284
+ while (
285
+ root.find(j.Identifier, { name: importName }).size() > 0 ||
286
+ root.find(j.JSXIdentifier, { name: importName }).size() > 0
287
+ ) {
288
+ importName = `CarbonFeatureFlags${suffix === 2 ? '' : suffix}`;
289
+ suffix++;
290
+ }
291
+
292
+ return importName;
293
+ }
294
+
295
+ function addFeatureFlagsImport(root, featureFlags, j) {
296
+ const carbonImport = root
297
+ .find(j.ImportDeclaration, {
298
+ source: { value: '@carbon/react' },
299
+ })
300
+ .filter(
301
+ (path) =>
302
+ path.node.importKind !== 'type' &&
303
+ !path.node.specifiers.some(
304
+ (specifier) => specifier.type === 'ImportNamespaceSpecifier'
305
+ )
306
+ )
307
+ .at(0);
308
+ const local =
309
+ featureFlags.importName === 'FeatureFlags'
310
+ ? null
311
+ : j.identifier(featureFlags.importName);
312
+ const specifier = j.importSpecifier(j.identifier('FeatureFlags'), local);
313
+
314
+ if (carbonImport.size() > 0) {
315
+ carbonImport.get().node.specifiers.push(specifier);
316
+ return;
317
+ }
318
+
319
+ const declaration = j.importDeclaration(
320
+ [specifier],
321
+ j.literal('@carbon/react')
322
+ );
323
+ const lastImport = root.find(j.ImportDeclaration).at(-1);
324
+
325
+ if (lastImport.size() > 0) {
326
+ lastImport.insertAfter(declaration);
327
+ } else {
328
+ root.get().node.program.body.unshift(declaration);
329
+ }
330
+ }
331
+
332
+ function isNamedJSXElement(node, expectedName) {
333
+ return (
334
+ node.type === 'JSXElement' &&
335
+ areJSXNamesEqual(node.openingElement.name, expectedName)
336
+ );
337
+ }
338
+
339
+ function areJSXNamesEqual(actual, expected) {
340
+ if (actual.type !== expected.type) {
341
+ return false;
342
+ }
343
+
344
+ if (actual.type === 'JSXIdentifier') {
345
+ return actual.name === expected.name;
346
+ }
347
+
348
+ if (actual.type === 'JSXMemberExpression') {
349
+ return (
350
+ areJSXNamesEqual(actual.object, expected.object) &&
351
+ areJSXNamesEqual(actual.property, expected.property)
352
+ );
353
+ }
354
+
355
+ return false;
356
+ }
357
+
358
+ function enableV12Release(openingElement, j) {
359
+ const attributes = openingElement.attributes;
360
+ const releaseAttributes = attributes.filter(
361
+ (attribute) =>
362
+ attribute.type === 'JSXAttribute' &&
363
+ attribute.name.name === 'enableV12Release'
364
+ );
365
+ const lastAttribute = attributes[attributes.length - 1];
366
+ const isEnabled =
367
+ releaseAttributes.length === 1 &&
368
+ releaseAttributes[0] === lastAttribute &&
369
+ (lastAttribute.value === null ||
370
+ (lastAttribute.value.type === 'JSXExpressionContainer' &&
371
+ (lastAttribute.value.expression.type === 'BooleanLiteral' ||
372
+ lastAttribute.value.expression.type === 'Literal') &&
373
+ lastAttribute.value.expression.value === true));
374
+
375
+ if (isEnabled) {
376
+ return false;
377
+ }
378
+
379
+ openingElement.attributes = attributes
380
+ .filter(
381
+ (attribute) =>
382
+ attribute.type !== 'JSXAttribute' ||
383
+ attribute.name.name !== 'enableV12Release'
384
+ )
385
+ .concat(j.jsxAttribute(j.jsxIdentifier('enableV12Release')));
386
+ return true;
387
+ }
388
+
389
+ function createFeatureFlagsWrapper(application, featureFlags, j) {
390
+ return j.jsxElement(
391
+ j.jsxOpeningElement(
392
+ cloneJSXName(featureFlags.jsxName),
393
+ [j.jsxAttribute(j.jsxIdentifier('enableV12Release'))],
394
+ false
395
+ ),
396
+ j.jsxClosingElement(cloneJSXName(featureFlags.jsxName)),
397
+ [j.jsxText('\n'), application, j.jsxText('\n')]
398
+ );
399
+ }
400
+
401
+ function cloneJSXName(name) {
402
+ return JSON.parse(JSON.stringify(name));
403
+ }
404
+
405
+ module.exports = transform;
406
+ module.exports.parser = 'tsx';