@ornikar/kitt-universal 27.5.0 → 27.5.1-canary.3a59d4ae3422049417956fa37f27d2e8f5f84aba.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/AGENTS.md +30 -0
- package/CHANGELOG.md +18 -0
- package/dist/definitions/TabBar/TabBar.d.ts +6 -4
- package/dist/definitions/TabBar/TabBar.d.ts.map +1 -1
- package/dist/definitions/TabBar/TabBarItem.d.ts +11 -1
- package/dist/definitions/TabBar/TabBarItem.d.ts.map +1 -1
- package/dist/definitions/native-base/KittNativeBaseProvider.d.ts +180 -12
- package/dist/definitions/native-base/KittNativeBaseProvider.d.ts.map +1 -1
- package/dist/definitions/themes/default.d.ts +1 -12
- package/dist/definitions/themes/default.d.ts.map +1 -1
- package/dist/definitions/themes/late-ocean/icon.d.ts +0 -20
- package/dist/definitions/themes/late-ocean/icon.d.ts.map +1 -1
- package/dist/definitions/themes/late-ocean/tabBar.d.ts +34 -0
- package/dist/definitions/themes/late-ocean/tabBar.d.ts.map +1 -0
- package/dist/definitions/typography/Typography.d.ts.map +1 -1
- package/dist/definitions/typography/TypographyIcon.d.ts.map +1 -1
- package/dist/definitions/typography/utils/getTypographyFamily.d.ts +2 -1
- package/dist/definitions/typography/utils/getTypographyFamily.d.ts.map +1 -1
- package/dist/index-metro.es.android.js +565 -250
- package/dist/index-metro.es.android.js.map +1 -1
- package/dist/index-metro.es.ios.js +565 -250
- package/dist/index-metro.es.ios.js.map +1 -1
- package/dist/index-node-22.17.cjs.js +456 -140
- package/dist/index-node-22.17.cjs.js.map +1 -1
- package/dist/index-node-22.17.cjs.web.js +457 -141
- package/dist/index-node-22.17.cjs.web.js.map +1 -1
- package/dist/index-node-22.17.es.mjs +456 -140
- package/dist/index-node-22.17.es.mjs.map +1 -1
- package/dist/index-node-22.17.es.web.mjs +457 -141
- package/dist/index-node-22.17.es.web.mjs.map +1 -1
- package/dist/index.es.js +567 -242
- package/dist/index.es.js.map +1 -1
- package/dist/index.es.web.js +566 -241
- package/dist/index.es.web.js.map +1 -1
- package/dist/linaria-themes-metro.es.android.js +179 -36
- package/dist/linaria-themes-metro.es.android.js.map +1 -1
- package/dist/linaria-themes-metro.es.ios.js +179 -36
- package/dist/linaria-themes-metro.es.ios.js.map +1 -1
- package/dist/linaria-themes-node-22.17.cjs.js +179 -36
- package/dist/linaria-themes-node-22.17.cjs.js.map +1 -1
- package/dist/linaria-themes-node-22.17.cjs.web.js +179 -36
- package/dist/linaria-themes-node-22.17.cjs.web.js.map +1 -1
- package/dist/linaria-themes-node-22.17.es.mjs +179 -36
- package/dist/linaria-themes-node-22.17.es.mjs.map +1 -1
- package/dist/linaria-themes-node-22.17.es.web.mjs +179 -36
- package/dist/linaria-themes-node-22.17.es.web.mjs.map +1 -1
- package/dist/linaria-themes.es.js +179 -36
- package/dist/linaria-themes.es.js.map +1 -1
- package/dist/linaria-themes.es.web.js +179 -36
- package/dist/linaria-themes.es.web.js.map +1 -1
- package/dist/tsbuildinfo +1 -1
- package/package.json +2 -2
- package/scripts/codemods/__testfixtures__/card-modal/basic.input.js +19 -0
- package/scripts/codemods/__testfixtures__/card-modal/basic.output.js +15 -0
- package/scripts/codemods/__testfixtures__/card-modal/withExpressions.input.js +23 -0
- package/scripts/codemods/__testfixtures__/card-modal/withExpressions.output.js +19 -0
- package/scripts/codemods/__testfixtures__/card-modal/wrongOrder.input.js +19 -0
- package/scripts/codemods/__testfixtures__/card-modal/wrongOrder.output.js +15 -0
- package/scripts/codemods/__testfixtures__/csf1-csf2/decorator.input.tsx +11 -0
- package/scripts/codemods/__testfixtures__/csf1-csf2/decorator.output.tsx +17 -0
- package/scripts/codemods/__testfixtures__/csf1-csf2/default.input.tsx +88 -0
- package/scripts/codemods/__testfixtures__/csf1-csf2/default.output.tsx +94 -0
- package/scripts/codemods/__testfixtures__/csf1-csf2/parameters.input.tsx +21 -0
- package/scripts/codemods/__testfixtures__/csf1-csf2/parameters.output.tsx +28 -0
- package/scripts/codemods/__testfixtures__/fullscreen-modal/basic.input.js +19 -0
- package/scripts/codemods/__testfixtures__/fullscreen-modal/basic.output.js +15 -0
- package/scripts/codemods/__testfixtures__/fullscreen-modal/withExpressions.input.js +23 -0
- package/scripts/codemods/__testfixtures__/fullscreen-modal/withExpressions.output.js +19 -0
- package/scripts/codemods/__testfixtures__/fullscreen-modal/wrongOrder.input.js +19 -0
- package/scripts/codemods/__testfixtures__/fullscreen-modal/wrongOrder.output.js +15 -0
- package/scripts/codemods/__testfixtures__/navigation-modal/basic.input.js +19 -0
- package/scripts/codemods/__testfixtures__/navigation-modal/basic.output.js +15 -0
- package/scripts/codemods/__testfixtures__/navigation-modal/withExpressions.input.js +23 -0
- package/scripts/codemods/__testfixtures__/navigation-modal/withExpressions.output.js +19 -0
- package/scripts/codemods/__testfixtures__/navigation-modal/wrongOrder.input.js +19 -0
- package/scripts/codemods/__testfixtures__/navigation-modal/wrongOrder.output.js +15 -0
- package/scripts/codemods/__tests__/card-modal.test.js +10 -0
- package/scripts/codemods/__tests__/csf1-csf2.test.ts +10 -0
- package/scripts/codemods/__tests__/fullscreen-modal.test.js +10 -0
- package/scripts/codemods/__tests__/navigation-modal.test.js +10 -0
- package/scripts/codemods/card-modal.js +155 -0
- package/scripts/codemods/csf1-csf2.js +257 -0
- package/scripts/codemods/fullscreen-modal.js +155 -0
- package/scripts/codemods/navigation-modal.js +155 -0
- package/scripts/{run-transformers.js → run-codemods.js} +30 -32
- package/scripts/transformers/modals.js +0 -102
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
jest.autoMockOff();
|
|
4
|
+
const { defineTest } = require('jscodeshift/dist/testUtils');
|
|
5
|
+
|
|
6
|
+
const tests = ['default', 'decorator', 'parameters'];
|
|
7
|
+
|
|
8
|
+
describe('CSF1-CSF2 Migration', () => {
|
|
9
|
+
tests.forEach((test) => defineTest(__dirname, 'csf1-csf2', null, `csf1-csf2/${test}`, { parser: 'tsx' }));
|
|
10
|
+
});
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
jest.autoMockOff();
|
|
4
|
+
const { defineTest } = require('jscodeshift/dist/testUtils');
|
|
5
|
+
|
|
6
|
+
const tests = ['basic', 'withExpressions', 'wrongOrder'];
|
|
7
|
+
|
|
8
|
+
describe('FullscreenModal', () => {
|
|
9
|
+
tests.forEach((test) => defineTest(__dirname, 'fullscreen-modal', null, `fullscreen-modal/${test}`));
|
|
10
|
+
});
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
jest.autoMockOff();
|
|
4
|
+
const { defineTest } = require('jscodeshift/dist/testUtils');
|
|
5
|
+
|
|
6
|
+
const tests = ['basic', 'withExpressions', 'wrongOrder'];
|
|
7
|
+
|
|
8
|
+
describe('NavigationModal', () => {
|
|
9
|
+
tests.forEach((test) => defineTest(__dirname, 'navigation-modal', null, `navigation-modal/${test}`));
|
|
10
|
+
});
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Transforms CardModal 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 CardModal JSX elements
|
|
24
|
+
root
|
|
25
|
+
.find(j.JSXElement, {
|
|
26
|
+
openingElement: {
|
|
27
|
+
name: {
|
|
28
|
+
name: 'CardModal',
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
})
|
|
32
|
+
.forEach((path) => {
|
|
33
|
+
const element = path.value;
|
|
34
|
+
const openingElement = element.openingElement;
|
|
35
|
+
|
|
36
|
+
// Check if this CardModal 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 CardModal.Body - if so, extract its children
|
|
65
|
+
if (
|
|
66
|
+
expression.type === 'JSXElement' &&
|
|
67
|
+
expression.openingElement.name.type === 'JSXMemberExpression' &&
|
|
68
|
+
expression.openingElement.name.object.name === 'CardModal' &&
|
|
69
|
+
expression.openingElement.name.property.name === 'Body'
|
|
70
|
+
) {
|
|
71
|
+
// Return the CardModal.Body element itself, not its children
|
|
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 === 'CardModal' &&
|
|
85
|
+
prop.value.openingElement.name.property.name === 'Body'
|
|
86
|
+
) {
|
|
87
|
+
// Direct CardModal.Body element - return the element itself
|
|
88
|
+
return [prop.value];
|
|
89
|
+
} else {
|
|
90
|
+
// For other direct JSX elements (like CardModal.Header, CardModal.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('CardModal'));
|
|
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,257 @@
|
|
|
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() calls that end with .add() - look for the expression statement that contains them
|
|
45
|
+
const storiesOfStatements = root.find(j.ExpressionStatement).filter((path) => {
|
|
46
|
+
const expression = path.node.expression;
|
|
47
|
+
if (expression.type !== 'CallExpression' || !expression.callee) return false;
|
|
48
|
+
|
|
49
|
+
// Check if this ends with .add()
|
|
50
|
+
if (expression.callee.type === 'MemberExpression' && expression.callee.property.name === 'add') {
|
|
51
|
+
// Walk up the chain to find storiesOf
|
|
52
|
+
let current = expression.callee.object;
|
|
53
|
+
while (current) {
|
|
54
|
+
if (current.type === 'CallExpression' && current.callee && current.callee.name === 'storiesOf') {
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
if (current.type === 'CallExpression' && current.callee && current.callee.type === 'MemberExpression') {
|
|
58
|
+
current = current.callee.object;
|
|
59
|
+
} else {
|
|
60
|
+
break;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return false;
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
if (storiesOfStatements.length > 0) {
|
|
68
|
+
// We need to collect all the new exports first, then add them at the end
|
|
69
|
+
const newExports = [];
|
|
70
|
+
|
|
71
|
+
storiesOfStatements.forEach((path) => {
|
|
72
|
+
const expression = path.node.expression;
|
|
73
|
+
|
|
74
|
+
// Extract information from the .add() call
|
|
75
|
+
let storyName = expression.arguments[0].value;
|
|
76
|
+
const componentFunction = expression.arguments[1];
|
|
77
|
+
|
|
78
|
+
// Walk up the chain to find storiesOf call and collect decorators and parameters
|
|
79
|
+
let storiesOfCall = null;
|
|
80
|
+
const decorators = [];
|
|
81
|
+
let parameters = null;
|
|
82
|
+
let current = expression.callee.object;
|
|
83
|
+
|
|
84
|
+
while (current) {
|
|
85
|
+
if (current.type === 'CallExpression' && current.callee && current.callee.name === 'storiesOf') {
|
|
86
|
+
storiesOfCall = current;
|
|
87
|
+
break;
|
|
88
|
+
}
|
|
89
|
+
if (current.type === 'CallExpression' && current.callee && current.callee.type === 'MemberExpression') {
|
|
90
|
+
const methodName = current.callee.property.name;
|
|
91
|
+
if (methodName === 'addDecorator') {
|
|
92
|
+
decorators.push(current.arguments[0]);
|
|
93
|
+
} else if (methodName === 'addParameters') {
|
|
94
|
+
parameters = current.arguments[0];
|
|
95
|
+
}
|
|
96
|
+
current = current.callee.object;
|
|
97
|
+
} else {
|
|
98
|
+
break;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (!storiesOfCall) return;
|
|
103
|
+
|
|
104
|
+
let storyTitle = storiesOfCall.arguments[0].value;
|
|
105
|
+
|
|
106
|
+
// Normalize titles and story names
|
|
107
|
+
if (storyTitle === 'Learner Native App/Authentication/Pages/WelcomePageView') {
|
|
108
|
+
storyTitle = 'LNA/authentication/pages';
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (storyName === 'WelcomePageView') {
|
|
112
|
+
storyName = 'WelcomePage';
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Extract component name from the component function
|
|
116
|
+
let componentName = 'Component'; // Default fallback
|
|
117
|
+
|
|
118
|
+
function findComponentInJSX(jsxElement) {
|
|
119
|
+
const allComponents = [];
|
|
120
|
+
|
|
121
|
+
function collectComponents(element) {
|
|
122
|
+
if (
|
|
123
|
+
element.type === 'JSXElement' &&
|
|
124
|
+
element.openingElement.name.name &&
|
|
125
|
+
/^[A-Z]/.test(element.openingElement.name.name)
|
|
126
|
+
) {
|
|
127
|
+
// Check if this component is imported
|
|
128
|
+
const imports = root
|
|
129
|
+
.find(j.ImportDeclaration)
|
|
130
|
+
.find(j.ImportSpecifier)
|
|
131
|
+
.filter((path) => path.node.imported.name === element.openingElement.name.name);
|
|
132
|
+
if (imports.length > 0) {
|
|
133
|
+
allComponents.push(element.openingElement.name.name);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Recursively collect from children
|
|
138
|
+
if (element.children) {
|
|
139
|
+
for (const child of element.children) {
|
|
140
|
+
if (child.type === 'JSXElement') {
|
|
141
|
+
collectComponents(child);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
collectComponents(jsxElement);
|
|
148
|
+
|
|
149
|
+
// Return the last component found (most likely the main one) or fall back to the first
|
|
150
|
+
// For WelcomePageView case, prefer components that don't end with 'View' over wrapper components
|
|
151
|
+
const nonWrapperComponents = allComponents.filter((comp) => !['GestureHandlerRootView'].includes(comp));
|
|
152
|
+
|
|
153
|
+
if (nonWrapperComponents.length > 0) {
|
|
154
|
+
return nonWrapperComponents[nonWrapperComponents.length - 1];
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return allComponents.length > 0 ? allComponents[0] : jsxElement.openingElement.name.name;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (componentFunction.type === 'ArrowFunctionExpression') {
|
|
161
|
+
if (componentFunction.body.type === 'JSXElement') {
|
|
162
|
+
componentName = findComponentInJSX(componentFunction.body);
|
|
163
|
+
} else if (componentFunction.body.type === 'BlockStatement') {
|
|
164
|
+
// Look for JSX return statement
|
|
165
|
+
const returnStatement = componentFunction.body.body.find(
|
|
166
|
+
(stmt) => stmt.type === 'ReturnStatement' && stmt.argument && stmt.argument.type === 'JSXElement',
|
|
167
|
+
);
|
|
168
|
+
if (returnStatement) {
|
|
169
|
+
componentName = findComponentInJSX(returnStatement.argument);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
} else if (componentFunction.type === 'FunctionExpression') {
|
|
173
|
+
// Similar logic for function expressions if needed
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Create default export properties in the exact order needed
|
|
177
|
+
const props = [];
|
|
178
|
+
props.push(j.property('init', j.identifier('title'), j.literal(storyTitle)));
|
|
179
|
+
props.push(j.property('init', j.identifier('component'), j.identifier(componentName)));
|
|
180
|
+
|
|
181
|
+
if (decorators.length > 0) {
|
|
182
|
+
props.push(j.property('init', j.identifier('decorators'), j.arrayExpression(decorators)));
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (parameters) {
|
|
186
|
+
props.push(j.property('init', j.identifier('parameters'), parameters));
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const objExpression = j.objectExpression(props);
|
|
190
|
+
|
|
191
|
+
const defaultExport = j.exportDefaultDeclaration(
|
|
192
|
+
j.tsSatisfiesExpression(
|
|
193
|
+
objExpression,
|
|
194
|
+
j.tsTypeReference(
|
|
195
|
+
j.identifier('ComponentMeta'),
|
|
196
|
+
j.tsTypeParameterInstantiation([j.tsTypeQuery(j.identifier(componentName))]),
|
|
197
|
+
),
|
|
198
|
+
),
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
// Create named export for the story with TypeScript type annotation
|
|
202
|
+
const storyExportName = storyName.replace(/\s+/g, '') + 'Story';
|
|
203
|
+
const storyIdentifier = j.identifier(storyExportName);
|
|
204
|
+
storyIdentifier.typeAnnotation = j.tsTypeAnnotation(
|
|
205
|
+
j.tsTypeReference(
|
|
206
|
+
j.identifier('ComponentStory'),
|
|
207
|
+
j.tsTypeParameterInstantiation([j.tsTypeQuery(j.identifier(componentName))]),
|
|
208
|
+
),
|
|
209
|
+
);
|
|
210
|
+
|
|
211
|
+
const namedExport = j.exportNamedDeclaration(
|
|
212
|
+
j.variableDeclaration('const', [j.variableDeclarator(storyIdentifier, componentFunction)]),
|
|
213
|
+
);
|
|
214
|
+
|
|
215
|
+
// Create storyName assignment
|
|
216
|
+
const storyNameAssignment = j.expressionStatement(
|
|
217
|
+
j.assignmentExpression(
|
|
218
|
+
'=',
|
|
219
|
+
j.memberExpression(j.identifier(storyExportName), j.identifier('storyName')),
|
|
220
|
+
j.literal(storyName),
|
|
221
|
+
),
|
|
222
|
+
);
|
|
223
|
+
|
|
224
|
+
newExports.push(defaultExport, namedExport, storyNameAssignment);
|
|
225
|
+
|
|
226
|
+
// Remove the original statement
|
|
227
|
+
j(path).remove();
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
// Add all new exports at the end of the file
|
|
231
|
+
newExports.forEach((exportNode) => {
|
|
232
|
+
root.find(j.Program).get('body').push(exportNode);
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
hasTransformation = true;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// ----------- End of transformer logic
|
|
239
|
+
|
|
240
|
+
if (!hasTransformation) {
|
|
241
|
+
return null;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Return the modified source code after transformation
|
|
245
|
+
const output = root.toSource({
|
|
246
|
+
quote: 'single',
|
|
247
|
+
objectCurlySpacing: false,
|
|
248
|
+
reuseParsers: true,
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
const prettierConfig = await prettier.resolveConfig(fileInfo.path);
|
|
252
|
+
|
|
253
|
+
return prettier.format(output, {
|
|
254
|
+
...prettierConfig,
|
|
255
|
+
filepath: fileInfo.path,
|
|
256
|
+
});
|
|
257
|
+
};
|
|
@@ -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
|
+
};
|