@ornikar/kitt-universal 27.5.1-canary.4d3ec9b2bbb3371c254ae88141f81bb445a59f73.0 → 27.5.1-canary.74b3661a3818b6b6b6cd66a9bf11ce3340651bb8.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.
Files changed (90) hide show
  1. package/AGENTS.md +30 -0
  2. package/CHANGELOG.md +9 -5
  3. package/dist/definitions/TabBar/TabBar.d.ts +6 -4
  4. package/dist/definitions/TabBar/TabBar.d.ts.map +1 -1
  5. package/dist/definitions/TabBar/TabBarItem.d.ts +11 -1
  6. package/dist/definitions/TabBar/TabBarItem.d.ts.map +1 -1
  7. package/dist/definitions/native-base/KittNativeBaseProvider.d.ts +180 -12
  8. package/dist/definitions/native-base/KittNativeBaseProvider.d.ts.map +1 -1
  9. package/dist/definitions/themes/default.d.ts +1 -12
  10. package/dist/definitions/themes/default.d.ts.map +1 -1
  11. package/dist/definitions/themes/late-ocean/icon.d.ts +0 -20
  12. package/dist/definitions/themes/late-ocean/icon.d.ts.map +1 -1
  13. package/dist/definitions/themes/late-ocean/tabBar.d.ts +34 -0
  14. package/dist/definitions/themes/late-ocean/tabBar.d.ts.map +1 -0
  15. package/dist/definitions/typography/Typography.d.ts.map +1 -1
  16. package/dist/definitions/typography/TypographyIcon.d.ts.map +1 -1
  17. package/dist/definitions/typography/utils/getTypographyFamily.d.ts +2 -1
  18. package/dist/definitions/typography/utils/getTypographyFamily.d.ts.map +1 -1
  19. package/dist/index-metro.es.android.js +565 -250
  20. package/dist/index-metro.es.android.js.map +1 -1
  21. package/dist/index-metro.es.ios.js +565 -250
  22. package/dist/index-metro.es.ios.js.map +1 -1
  23. package/dist/index-node-22.17.cjs.js +456 -140
  24. package/dist/index-node-22.17.cjs.js.map +1 -1
  25. package/dist/index-node-22.17.cjs.web.js +457 -141
  26. package/dist/index-node-22.17.cjs.web.js.map +1 -1
  27. package/dist/index-node-22.17.es.mjs +456 -140
  28. package/dist/index-node-22.17.es.mjs.map +1 -1
  29. package/dist/index-node-22.17.es.web.mjs +457 -141
  30. package/dist/index-node-22.17.es.web.mjs.map +1 -1
  31. package/dist/index.es.js +567 -242
  32. package/dist/index.es.js.map +1 -1
  33. package/dist/index.es.web.js +566 -241
  34. package/dist/index.es.web.js.map +1 -1
  35. package/dist/linaria-themes-metro.es.android.js +179 -36
  36. package/dist/linaria-themes-metro.es.android.js.map +1 -1
  37. package/dist/linaria-themes-metro.es.ios.js +179 -36
  38. package/dist/linaria-themes-metro.es.ios.js.map +1 -1
  39. package/dist/linaria-themes-node-22.17.cjs.js +179 -36
  40. package/dist/linaria-themes-node-22.17.cjs.js.map +1 -1
  41. package/dist/linaria-themes-node-22.17.cjs.web.js +179 -36
  42. package/dist/linaria-themes-node-22.17.cjs.web.js.map +1 -1
  43. package/dist/linaria-themes-node-22.17.es.mjs +179 -36
  44. package/dist/linaria-themes-node-22.17.es.mjs.map +1 -1
  45. package/dist/linaria-themes-node-22.17.es.web.mjs +179 -36
  46. package/dist/linaria-themes-node-22.17.es.web.mjs.map +1 -1
  47. package/dist/linaria-themes.es.js +179 -36
  48. package/dist/linaria-themes.es.js.map +1 -1
  49. package/dist/linaria-themes.es.web.js +179 -36
  50. package/dist/linaria-themes.es.web.js.map +1 -1
  51. package/dist/tsbuildinfo +1 -1
  52. package/package.json +2 -2
  53. package/scripts/codemods/__testfixtures__/csf1-csf2/decorator.input.tsx +11 -0
  54. package/scripts/codemods/__testfixtures__/csf1-csf2/decorator.output.tsx +17 -0
  55. package/scripts/codemods/__testfixtures__/csf1-csf2/default.input.tsx +88 -0
  56. package/scripts/codemods/__testfixtures__/csf1-csf2/default.output.tsx +94 -0
  57. package/scripts/codemods/__testfixtures__/csf1-csf2/new.input.tsx +77 -0
  58. package/scripts/codemods/__testfixtures__/csf1-csf2/new.output.tsx +92 -0
  59. package/scripts/codemods/__testfixtures__/csf1-csf2/parameters.input.tsx +21 -0
  60. package/scripts/codemods/__testfixtures__/csf1-csf2/parameters.output.tsx +28 -0
  61. package/scripts/codemods/__tests__/csf1-csf2.test.ts +10 -0
  62. package/scripts/codemods/card-modal.js +155 -0
  63. package/scripts/codemods/csf1-csf2.js +323 -0
  64. package/scripts/codemods/fullscreen-modal.js +155 -0
  65. package/scripts/codemods/navigation-modal.js +155 -0
  66. package/scripts/{run-transformers.js → run-codemods.js} +12 -12
  67. package/scripts/transformers/card-modal.js +0 -136
  68. package/scripts/transformers/fullscreen-modal.js +0 -138
  69. package/scripts/transformers/navigation-modal.js +0 -138
  70. /package/scripts/{transformers → codemods}/__testfixtures__/card-modal/basic.input.js +0 -0
  71. /package/scripts/{transformers → codemods}/__testfixtures__/card-modal/basic.output.js +0 -0
  72. /package/scripts/{transformers → codemods}/__testfixtures__/card-modal/withExpressions.input.js +0 -0
  73. /package/scripts/{transformers → codemods}/__testfixtures__/card-modal/withExpressions.output.js +0 -0
  74. /package/scripts/{transformers → codemods}/__testfixtures__/card-modal/wrongOrder.input.js +0 -0
  75. /package/scripts/{transformers → codemods}/__testfixtures__/card-modal/wrongOrder.output.js +0 -0
  76. /package/scripts/{transformers → codemods}/__testfixtures__/fullscreen-modal/basic.input.js +0 -0
  77. /package/scripts/{transformers → codemods}/__testfixtures__/fullscreen-modal/basic.output.js +0 -0
  78. /package/scripts/{transformers → codemods}/__testfixtures__/fullscreen-modal/withExpressions.input.js +0 -0
  79. /package/scripts/{transformers → codemods}/__testfixtures__/fullscreen-modal/withExpressions.output.js +0 -0
  80. /package/scripts/{transformers → codemods}/__testfixtures__/fullscreen-modal/wrongOrder.input.js +0 -0
  81. /package/scripts/{transformers → codemods}/__testfixtures__/fullscreen-modal/wrongOrder.output.js +0 -0
  82. /package/scripts/{transformers → codemods}/__testfixtures__/navigation-modal/basic.input.js +0 -0
  83. /package/scripts/{transformers → codemods}/__testfixtures__/navigation-modal/basic.output.js +0 -0
  84. /package/scripts/{transformers → codemods}/__testfixtures__/navigation-modal/withExpressions.input.js +0 -0
  85. /package/scripts/{transformers → codemods}/__testfixtures__/navigation-modal/withExpressions.output.js +0 -0
  86. /package/scripts/{transformers → codemods}/__testfixtures__/navigation-modal/wrongOrder.input.js +0 -0
  87. /package/scripts/{transformers → codemods}/__testfixtures__/navigation-modal/wrongOrder.output.js +0 -0
  88. /package/scripts/{transformers → codemods}/__tests__/card-modal.test.js +0 -0
  89. /package/scripts/{transformers → codemods}/__tests__/fullscreen-modal.test.js +0 -0
  90. /package/scripts/{transformers → codemods}/__tests__/navigation-modal.test.js +0 -0
@@ -0,0 +1,323 @@
1
+ 'use strict';
2
+
3
+ // Codemod to convert Storybook CSF1 format to CSF2 format
4
+
5
+ // Use local jscodeshift instance directly
6
+ const jscodeshift = require('jscodeshift');
7
+ const prettier = require('prettier');
8
+
9
+ module.exports = async function transformer(fileInfo, api) {
10
+ // Use the jscodeshift API to parse the file
11
+ const j = api.jscodeshift || jscodeshift;
12
+
13
+ // Parse the source code of the file
14
+ const root = j(fileInfo.source);
15
+
16
+ // ----------- Start of transformer logic
17
+
18
+ let hasTransformation = false;
19
+
20
+ // Find storiesOf import and replace it
21
+ const storiesOfImports = root
22
+ .find(j.ImportDeclaration, {
23
+ source: { value: '@storybook/react-native' },
24
+ })
25
+ .filter((path) => {
26
+ return path.node.specifiers.some((spec) => spec.type === 'ImportSpecifier' && spec.imported.name === 'storiesOf');
27
+ });
28
+
29
+ if (storiesOfImports.length > 0) {
30
+ // Replace with ComponentMeta and ComponentStory type imports
31
+ storiesOfImports.replaceWith(() => {
32
+ return j.importDeclaration(
33
+ [
34
+ j.importSpecifier(j.identifier('ComponentMeta'), j.identifier('ComponentMeta')),
35
+ j.importSpecifier(j.identifier('ComponentStory'), j.identifier('ComponentStory')),
36
+ ],
37
+ j.literal('@storybook/react-native'),
38
+ 'type',
39
+ );
40
+ });
41
+ hasTransformation = true;
42
+ }
43
+
44
+ // Find storiesOf() chains - look for any expression that contains storiesOf
45
+ const storiesOfStatements = root.find(j.ExpressionStatement).filter((path) => {
46
+ const expression = path.node.expression;
47
+ if (expression.type !== 'CallExpression') return false;
48
+
49
+ // Look for storiesOf anywhere in the call chain
50
+ function containsStoriesOf(node) {
51
+ if (node.type === 'CallExpression' && node.callee && node.callee.name === 'storiesOf') {
52
+ return true;
53
+ }
54
+ if (node.type === 'CallExpression' && node.callee && node.callee.type === 'MemberExpression') {
55
+ return containsStoriesOf(node.callee.object);
56
+ }
57
+ return false;
58
+ }
59
+
60
+ return containsStoriesOf(expression);
61
+ });
62
+
63
+ if (storiesOfStatements.length > 0) {
64
+ // We need to collect all the new exports first, then add them at the end
65
+ const newExports = [];
66
+
67
+ storiesOfStatements.forEach((path) => {
68
+ const expression = path.node.expression;
69
+
70
+ // Parse the entire call chain to extract all stories, decorators, and parameters
71
+ function parseStoriesChain(node) {
72
+ const stories = [];
73
+ const decorators = [];
74
+ let globalParameters = null;
75
+ let storiesOfCall = null;
76
+
77
+ function walkChain(current) {
78
+ if (current.type === 'CallExpression' && current.callee) {
79
+ if (current.callee.name === 'storiesOf') {
80
+ // Found the root storiesOf call
81
+ storiesOfCall = current;
82
+ } else if (current.callee.type === 'MemberExpression') {
83
+ const methodName = current.callee.property.name;
84
+
85
+ if (methodName === 'add') {
86
+ // Extract story information
87
+ const storyName = current.arguments[0].value;
88
+ const componentFunction = current.arguments[1];
89
+ const storyParameters = current.arguments[2]; // Third argument contains parameters
90
+
91
+ stories.push({
92
+ name: storyName,
93
+ component: componentFunction,
94
+ parameters: storyParameters,
95
+ });
96
+ } else if (methodName === 'addDecorator') {
97
+ let decorator = current.arguments[0];
98
+ // Remove 'as any' type assertions from decorators
99
+ if (decorator.type === 'TSAsExpression' && decorator.typeAnnotation.type === 'TSAnyKeyword') {
100
+ decorator = decorator.expression;
101
+ }
102
+ decorators.push(decorator); // Collect decorators in order
103
+ } else if (methodName === 'addParameters') {
104
+ globalParameters = current.arguments[0];
105
+ }
106
+
107
+ // Continue walking up the chain
108
+ walkChain(current.callee.object);
109
+ }
110
+ }
111
+ }
112
+
113
+ walkChain(node);
114
+
115
+ return {
116
+ stories: stories.reverse(), // Reverse to get correct order
117
+ decorators: decorators.reverse(), // Reverse to get correct order
118
+ globalParameters,
119
+ storiesOfCall,
120
+ };
121
+ }
122
+
123
+ const { stories, decorators, globalParameters, storiesOfCall } = parseStoriesChain(expression);
124
+
125
+ if (!storiesOfCall || stories.length === 0) return;
126
+
127
+ let storyTitle = storiesOfCall.arguments[0].value;
128
+
129
+ // Normalize titles and story names
130
+ if (storyTitle === 'Learner Native App/Authentication/Pages/WelcomePageView') {
131
+ storyTitle = 'LNA/authentication/pages';
132
+ }
133
+
134
+ // Extract component name from the first story's component function
135
+ let componentName = 'Component'; // Default fallback
136
+
137
+ function findComponentInJSX(jsxElement) {
138
+ const allComponents = [];
139
+
140
+ function collectComponents(element) {
141
+ if (
142
+ element.type === 'JSXElement' &&
143
+ element.openingElement.name.name &&
144
+ /^[A-Z]/.test(element.openingElement.name.name)
145
+ ) {
146
+ // Check if this component is imported
147
+ const imports = root
148
+ .find(j.ImportDeclaration)
149
+ .find(j.ImportSpecifier)
150
+ .filter((path) => path.node.imported.name === element.openingElement.name.name);
151
+ if (imports.length > 0) {
152
+ allComponents.push(element.openingElement.name.name);
153
+ }
154
+ }
155
+
156
+ // Recursively collect from children
157
+ if (element.children) {
158
+ for (const child of element.children) {
159
+ if (child.type === 'JSXElement') {
160
+ collectComponents(child);
161
+ }
162
+ }
163
+ }
164
+ }
165
+
166
+ collectComponents(jsxElement);
167
+
168
+ // Return the last component found (most likely the main one) or fall back to the first
169
+ // For WelcomePageView case, prefer components that don't end with 'View' over wrapper components
170
+ const nonWrapperComponents = allComponents.filter((comp) => !['GestureHandlerRootView'].includes(comp));
171
+
172
+ if (nonWrapperComponents.length > 0) {
173
+ return nonWrapperComponents[nonWrapperComponents.length - 1];
174
+ }
175
+
176
+ return allComponents.length > 0 ? allComponents[0] : jsxElement.openingElement.name.name;
177
+ }
178
+
179
+ function extractComponentName(componentFunction) {
180
+ if (componentFunction.type === 'ArrowFunctionExpression') {
181
+ if (componentFunction.body.type === 'JSXElement') {
182
+ return findComponentInJSX(componentFunction.body);
183
+ } else if (componentFunction.body.type === 'BlockStatement') {
184
+ // Look for JSX return statement
185
+ const returnStatement = componentFunction.body.body.find(
186
+ (stmt) => stmt.type === 'ReturnStatement' && stmt.argument && stmt.argument.type === 'JSXElement',
187
+ );
188
+ if (returnStatement) {
189
+ return findComponentInJSX(returnStatement.argument);
190
+ }
191
+ }
192
+ } else if (componentFunction.type === 'FunctionExpression') {
193
+ // Similar logic for function expressions if needed
194
+ }
195
+ return 'Component';
196
+ }
197
+
198
+ // Use the first story to determine the main component
199
+ if (stories.length > 0) {
200
+ componentName = extractComponentName(stories[0].component);
201
+ }
202
+
203
+ // Create default export properties in the exact order needed
204
+ const props = [];
205
+ props.push(j.property('init', j.identifier('title'), j.literal(storyTitle)));
206
+ props.push(j.property('init', j.identifier('component'), j.identifier(componentName)));
207
+
208
+ if (decorators.length > 0) {
209
+ props.push(j.property('init', j.identifier('decorators'), j.arrayExpression(decorators)));
210
+ }
211
+
212
+ if (globalParameters) {
213
+ props.push(j.property('init', j.identifier('parameters'), globalParameters));
214
+ }
215
+
216
+ const objExpression = j.objectExpression(props);
217
+
218
+ const defaultExport = j.exportDefaultDeclaration(
219
+ j.tsSatisfiesExpression(
220
+ objExpression,
221
+ j.tsTypeReference(
222
+ j.identifier('ComponentMeta'),
223
+ j.tsTypeParameterInstantiation([j.tsTypeQuery(j.identifier(componentName))]),
224
+ ),
225
+ ),
226
+ );
227
+
228
+ // Add the default export first
229
+ newExports.push(defaultExport);
230
+
231
+ // Process each story
232
+ stories.forEach((story) => {
233
+ let storyName = story.name;
234
+
235
+ // Normalize story names
236
+ if (storyName === 'WelcomePageView') {
237
+ storyName = 'WelcomePage';
238
+ }
239
+
240
+ // Create named export for the story with TypeScript type annotation
241
+ // Sanitize story name to create valid JavaScript identifier
242
+ function sanitizeStoryName(name) {
243
+ // Special case: remove "(default)" suffix as it's redundant in CSF2
244
+ let sanitized = name.replace(/\s*\(default\)\s*$/i, '');
245
+
246
+ // Remove special characters and normalize spacing while preserving word boundaries
247
+ return sanitized
248
+ .replace(/[^a-zA-Z0-9\s]/g, '') // Remove special characters
249
+ .split(/\s+/) // Split on whitespace
250
+ .filter((word) => word.length > 0) // Remove empty parts
251
+ .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
252
+ .join('');
253
+ }
254
+
255
+ const storyExportName = sanitizeStoryName(storyName) + 'Story';
256
+ const storyIdentifier = j.identifier(storyExportName);
257
+ storyIdentifier.typeAnnotation = j.tsTypeAnnotation(
258
+ j.tsTypeReference(
259
+ j.identifier('ComponentStory'),
260
+ j.tsTypeParameterInstantiation([j.tsTypeQuery(j.identifier(componentName))]),
261
+ ),
262
+ );
263
+
264
+ const namedExport = j.exportNamedDeclaration(
265
+ j.variableDeclaration('const', [j.variableDeclarator(storyIdentifier, story.component)]),
266
+ );
267
+
268
+ // Create storyName assignment
269
+ const storyNameAssignment = j.expressionStatement(
270
+ j.assignmentExpression(
271
+ '=',
272
+ j.memberExpression(j.identifier(storyExportName), j.identifier('storyName')),
273
+ j.literal(storyName),
274
+ ),
275
+ );
276
+
277
+ newExports.push(namedExport, storyNameAssignment);
278
+
279
+ // Add parameters if the story has them
280
+ if (story.parameters) {
281
+ const parametersAssignment = j.expressionStatement(
282
+ j.assignmentExpression(
283
+ '=',
284
+ j.memberExpression(j.identifier(storyExportName), j.identifier('parameters')),
285
+ story.parameters,
286
+ ),
287
+ );
288
+ newExports.push(parametersAssignment);
289
+ }
290
+ });
291
+
292
+ // Remove the original statement
293
+ j(path).remove();
294
+ });
295
+
296
+ // Add all new exports at the end of the file
297
+ newExports.forEach((exportNode) => {
298
+ root.find(j.Program).get('body').push(exportNode);
299
+ });
300
+
301
+ hasTransformation = true;
302
+ }
303
+
304
+ // ----------- End of transformer logic
305
+
306
+ if (!hasTransformation) {
307
+ return null;
308
+ }
309
+
310
+ // Return the modified source code after transformation
311
+ const output = root.toSource({
312
+ quote: 'single',
313
+ objectCurlySpacing: false,
314
+ reuseParsers: true,
315
+ });
316
+
317
+ const prettierConfig = await prettier.resolveConfig(fileInfo.path);
318
+
319
+ return prettier.format(output, {
320
+ ...prettierConfig,
321
+ filepath: fileInfo.path,
322
+ });
323
+ };
@@ -0,0 +1,155 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Transforms FullscreenModal components from prop-based structure to children-based structure.
5
+ * Converts header, body, and footer props into direct children, maintaining proper order.
6
+ */
7
+
8
+ // Use local jscodeshift instance directly
9
+ const jscodeshift = require('jscodeshift');
10
+ const prettier = require('prettier');
11
+
12
+ module.exports = async function transformer(fileInfo, api) {
13
+ // Use the jscodeshift API to parse the file
14
+ const j = api.jscodeshift || jscodeshift;
15
+
16
+ // Parse the source code of the file
17
+ const root = j(fileInfo.source);
18
+
19
+ // ----------- Start of codemod logic
20
+
21
+ let hasChanges = false;
22
+
23
+ // Find all FullscreenModal JSX elements
24
+ root
25
+ .find(j.JSXElement, {
26
+ openingElement: {
27
+ name: {
28
+ name: 'FullscreenModal',
29
+ },
30
+ },
31
+ })
32
+ .forEach((path) => {
33
+ const element = path.value;
34
+ const openingElement = element.openingElement;
35
+
36
+ // Check if this FullscreenModal has header, body, or footer props
37
+ const headerProp = openingElement.attributes.find(
38
+ (attr) => attr.type === 'JSXAttribute' && attr.name.name === 'header',
39
+ );
40
+ const bodyProp = openingElement.attributes.find(
41
+ (attr) => attr.type === 'JSXAttribute' && attr.name.name === 'body',
42
+ );
43
+ const footerProp = openingElement.attributes.find(
44
+ (attr) => attr.type === 'JSXAttribute' && attr.name.name === 'footer',
45
+ );
46
+
47
+ // Only transform if at least one of these props exists
48
+ if (!headerProp && !bodyProp && !footerProp) {
49
+ return;
50
+ }
51
+
52
+ hasChanges = true;
53
+
54
+ // Extract the prop values and create children
55
+ const children = [];
56
+
57
+ // Helper function to extract content from prop value
58
+ const extractPropContent = (prop) => {
59
+ if (!prop || !prop.value) return [];
60
+
61
+ if (prop.value.type === 'JSXExpressionContainer') {
62
+ const expression = prop.value.expression;
63
+
64
+ // Check if the expression is a FullscreenModal.Body - if so, keep the element
65
+ if (
66
+ expression.type === 'JSXElement' &&
67
+ expression.openingElement.name.type === 'JSXMemberExpression' &&
68
+ expression.openingElement.name.object.name === 'FullscreenModal' &&
69
+ expression.openingElement.name.property.name === 'Body'
70
+ ) {
71
+ // Return the FullscreenModal.Body element itself
72
+ return [expression];
73
+ } else if (expression.type === 'JSXElement') {
74
+ // For other JSX elements (header, footer), return the element directly (not wrapped)
75
+ return [expression];
76
+ } else {
77
+ // For other expressions (including conditionals), return as JSX expression
78
+ return [j.jsxExpressionContainer(expression)];
79
+ }
80
+ } else if (prop.value.type === 'JSXElement') {
81
+ // Direct JSX element
82
+ if (
83
+ prop.value.openingElement.name.type === 'JSXMemberExpression' &&
84
+ prop.value.openingElement.name.object.name === 'FullscreenModal' &&
85
+ prop.value.openingElement.name.property.name === 'Body'
86
+ ) {
87
+ // Direct FullscreenModal.Body element - return the element itself
88
+ return [prop.value];
89
+ } else {
90
+ // For other direct JSX elements (like FullscreenModal.Header, FullscreenModal.Footer), return as-is
91
+ return [prop.value];
92
+ }
93
+ } else {
94
+ // For other types, return as-is
95
+ return [prop.value];
96
+ }
97
+ };
98
+
99
+ // Add header first
100
+ if (headerProp) {
101
+ const headerContent = extractPropContent(headerProp);
102
+ children.push(...headerContent);
103
+ }
104
+
105
+ // Add body second
106
+ if (bodyProp) {
107
+ const bodyContent = extractPropContent(bodyProp);
108
+ children.push(...bodyContent);
109
+ }
110
+
111
+ // Add footer last
112
+ if (footerProp) {
113
+ const footerContent = extractPropContent(footerProp);
114
+ children.push(...footerContent);
115
+ }
116
+
117
+ // Remove the header, body, and footer props from attributes
118
+ openingElement.attributes = openingElement.attributes.filter((attr) => {
119
+ return !(
120
+ attr.type === 'JSXAttribute' &&
121
+ (attr.name.name === 'header' || attr.name.name === 'body' || attr.name.name === 'footer')
122
+ );
123
+ });
124
+
125
+ // Update the element to be self-closing if no children, or add children
126
+ if (children.length === 0) {
127
+ openingElement.selfClosing = true;
128
+ element.closingElement = null;
129
+ element.children = [];
130
+ } else {
131
+ openingElement.selfClosing = false;
132
+ if (!element.closingElement) {
133
+ element.closingElement = j.jsxClosingElement(j.jsxIdentifier('FullscreenModal'));
134
+ }
135
+ element.children = children;
136
+ }
137
+ });
138
+
139
+ // Only return modified code if changes were made
140
+ if (!hasChanges) {
141
+ return fileInfo.source;
142
+ }
143
+
144
+ // ----------- End of codemod logic
145
+
146
+ // Return the modified source code after transformation
147
+ const output = root.toSource({ quote: 'single' });
148
+
149
+ const prettierConfig = await prettier.resolveConfig(fileInfo.path);
150
+
151
+ return prettier.format(output, {
152
+ ...prettierConfig,
153
+ filepath: fileInfo.path,
154
+ });
155
+ };
@@ -0,0 +1,155 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Transforms NavigationModal components from prop-based structure to children-based structure.
5
+ * Converts header, body, and footer props into direct children, maintaining proper order.
6
+ */
7
+
8
+ // Use local jscodeshift instance directly
9
+ const jscodeshift = require('jscodeshift');
10
+ const prettier = require('prettier');
11
+
12
+ module.exports = async function transformer(fileInfo, api) {
13
+ // Use the jscodeshift API to parse the file
14
+ const j = api.jscodeshift || jscodeshift;
15
+
16
+ // Parse the source code of the file
17
+ const root = j(fileInfo.source);
18
+
19
+ // ----------- Start of codemod logic
20
+
21
+ let hasChanges = false;
22
+
23
+ // Find all NavigationModal JSX elements
24
+ root
25
+ .find(j.JSXElement, {
26
+ openingElement: {
27
+ name: {
28
+ name: 'NavigationModal',
29
+ },
30
+ },
31
+ })
32
+ .forEach((path) => {
33
+ const element = path.value;
34
+ const openingElement = element.openingElement;
35
+
36
+ // Check if this NavigationModal has header, body, or footer props
37
+ const headerProp = openingElement.attributes.find(
38
+ (attr) => attr.type === 'JSXAttribute' && attr.name.name === 'header',
39
+ );
40
+ const bodyProp = openingElement.attributes.find(
41
+ (attr) => attr.type === 'JSXAttribute' && attr.name.name === 'body',
42
+ );
43
+ const footerProp = openingElement.attributes.find(
44
+ (attr) => attr.type === 'JSXAttribute' && attr.name.name === 'footer',
45
+ );
46
+
47
+ // Only transform if at least one of these props exists
48
+ if (!headerProp && !bodyProp && !footerProp) {
49
+ return;
50
+ }
51
+
52
+ hasChanges = true;
53
+
54
+ // Extract the prop values and create children
55
+ const children = [];
56
+
57
+ // Helper function to extract content from prop value
58
+ const extractPropContent = (prop) => {
59
+ if (!prop || !prop.value) return [];
60
+
61
+ if (prop.value.type === 'JSXExpressionContainer') {
62
+ const expression = prop.value.expression;
63
+
64
+ // Check if the expression is a NavigationModal.Body - if so, keep the element
65
+ if (
66
+ expression.type === 'JSXElement' &&
67
+ expression.openingElement.name.type === 'JSXMemberExpression' &&
68
+ expression.openingElement.name.object.name === 'NavigationModal' &&
69
+ expression.openingElement.name.property.name === 'Body'
70
+ ) {
71
+ // Return the NavigationModal.Body element itself
72
+ return [expression];
73
+ } else if (expression.type === 'JSXElement') {
74
+ // For other JSX elements (header, footer), return the element directly (not wrapped)
75
+ return [expression];
76
+ } else {
77
+ // For other expressions (including conditionals), return as JSX expression
78
+ return [j.jsxExpressionContainer(expression)];
79
+ }
80
+ } else if (prop.value.type === 'JSXElement') {
81
+ // Direct JSX element
82
+ if (
83
+ prop.value.openingElement.name.type === 'JSXMemberExpression' &&
84
+ prop.value.openingElement.name.object.name === 'NavigationModal' &&
85
+ prop.value.openingElement.name.property.name === 'Body'
86
+ ) {
87
+ // Direct NavigationModal.Body element - return the element itself
88
+ return [prop.value];
89
+ } else {
90
+ // For other direct JSX elements (like NavigationModal.Header, NavigationModal.Footer), return as-is
91
+ return [prop.value];
92
+ }
93
+ } else {
94
+ // For other types, return as-is
95
+ return [prop.value];
96
+ }
97
+ };
98
+
99
+ // Add header first
100
+ if (headerProp) {
101
+ const headerContent = extractPropContent(headerProp);
102
+ children.push(...headerContent);
103
+ }
104
+
105
+ // Add body second
106
+ if (bodyProp) {
107
+ const bodyContent = extractPropContent(bodyProp);
108
+ children.push(...bodyContent);
109
+ }
110
+
111
+ // Add footer last
112
+ if (footerProp) {
113
+ const footerContent = extractPropContent(footerProp);
114
+ children.push(...footerContent);
115
+ }
116
+
117
+ // Remove the header, body, and footer props from attributes
118
+ openingElement.attributes = openingElement.attributes.filter((attr) => {
119
+ return !(
120
+ attr.type === 'JSXAttribute' &&
121
+ (attr.name.name === 'header' || attr.name.name === 'body' || attr.name.name === 'footer')
122
+ );
123
+ });
124
+
125
+ // Update the element to be self-closing if no children, or add children
126
+ if (children.length === 0) {
127
+ openingElement.selfClosing = true;
128
+ element.closingElement = null;
129
+ element.children = [];
130
+ } else {
131
+ openingElement.selfClosing = false;
132
+ if (!element.closingElement) {
133
+ element.closingElement = j.jsxClosingElement(j.jsxIdentifier('NavigationModal'));
134
+ }
135
+ element.children = children;
136
+ }
137
+ });
138
+
139
+ // Only return modified code if changes were made
140
+ if (!hasChanges) {
141
+ return fileInfo.source;
142
+ }
143
+
144
+ // ----------- End of codemod logic
145
+
146
+ // Return the modified source code after transformation
147
+ const output = root.toSource({ quote: 'single' });
148
+
149
+ const prettierConfig = await prettier.resolveConfig(fileInfo.path);
150
+
151
+ return prettier.format(output, {
152
+ ...prettierConfig,
153
+ filepath: fileInfo.path,
154
+ });
155
+ };
@@ -16,12 +16,12 @@ const [targetPath] = args;
16
16
  // Check for --dry argument to enable dry run mode
17
17
  const isDryRun = args.includes('--dry');
18
18
 
19
- // Check for --only argument to run a specific transformer
19
+ // Check for --only argument to run a specific codemod
20
20
  const onlyArg = args.find((arg) => arg.startsWith('--only='));
21
- const transformerName = onlyArg ? onlyArg.split('=')[1] : null;
21
+ const codemodName = onlyArg ? onlyArg.split('=')[1] : null;
22
22
 
23
23
  if (!targetPath) {
24
- console.error('❌ Usage: node scripts/run-transformers.js <targetPath> [--dry] [--only=<transformerName>]');
24
+ console.error('❌ Usage: node scripts/run-codemods.js <targetPath> [--dry] [--only=<codemodName>]');
25
25
  process.exit(1);
26
26
  }
27
27
 
@@ -30,17 +30,17 @@ if (!fs.existsSync(targetPath)) {
30
30
  process.exit(1);
31
31
  }
32
32
 
33
- const transformsDir = path.join(__dirname, 'transformers');
33
+ const transformsDir = path.join(__dirname, 'codemods');
34
34
  const transformFiles = fs
35
35
  .readdirSync(transformsDir)
36
- .filter((file) => file.endsWith('.js') && (!transformerName || file === `${transformerName}.js`));
36
+ .filter((file) => file.endsWith('.js') && (!codemodName || file === `${codemodName}.js`));
37
37
 
38
38
  if (transformFiles.length === 0) {
39
- console.log('✅ No transformers to apply.');
39
+ console.log('✅ No codemods to apply.');
40
40
  process.exit(0);
41
41
  }
42
42
 
43
- console.log(`🛠 Found ${transformFiles.length} transformer(s) to apply to: ${targetPath}`);
43
+ console.log(`🛠 Found ${transformFiles.length} codemod(s) to apply to: ${targetPath}`);
44
44
 
45
45
  // Utility to recursively collect all .ts/.tsx files
46
46
  function getAllFiles(dir) {
@@ -60,17 +60,17 @@ function getAllFiles(dir) {
60
60
  const filesToTransform = fs.statSync(targetPath).isDirectory() ? getAllFiles(targetPath) : [targetPath];
61
61
  const updatedFiles = new Set();
62
62
 
63
- // Execute each transformer
63
+ // Execute each codemod
64
64
  for (const transformFile of transformFiles) {
65
65
  const transformPath = path.join(transformsDir, transformFile);
66
- const transformer = require(transformPath);
67
- console.log(`➡️ Running transformer: ${transformFile}`);
66
+ const codemod = require(transformPath);
67
+ console.log(`➡️ Running codemod: ${transformFile}`);
68
68
 
69
69
  for (const filePath of filesToTransform) {
70
70
  const source = fs.readFileSync(filePath, 'utf8');
71
71
 
72
72
  try {
73
- transformer(
73
+ codemod(
74
74
  { path: filePath, source },
75
75
  { jscodeshift: jscodeshift.withParser('tsx') },
76
76
  { printOptions: { quote: 'single', trailingComma: true } },
@@ -107,5 +107,5 @@ if (isDryRun) {
107
107
  console.log(`🚧 Dry run complete. ${updatedFiles.size} file(s) would be modified.`);
108
108
  console.log('Run without --dry to apply changes.');
109
109
  } else {
110
- console.log(`🏁 All transformers done. ${updatedFiles.size} file(s) modified.`);
110
+ console.log(`🏁 All codemods done. ${updatedFiles.size} file(s) modified.`);
111
111
  }