@commercetools-frontend/codemod 0.0.0-CRAFT-1791-20251006162610

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2022 commercetools GmbH
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,89 @@
1
+ # @commercetools-frontend/codemod
2
+
3
+ <p align="center">
4
+ <a href="https://www.npmjs.com/package/@commercetools-frontend/codemod"><img src="https://badgen.net/npm/v/@commercetools-frontend/codemod" alt="Latest release (latest dist-tag)" /></a> <a href="https://www.npmjs.com/package/@commercetools-frontend/codemod"><img src="https://badgen.net/npm/v/@commercetools-frontend/codemod/next" alt="Latest release (next dist-tag)" /></a> <a href="https://bundlephobia.com/result?p=@commercetools-frontend/codemod"><img src="https://badgen.net/bundlephobia/minzip/@commercetools-frontend/codemod" alt="Minified + GZipped size" /></a> <a href="https://github.com/commercetools/merchant-center-application-kit/blob/main/LICENSE"><img src="https://badgen.net/github/license/commercetools/merchant-center-application-kit" alt="GitHub license" /></a>
5
+ </p>
6
+
7
+ Codemod transformations for Custom Applications.
8
+
9
+ ## Usage
10
+
11
+ ```bash
12
+ $ npx @commercetools-frontend/codemod@latest <transform> <glob_pattern>
13
+ ```
14
+
15
+ We recommend to run `prettier` on the modified files to preserve the formatting configured on your project. For example, you can run `prettier --write $(git diff --name-only)`.
16
+
17
+ > If you are using `lint-staged` there is a high chance that you already run `prettier` on the staged files. Therefore, you don't need to run it manually.
18
+
19
+ ## Transforms
20
+
21
+ ### `remove-deprecated-modal-level-props`
22
+
23
+ Remove deprecated `level` and `baseZIndex` props from modal page components.
24
+
25
+ ```
26
+ $ npx @commercetools-frontend/codemod@latest remove-deprecated-modal-level-props 'src/**/*.{js,jsx,ts,tsx}'
27
+ ```
28
+
29
+ ### `rename-js-to-jsx`
30
+
31
+ Rename `.js` files using React JSX syntax to `.jsx`.
32
+
33
+ ```
34
+ $ npx @commercetools-frontend/codemod@latest rename-js-to-jsx 'src/**/*.js'
35
+ ```
36
+
37
+ ### `rename-mod-css-to-module-css`
38
+
39
+ Rename `.mod.css` files to `.module.css` and update imports.
40
+
41
+ ```
42
+ $ npx @commercetools-frontend/codemod@latest rename-mod-css-to-module-css 'src/**/*.{js,jsx,ts,tsx}'
43
+ ```
44
+
45
+ ### `redesign-cleanup`
46
+
47
+ Remove code related to the old design when using the `useTheme` hook, for example the usage of `themedValue`.
48
+
49
+ ```
50
+ $ npx @commercetools-frontend/codemod@latest redesign-cleanup 'src/**/*.{jsx,tsx}'
51
+ ```
52
+
53
+ ### `react-default-props-migration`
54
+
55
+ Migrates the way React Components `defaultProps` to use JavaScript default parameters instead. This is needed for React v18 or later.
56
+ Example:
57
+
58
+ ```jsx
59
+ // BEFORE
60
+ function MyComponent(props) {
61
+ return (
62
+ <ul>
63
+ <li>Prop 1: {props.prop1}</li>
64
+ <li>Prop 2: {props.prop2}</li>
65
+ <li>Prop 3: {props.prop3}</li>
66
+ </ul>
67
+ );
68
+ }
69
+ MyComponent.defaultProps = {
70
+ prop1: 'My default value',
71
+ };
72
+
73
+ // AFTER
74
+ function MyComponent({ prop1: 'My default value', ...props }) {
75
+ return (
76
+ <ul>
77
+ <li>Prop 1: {prop1}</li>
78
+ <li>Prop 2: {props.prop2}</li>
79
+ <li>Prop 3: {props.prop3}</li>
80
+ </ul>
81
+ );
82
+ }
83
+ ```
84
+
85
+ You can run this codemod by using the following command:
86
+
87
+ ```
88
+ $ npx @commercetools-frontend/codemod@latest react-default-props-migration 'src/**/*.{jsx,tsx}'
89
+ ```
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env node
2
+
3
+ process.env.NODE_ENV = 'production';
4
+
5
+ require('../').cli();
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "@commercetools-frontend/codemod",
3
+ "version": "24.7.2",
4
+ "description": "Codemod transformations for Custom Applications",
5
+ "bugs": "https://github.com/commercetools/merchant-center-application-kit/issues",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/commercetools/merchant-center-application-kit.git",
9
+ "directory": "packages/codemod"
10
+ },
11
+ "homepage": "https://docs.commercetools.com/merchant-center-customizations",
12
+ "keywords": ["javascript", "frontend", "codemod", "toolkit"],
13
+ "license": "MIT",
14
+ "publishConfig": {
15
+ "access": "public"
16
+ },
17
+ "main": "./build/src/index.js",
18
+ "files": ["bin", "build", "package.json", "LICENSE", "README.md"],
19
+ "bin": {
20
+ "mc-codemod": "./bin/mc-codemod.js"
21
+ },
22
+ "scripts": {
23
+ "build": "rimraf build && tsc",
24
+ "build:bundles:watch": "pnpm run build -w"
25
+ },
26
+ "dependencies": {
27
+ "@babel/core": "^7.22.17",
28
+ "@babel/preset-env": "^7.22.15",
29
+ "commander": "^13.1.0",
30
+ "glob": "8.1.0",
31
+ "jscodeshift": "0.16.1",
32
+ "prettier": "2.8.8"
33
+ },
34
+ "devDependencies": {
35
+ "@commercetools-frontend/application-components": "workspace:*",
36
+ "@emotion/react": "^11.14.0",
37
+ "@tsconfig/node22": "^22.0.0",
38
+ "@types/glob": "8.1.0",
39
+ "@types/jscodeshift": "0.12.0",
40
+ "@types/prettier": "2.7.3",
41
+ "rimraf": "5.0.10",
42
+ "typescript": "5.9.2"
43
+ },
44
+ "engines": {
45
+ "node": "18.x || 20.x || >=22.0.0"
46
+ }
47
+ }
@@ -0,0 +1,80 @@
1
+ import path from 'path';
2
+ import { program } from 'commander';
3
+ import glob from 'glob';
4
+ // @ts-ignore internal module
5
+ import Runner from 'jscodeshift/src/Runner';
6
+ import pkgJson from '../package.json';
7
+ program
8
+ .name('mc-codemod')
9
+ .description('Codemods for updating Merchant Center customizations.')
10
+ .version(pkgJson.version)
11
+ .option('--dry-run', `(optional) Executes the command but does not send any mutation request.`, false);
12
+ const transforms = [
13
+ {
14
+ name: 'remove-deprecated-modal-level-props',
15
+ description: 'Remove deprecated "level" and "baseZIndex" props from modal page components.',
16
+ },
17
+ {
18
+ name: 'rename-js-to-jsx',
19
+ description: 'Rename ".js" files using React JSX syntax to ".jsx".',
20
+ },
21
+ {
22
+ name: 'rename-mod-css-to-module-css',
23
+ description: 'Rename ".mod.css" files to ".module.css" and update imports.',
24
+ },
25
+ {
26
+ name: 'redesign-cleanup',
27
+ description: 'Remove code related to the old design when using the "useTheme" hook, for example the usage of "themedValue".',
28
+ },
29
+ {
30
+ name: 'react-default-props-migration',
31
+ description: 'Migrate React components using defaultProps as a component property to a destructured object param.',
32
+ },
33
+ ];
34
+ const executeCodemod = async (transform, globPattern, globalOptions) => {
35
+ const absoluteGlobPattern = path.resolve(globPattern);
36
+ const files = glob.sync(path.join(absoluteGlobPattern, '**/*.{ts,tsx,js,jsx}'), {
37
+ ignore: [
38
+ '**/node_modules/**',
39
+ '**/public/**',
40
+ '**/dist/**',
41
+ '**/build/**',
42
+ ],
43
+ });
44
+ const runJscodeshift = async (transformPath, filePaths, options) => {
45
+ await Runner.run(transformPath, filePaths, options);
46
+ };
47
+ switch (transform) {
48
+ case 'redesign-cleanup':
49
+ case 'react-default-props-migration':
50
+ case 'remove-deprecated-modal-level-props':
51
+ case 'rename-js-to-jsx':
52
+ case 'rename-mod-css-to-module-css': {
53
+ const transformPath = path.join(__dirname, `transforms/${transform}.js`);
54
+ await runJscodeshift(transformPath, files, {
55
+ extensions: 'tsx,ts,jsx,js',
56
+ parser: 'tsx',
57
+ verbose: 0,
58
+ dry: globalOptions.dryRun,
59
+ });
60
+ break;
61
+ }
62
+ default:
63
+ throw new Error(`Unknown transform ${transform}.`);
64
+ }
65
+ };
66
+ export const run = () => {
67
+ // Transform commands
68
+ transforms.forEach((transform) => {
69
+ program
70
+ .command(transform.name)
71
+ .argument('<glob-pattern>')
72
+ .description(transform.description)
73
+ .action((globPattern) => {
74
+ const globalOptions = program.opts();
75
+ executeCodemod(transform.name, globPattern, globalOptions);
76
+ });
77
+ });
78
+ program.parse();
79
+ };
80
+ export default run;
@@ -0,0 +1,2 @@
1
+ import cli from './cli';
2
+ export { cli };
@@ -0,0 +1,400 @@
1
+ import prettier from 'prettier';
2
+ // When adjusting the component body where the previous props object was used
3
+ // we don't need to adjust the function calls that are listed here
4
+ const IGNORED_FUNCTIONS_ON_BODY_ADJUSTMENTS = [
5
+ 'filterAriaAttributes',
6
+ 'filterDataAttributes',
7
+ ];
8
+ /*
9
+ Given the component function parameter description, we extract the
10
+ Typescript type name (if any).
11
+
12
+ Somethibng like this:
13
+ (props: MyComponentProps) -> MyComponentProps
14
+ */
15
+ function resolvePropsTypescriptType(propsParam) {
16
+ if (propsParam.type === 'ObjectPattern' &&
17
+ propsParam.typeAnnotation &&
18
+ propsParam.typeAnnotation.type === 'TSTypeAnnotation' &&
19
+ propsParam.typeAnnotation.typeAnnotation.type === 'TSTypeReference' &&
20
+ propsParam.typeAnnotation.typeAnnotation.typeName.type === 'Identifier') {
21
+ return propsParam.typeAnnotation.typeAnnotation.typeName.name;
22
+ }
23
+ return undefined;
24
+ }
25
+ /*
26
+ This helper takes care of replacing the defaultProps usage in the component body
27
+ Previously the component code was relying on the props object to access the default values
28
+ but now the default props are destructured in the function signature
29
+ Example:
30
+ ```
31
+ // BEFORE
32
+ const MyComponent = (props) => {
33
+ return <div>{props.prop1}</div>;
34
+ }
35
+ // AFTER
36
+ const MyComponent = ({ prop1 }) => {
37
+ return <div>{prop1}</div>;
38
+ }
39
+ ```
40
+ */
41
+ function replacePropsUsage({ j, defaultPropsKeys, scope, }) {
42
+ /*
43
+ Next code block replaces destructured props usage in the component body.
44
+ ```
45
+ // BEFORE
46
+ const MyComponent = (props) => {
47
+ return <div>{props.prop1}</div>;
48
+ }
49
+ }
50
+
51
+ // AFTER
52
+ const MyComponent = ({ prop1, ...props }) => {
53
+ return <div>{prop1}</div>;
54
+ }
55
+ ```
56
+ */
57
+ scope
58
+ .find(j.MemberExpression, {
59
+ object: { type: 'Identifier', name: 'props' },
60
+ })
61
+ .forEach((memberPath) => {
62
+ const property = memberPath.node.property;
63
+ // Add type guard for Identifier
64
+ if (property.type === 'Identifier' &&
65
+ defaultPropsKeys.includes(property.name)) {
66
+ j(memberPath).replaceWith(j.identifier(property.name));
67
+ }
68
+ });
69
+ /*
70
+ Next code block replaces props usage in the component body where
71
+ props is passed as an argument to a function.
72
+ ```
73
+ // BEFORE
74
+ const MyComponent = (props) => {
75
+ return <div>{getStyles(props)}</div>;
76
+ }
77
+
78
+ // AFTER
79
+ const MyComponent = ({ prop1, ...props }) => {
80
+ return <div>{getStyles({ prop1, ...props })}</div>;
81
+ }
82
+ ```
83
+ */
84
+ scope
85
+ .find(j.CallExpression, {
86
+ // There are some functions that don't need to be adjusted
87
+ // (example: filterAriaAttributes, filterDataAttributes)
88
+ callee: {
89
+ type: 'Identifier',
90
+ name: (name) => !IGNORED_FUNCTIONS_ON_BODY_ADJUSTMENTS.includes(name),
91
+ },
92
+ arguments: [{ type: 'Identifier', name: 'props' }],
93
+ })
94
+ .forEach((callPath) => {
95
+ // Create a destructured object
96
+ const properties = [
97
+ ...defaultPropsKeys.map((key) => {
98
+ const id = j.identifier(key);
99
+ const newProp = j.property('init', id, id);
100
+ newProp.shorthand = true;
101
+ return newProp;
102
+ }),
103
+ j.spreadElement(j.identifier('props')),
104
+ ];
105
+ const objectExpression = j.objectExpression(properties);
106
+ // Replace the 'props' argument with the destructured object
107
+ callPath.node.arguments[0] = objectExpression;
108
+ });
109
+ /*
110
+ Next code block replaces props spread in JSX elements
111
+ ```
112
+ // BEFORE
113
+ const MyComponent = (props) => {
114
+ return <SubComponent {...props} />
115
+ }
116
+
117
+ // AFTER
118
+ const MyComponent = ({ prop1, ...props }) => {
119
+ return <SubComponent prop1={prop1} prop2={prop2} {...props} />
120
+ }
121
+ ```
122
+ */
123
+ scope
124
+ .find(j.JSXSpreadAttribute, {
125
+ argument: { type: 'Identifier', name: 'props' },
126
+ })
127
+ .forEach((path) => {
128
+ const attributes = defaultPropsKeys.map((key) => j.jsxAttribute(j.jsxIdentifier(key), j.jsxExpressionContainer(j.identifier(key))));
129
+ // Replace the spread with individual attributes
130
+ j(path).replaceWith([
131
+ ...attributes,
132
+ j.jsxSpreadAttribute(j.identifier('props')),
133
+ ]);
134
+ });
135
+ }
136
+ /*
137
+ We need to make sure the component type definition is updated to reflect the
138
+ props that are now optional.
139
+ Example:
140
+ ```
141
+ // BEFORE
142
+ type MyComponentProps = {
143
+ prop1: string;
144
+ prop2: string;
145
+ prop3: string;
146
+ }
147
+ function MyComponent(props: MyComponentProps) { ... }
148
+ MyComponent.defaultProps = {
149
+ prop1: 'default value',
150
+ }
151
+
152
+ // AFTER
153
+ type MyComponentProps = {
154
+ prop1?: string;
155
+ prop2: string;
156
+ prop3: string;
157
+ }
158
+ function MyComponent({ prop1, ...props }: MyComponentProps) { ... }
159
+ ```
160
+ */
161
+ function updateComponentTypes({ j, root, typeName, destructuredKeys, }) {
162
+ // Find the type definition of the component props
163
+ root
164
+ .find(j.TSTypeAliasDeclaration)
165
+ .forEach((typePath) => {
166
+ if (typePath.node.id.name === typeName) {
167
+ const typeAnnotation = typePath.node.typeAnnotation;
168
+ if (typeAnnotation.type === 'TSTypeLiteral') {
169
+ typeAnnotation.members.forEach((member) => {
170
+ if (member.type === 'TSPropertySignature' &&
171
+ member.key.type === 'Identifier' &&
172
+ destructuredKeys.includes(member.key.name)) {
173
+ member.optional = true;
174
+ }
175
+ });
176
+ }
177
+ }
178
+ });
179
+ }
180
+ /*
181
+ This helper transforms the component function signature to use a destructured object
182
+ as first parameter, so we can append the default props to it.
183
+ Example:
184
+ ```
185
+ // BEFORE
186
+ const MyComponent = (props) => { ... }
187
+
188
+ // AFTER
189
+ const MyComponent = ({ prop1, ...props }) => { ... }
190
+ ```
191
+ */
192
+ function transformComponentFunctionSignature({ functionPropsParam, defaultPropsMap, componentName, j, }) {
193
+ let refactoredParameter;
194
+ const defaultPropsKeys = Object.keys(defaultPropsMap);
195
+ switch (functionPropsParam.type) {
196
+ // In this case, the component already has a destructured object as first parameter
197
+ // so we need to append the defaultProps to it
198
+ // const MyComnponent = ({ prop1, ...props }) => { ... }
199
+ case 'ObjectPattern':
200
+ refactoredParameter = functionPropsParam;
201
+ refactoredParameter.properties = [
202
+ ...transformDefaultPropsToAST(defaultPropsMap, j),
203
+ // If the destructured object already had one of the default props, filter it out
204
+ ...functionPropsParam.properties.filter((prop) => {
205
+ return (prop.type !== 'ObjectProperty' ||
206
+ prop.key.type !== 'Identifier' ||
207
+ !defaultPropsKeys.includes(prop.key.name));
208
+ }),
209
+ ];
210
+ break;
211
+ // In this case, the component has a simple parameter as first parameter
212
+ // so we need to refactor it to a destructured object
213
+ // const MyComnponent = (props) => { ... }
214
+ case 'Identifier':
215
+ refactoredParameter = j.objectPattern([
216
+ ...transformDefaultPropsToAST(defaultPropsMap, j),
217
+ j.spreadProperty(j.identifier('props')),
218
+ ]);
219
+ // Make sure the refactored parameter has the same type annotation
220
+ // as the original one
221
+ refactoredParameter.typeAnnotation = functionPropsParam.typeAnnotation;
222
+ break;
223
+ default:
224
+ console.warn(`[WARNING]: Could not parse component function first parameter "${componentName}"`);
225
+ }
226
+ return refactoredParameter;
227
+ }
228
+ /*
229
+ This helper extracts the default props keys/values from the defaultProps object node
230
+ */
231
+ function extractDefaultPropsFromNode(defaultPropsNode) {
232
+ return defaultPropsNode.properties.reduce((acc, prop) => {
233
+ if ((prop.type === 'ObjectProperty' || prop.type === 'Property') &&
234
+ prop.key.type === 'Identifier') {
235
+ return {
236
+ ...acc,
237
+ [prop.key.name]: prop.value,
238
+ };
239
+ }
240
+ return acc;
241
+ }, {});
242
+ }
243
+ /*
244
+ This helper transforms the default props keys/values to an AST representation
245
+ so we can easily append them to the component function signature.
246
+ ```
247
+ */
248
+ function transformDefaultPropsToAST(defaultPropsMap, j) {
249
+ return Object.entries(defaultPropsMap).map(([key, value]) => {
250
+ const propNode = j.objectProperty(j.identifier(key), j.assignmentPattern(j.identifier(key), value));
251
+ propNode.shorthand = true;
252
+ return propNode;
253
+ });
254
+ }
255
+ async function reactDefaultPropsMigration(file, api, options) {
256
+ const j = api.jscodeshift;
257
+ const root = j(file.source, { comment: false });
258
+ const originalSource = root.toSource();
259
+ console.log('Processing file:', file.path);
260
+ // 1. Search for "defaultProps" definitions
261
+ root
262
+ .find(j.AssignmentExpression, {
263
+ left: {
264
+ type: 'MemberExpression',
265
+ property: { name: 'defaultProps' },
266
+ },
267
+ })
268
+ .forEach((path) => {
269
+ // Types validation to please Typescript
270
+ if (path.node.left.type === 'MemberExpression' &&
271
+ path.node.left.object.type === 'Identifier') {
272
+ // The node path looks like this:
273
+ // defaultProps: MyComponent.defaultProps = defaultProps;
274
+ const componentName = path.node.left.object.name;
275
+ const defaultPropsNode = path.node.right;
276
+ let componentPropsTypescriptType; // Only TypeScript files have type annotations
277
+ let defaultPropsMap = {};
278
+ let functionScope;
279
+ // 2. We now extract the default props values
280
+ // Default props can be defined inline or as a reference to another object
281
+ // INLINE -- MyComponent.defaultProps: { prop1: 'value1', prop2: 'value2' }
282
+ // REFERENCE -- MyComponent.defaultProps: defaultProps
283
+ if (defaultPropsNode.type === 'Identifier') {
284
+ // REFERENCE -- MyComponent.defaultProps: defaultProps
285
+ // A) Look for the identifier declaration
286
+ const defaultPropsDeclarations = root.find(j.VariableDeclarator, {
287
+ id: { type: 'Identifier', name: defaultPropsNode.name },
288
+ });
289
+ if (defaultPropsDeclarations.size() === 1) {
290
+ // B) Extract default props keys/values
291
+ defaultPropsMap = extractDefaultPropsFromNode(defaultPropsDeclarations.nodes()[0].init);
292
+ // C) Remove the identifier declaration
293
+ defaultPropsDeclarations.remove();
294
+ }
295
+ else {
296
+ console.warn(`[WARNING]: Could not find defaultProps declaration for "${componentName}"`);
297
+ }
298
+ }
299
+ else if (defaultPropsNode.type === 'ObjectExpression') {
300
+ // INLINE -- MyComponent.defaultProps: { prop1: 'value1', prop2: 'value2' }
301
+ // Extract default props keys/values
302
+ defaultPropsMap = extractDefaultPropsFromNode(defaultPropsNode);
303
+ }
304
+ else {
305
+ console.warn(`[WARNING]: Do not know how to process default props for component "${componentName}": ${j(path).toSource()}`);
306
+ return;
307
+ }
308
+ // 3. Next we update the component function signature
309
+ // We first look for classic function declarations
310
+ // function MyComnponent(props) { ... }
311
+ const functionComponentDeclaration = root.find(j.FunctionDeclaration, {
312
+ id: { name: componentName },
313
+ });
314
+ if (functionComponentDeclaration.length === 1) {
315
+ functionComponentDeclaration.nodes()[0].params[0] =
316
+ transformComponentFunctionSignature({
317
+ functionPropsParam: functionComponentDeclaration.nodes()[0].params[0],
318
+ defaultPropsMap,
319
+ componentName,
320
+ j,
321
+ });
322
+ // Extract the component props TS type name
323
+ componentPropsTypescriptType = resolvePropsTypescriptType(functionComponentDeclaration.nodes()[0].params[0]);
324
+ // Get the function body scope so we only do the replacement
325
+ // within the component function we're currently processing
326
+ functionScope = j(functionComponentDeclaration.nodes()[0].body);
327
+ }
328
+ else {
329
+ // If we don't find a function declaration, we look for arrow function declarations
330
+ // const MyComnponent = (props) => { ... }
331
+ // const MyComnponent = ({ prop1, ...props }) => { ... }
332
+ const variableComponentDeclaration = root.find(j.VariableDeclaration, {
333
+ declarations: [
334
+ {
335
+ id: { name: componentName },
336
+ },
337
+ ],
338
+ });
339
+ if (variableComponentDeclaration.length === 1) {
340
+ const functionFirstParamNode = variableComponentDeclaration.nodes()[0].declarations[0];
341
+ if (functionFirstParamNode.type === 'VariableDeclarator' &&
342
+ functionFirstParamNode.init?.type === 'ArrowFunctionExpression') {
343
+ functionFirstParamNode.init.params[0] =
344
+ transformComponentFunctionSignature({
345
+ functionPropsParam: functionFirstParamNode.init.params[0],
346
+ defaultPropsMap,
347
+ componentName,
348
+ j,
349
+ });
350
+ // Extract the component props TS type name
351
+ componentPropsTypescriptType = resolvePropsTypescriptType(functionFirstParamNode.init.params[0]);
352
+ // Get the function body scope so we only do the replacement
353
+ // within the component function we're currently processing
354
+ functionScope = j(functionFirstParamNode.init.body);
355
+ }
356
+ else {
357
+ console.warn(`[WARNING]: Could parse component function first parameter "${componentName}"`);
358
+ return;
359
+ }
360
+ }
361
+ else {
362
+ console.warn(`[WARNING]: Could not find component declaration for "${componentName}" (class component are ignored)`);
363
+ return;
364
+ }
365
+ }
366
+ // 4. Refactor the usages of the default props in the body of the component
367
+ replacePropsUsage({
368
+ j,
369
+ defaultPropsKeys: Object.keys(defaultPropsMap),
370
+ scope: functionScope,
371
+ });
372
+ // 5. Update the component TS type definition so we make sure the default props are optional
373
+ // (not needed for Javascript files)
374
+ if (componentPropsTypescriptType) {
375
+ updateComponentTypes({
376
+ j,
377
+ root,
378
+ typeName: componentPropsTypescriptType,
379
+ destructuredKeys: Object.keys(defaultPropsMap),
380
+ });
381
+ }
382
+ // 6. Remove the defaultProps assignment from the component
383
+ j(path).remove();
384
+ }
385
+ });
386
+ // Do not return anything if no changes were applied
387
+ // so we don't rewrite the file
388
+ if (originalSource === root.toSource()) {
389
+ return null;
390
+ }
391
+ if (!options.dry) {
392
+ // Format output code with prettier
393
+ const prettierConfig = await prettier.resolveConfig(file.path);
394
+ return prettier.format(root.toSource(), prettierConfig);
395
+ }
396
+ else {
397
+ return null;
398
+ }
399
+ }
400
+ export default reactDefaultPropsMigration;
@@ -0,0 +1,384 @@
1
+ import prettier from 'prettier';
2
+ function getArgumentValue(argument, j) {
3
+ let computedArgumentValue;
4
+ switch (argument.type) {
5
+ case 'StringLiteral':
6
+ computedArgumentValue = argument;
7
+ break;
8
+ default:
9
+ computedArgumentValue = j.jsxExpressionContainer(argument);
10
+ break;
11
+ }
12
+ return computedArgumentValue;
13
+ }
14
+ function isArgumentValueNullish(argumentValue, j) {
15
+ return (!argumentValue ||
16
+ (j.StringLiteral.check(argumentValue) && !argumentValue.value) ||
17
+ (j.JSXExpressionContainer.check(argumentValue) &&
18
+ j.Identifier.check(argumentValue.expression) &&
19
+ argumentValue.expression.name === 'undefined') ||
20
+ (j.JSXExpressionContainer.check(argumentValue) &&
21
+ j.NullLiteral.check(argumentValue.expression)));
22
+ }
23
+ // Update props in components which use `themedValue` helper to always use new theme value
24
+ // Remove the prop in case the new theme value is undefined
25
+ // Some examples to process:
26
+ // <Spacings.Stack scale={themedValue('m', 'l')}>
27
+ //
28
+ // <Spacings.Stack scale={themedValue(designTokens.A, designTokensB)}>
29
+ //
30
+ // <div
31
+ // css={css`
32
+ // color: ${customProperties.colorSurface};
33
+ // text-align: ${themedValue('left', 'center')};
34
+ // `}
35
+ // >
36
+ function updateThemedValueUsages(tree, j) {
37
+ // Props using themedValue helper
38
+ // Example:
39
+ // <CheckboxIconWrapper
40
+ // width={themedValue('auto', '26px')}
41
+ // height={themedValue('auto', '26px')}
42
+ // isHovered={canForcedHoverEffect}
43
+ // >
44
+ tree
45
+ .find(j.JSXAttribute, {
46
+ value: {
47
+ expression: {
48
+ callee: {
49
+ name: 'themedValue',
50
+ },
51
+ },
52
+ },
53
+ })
54
+ .replaceWith((attribute) => {
55
+ const { node } = attribute;
56
+ if (node.value?.type === 'JSXExpressionContainer' &&
57
+ node.value?.expression.type === 'CallExpression') {
58
+ const secondArgument = node.value.expression.arguments[1];
59
+ let computedArgumentValue = getArgumentValue(secondArgument, j);
60
+ // In case the value for the new theme is nullish,
61
+ // we remove the property altogether
62
+ if (isArgumentValueNullish(computedArgumentValue, j)) {
63
+ return null;
64
+ }
65
+ else {
66
+ attribute.node.value = computedArgumentValue;
67
+ }
68
+ }
69
+ return attribute.node;
70
+ });
71
+ // Variables used to proxy a themed value.
72
+ // This section removes the proxy component and updates its references/
73
+ //
74
+ // Example:
75
+ // const Button = themedValue(IconButton, SecondaryIconButton);
76
+ // <Button
77
+ // label={intl.formatMessage(messages.hideNotification)}
78
+ // onClick={props.onCloseClick}
79
+ // icon={<CloseBoldIcon />}
80
+ // size="medium"
81
+ // />
82
+ const variablesToUpdate = [];
83
+ tree
84
+ .find(j.VariableDeclaration)
85
+ .filter((nodePath) => {
86
+ const declaration = nodePath.node.declarations[0];
87
+ return (j.VariableDeclarator.check(declaration) &&
88
+ j.CallExpression.check(declaration.init) &&
89
+ j.Identifier.check(declaration.init.callee) &&
90
+ declaration.init.callee.name === 'themedValue');
91
+ })
92
+ .forEach((nodePath) => {
93
+ const declaration = nodePath.node.declarations[0];
94
+ const variableName = declaration.id.name;
95
+ const newThemeValue = declaration.init.arguments[1];
96
+ variablesToUpdate.push(declaration);
97
+ tree
98
+ .find(j.JSXElement, {
99
+ openingElement: {
100
+ name: {
101
+ name: variableName,
102
+ },
103
+ },
104
+ })
105
+ .forEach((nodePath) => {
106
+ if (j.Identifier.check(newThemeValue)) {
107
+ nodePath.node.openingElement.name = j.jsxIdentifier(newThemeValue.name);
108
+ }
109
+ if (j.MemberExpression.check(newThemeValue)) {
110
+ nodePath.node.openingElement.name = j.jsxMemberExpression(j.jsxIdentifier(newThemeValue.object.name), j.jsxIdentifier(newThemeValue.property.name));
111
+ }
112
+ if (nodePath.node.closingElement) {
113
+ if (j.Identifier.check(newThemeValue)) {
114
+ nodePath.node.closingElement.name = j.jsxIdentifier(newThemeValue.name);
115
+ }
116
+ if (j.MemberExpression.check(newThemeValue)) {
117
+ nodePath.node.closingElement.name = j.jsxMemberExpression(j.jsxIdentifier(newThemeValue.object.name), j.jsxIdentifier(newThemeValue.property.name));
118
+ }
119
+ }
120
+ });
121
+ tree
122
+ .find(j.Identifier, {
123
+ name: variableName,
124
+ })
125
+ .filter((nodePath) => {
126
+ return (j.JSXExpressionContainer.check(nodePath.parent.node) ||
127
+ j.CallExpression.check(nodePath.parent.node));
128
+ })
129
+ .replaceWith(() => {
130
+ return newThemeValue;
131
+ });
132
+ })
133
+ .remove();
134
+ // Rest of use cases like template literals or direct JSX
135
+ // Example 1:
136
+ // <div
137
+ // css={[
138
+ // css`
139
+ // width: ${themedValue('16px', '18px')};
140
+ // height: ${themedValue('16px', '18px')};
141
+ //
142
+ // Example 2:
143
+ // <li className={styles['list-item']} key={val.id}>
144
+ // {themedValue(
145
+ // <Spacings.Inset scale="s">
146
+ // <ValueCheckbox
147
+ // onSelection={props.onSelection}
148
+ // id={val.id}
149
+ // obj={val.obj}
150
+ // onRenderItemName={props.onRenderItemName}
151
+ // />
152
+ // </Spacings.Inset>,
153
+ // <ValueCheckbox
154
+ // onSelection={props.onSelection}
155
+ // id={val.id}
156
+ // obj={val.obj}
157
+ // onRenderItemName={props.onRenderItemName}
158
+ // />
159
+ // )}
160
+ // </li>
161
+ tree
162
+ .find(j.CallExpression, {
163
+ callee: {
164
+ name: 'themedValue',
165
+ },
166
+ })
167
+ .replaceWith((callExpression) => {
168
+ const secondArgument = callExpression.node.arguments[1];
169
+ let computedArgumentValue = getArgumentValue(secondArgument, j);
170
+ if (isArgumentValueNullish(computedArgumentValue, j)) {
171
+ return null;
172
+ }
173
+ else {
174
+ return callExpression.node.arguments[1];
175
+ }
176
+ });
177
+ }
178
+ // Process the useTheme hook to remove it or updated it
179
+ // (maybe we're also using "theme" property from the hook)
180
+ function processUseThemeHook(tree, j) {
181
+ let wasHookRemoved = false;
182
+ // Skip if there are calls to "themedValue" function
183
+ const themedValueCalls = tree.find(j.CallExpression, {
184
+ callee: {
185
+ name: 'themedValue',
186
+ },
187
+ });
188
+ // Skip if "themeValue" is passed as a parameter to another function
189
+ const themeValueArguments = tree.find(j.CallExpression, {
190
+ arguments(values) {
191
+ return values.some((value) => j.Identifier.check(value) && value.name === 'themedValue');
192
+ },
193
+ });
194
+ if (themedValueCalls.length > 0 || themeValueArguments.length > 0) {
195
+ return wasHookRemoved;
196
+ }
197
+ tree
198
+ .find(j.VariableDeclaration, {
199
+ declarations(values) {
200
+ const [declaration] = values;
201
+ return (declaration.type === 'VariableDeclarator' &&
202
+ declaration.init?.type === 'CallExpression' &&
203
+ declaration.init.callee.type === 'Identifier' &&
204
+ declaration.init.callee.name === 'useTheme' &&
205
+ declaration.id.type === 'ObjectPattern');
206
+ },
207
+ })
208
+ .replaceWith((value) => {
209
+ const declaration = value.node.declarations[0];
210
+ if (declaration.id.type === 'ObjectPattern') {
211
+ // Remove the declaration altogether if we were only getting the `themedValue`
212
+ // helper from the `useTheme` hook
213
+ // Ex: const { themedValue } = useTheme();
214
+ if (declaration.id.properties.length === 1 &&
215
+ declaration.id.properties[0].type === 'ObjectProperty' &&
216
+ declaration.id.properties[0].value.type === 'Identifier' &&
217
+ declaration.id.properties[0].value.name === 'themedValue') {
218
+ wasHookRemoved = true;
219
+ return null;
220
+ }
221
+ // Update the destructuring of the `useTheme` hook to remove the `themedValue`
222
+ // const when there were more helpers in the destructuring
223
+ // Ex: const { theme, themedValue } = useTheme();
224
+ const index = declaration.id.properties.findIndex((prop) => prop.type === 'ObjectProperty' &&
225
+ prop.key.type === 'Identifier' &&
226
+ prop.key.name === 'themedValue');
227
+ if (index !== -1) {
228
+ declaration.id.properties.splice(index, 1);
229
+ }
230
+ }
231
+ return value.node;
232
+ });
233
+ return wasHookRemoved;
234
+ }
235
+ function removeUnusedImports(tree, j) {
236
+ let isUseThemeHookRemoved = false;
237
+ // Find all the used identifiers in the file
238
+ const usedIdentifiersMap = new Map();
239
+ tree.find(j.Identifier).forEach((identifier) => {
240
+ if ((j.Identifier.check(identifier.node) &&
241
+ !j.ImportSpecifier.check(identifier.parent.node) &&
242
+ !j.ImportDefaultSpecifier.check(identifier.parent.node)) ||
243
+ j.JSXIdentifier.check(identifier.node)) {
244
+ const previousCount = usedIdentifiersMap.get(identifier.node.name) || 0;
245
+ usedIdentifiersMap.set(identifier.node.name, previousCount + 1);
246
+ }
247
+ });
248
+ // Find types annotations
249
+ tree
250
+ .find(j.CallExpression)
251
+ .filter((callExpression) => {
252
+ return j.TSTypeParameterInstantiation.check(callExpression.node
253
+ .typeParameters);
254
+ })
255
+ .forEach((callExpression) => {
256
+ callExpression.node.typeParameters?.params.forEach((param) => {
257
+ if (j.TSTypeReference.check(param) &&
258
+ j.Identifier.check(param.typeName)) {
259
+ const previousCount = usedIdentifiersMap.get(param.typeName.name) || 0;
260
+ usedIdentifiersMap.set(param.typeName.name, previousCount + 1);
261
+ }
262
+ if (j.TSArrayType.check(param) &&
263
+ j.TSTypeReference.check(param.elementType) &&
264
+ j.Identifier.check(param.elementType.typeName)) {
265
+ const name = param.elementType.typeName.name;
266
+ const previousCount = usedIdentifiersMap.get(name) || 0;
267
+ usedIdentifiersMap.set(name, previousCount + 1);
268
+ }
269
+ // Declaration of a custom type for a function parameter
270
+ if (j.TSTypeLiteral.check(param)) {
271
+ param.members.forEach((propSignature) => {
272
+ // TODO: This is to satisfy TS. Can we do it simpler?
273
+ if (j.TSPropertySignature.check(propSignature) &&
274
+ j.TSTypeAnnotation.check(propSignature.typeAnnotation) &&
275
+ j.TSTypeReference.check(propSignature.typeAnnotation.typeAnnotation) &&
276
+ j.Identifier.check(propSignature.typeAnnotation.typeAnnotation.typeName)) {
277
+ const previousCount = usedIdentifiersMap.get(propSignature.typeAnnotation.typeAnnotation.typeName.name) || 0;
278
+ usedIdentifiersMap.set(propSignature.typeAnnotation.typeAnnotation.typeName.name, previousCount + 1);
279
+ }
280
+ });
281
+ }
282
+ // Return types in function types
283
+ if (j.TSFunctionType.check(param) &&
284
+ j.TSTypeReference.check(param.typeAnnotation?.typeAnnotation)) {
285
+ const typeName = (param.typeAnnotation?.typeAnnotation.typeName).name;
286
+ const previousCount = usedIdentifiersMap.get(typeName) || 0;
287
+ usedIdentifiersMap.set(typeName, previousCount + 1);
288
+ param.typeAnnotation?.typeAnnotation.typeParameters?.params.forEach((annotationParam) => {
289
+ const annotationParamName = j.TSTypeReference.check(annotationParam) &&
290
+ annotationParam.typeName.name;
291
+ if (annotationParamName) {
292
+ const previousCount = usedIdentifiersMap.get(annotationParamName) || 0;
293
+ usedIdentifiersMap.set(annotationParamName, previousCount + 1);
294
+ }
295
+ });
296
+ }
297
+ // Find TS types in function types parameters signatures
298
+ if (j.TSFunctionType.check(param)) {
299
+ param.parameters.forEach((fnParam) => {
300
+ let typeName = undefined;
301
+ if (j.Identifier.check(fnParam) &&
302
+ j.TSTypeReference.check(fnParam.typeAnnotation?.typeAnnotation) &&
303
+ j.Identifier.check(fnParam.typeAnnotation?.typeAnnotation.typeName)) {
304
+ typeName = fnParam.typeAnnotation?.typeAnnotation.typeName.name;
305
+ }
306
+ if (j.Identifier.check(fnParam) &&
307
+ j.TSArrayType.check(fnParam.typeAnnotation?.typeAnnotation) &&
308
+ j.TSTypeReference.check(fnParam.typeAnnotation?.typeAnnotation.elementType) &&
309
+ j.Identifier.check(fnParam.typeAnnotation?.typeAnnotation.elementType.typeName)) {
310
+ typeName =
311
+ fnParam.typeAnnotation?.typeAnnotation.elementType.typeName
312
+ .name;
313
+ }
314
+ if (typeName) {
315
+ const previousCount = usedIdentifiersMap.get(typeName) || 0;
316
+ usedIdentifiersMap.set(typeName, previousCount + 1);
317
+ }
318
+ });
319
+ }
320
+ });
321
+ });
322
+ // Remove unused imports
323
+ tree
324
+ .find(j.ImportDeclaration)
325
+ .forEach((importDeclaration) => {
326
+ const importSpecifiers = importDeclaration.node.specifiers;
327
+ // Skip if this is an import just to auto-initialize any module
328
+ // or it's a css import
329
+ if (!importSpecifiers ||
330
+ importSpecifiers.length < 1 ||
331
+ importDeclaration.node.source.value.endsWith('.css')) {
332
+ return;
333
+ }
334
+ const usedSpecifiers = importSpecifiers.filter((specifier) => (usedIdentifiersMap.get(specifier.local?.name || '') || 0) > 0);
335
+ if (usedSpecifiers.length === 0) {
336
+ // remove the entire import declaration
337
+ j(importDeclaration).remove();
338
+ isUseThemeHookRemoved = importSpecifiers.some((specifier) => specifier.local?.name === 'useTheme');
339
+ }
340
+ else if (usedSpecifiers.length < importSpecifiers.length) {
341
+ // remove unused specifiers from the import declaration
342
+ importDeclaration.node.specifiers = usedSpecifiers;
343
+ isUseThemeHookRemoved = importSpecifiers.some((specifier) => specifier.local?.name === 'useTheme');
344
+ }
345
+ });
346
+ return isUseThemeHookRemoved;
347
+ }
348
+ function removeMigrationCleanupComment(tree, j) {
349
+ tree
350
+ // @ts-ignore
351
+ .find(j.Comment)
352
+ .filter((path) => j.CommentLine.check(path.value) &&
353
+ path.value.value.indexOf('@redesign cleanup') !== -1)
354
+ .remove();
355
+ }
356
+ async function themeMigrationCleanup(file, api, options) {
357
+ const j = api.jscodeshift;
358
+ const root = j(file.source, { comment: true });
359
+ const originalSource = root.toSource();
360
+ // 1. Update props which use `themedValue` helper to always use new theme value
361
+ updateThemedValueUsages(root, j);
362
+ // 2. Process the useTheme hook to remove it or updated it
363
+ processUseThemeHook(root, j);
364
+ // 3. After updating the useTheme usages, let's potentially remove unnecessary imports
365
+ const hasUseThemeHookBeenRemoved = removeUnusedImports(root, j);
366
+ // 4. Remove migration comment -> TODO: @redesign cleanup
367
+ if (hasUseThemeHookBeenRemoved) {
368
+ removeMigrationCleanupComment(root, j);
369
+ }
370
+ // Do not return anything if no changes were applied
371
+ // so we don't rewrite the file
372
+ if (originalSource === root.toSource()) {
373
+ return null;
374
+ }
375
+ if (!options.dry) {
376
+ // Format output code with prettier
377
+ const prettierConfig = await prettier.resolveConfig(file.path);
378
+ return prettier.format(root.toSource(), prettierConfig);
379
+ }
380
+ else {
381
+ return null;
382
+ }
383
+ }
384
+ export default themeMigrationCleanup;
@@ -0,0 +1,38 @@
1
+ const componentNamesWithDeprecatedZIndexProps = [
2
+ 'InfoModalPage',
3
+ 'FormModalPage',
4
+ 'CustomFormModalPage',
5
+ 'TabularModalPage',
6
+ ];
7
+ const deprecatedPropsToBeRemoved = ['level', 'baseZIndex'];
8
+ function removeDeprecatedModalLevelProps(file, api, options) {
9
+ const j = api.jscodeshift;
10
+ const root = j(file.source);
11
+ let hasModifications = false;
12
+ function removeDepreactedPropsFromComponent(path) {
13
+ const node = path.node;
14
+ if (node.name.type === 'JSXIdentifier' &&
15
+ componentNamesWithDeprecatedZIndexProps.includes(node.name.name)) {
16
+ if (options.dry) {
17
+ api.stats(file.path);
18
+ }
19
+ else {
20
+ node.attributes =
21
+ node.attributes?.filter((attribute) => {
22
+ if (attribute.type === 'JSXAttribute' &&
23
+ attribute.name.type === 'JSXIdentifier') {
24
+ const hasDeprecatedAttribute = deprecatedPropsToBeRemoved.includes(attribute.name.name);
25
+ hasModifications = true;
26
+ return !hasDeprecatedAttribute;
27
+ }
28
+ return true;
29
+ }) ?? [];
30
+ }
31
+ }
32
+ }
33
+ root.find(j.JSXOpeningElement).forEach(removeDepreactedPropsFromComponent);
34
+ return hasModifications
35
+ ? root.toSource({ ...options, quote: 'single' })
36
+ : null;
37
+ }
38
+ export default removeDeprecatedModalLevelProps;
@@ -0,0 +1,20 @@
1
+ import fs from 'fs';
2
+ function renameJsToJsx(file, api, options) {
3
+ const j = api.jscodeshift;
4
+ const root = j(file.source);
5
+ if (file.path.endsWith('.js') && !file.path.endsWith('.spec.js')) {
6
+ const hasJSXElements = root.findJSXElements().length > 0;
7
+ if (hasJSXElements) {
8
+ if (options.dry) {
9
+ api.stats(file.path);
10
+ }
11
+ else {
12
+ const renamedFilePath = file.path.replace('.js', '.jsx');
13
+ fs.renameSync(file.path, renamedFilePath);
14
+ api.report(`renamed to ${renamedFilePath}`);
15
+ }
16
+ }
17
+ }
18
+ return null;
19
+ }
20
+ export default renameJsToJsx;
@@ -0,0 +1,29 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ function renameModCssToModuleCss(file, api, options) {
4
+ const j = api.jscodeshift;
5
+ let hasModifications = false;
6
+ function renameImportModCssToModuleCss(astPath) {
7
+ const node = astPath.node;
8
+ if (typeof node.source.value === 'string' &&
9
+ node.source.value.endsWith('.mod.css')) {
10
+ if (options.dry) {
11
+ api.stats(file.path);
12
+ }
13
+ else {
14
+ const previousCssModulesFilePath = node.source.value;
15
+ const nextCssModulesFilePath = previousCssModulesFilePath.replace('.mod.css', '.module.css');
16
+ node.source.value = nextCssModulesFilePath;
17
+ hasModifications = true;
18
+ // Side effect for renaming the actual CSS module file.
19
+ fs.renameSync(path.join(path.dirname(file.path), previousCssModulesFilePath), path.join(path.dirname(file.path), nextCssModulesFilePath));
20
+ }
21
+ }
22
+ }
23
+ const root = j(file.source);
24
+ root.find(j.ImportDeclaration).forEach(renameImportModCssToModuleCss);
25
+ return hasModifications
26
+ ? root.toSource({ ...options, quote: 'single' })
27
+ : null;
28
+ }
29
+ export default renameModCssToModuleCss;
@@ -0,0 +1 @@
1
+ export {};
package/package.json ADDED
@@ -0,0 +1,58 @@
1
+ {
2
+ "name": "@commercetools-frontend/codemod",
3
+ "version": "0.0.0-CRAFT-1791-20251006162610",
4
+ "description": "Codemod transformations for Custom Applications",
5
+ "bugs": "https://github.com/commercetools/merchant-center-application-kit/issues",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/commercetools/merchant-center-application-kit.git",
9
+ "directory": "packages/codemod"
10
+ },
11
+ "homepage": "https://docs.commercetools.com/merchant-center-customizations",
12
+ "keywords": [
13
+ "javascript",
14
+ "frontend",
15
+ "codemod",
16
+ "toolkit"
17
+ ],
18
+ "license": "MIT",
19
+ "publishConfig": {
20
+ "access": "public"
21
+ },
22
+ "main": "./build/src/index.js",
23
+ "files": [
24
+ "bin",
25
+ "build",
26
+ "package.json",
27
+ "LICENSE",
28
+ "README.md"
29
+ ],
30
+ "bin": {
31
+ "mc-codemod": "./bin/mc-codemod.js"
32
+ },
33
+ "dependencies": {
34
+ "@babel/core": "^7.22.17",
35
+ "@babel/preset-env": "^7.22.15",
36
+ "commander": "^13.1.0",
37
+ "glob": "8.1.0",
38
+ "jscodeshift": "0.16.1",
39
+ "prettier": "2.8.8"
40
+ },
41
+ "devDependencies": {
42
+ "@emotion/react": "^11.14.0",
43
+ "@tsconfig/node22": "^22.0.0",
44
+ "@types/glob": "8.1.0",
45
+ "@types/jscodeshift": "0.12.0",
46
+ "@types/prettier": "2.7.3",
47
+ "rimraf": "5.0.10",
48
+ "typescript": "5.9.2",
49
+ "@commercetools-frontend/application-components": "0.0.0-CRAFT-1791-20251006162610"
50
+ },
51
+ "engines": {
52
+ "node": "18.x || 20.x || >=22.0.0"
53
+ },
54
+ "scripts": {
55
+ "build": "rimraf build && tsc",
56
+ "build:bundles:watch": "pnpm run build -w"
57
+ }
58
+ }