@commercetools-frontend/codemod 0.0.0-FEC-157-preview-releases-20241126230331
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 +21 -0
- package/README.md +51 -0
- package/bin/mc-codemod.js +3 -0
- package/build/package.json +46 -0
- package/build/src/cli.js +82 -0
- package/build/src/index.js +8 -0
- package/build/src/transforms/redesign-cleanup.js +389 -0
- package/build/src/transforms/remove-deprecated-modal-level-props.js +40 -0
- package/build/src/transforms/rename-js-to-jsx.js +25 -0
- package/build/src/transforms/rename-mod-css-to-module-css.js +34 -0
- package/build/src/types.js +2 -0
- package/package.json +57 -0
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,51 @@
|
|
|
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
|
+
```
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@commercetools-frontend/codemod",
|
|
3
|
+
"version": "22.35.1",
|
|
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
|
+
"cac": "6.7.14",
|
|
30
|
+
"glob": "8.1.0",
|
|
31
|
+
"jscodeshift": "0.15.2",
|
|
32
|
+
"prettier": "2.8.8"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@commercetools-frontend/application-components": "workspace:*",
|
|
36
|
+
"@emotion/react": "^11.11.4",
|
|
37
|
+
"@tsconfig/node16": "^16.1.1",
|
|
38
|
+
"@types/glob": "8.1.0",
|
|
39
|
+
"@types/jscodeshift": "0.11.11",
|
|
40
|
+
"rimraf": "5.0.7",
|
|
41
|
+
"typescript": "5.0.4"
|
|
42
|
+
},
|
|
43
|
+
"engines": {
|
|
44
|
+
"node": "16.x || >=18.0.0"
|
|
45
|
+
}
|
|
46
|
+
}
|
package/build/src/cli.js
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.run = void 0;
|
|
7
|
+
const path_1 = __importDefault(require("path"));
|
|
8
|
+
const cac_1 = __importDefault(require("cac"));
|
|
9
|
+
const glob_1 = __importDefault(require("glob"));
|
|
10
|
+
// @ts-ignore internal module
|
|
11
|
+
const Runner_1 = __importDefault(require("jscodeshift/src/Runner"));
|
|
12
|
+
const package_json_1 = __importDefault(require("../package.json"));
|
|
13
|
+
const cli = (0, cac_1.default)('mc-codemod');
|
|
14
|
+
const transforms = [
|
|
15
|
+
{
|
|
16
|
+
name: 'remove-deprecated-modal-level-props',
|
|
17
|
+
description: 'Remove deprecated "level" and "baseZIndex" props from modal page components.',
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
name: 'rename-js-to-jsx',
|
|
21
|
+
description: 'Rename ".js" files using React JSX syntax to ".jsx".',
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
name: 'rename-mod-css-to-module-css',
|
|
25
|
+
description: 'Rename ".mod.css" files to ".module.css" and update imports.',
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
name: 'redesign-cleanup',
|
|
29
|
+
description: 'Remove code related to the old design when using the "useTheme" hook, for example the usage of "themedValue".',
|
|
30
|
+
},
|
|
31
|
+
];
|
|
32
|
+
const executeCodemod = async (transform, globPattern, globalOptions) => {
|
|
33
|
+
const files = glob_1.default.sync(globPattern);
|
|
34
|
+
const runJscodeshift = async (transformPath, filePaths, options) => {
|
|
35
|
+
await Runner_1.default.run(transformPath, filePaths, options);
|
|
36
|
+
};
|
|
37
|
+
switch (transform) {
|
|
38
|
+
case 'redesign-cleanup':
|
|
39
|
+
case 'remove-deprecated-modal-level-props':
|
|
40
|
+
case 'rename-js-to-jsx':
|
|
41
|
+
case 'rename-mod-css-to-module-css': {
|
|
42
|
+
const transformPath = path_1.default.join(__dirname, `transforms/${transform}.js`);
|
|
43
|
+
await runJscodeshift(transformPath, files, {
|
|
44
|
+
extensions: 'tsx,ts,jsx,js',
|
|
45
|
+
ignorePattern: [
|
|
46
|
+
'**/node_modules/**',
|
|
47
|
+
'**/public/**',
|
|
48
|
+
'**/dist/**',
|
|
49
|
+
'**/build/**',
|
|
50
|
+
],
|
|
51
|
+
parser: 'tsx',
|
|
52
|
+
verbose: 0,
|
|
53
|
+
dry: globalOptions.dryRun,
|
|
54
|
+
});
|
|
55
|
+
break;
|
|
56
|
+
}
|
|
57
|
+
default:
|
|
58
|
+
throw new Error(`Unknown transform ${transform}.`);
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
const run = () => {
|
|
62
|
+
cli.option('--dry-run', `(optional) Executes the command but does not send any mutation request.`, { default: false });
|
|
63
|
+
// Default command
|
|
64
|
+
cli
|
|
65
|
+
.command('')
|
|
66
|
+
.usage('\n\n Codemods for updating Custom Applications.')
|
|
67
|
+
.action(() => {
|
|
68
|
+
cli.outputHelp();
|
|
69
|
+
});
|
|
70
|
+
// Transform commands
|
|
71
|
+
transforms.forEach((transform) => {
|
|
72
|
+
cli
|
|
73
|
+
.command(`${transform.name} <glob-pattern>`, transform.description)
|
|
74
|
+
.usage(`${transform.name} <glob-pattern>\n\n ${transform.description}`)
|
|
75
|
+
.action((globPattern, globalOptions) => executeCodemod(transform.name, globPattern, globalOptions));
|
|
76
|
+
});
|
|
77
|
+
cli.help();
|
|
78
|
+
cli.version(package_json_1.default.version);
|
|
79
|
+
cli.parse();
|
|
80
|
+
};
|
|
81
|
+
exports.run = run;
|
|
82
|
+
exports.default = exports.run;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.cli = void 0;
|
|
7
|
+
const cli_1 = __importDefault(require("./cli"));
|
|
8
|
+
exports.cli = cli_1.default;
|
|
@@ -0,0 +1,389 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const prettier_1 = __importDefault(require("prettier"));
|
|
7
|
+
function getArgumentValue(argument, j) {
|
|
8
|
+
let computedArgumentValue;
|
|
9
|
+
switch (argument.type) {
|
|
10
|
+
case 'StringLiteral':
|
|
11
|
+
computedArgumentValue = argument;
|
|
12
|
+
break;
|
|
13
|
+
default:
|
|
14
|
+
computedArgumentValue = j.jsxExpressionContainer(argument);
|
|
15
|
+
break;
|
|
16
|
+
}
|
|
17
|
+
return computedArgumentValue;
|
|
18
|
+
}
|
|
19
|
+
function isArgumentValueNullish(argumentValue, j) {
|
|
20
|
+
return (!argumentValue ||
|
|
21
|
+
(j.StringLiteral.check(argumentValue) && !argumentValue.value) ||
|
|
22
|
+
(j.JSXExpressionContainer.check(argumentValue) &&
|
|
23
|
+
j.Identifier.check(argumentValue.expression) &&
|
|
24
|
+
argumentValue.expression.name === 'undefined') ||
|
|
25
|
+
(j.JSXExpressionContainer.check(argumentValue) &&
|
|
26
|
+
j.NullLiteral.check(argumentValue.expression)));
|
|
27
|
+
}
|
|
28
|
+
// Update props in components which use `themedValue` helper to always use new theme value
|
|
29
|
+
// Remove the prop in case the new theme value is undefined
|
|
30
|
+
// Some examples to process:
|
|
31
|
+
// <Spacings.Stack scale={themedValue('m', 'l')}>
|
|
32
|
+
//
|
|
33
|
+
// <Spacings.Stack scale={themedValue(designTokens.A, designTokensB)}>
|
|
34
|
+
//
|
|
35
|
+
// <div
|
|
36
|
+
// css={css`
|
|
37
|
+
// color: ${customProperties.colorSurface};
|
|
38
|
+
// text-align: ${themedValue('left', 'center')};
|
|
39
|
+
// `}
|
|
40
|
+
// >
|
|
41
|
+
function updateThemedValueUsages(tree, j) {
|
|
42
|
+
// Props using themedValue helper
|
|
43
|
+
// Example:
|
|
44
|
+
// <CheckboxIconWrapper
|
|
45
|
+
// width={themedValue('auto', '26px')}
|
|
46
|
+
// height={themedValue('auto', '26px')}
|
|
47
|
+
// isHovered={canForcedHoverEffect}
|
|
48
|
+
// >
|
|
49
|
+
tree
|
|
50
|
+
.find(j.JSXAttribute, {
|
|
51
|
+
value: {
|
|
52
|
+
expression: {
|
|
53
|
+
callee: {
|
|
54
|
+
name: 'themedValue',
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
})
|
|
59
|
+
.replaceWith((attribute) => {
|
|
60
|
+
const { node } = attribute;
|
|
61
|
+
if (node.value?.type === 'JSXExpressionContainer' &&
|
|
62
|
+
node.value?.expression.type === 'CallExpression') {
|
|
63
|
+
const secondArgument = node.value.expression.arguments[1];
|
|
64
|
+
let computedArgumentValue = getArgumentValue(secondArgument, j);
|
|
65
|
+
// In case the value for the new theme is nullish,
|
|
66
|
+
// we remove the property altogether
|
|
67
|
+
if (isArgumentValueNullish(computedArgumentValue, j)) {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
attribute.node.value = computedArgumentValue;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return attribute.node;
|
|
75
|
+
});
|
|
76
|
+
// Variables used to proxy a themed value.
|
|
77
|
+
// This section removes the proxy component and updates its references/
|
|
78
|
+
//
|
|
79
|
+
// Example:
|
|
80
|
+
// const Button = themedValue(IconButton, SecondaryIconButton);
|
|
81
|
+
// <Button
|
|
82
|
+
// label={intl.formatMessage(messages.hideNotification)}
|
|
83
|
+
// onClick={props.onCloseClick}
|
|
84
|
+
// icon={<CloseBoldIcon />}
|
|
85
|
+
// size="medium"
|
|
86
|
+
// />
|
|
87
|
+
const variablesToUpdate = [];
|
|
88
|
+
tree
|
|
89
|
+
.find(j.VariableDeclaration)
|
|
90
|
+
.filter((nodePath) => {
|
|
91
|
+
const declaration = nodePath.node.declarations[0];
|
|
92
|
+
return (j.VariableDeclarator.check(declaration) &&
|
|
93
|
+
j.CallExpression.check(declaration.init) &&
|
|
94
|
+
j.Identifier.check(declaration.init.callee) &&
|
|
95
|
+
declaration.init.callee.name === 'themedValue');
|
|
96
|
+
})
|
|
97
|
+
.forEach((nodePath) => {
|
|
98
|
+
const declaration = nodePath.node.declarations[0];
|
|
99
|
+
const variableName = declaration.id.name;
|
|
100
|
+
const newThemeValue = declaration.init.arguments[1];
|
|
101
|
+
variablesToUpdate.push(declaration);
|
|
102
|
+
tree
|
|
103
|
+
.find(j.JSXElement, {
|
|
104
|
+
openingElement: {
|
|
105
|
+
name: {
|
|
106
|
+
name: variableName,
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
})
|
|
110
|
+
.forEach((nodePath) => {
|
|
111
|
+
if (j.Identifier.check(newThemeValue)) {
|
|
112
|
+
nodePath.node.openingElement.name = j.jsxIdentifier(newThemeValue.name);
|
|
113
|
+
}
|
|
114
|
+
if (j.MemberExpression.check(newThemeValue)) {
|
|
115
|
+
nodePath.node.openingElement.name = j.jsxMemberExpression(j.jsxIdentifier(newThemeValue.object.name), j.jsxIdentifier(newThemeValue.property.name));
|
|
116
|
+
}
|
|
117
|
+
if (nodePath.node.closingElement) {
|
|
118
|
+
if (j.Identifier.check(newThemeValue)) {
|
|
119
|
+
nodePath.node.closingElement.name = j.jsxIdentifier(newThemeValue.name);
|
|
120
|
+
}
|
|
121
|
+
if (j.MemberExpression.check(newThemeValue)) {
|
|
122
|
+
nodePath.node.closingElement.name = j.jsxMemberExpression(j.jsxIdentifier(newThemeValue.object.name), j.jsxIdentifier(newThemeValue.property.name));
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
tree
|
|
127
|
+
.find(j.Identifier, {
|
|
128
|
+
name: variableName,
|
|
129
|
+
})
|
|
130
|
+
.filter((nodePath) => {
|
|
131
|
+
return (j.JSXExpressionContainer.check(nodePath.parent.node) ||
|
|
132
|
+
j.CallExpression.check(nodePath.parent.node));
|
|
133
|
+
})
|
|
134
|
+
.replaceWith(() => {
|
|
135
|
+
return newThemeValue;
|
|
136
|
+
});
|
|
137
|
+
})
|
|
138
|
+
.remove();
|
|
139
|
+
// Rest of use cases like template literals or direct JSX
|
|
140
|
+
// Example 1:
|
|
141
|
+
// <div
|
|
142
|
+
// css={[
|
|
143
|
+
// css`
|
|
144
|
+
// width: ${themedValue('16px', '18px')};
|
|
145
|
+
// height: ${themedValue('16px', '18px')};
|
|
146
|
+
//
|
|
147
|
+
// Example 2:
|
|
148
|
+
// <li className={styles['list-item']} key={val.id}>
|
|
149
|
+
// {themedValue(
|
|
150
|
+
// <Spacings.Inset scale="s">
|
|
151
|
+
// <ValueCheckbox
|
|
152
|
+
// onSelection={props.onSelection}
|
|
153
|
+
// id={val.id}
|
|
154
|
+
// obj={val.obj}
|
|
155
|
+
// onRenderItemName={props.onRenderItemName}
|
|
156
|
+
// />
|
|
157
|
+
// </Spacings.Inset>,
|
|
158
|
+
// <ValueCheckbox
|
|
159
|
+
// onSelection={props.onSelection}
|
|
160
|
+
// id={val.id}
|
|
161
|
+
// obj={val.obj}
|
|
162
|
+
// onRenderItemName={props.onRenderItemName}
|
|
163
|
+
// />
|
|
164
|
+
// )}
|
|
165
|
+
// </li>
|
|
166
|
+
tree
|
|
167
|
+
.find(j.CallExpression, {
|
|
168
|
+
callee: {
|
|
169
|
+
name: 'themedValue',
|
|
170
|
+
},
|
|
171
|
+
})
|
|
172
|
+
.replaceWith((callExpression) => {
|
|
173
|
+
const secondArgument = callExpression.node.arguments[1];
|
|
174
|
+
let computedArgumentValue = getArgumentValue(secondArgument, j);
|
|
175
|
+
if (isArgumentValueNullish(computedArgumentValue, j)) {
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
else {
|
|
179
|
+
return callExpression.node.arguments[1];
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
// Process the useTheme hook to remove it or updated it
|
|
184
|
+
// (maybe we're also using "theme" property from the hook)
|
|
185
|
+
function processUseThemeHook(tree, j) {
|
|
186
|
+
let wasHookRemoved = false;
|
|
187
|
+
// Skip if there are calls to "themedValue" function
|
|
188
|
+
const themedValueCalls = tree.find(j.CallExpression, {
|
|
189
|
+
callee: {
|
|
190
|
+
name: 'themedValue',
|
|
191
|
+
},
|
|
192
|
+
});
|
|
193
|
+
// Skip if "themeValue" is passed as a parameter to another function
|
|
194
|
+
const themeValueArguments = tree.find(j.CallExpression, {
|
|
195
|
+
arguments(values) {
|
|
196
|
+
return values.some((value) => j.Identifier.check(value) && value.name === 'themedValue');
|
|
197
|
+
},
|
|
198
|
+
});
|
|
199
|
+
if (themedValueCalls.length > 0 || themeValueArguments.length > 0) {
|
|
200
|
+
return wasHookRemoved;
|
|
201
|
+
}
|
|
202
|
+
tree
|
|
203
|
+
.find(j.VariableDeclaration, {
|
|
204
|
+
declarations(values) {
|
|
205
|
+
const [declaration] = values;
|
|
206
|
+
return (declaration.type === 'VariableDeclarator' &&
|
|
207
|
+
declaration.init?.type === 'CallExpression' &&
|
|
208
|
+
declaration.init.callee.type === 'Identifier' &&
|
|
209
|
+
declaration.init.callee.name === 'useTheme' &&
|
|
210
|
+
declaration.id.type === 'ObjectPattern');
|
|
211
|
+
},
|
|
212
|
+
})
|
|
213
|
+
.replaceWith((value) => {
|
|
214
|
+
const declaration = value.node.declarations[0];
|
|
215
|
+
if (declaration.id.type === 'ObjectPattern') {
|
|
216
|
+
// Remove the declaration altogether if we were only getting the `themedValue`
|
|
217
|
+
// helper from the `useTheme` hook
|
|
218
|
+
// Ex: const { themedValue } = useTheme();
|
|
219
|
+
if (declaration.id.properties.length === 1 &&
|
|
220
|
+
declaration.id.properties[0].type === 'ObjectProperty' &&
|
|
221
|
+
declaration.id.properties[0].value.type === 'Identifier' &&
|
|
222
|
+
declaration.id.properties[0].value.name === 'themedValue') {
|
|
223
|
+
wasHookRemoved = true;
|
|
224
|
+
return null;
|
|
225
|
+
}
|
|
226
|
+
// Update the destructuring of the `useTheme` hook to remove the `themedValue`
|
|
227
|
+
// const when there were more helpers in the destructuring
|
|
228
|
+
// Ex: const { theme, themedValue } = useTheme();
|
|
229
|
+
const index = declaration.id.properties.findIndex((prop) => prop.type === 'ObjectProperty' &&
|
|
230
|
+
prop.key.type === 'Identifier' &&
|
|
231
|
+
prop.key.name === 'themedValue');
|
|
232
|
+
if (index !== -1) {
|
|
233
|
+
declaration.id.properties.splice(index, 1);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
return value.node;
|
|
237
|
+
});
|
|
238
|
+
return wasHookRemoved;
|
|
239
|
+
}
|
|
240
|
+
function removeUnusedImports(tree, j) {
|
|
241
|
+
let isUseThemeHookRemoved = false;
|
|
242
|
+
// Find all the used identifiers in the file
|
|
243
|
+
const usedIdentifiersMap = new Map();
|
|
244
|
+
tree.find(j.Identifier).forEach((identifier) => {
|
|
245
|
+
if ((j.Identifier.check(identifier.node) &&
|
|
246
|
+
!j.ImportSpecifier.check(identifier.parent.node) &&
|
|
247
|
+
!j.ImportDefaultSpecifier.check(identifier.parent.node)) ||
|
|
248
|
+
j.JSXIdentifier.check(identifier.node)) {
|
|
249
|
+
const previousCount = usedIdentifiersMap.get(identifier.node.name) || 0;
|
|
250
|
+
usedIdentifiersMap.set(identifier.node.name, previousCount + 1);
|
|
251
|
+
}
|
|
252
|
+
});
|
|
253
|
+
// Find types annotations
|
|
254
|
+
tree
|
|
255
|
+
.find(j.CallExpression)
|
|
256
|
+
.filter((callExpression) => {
|
|
257
|
+
return j.TSTypeParameterInstantiation.check(callExpression.node
|
|
258
|
+
.typeParameters);
|
|
259
|
+
})
|
|
260
|
+
.forEach((callExpression) => {
|
|
261
|
+
callExpression.node.typeParameters?.params.forEach((param) => {
|
|
262
|
+
if (j.TSTypeReference.check(param) &&
|
|
263
|
+
j.Identifier.check(param.typeName)) {
|
|
264
|
+
const previousCount = usedIdentifiersMap.get(param.typeName.name) || 0;
|
|
265
|
+
usedIdentifiersMap.set(param.typeName.name, previousCount + 1);
|
|
266
|
+
}
|
|
267
|
+
if (j.TSArrayType.check(param) &&
|
|
268
|
+
j.TSTypeReference.check(param.elementType) &&
|
|
269
|
+
j.Identifier.check(param.elementType.typeName)) {
|
|
270
|
+
const name = param.elementType.typeName.name;
|
|
271
|
+
const previousCount = usedIdentifiersMap.get(name) || 0;
|
|
272
|
+
usedIdentifiersMap.set(name, previousCount + 1);
|
|
273
|
+
}
|
|
274
|
+
// Declaration of a custom type for a function parameter
|
|
275
|
+
if (j.TSTypeLiteral.check(param)) {
|
|
276
|
+
param.members.forEach((propSignature) => {
|
|
277
|
+
// TODO: This is to satisfy TS. Can we do it simpler?
|
|
278
|
+
if (j.TSPropertySignature.check(propSignature) &&
|
|
279
|
+
j.TSTypeAnnotation.check(propSignature.typeAnnotation) &&
|
|
280
|
+
j.TSTypeReference.check(propSignature.typeAnnotation.typeAnnotation) &&
|
|
281
|
+
j.Identifier.check(propSignature.typeAnnotation.typeAnnotation.typeName)) {
|
|
282
|
+
const previousCount = usedIdentifiersMap.get(propSignature.typeAnnotation.typeAnnotation.typeName.name) || 0;
|
|
283
|
+
usedIdentifiersMap.set(propSignature.typeAnnotation.typeAnnotation.typeName.name, previousCount + 1);
|
|
284
|
+
}
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
// Return types in function types
|
|
288
|
+
if (j.TSFunctionType.check(param) &&
|
|
289
|
+
j.TSTypeReference.check(param.typeAnnotation?.typeAnnotation)) {
|
|
290
|
+
const typeName = (param.typeAnnotation?.typeAnnotation.typeName).name;
|
|
291
|
+
const previousCount = usedIdentifiersMap.get(typeName) || 0;
|
|
292
|
+
usedIdentifiersMap.set(typeName, previousCount + 1);
|
|
293
|
+
param.typeAnnotation?.typeAnnotation.typeParameters?.params.forEach((annotationParam) => {
|
|
294
|
+
const annotationParamName = j.TSTypeReference.check(annotationParam) &&
|
|
295
|
+
annotationParam.typeName.name;
|
|
296
|
+
if (annotationParamName) {
|
|
297
|
+
const previousCount = usedIdentifiersMap.get(annotationParamName) || 0;
|
|
298
|
+
usedIdentifiersMap.set(annotationParamName, previousCount + 1);
|
|
299
|
+
}
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
// Find TS types in function types parameters signatures
|
|
303
|
+
if (j.TSFunctionType.check(param)) {
|
|
304
|
+
param.parameters.forEach((fnParam) => {
|
|
305
|
+
let typeName = undefined;
|
|
306
|
+
if (j.Identifier.check(fnParam) &&
|
|
307
|
+
j.TSTypeReference.check(fnParam.typeAnnotation?.typeAnnotation) &&
|
|
308
|
+
j.Identifier.check(fnParam.typeAnnotation?.typeAnnotation.typeName)) {
|
|
309
|
+
typeName = fnParam.typeAnnotation?.typeAnnotation.typeName.name;
|
|
310
|
+
}
|
|
311
|
+
if (j.Identifier.check(fnParam) &&
|
|
312
|
+
j.TSArrayType.check(fnParam.typeAnnotation?.typeAnnotation) &&
|
|
313
|
+
j.TSTypeReference.check(fnParam.typeAnnotation?.typeAnnotation.elementType) &&
|
|
314
|
+
j.Identifier.check(fnParam.typeAnnotation?.typeAnnotation.elementType.typeName)) {
|
|
315
|
+
typeName =
|
|
316
|
+
fnParam.typeAnnotation?.typeAnnotation.elementType.typeName
|
|
317
|
+
.name;
|
|
318
|
+
}
|
|
319
|
+
if (typeName) {
|
|
320
|
+
const previousCount = usedIdentifiersMap.get(typeName) || 0;
|
|
321
|
+
usedIdentifiersMap.set(typeName, previousCount + 1);
|
|
322
|
+
}
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
});
|
|
326
|
+
});
|
|
327
|
+
// Remove unused imports
|
|
328
|
+
tree
|
|
329
|
+
.find(j.ImportDeclaration)
|
|
330
|
+
.forEach((importDeclaration) => {
|
|
331
|
+
const importSpecifiers = importDeclaration.node.specifiers;
|
|
332
|
+
// Skip if this is an import just to auto-initialize any module
|
|
333
|
+
// or it's a css import
|
|
334
|
+
if (!importSpecifiers ||
|
|
335
|
+
importSpecifiers.length < 1 ||
|
|
336
|
+
importDeclaration.node.source.value.endsWith('.css')) {
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
const usedSpecifiers = importSpecifiers.filter((specifier) => (usedIdentifiersMap.get(specifier.local?.name || '') || 0) > 0);
|
|
340
|
+
if (usedSpecifiers.length === 0) {
|
|
341
|
+
// remove the entire import declaration
|
|
342
|
+
j(importDeclaration).remove();
|
|
343
|
+
isUseThemeHookRemoved = importSpecifiers.some((specifier) => specifier.local?.name === 'useTheme');
|
|
344
|
+
}
|
|
345
|
+
else if (usedSpecifiers.length < importSpecifiers.length) {
|
|
346
|
+
// remove unused specifiers from the import declaration
|
|
347
|
+
importDeclaration.node.specifiers = usedSpecifiers;
|
|
348
|
+
isUseThemeHookRemoved = importSpecifiers.some((specifier) => specifier.local?.name === 'useTheme');
|
|
349
|
+
}
|
|
350
|
+
});
|
|
351
|
+
return isUseThemeHookRemoved;
|
|
352
|
+
}
|
|
353
|
+
function removeMigrationCleanupComment(tree, j) {
|
|
354
|
+
tree
|
|
355
|
+
// @ts-ignore
|
|
356
|
+
.find(j.Comment)
|
|
357
|
+
.filter((path) => j.CommentLine.check(path.value) &&
|
|
358
|
+
path.value.value.indexOf('@redesign cleanup') !== -1)
|
|
359
|
+
.remove();
|
|
360
|
+
}
|
|
361
|
+
async function themeMigrationCleanup(file, api, options) {
|
|
362
|
+
const j = api.jscodeshift;
|
|
363
|
+
const root = j(file.source, { comment: true });
|
|
364
|
+
const originalSource = root.toSource();
|
|
365
|
+
// 1. Update props which use `themedValue` helper to always use new theme value
|
|
366
|
+
updateThemedValueUsages(root, j);
|
|
367
|
+
// 2. Process the useTheme hook to remove it or updated it
|
|
368
|
+
processUseThemeHook(root, j);
|
|
369
|
+
// 3. After updating the useTheme usages, let's potentially remove unnecessary imports
|
|
370
|
+
const hasUseThemeHookBeenRemoved = removeUnusedImports(root, j);
|
|
371
|
+
// 4. Remove migration comment -> TODO: @redesign cleanup
|
|
372
|
+
if (hasUseThemeHookBeenRemoved) {
|
|
373
|
+
removeMigrationCleanupComment(root, j);
|
|
374
|
+
}
|
|
375
|
+
// Do not return anything if no changes were applied
|
|
376
|
+
// so we don't rewrite the file
|
|
377
|
+
if (originalSource === root.toSource()) {
|
|
378
|
+
return null;
|
|
379
|
+
}
|
|
380
|
+
if (!options.dry) {
|
|
381
|
+
// Format output code with prettier
|
|
382
|
+
const prettierConfig = await prettier_1.default.resolveConfig(file.path);
|
|
383
|
+
return prettier_1.default.format(root.toSource(), prettierConfig);
|
|
384
|
+
}
|
|
385
|
+
else {
|
|
386
|
+
return null;
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
exports.default = themeMigrationCleanup;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const componentNamesWithDeprecatedZIndexProps = [
|
|
4
|
+
'InfoModalPage',
|
|
5
|
+
'FormModalPage',
|
|
6
|
+
'CustomFormModalPage',
|
|
7
|
+
'TabularModalPage',
|
|
8
|
+
];
|
|
9
|
+
const deprecatedPropsToBeRemoved = ['level', 'baseZIndex'];
|
|
10
|
+
function removeDeprecatedModalLevelProps(file, api, options) {
|
|
11
|
+
const j = api.jscodeshift;
|
|
12
|
+
const root = j(file.source);
|
|
13
|
+
let hasModifications = false;
|
|
14
|
+
function removeDepreactedPropsFromComponent(path) {
|
|
15
|
+
const node = path.node;
|
|
16
|
+
if (node.name.type === 'JSXIdentifier' &&
|
|
17
|
+
componentNamesWithDeprecatedZIndexProps.includes(node.name.name)) {
|
|
18
|
+
if (options.dry) {
|
|
19
|
+
api.stats(file.path);
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
node.attributes =
|
|
23
|
+
node.attributes?.filter((attribute) => {
|
|
24
|
+
if (attribute.type === 'JSXAttribute' &&
|
|
25
|
+
attribute.name.type === 'JSXIdentifier') {
|
|
26
|
+
const hasDeprecatedAttribute = deprecatedPropsToBeRemoved.includes(attribute.name.name);
|
|
27
|
+
hasModifications = true;
|
|
28
|
+
return !hasDeprecatedAttribute;
|
|
29
|
+
}
|
|
30
|
+
return true;
|
|
31
|
+
}) ?? [];
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
root.find(j.JSXOpeningElement).forEach(removeDepreactedPropsFromComponent);
|
|
36
|
+
return hasModifications
|
|
37
|
+
? root.toSource({ ...options, quote: 'single' })
|
|
38
|
+
: null;
|
|
39
|
+
}
|
|
40
|
+
exports.default = removeDeprecatedModalLevelProps;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const fs_1 = __importDefault(require("fs"));
|
|
7
|
+
function renameJsToJsx(file, api, options) {
|
|
8
|
+
const j = api.jscodeshift;
|
|
9
|
+
const root = j(file.source);
|
|
10
|
+
if (file.path.endsWith('.js') && !file.path.endsWith('.spec.js')) {
|
|
11
|
+
const hasJSXElements = root.findJSXElements().length > 0;
|
|
12
|
+
if (hasJSXElements) {
|
|
13
|
+
if (options.dry) {
|
|
14
|
+
api.stats(file.path);
|
|
15
|
+
}
|
|
16
|
+
else {
|
|
17
|
+
const renamedFilePath = file.path.replace('.js', '.jsx');
|
|
18
|
+
fs_1.default.renameSync(file.path, renamedFilePath);
|
|
19
|
+
api.report(`renamed to ${renamedFilePath}`);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
exports.default = renameJsToJsx;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const fs_1 = __importDefault(require("fs"));
|
|
7
|
+
const path_1 = __importDefault(require("path"));
|
|
8
|
+
function renameModCssToModuleCss(file, api, options) {
|
|
9
|
+
const j = api.jscodeshift;
|
|
10
|
+
let hasModifications = false;
|
|
11
|
+
function renameImportModCssToModuleCss(astPath) {
|
|
12
|
+
const node = astPath.node;
|
|
13
|
+
if (typeof node.source.value === 'string' &&
|
|
14
|
+
node.source.value.endsWith('.mod.css')) {
|
|
15
|
+
if (options.dry) {
|
|
16
|
+
api.stats(file.path);
|
|
17
|
+
}
|
|
18
|
+
else {
|
|
19
|
+
const previousCssModulesFilePath = node.source.value;
|
|
20
|
+
const nextCssModulesFilePath = previousCssModulesFilePath.replace('.mod.css', '.module.css');
|
|
21
|
+
node.source.value = nextCssModulesFilePath;
|
|
22
|
+
hasModifications = true;
|
|
23
|
+
// Side effect for renaming the actual CSS module file.
|
|
24
|
+
fs_1.default.renameSync(path_1.default.join(path_1.default.dirname(file.path), previousCssModulesFilePath), path_1.default.join(path_1.default.dirname(file.path), nextCssModulesFilePath));
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
const root = j(file.source);
|
|
29
|
+
root.find(j.ImportDeclaration).forEach(renameImportModCssToModuleCss);
|
|
30
|
+
return hasModifications
|
|
31
|
+
? root.toSource({ ...options, quote: 'single' })
|
|
32
|
+
: null;
|
|
33
|
+
}
|
|
34
|
+
exports.default = renameModCssToModuleCss;
|
package/package.json
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@commercetools-frontend/codemod",
|
|
3
|
+
"version": "0.0.0-FEC-157-preview-releases-20241126230331",
|
|
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
|
+
"cac": "6.7.14",
|
|
37
|
+
"glob": "8.1.0",
|
|
38
|
+
"jscodeshift": "0.15.2",
|
|
39
|
+
"prettier": "2.8.8"
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"@emotion/react": "^11.11.4",
|
|
43
|
+
"@tsconfig/node16": "^16.1.1",
|
|
44
|
+
"@types/glob": "8.1.0",
|
|
45
|
+
"@types/jscodeshift": "0.11.11",
|
|
46
|
+
"rimraf": "5.0.7",
|
|
47
|
+
"typescript": "5.0.4",
|
|
48
|
+
"@commercetools-frontend/application-components": "0.0.0-FEC-157-preview-releases-20241126230331"
|
|
49
|
+
},
|
|
50
|
+
"engines": {
|
|
51
|
+
"node": "16.x || >=18.0.0"
|
|
52
|
+
},
|
|
53
|
+
"scripts": {
|
|
54
|
+
"build": "rimraf build && tsc",
|
|
55
|
+
"build:bundles:watch": "pnpm run build -w"
|
|
56
|
+
}
|
|
57
|
+
}
|