@carbon/upgrade 11.42.0 → 11.43.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/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",
5
5
  "license": "Apache-2.0",
6
6
  "bin": {
7
7
  "carbon-upgrade": "./bin/carbon-upgrade.js"
@@ -39,7 +39,7 @@
39
39
  "postinstall": "ibmtelemetry --config=telemetry.yml"
40
40
  },
41
41
  "devDependencies": {
42
- "chalk": "^5.4.1",
42
+ "chalk": "^6.0.0",
43
43
  "execa": "^9.6.0",
44
44
  "fast-glob": "^3.3.3",
45
45
  "fs-extra": "^11.0.0",
@@ -49,7 +49,7 @@
49
49
  "lodash.clonedeep": "^4.5.0",
50
50
  "lodash.merge": "^4.6.2",
51
51
  "memfs": "^4.0.0",
52
- "nanoid": "^5.0.0",
52
+ "nanoid": "^6.0.0",
53
53
  "npm-which": "^3.0.1",
54
54
  "rimraf": "^6.0.1",
55
55
  "semver": "^7.7.2",
@@ -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": "188d23202ec1092322dee92cf0df9d9958224ae4"
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';