@carbon/upgrade 11.40.0 → 11.41.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 +313 -251
- package/package.json +3 -3
- package/transforms/__testfixtures__/ibm-products-update-page-header-composable-aliased.input.js +19 -0
- package/transforms/__testfixtures__/ibm-products-update-page-header-composable-aliased.output.js +16 -0
- package/transforms/__testfixtures__/ibm-products-update-page-header-composable.input.js +182 -0
- package/transforms/__testfixtures__/ibm-products-update-page-header-composable.output.js +152 -0
- package/transforms/__tests__/ibm-products-update-page-header-composable-test.js +12 -0
- package/transforms/ibm-products-update-page-header-composable.js +391 -0
|
@@ -0,0 +1,391 @@
|
|
|
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
|
+
* Rewrites stable PageHeader to composable PageHeader
|
|
10
|
+
*
|
|
11
|
+
* Transforms:
|
|
12
|
+
*
|
|
13
|
+
* <PageHeader
|
|
14
|
+
* title="Page title"
|
|
15
|
+
* subtitle="Optional subtitle"
|
|
16
|
+
* breadcrumbs={[...]}
|
|
17
|
+
* pageActions={[...]}
|
|
18
|
+
* actionBarItems={[...]}
|
|
19
|
+
* navigation={<Tabs>...</Tabs>}
|
|
20
|
+
* tags={[...]}
|
|
21
|
+
* >
|
|
22
|
+
* <p>Content</p>
|
|
23
|
+
* </PageHeader>
|
|
24
|
+
*
|
|
25
|
+
* Into:
|
|
26
|
+
*
|
|
27
|
+
* <PageHeader>
|
|
28
|
+
* <PageHeader.BreadcrumbBar breadcrumbs={[...]} />
|
|
29
|
+
* <PageHeader.Content title="Page title" pageActions={[...]} contextualActions={[...]}>
|
|
30
|
+
* <PageHeader.ContentText subtitle="Optional subtitle" />
|
|
31
|
+
* <p>Content</p>
|
|
32
|
+
* </PageHeader.Content>
|
|
33
|
+
* <PageHeader.TabBar>{<Tabs>...</Tabs>}</PageHeader.TabBar>
|
|
34
|
+
* <PageHeader.TagOverflow tags={[...]} />
|
|
35
|
+
* </PageHeader>
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
'use strict';
|
|
39
|
+
|
|
40
|
+
const transform = (fileInfo, api) => {
|
|
41
|
+
const j = api.jscodeshift;
|
|
42
|
+
const root = j(fileInfo.source);
|
|
43
|
+
let dirtyFlag = false;
|
|
44
|
+
|
|
45
|
+
// Find the local name used for PageHeader import (could be aliased)
|
|
46
|
+
let pageHeaderLocalName = null;
|
|
47
|
+
root
|
|
48
|
+
.find(j.ImportDeclaration)
|
|
49
|
+
.filter((path) => path.node.source.value === '@carbon/ibm-products')
|
|
50
|
+
.forEach((path) => {
|
|
51
|
+
path.node.specifiers.forEach((specifier) => {
|
|
52
|
+
if (
|
|
53
|
+
specifier.type === 'ImportSpecifier' &&
|
|
54
|
+
specifier.imported &&
|
|
55
|
+
specifier.imported.name === 'PageHeader'
|
|
56
|
+
) {
|
|
57
|
+
pageHeaderLocalName = specifier.local
|
|
58
|
+
? specifier.local.name
|
|
59
|
+
: 'PageHeader';
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
// If PageHeader is not imported, nothing to transform
|
|
65
|
+
if (!pageHeaderLocalName) {
|
|
66
|
+
return root.toSource();
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Helper to create JSX member expression (e.g., PageHeader.Content)
|
|
70
|
+
const createMemberExpression = (object, property) => {
|
|
71
|
+
return j.jsxMemberExpression(
|
|
72
|
+
j.jsxIdentifier(object),
|
|
73
|
+
j.jsxIdentifier(property)
|
|
74
|
+
);
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
// Helper to create JSX element with member expression
|
|
78
|
+
const createMemberJSXElement = (object, property, attributes, children) => {
|
|
79
|
+
const memberExpression = createMemberExpression(object, property);
|
|
80
|
+
const openingElement = j.jsxOpeningElement(
|
|
81
|
+
memberExpression,
|
|
82
|
+
attributes,
|
|
83
|
+
children.length === 0
|
|
84
|
+
);
|
|
85
|
+
return children.length === 0
|
|
86
|
+
? j.jsxElement(openingElement, null, [])
|
|
87
|
+
: j.jsxElement(
|
|
88
|
+
openingElement,
|
|
89
|
+
j.jsxClosingElement(memberExpression),
|
|
90
|
+
children
|
|
91
|
+
);
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
// Transform PageHeader components (using the local name which could be an alias)
|
|
95
|
+
root
|
|
96
|
+
.find(j.JSXElement, {
|
|
97
|
+
openingElement: { name: { name: pageHeaderLocalName } },
|
|
98
|
+
})
|
|
99
|
+
.forEach((path) => {
|
|
100
|
+
const attributes = path.node.openingElement.attributes;
|
|
101
|
+
const originalChildren = path.node.children || [];
|
|
102
|
+
|
|
103
|
+
// Props to extract
|
|
104
|
+
let titleProp = null;
|
|
105
|
+
let subtitleProp = null;
|
|
106
|
+
let breadcrumbsProp = null;
|
|
107
|
+
let breadcrumbOverflowAriaLabelProp = null;
|
|
108
|
+
let pageActionsProp = null;
|
|
109
|
+
let actionBarItemsProp = null;
|
|
110
|
+
let navigationProp = null;
|
|
111
|
+
let tagsProp = null;
|
|
112
|
+
let showAllTagsLabelProp = null;
|
|
113
|
+
let allTagsModalTitleProp = null;
|
|
114
|
+
let allTagsModalSearchLabelProp = null;
|
|
115
|
+
let allTagsModalSearchPlaceholderTextProp = null;
|
|
116
|
+
|
|
117
|
+
// Props to keep on root PageHeader
|
|
118
|
+
const rootAttributes = [];
|
|
119
|
+
|
|
120
|
+
// Deprecated props to ignore
|
|
121
|
+
const deprecatedProps = [
|
|
122
|
+
'collapseHeader',
|
|
123
|
+
'collapseHeaderIconDescription',
|
|
124
|
+
'expandHeaderIconDescription',
|
|
125
|
+
'hasCollapseHeaderToggle',
|
|
126
|
+
'enableBreadcrumbScroll',
|
|
127
|
+
'withoutBackground',
|
|
128
|
+
];
|
|
129
|
+
|
|
130
|
+
// Process attributes
|
|
131
|
+
attributes.forEach((attr) => {
|
|
132
|
+
if (attr.type !== 'JSXAttribute') {
|
|
133
|
+
rootAttributes.push(attr);
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const attrName = attr.name.name;
|
|
138
|
+
|
|
139
|
+
// Extract props for transformation
|
|
140
|
+
if (attrName === 'title') {
|
|
141
|
+
titleProp = attr;
|
|
142
|
+
} else if (attrName === 'subtitle') {
|
|
143
|
+
subtitleProp = attr;
|
|
144
|
+
} else if (attrName === 'breadcrumbs') {
|
|
145
|
+
breadcrumbsProp = attr;
|
|
146
|
+
} else if (attrName === 'breadcrumbOverflowAriaLabel') {
|
|
147
|
+
breadcrumbOverflowAriaLabelProp = attr;
|
|
148
|
+
} else if (attrName === 'pageActions') {
|
|
149
|
+
pageActionsProp = attr;
|
|
150
|
+
} else if (attrName === 'actionBarItems') {
|
|
151
|
+
actionBarItemsProp = attr;
|
|
152
|
+
} else if (attrName === 'navigation') {
|
|
153
|
+
navigationProp = attr;
|
|
154
|
+
} else if (attrName === 'tags') {
|
|
155
|
+
tagsProp = attr;
|
|
156
|
+
} else if (attrName === 'showAllTagsLabel') {
|
|
157
|
+
showAllTagsLabelProp = attr;
|
|
158
|
+
} else if (attrName === 'allTagsModalTitle') {
|
|
159
|
+
allTagsModalTitleProp = attr;
|
|
160
|
+
} else if (attrName === 'allTagsModalSearchLabel') {
|
|
161
|
+
allTagsModalSearchLabelProp = attr;
|
|
162
|
+
} else if (attrName === 'allTagsModalSearchPlaceholderText') {
|
|
163
|
+
allTagsModalSearchPlaceholderTextProp = attr;
|
|
164
|
+
} else if (deprecatedProps.includes(attrName)) {
|
|
165
|
+
// Ignore deprecated props
|
|
166
|
+
} else {
|
|
167
|
+
// Keep other props on root
|
|
168
|
+
rootAttributes.push(attr);
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
// Only transform if we have any props that need migration
|
|
173
|
+
const needsTransformation =
|
|
174
|
+
titleProp ||
|
|
175
|
+
subtitleProp ||
|
|
176
|
+
breadcrumbsProp ||
|
|
177
|
+
pageActionsProp ||
|
|
178
|
+
actionBarItemsProp ||
|
|
179
|
+
navigationProp ||
|
|
180
|
+
tagsProp;
|
|
181
|
+
|
|
182
|
+
if (!needsTransformation) {
|
|
183
|
+
return; // Skip this PageHeader, no transformation needed
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
dirtyFlag = true;
|
|
187
|
+
|
|
188
|
+
const newChildren = [];
|
|
189
|
+
|
|
190
|
+
// 1. Add BreadcrumbBar if breadcrumbs exist
|
|
191
|
+
if (breadcrumbsProp) {
|
|
192
|
+
const breadcrumbBarAttrs = [breadcrumbsProp];
|
|
193
|
+
if (breadcrumbOverflowAriaLabelProp) {
|
|
194
|
+
breadcrumbBarAttrs.push(breadcrumbOverflowAriaLabelProp);
|
|
195
|
+
}
|
|
196
|
+
newChildren.push(
|
|
197
|
+
createMemberJSXElement(
|
|
198
|
+
pageHeaderLocalName,
|
|
199
|
+
'BreadcrumbBar',
|
|
200
|
+
breadcrumbBarAttrs,
|
|
201
|
+
[]
|
|
202
|
+
)
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// 2. Add Content if title exists
|
|
207
|
+
if (titleProp) {
|
|
208
|
+
const contentAttrs = [titleProp];
|
|
209
|
+
|
|
210
|
+
// Handle title as object with icon
|
|
211
|
+
if (
|
|
212
|
+
titleProp.value &&
|
|
213
|
+
titleProp.value.type === 'JSXExpressionContainer'
|
|
214
|
+
) {
|
|
215
|
+
const titleExpr = titleProp.value.expression;
|
|
216
|
+
if (titleExpr.type === 'ObjectExpression') {
|
|
217
|
+
// Extract text and icon from title object
|
|
218
|
+
const textProp = titleExpr.properties.find(
|
|
219
|
+
(p) => p.key && p.key.name === 'text'
|
|
220
|
+
);
|
|
221
|
+
const iconProp = titleExpr.properties.find(
|
|
222
|
+
(p) => p.key && p.key.name === 'icon'
|
|
223
|
+
);
|
|
224
|
+
|
|
225
|
+
if (textProp) {
|
|
226
|
+
// Replace title with just the text value
|
|
227
|
+
contentAttrs[0] = j.jsxAttribute(
|
|
228
|
+
j.jsxIdentifier('title'),
|
|
229
|
+
j.jsxExpressionContainer(textProp.value)
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
if (iconProp) {
|
|
234
|
+
// Add renderIcon prop
|
|
235
|
+
contentAttrs.push(
|
|
236
|
+
j.jsxAttribute(
|
|
237
|
+
j.jsxIdentifier('renderIcon'),
|
|
238
|
+
j.jsxExpressionContainer(iconProp.value)
|
|
239
|
+
)
|
|
240
|
+
);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Add pageActions if exists
|
|
246
|
+
if (pageActionsProp) {
|
|
247
|
+
contentAttrs.push(pageActionsProp);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Add contextualActions (from actionBarItems) if exists
|
|
251
|
+
if (actionBarItemsProp) {
|
|
252
|
+
const contextualActionsAttr = j.jsxAttribute(
|
|
253
|
+
j.jsxIdentifier('contextualActions'),
|
|
254
|
+
actionBarItemsProp.value
|
|
255
|
+
);
|
|
256
|
+
contentAttrs.push(contextualActionsAttr);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const contentChildren = [];
|
|
260
|
+
|
|
261
|
+
// Add ContentText if subtitle exists
|
|
262
|
+
if (subtitleProp) {
|
|
263
|
+
contentChildren.push(
|
|
264
|
+
createMemberJSXElement(
|
|
265
|
+
pageHeaderLocalName,
|
|
266
|
+
'ContentText',
|
|
267
|
+
[subtitleProp],
|
|
268
|
+
[]
|
|
269
|
+
)
|
|
270
|
+
);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Add original children to Content
|
|
274
|
+
contentChildren.push(...originalChildren);
|
|
275
|
+
|
|
276
|
+
newChildren.push(
|
|
277
|
+
createMemberJSXElement(
|
|
278
|
+
pageHeaderLocalName,
|
|
279
|
+
'Content',
|
|
280
|
+
contentAttrs,
|
|
281
|
+
contentChildren
|
|
282
|
+
)
|
|
283
|
+
);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// 3. Add TabBar if navigation exists
|
|
287
|
+
if (navigationProp) {
|
|
288
|
+
const tabBarChildren = [];
|
|
289
|
+
if (navigationProp.value.type === 'JSXExpressionContainer') {
|
|
290
|
+
tabBarChildren.push(navigationProp.value.expression);
|
|
291
|
+
}
|
|
292
|
+
newChildren.push(
|
|
293
|
+
createMemberJSXElement(
|
|
294
|
+
pageHeaderLocalName,
|
|
295
|
+
'TabBar',
|
|
296
|
+
[],
|
|
297
|
+
tabBarChildren
|
|
298
|
+
)
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// 4. Add TagOverflow if tags exist
|
|
303
|
+
if (tagsProp) {
|
|
304
|
+
const tagOverflowAttrs = [tagsProp];
|
|
305
|
+
if (showAllTagsLabelProp) {
|
|
306
|
+
tagOverflowAttrs.push(showAllTagsLabelProp);
|
|
307
|
+
}
|
|
308
|
+
if (allTagsModalTitleProp) {
|
|
309
|
+
tagOverflowAttrs.push(allTagsModalTitleProp);
|
|
310
|
+
}
|
|
311
|
+
if (allTagsModalSearchLabelProp) {
|
|
312
|
+
tagOverflowAttrs.push(allTagsModalSearchLabelProp);
|
|
313
|
+
}
|
|
314
|
+
if (allTagsModalSearchPlaceholderTextProp) {
|
|
315
|
+
tagOverflowAttrs.push(allTagsModalSearchPlaceholderTextProp);
|
|
316
|
+
}
|
|
317
|
+
newChildren.push(
|
|
318
|
+
createMemberJSXElement(
|
|
319
|
+
pageHeaderLocalName,
|
|
320
|
+
'TagOverflow',
|
|
321
|
+
tagOverflowAttrs,
|
|
322
|
+
[]
|
|
323
|
+
)
|
|
324
|
+
);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// Update the PageHeader element
|
|
328
|
+
path.node.openingElement.attributes = rootAttributes;
|
|
329
|
+
path.node.children = newChildren;
|
|
330
|
+
|
|
331
|
+
// If PageHeader was self-closing, convert it to have opening/closing tags
|
|
332
|
+
if (path.node.openingElement.selfClosing) {
|
|
333
|
+
path.node.openingElement.selfClosing = false;
|
|
334
|
+
}
|
|
335
|
+
if (!path.node.closingElement) {
|
|
336
|
+
path.node.closingElement = j.jsxClosingElement(
|
|
337
|
+
j.jsxIdentifier(pageHeaderLocalName)
|
|
338
|
+
);
|
|
339
|
+
}
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
// Update imports if transformation occurred
|
|
343
|
+
if (dirtyFlag) {
|
|
344
|
+
root
|
|
345
|
+
.find(j.ImportDeclaration)
|
|
346
|
+
.filter((path) => path.node.source.value === '@carbon/ibm-products')
|
|
347
|
+
.forEach((path) => {
|
|
348
|
+
const specifiers = path.node.specifiers;
|
|
349
|
+
let hasPageHeader = false;
|
|
350
|
+
|
|
351
|
+
// Check if PageHeader is imported
|
|
352
|
+
specifiers.forEach((specifier) => {
|
|
353
|
+
if (
|
|
354
|
+
specifier.type === 'ImportSpecifier' &&
|
|
355
|
+
specifier.imported &&
|
|
356
|
+
specifier.imported.name === 'PageHeader'
|
|
357
|
+
) {
|
|
358
|
+
hasPageHeader = true;
|
|
359
|
+
}
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
// If PageHeader is imported, update to preview version
|
|
363
|
+
if (hasPageHeader) {
|
|
364
|
+
const newSpecifiers = specifiers.map((specifier) => {
|
|
365
|
+
if (
|
|
366
|
+
specifier.type === 'ImportSpecifier' &&
|
|
367
|
+
specifier.imported &&
|
|
368
|
+
specifier.imported.name === 'PageHeader'
|
|
369
|
+
) {
|
|
370
|
+
// Change to preview__PageHeader, preserving the local name (alias)
|
|
371
|
+
// If there's a local name (alias), preserve it; otherwise use 'PageHeader'
|
|
372
|
+
const localName = specifier.local
|
|
373
|
+
? specifier.local.name
|
|
374
|
+
: 'PageHeader';
|
|
375
|
+
return j.importSpecifier(
|
|
376
|
+
j.identifier('preview__PageHeader'),
|
|
377
|
+
j.identifier(localName)
|
|
378
|
+
);
|
|
379
|
+
}
|
|
380
|
+
return specifier;
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
path.node.specifiers = newSpecifiers;
|
|
384
|
+
}
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
return root.toSource();
|
|
389
|
+
};
|
|
390
|
+
|
|
391
|
+
module.exports = transform;
|