@ornikar/kitt-universal 27.7.0 → 27.9.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/CHANGELOG.md +19 -0
- package/dist/definitions/TopNavBar/TopNavBar.d.ts +21 -0
- package/dist/definitions/TopNavBar/TopNavBar.d.ts.map +1 -0
- package/dist/definitions/index.d.ts +2 -0
- package/dist/definitions/index.d.ts.map +1 -1
- package/dist/definitions/story-components/StoryDecorator.d.ts +1 -1
- package/dist/definitions/story-components/StoryDecorator.d.ts.map +1 -1
- package/dist/definitions/utils/storybook/KittThemeDecorator.d.ts +1 -1
- package/dist/definitions/utils/storybook/KittThemeDecorator.d.ts.map +1 -1
- package/dist/index-metro.es.android.js +81 -4
- package/dist/index-metro.es.android.js.map +1 -1
- package/dist/index-metro.es.ios.js +81 -4
- package/dist/index-metro.es.ios.js.map +1 -1
- package/dist/index-node-22.17.cjs.js +82 -2
- package/dist/index-node-22.17.cjs.js.map +1 -1
- package/dist/index-node-22.17.cjs.web.js +82 -2
- package/dist/index-node-22.17.cjs.web.js.map +1 -1
- package/dist/index-node-22.17.es.mjs +83 -4
- package/dist/index-node-22.17.es.mjs.map +1 -1
- package/dist/index-node-22.17.es.web.mjs +83 -4
- package/dist/index-node-22.17.es.web.mjs.map +1 -1
- package/dist/index.es.js +86 -4
- package/dist/index.es.js.map +1 -1
- package/dist/index.es.web.js +86 -4
- package/dist/index.es.web.js.map +1 -1
- package/dist/tsbuildinfo +1 -1
- package/package.json +3 -3
- 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/multiple-add.input.tsx +77 -0
- package/scripts/codemods/__testfixtures__/csf1-csf2/multiple-add.output.tsx +92 -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/__tests__/csf1-csf2.test.js +10 -0
- package/scripts/codemods/codemod-template.js +27 -0
- package/scripts/codemods/csf1-csf2.js +323 -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
|
+
};
|