@ukic/codemod 1.0.0-alpha.2

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 ADDED
@@ -0,0 +1,14 @@
1
+ # Change Log
2
+
3
+ All notable changes to this project will be documented in this file.
4
+ See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
+
6
+ # [1.0.0-alpha.2](https://github.com/mi6/ic-ui-kit/compare/@ukic/codemod@1.0.0-alpha.1...@ukic/codemod@1.0.0-alpha.2) (2025-01-24)
7
+
8
+ **Note:** Version bump only for package @ukic/codemod
9
+
10
+ # 1.0.0-alpha.1 (2025-01-22)
11
+
12
+ ### Features
13
+
14
+ - **codemod:** codemod for v2 to v3 ([d313066](https://github.com/mi6/ic-ui-kit/commit/d313066c8dd198d206797d12bcdbc14e2fa3801f))
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2022 The Secret Intelligence Service (MI6)
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,72 @@
1
+ # Codemod
2
+
3
+ This script is used to modify files which use ICDS components to upgrade from v2 to v3 of the ICDS library.
4
+
5
+ ## What is this used for
6
+
7
+ This codemod has been developed to help users migrate from v2 of ICDS components to v3.
8
+
9
+ Given a directory, it will scan over files and find any relevant ICDS components that require a change either by component name or props used.
10
+
11
+ ## How to run
12
+
13
+
14
+ This package will be usable as an executable requiring a directory and optional test boolean argument to cover tests
15
+
16
+ ```console
17
+ - npx @ukic/codemod <dir> <test>
18
+ ```
19
+
20
+ ### Options:
21
+
22
+ ```console
23
+ --dir Directory your project is located in using ICDS components
24
+ [string]
25
+ ```
26
+ ```console
27
+ --test By default testing is false (with exception of types)
28
+ [boolean]: true | false
29
+ ```
30
+
31
+ ### Examples:
32
+ ```console
33
+ - npx @ukic/codemod --dir ./#path/app/src/components --test true
34
+ ```
35
+
36
+ or run locally via
37
+
38
+ ```console
39
+ - npm @ukic/codemod
40
+ - cd to codemod files
41
+ - node ./codemod --dir ./#path/app/src/components --test false
42
+ ```
43
+
44
+ ## Contributing
45
+
46
+ We have a couple of resources to help you with contributing.
47
+
48
+ - To find out more about the different types of contributions, the criteria, raising issues or our release roadmap, read [how to contribute to the Design System and UI Kit](https://design.sis.gov.uk/community/contribute).
49
+ - Make sure to also read our [coding standards and technical instructions](https://github.com/mi6/ic-ui-kit/blob/main/CONTRIBUTING.md).
50
+ - [IC Design System guidance site repository](https://github.com/mi6/ic-design-system) contains the code and content for the Design System guidance site.
51
+ - [IC UI Kit repository](https://github.com/mi6/ic-ui-kit) contains the code and content for the web components.
52
+
53
+ ### Security
54
+
55
+ If you've found a vulnerability, we want to know so that we can fix it. [Our security policy](https://github.com/mi6/ic-ui-kit/blob/main/SECURITY.md) tells you how to do this.
56
+
57
+ ## Questions about the departments
58
+
59
+ The team is only able to talk about the projects we've put on GitHub 🕵️. We unfortunately can't talk about the work of our departments 😢.
60
+
61
+ Visit our websites to learn more about:
62
+
63
+ - The [Secret Intelligence Service (MI6)](https://www.sis.gov.uk).
64
+ - The [Government Communications Headquarters (GCHQ)](https://www.gchq.gov.uk).
65
+ - The [Security Service (MI5)](https://www.mi5.gov.uk).
66
+
67
+
68
+ ## License
69
+
70
+ Unless stated otherwise, the codebase is released under the [MIT License](https://opensource.org/licenses/MIT). This covers both the codebase and any sample code in the documentation. The documentation is and available under the terms of the [Open Government License v3.0](https://www.nationalarchives.gov.uk/doc/open-government-licence/version/3/).
71
+
72
+ © Crown copyright 2022
@@ -0,0 +1,180 @@
1
+ import { compareComponent } from "../sections/component-changes.js";
2
+
3
+ describe("compareComponent", () => {
4
+ describe("React style changes", () => {
5
+ it("should replace old props with new props simple", () => {
6
+ const stringArray = `
7
+ <ExampleComponent oldProp1="value1" oldProp2="value2" />
8
+ `;
9
+ const jsonData = [
10
+ {
11
+ componentsAffected: "ExampleComponent",
12
+ type: "prop",
13
+ v2Name: "oldProp1",
14
+ v3Name: "newProp1",
15
+ },
16
+ {
17
+ componentsAffected: "ExampleComponent",
18
+ type: "prop",
19
+ v2Name: "oldProp2",
20
+ v3Name: "newProp2",
21
+ },
22
+ ];
23
+
24
+ const result = compareComponent(stringArray, jsonData);
25
+ expect(result).toContain('newProp1="value1"');
26
+ expect(result).toContain('newProp2="value2"');
27
+ });
28
+
29
+ it("should replace old props with new props absolute varient", () => {
30
+ const stringArray = `<ExampleComponent oldProp1 oldProp2="oldProp1" />`;
31
+ const jsonData = [
32
+ {
33
+ componentsAffected: "ExampleComponent",
34
+ type: "absolute",
35
+ v2Name: "oldProp1",
36
+ v3Name: "newProp1",
37
+ },
38
+ ];
39
+
40
+ const result = compareComponent(stringArray, jsonData);
41
+ expect(result).toBe('<ExampleComponent newProp1 oldProp2="oldProp1" />');
42
+ });
43
+
44
+ it("should replace old props with new props simple Component", () => {
45
+ const stringArray = `<ExampleComponent oldProp1="value1" oldProp2="value2" />`;
46
+ const jsonData = [
47
+ {
48
+ componentsAffected: "ExampleComponent",
49
+ type: "component",
50
+ v2Name: "ExampleComponent",
51
+ v3Name: "ExampleComponentNew",
52
+ },
53
+ ];
54
+
55
+ const result = compareComponent(stringArray, jsonData);
56
+ expect(result).toBe(
57
+ '<ExampleComponentNew oldProp1="value1" oldProp2="value2" />'
58
+ );
59
+ });
60
+
61
+ it("should replace old props with new props simple Component not self closing", () => {
62
+ const stringArray = `<ExampleComponent oldProp1="value1" oldProp2="value2" >
63
+ <div>Test</div>
64
+ </ExampleComponent>`;
65
+ const jsonData = [
66
+ {
67
+ componentsAffected: "ExampleComponent",
68
+ type: "component",
69
+ v2Name: "ExampleComponent",
70
+ v3Name: "ExampleComponentNew",
71
+ },
72
+ ];
73
+
74
+ const result = compareComponent(stringArray, jsonData);
75
+ expect(result).toBe(
76
+ `<ExampleComponentNew oldProp1="value1" oldProp2="value2" >
77
+ <div>Test</div>
78
+ </ExampleComponentNew>`
79
+ );
80
+ });
81
+
82
+ it("should not replace old props with new props simple Component", () => {
83
+ const stringArray = `<ExampleComponentDifferent oldProp1="value1" oldProp2="value2" />`;
84
+ const jsonData = [
85
+ {
86
+ componentsAffected: "ExampleComponent",
87
+ type: "component",
88
+ v2Name: "ExampleComponent",
89
+ v3Name: "ExampleComponentNew",
90
+ },
91
+ ];
92
+
93
+ const result = compareComponent(stringArray, jsonData);
94
+ expect(result).toBe(
95
+ '<ExampleComponentDifferent oldProp1="value1" oldProp2="value2" />'
96
+ );
97
+ });
98
+ });
99
+
100
+ describe("web-component style changes", () => {
101
+ it("should replace old props with new props simple", () => {
102
+ const stringArray = `
103
+ <example-component oldProp1="value1" oldProp2="value2" />
104
+ `;
105
+ const jsonData = [
106
+ {
107
+ componentsAffected: "example-component",
108
+ type: "prop",
109
+ v2Name: "oldProp1",
110
+ v3Name: "newProp1",
111
+ },
112
+ {
113
+ componentsAffected: "example-component",
114
+ type: "prop",
115
+ v2Name: "oldProp2",
116
+ v3Name: "newProp2",
117
+ },
118
+ ];
119
+
120
+ const result = compareComponent(stringArray, jsonData);
121
+ expect(result).toContain('newProp1="value1"');
122
+ expect(result).toContain('newProp2="value2"');
123
+ });
124
+
125
+ it("should replace old props with new props simple Component", () => {
126
+ const stringArray = `<example-component oldProp1="value1" oldProp2="value2" />`;
127
+ const jsonData = [
128
+ {
129
+ componentsAffected: "example-component",
130
+ type: "web-component",
131
+ v2Name: "example-component",
132
+ v3Name: "example-component-new",
133
+ },
134
+ ];
135
+
136
+ const result = compareComponent(stringArray, jsonData);
137
+ expect(result).toBe(
138
+ '<example-component-new oldProp1="value1" oldProp2="value2" />'
139
+ );
140
+ });
141
+
142
+ it("should replace old props with new props simple Component not self closing", () => {
143
+ const stringArray = `<example-component oldProp1="value1" oldProp2="value2" >
144
+ <div>Test</div>
145
+ </example-component>`;
146
+ const jsonData = [
147
+ {
148
+ componentsAffected: "example-component",
149
+ type: "web-component",
150
+ v2Name: "example-component",
151
+ v3Name: "example-component-new",
152
+ },
153
+ ];
154
+
155
+ const result = compareComponent(stringArray, jsonData);
156
+ expect(result).toBe(
157
+ `<example-component-new oldProp1="value1" oldProp2="value2" >
158
+ <div>Test</div>
159
+ </example-component-new>`
160
+ );
161
+ });
162
+
163
+ it("should not replace old props with new props simple Component", () => {
164
+ const stringArray = `<example-component-different oldProp1="value1" oldProp2="value2" />`;
165
+ const jsonData = [
166
+ {
167
+ componentsAffected: "example-component",
168
+ type: "web-component",
169
+ v2Name: "example-component",
170
+ v3Name: "example-component-new",
171
+ },
172
+ ];
173
+
174
+ const result = compareComponent(stringArray, jsonData);
175
+ expect(result).toBe(
176
+ '<example-component-different oldProp1="value1" oldProp2="value2" />'
177
+ );
178
+ });
179
+ });
180
+ });
@@ -0,0 +1,29 @@
1
+ import { testComparison } from "../sections/test-comparison.js";
2
+
3
+ describe("compareTest", () => {
4
+ it("should replace old props with new props simple", () => {
5
+ const stringArray = `
6
+ const stepOne = container.querySelector(
7
+ 'ic-step[step-title="Choose coffee"]',
8
+ ) as HTMLIcStepElement;
9
+ expect(stepOne.stepType).toBe(stepStates.current);
10
+ `.trim();
11
+ const jsonData = [
12
+ {
13
+ componentsAffected: "ic-step",
14
+ type: "prop",
15
+ v2Name: "stepType",
16
+ v3Name: "type",
17
+ },
18
+ ];
19
+
20
+ const result = testComparison(stringArray, jsonData);
21
+ expect(result).toBe(
22
+ `
23
+ const stepOne = container.querySelector(
24
+ 'ic-step[step-title="Choose coffee"]',
25
+ ) as HTMLIcStepElement;
26
+ expect(stepOne.type).toBe(stepStates.current);`.trim()
27
+ );
28
+ });
29
+ });
package/codemod.js ADDED
@@ -0,0 +1,103 @@
1
+ #!/usr/bin/env node
2
+ import fs from "fs";
3
+ import yargs from "yargs";
4
+ import { searchDirectory } from "./sections/directory-search.js";
5
+ import { compareComponent } from "./sections/component-changes.js";
6
+ import { simpleTestComparison } from "./sections/simple-test-comparison.js";
7
+ import htmlData from "./data/changes.js";
8
+ import reactData from "./data/reactChanges.js";
9
+
10
+ /**
11
+ *
12
+ * @param {*} filePath
13
+ * @returns list of files in the directory as strings
14
+ *
15
+ */
16
+ function readFileLinesToArray(filePath) {
17
+ try {
18
+ const data = fs.readFileSync(filePath, "utf8");
19
+ return data;
20
+ } catch (err) {
21
+ console.error("Error reading file:", err);
22
+ return [];
23
+ }
24
+ }
25
+
26
+ /**
27
+ *
28
+ * @param {*} path
29
+ * @param {*} type
30
+ *
31
+ * main function for code mod, will convert v2 file format to v3 file format
32
+ */
33
+ const main = async (path, test = false) => {
34
+ const files = await searchDirectory(path);
35
+ files.forEach((file) => {
36
+ const linesArray = readFileLinesToArray(file);
37
+ const isReact = file.includes(".tsx") || file.includes(".jsx");
38
+ if (test && (file.includes(".spec.") || file.includes(".test."))) {
39
+ const changedFile = simpleTestComparison(linesArray, [
40
+ ...htmlData,
41
+ ...reactData,
42
+ ]);
43
+ if (changedFile !== linesArray) {
44
+ fs.writeFile(file, changedFile, (err) => {
45
+ if (err) return console.log(err);
46
+ console.log(`${file.split("\\").slice(-1)} modified successfully`);
47
+ });
48
+ }
49
+ }
50
+ if (file.includes(`.cy.`)) {
51
+ const changedFile = compareComponent(linesArray, htmlData);
52
+ if (changedFile !== linesArray) {
53
+ fs.writeFile(file, changedFile, (err) => {
54
+ if (err) return console.log(err);
55
+ console.log(`${file.split("\\").slice(-1)} modified successfully`);
56
+ });
57
+ }
58
+ } else {
59
+ const changedComponentFile = compareComponent(
60
+ linesArray,
61
+ isReact ? reactData : htmlData
62
+ );
63
+ if (changedComponentFile !== linesArray) {
64
+ fs.writeFile(file, String(changedComponentFile), (err) => {
65
+ if (err) return console.log(err);
66
+ console.log(`${file.split("\\").slice(-1)} modified successfully`);
67
+ });
68
+ }
69
+ }
70
+ });
71
+ };
72
+
73
+ const cli = yargs(process.argv.slice(2));
74
+ cli
75
+ .usage("Usage: node ./$0 [options]")
76
+ .example("node ./$0 --dir ./src", "execute codemod in src directory")
77
+ .option("dir", {
78
+ alias: "d",
79
+ describe: "Directory to migrate",
80
+ nargs: 1,
81
+ demandOption: "Directory to migrate required",
82
+ type: "string",
83
+ })
84
+ .option("test", {
85
+ alias: "t",
86
+ describe: "Run codemod on directory provided, if false, skip migration",
87
+ type: "boolean",
88
+ default: false,
89
+ })
90
+ .help("h")
91
+ .alias("h", "help")
92
+ .alias("v", "version")
93
+ .epilog("Created by ICDS ♥")
94
+ .parse();
95
+
96
+ const { dir, test } = cli.argv;
97
+
98
+ if (!dir) {
99
+ console.error("Please provide a directory path to the codemod");
100
+ process.exit(1);
101
+ }
102
+
103
+ await main(dir, test);
@@ -0,0 +1,182 @@
1
+ export default [
2
+ {
3
+ v2Name: "small=true",
4
+ v3Name: 'size="small"',
5
+ componentsAffected: "ic-select",
6
+ type: "prop",
7
+ },
8
+ {
9
+ v2Name: "small",
10
+ v3Name: 'size="small"',
11
+ componentsAffected: "ic-select",
12
+ type: "absolute",
13
+ },
14
+ {
15
+ v2Name: 'size="default"',
16
+ v3Name: 'size="medium"',
17
+ componentsAffected: "ic-status-tag",
18
+ type: "prop",
19
+ },
20
+ {
21
+ v2Name: 'size="default"',
22
+ v3Name: 'size="medium"',
23
+ componentsAffected: "ic-switch",
24
+ type: "prop",
25
+ },
26
+ {
27
+ v2Name: "group-title",
28
+ v3Name: "label",
29
+ componentsAffected: "ic-accordion-group",
30
+ type: "prop",
31
+ },
32
+ {
33
+ v2Name: "group-title",
34
+ v3Name: "label",
35
+ componentsAffected: "ic-footer-link-group",
36
+ type: "prop",
37
+ },
38
+ {
39
+ v2Name: "text-label",
40
+ v3Name: "label",
41
+ componentsAffected: "ic-badge",
42
+ type: "prop",
43
+ },
44
+ {
45
+ v2Name: "body-max-lines",
46
+ v3Name: "max-lines",
47
+ componentsAffected: "ic-empty-state",
48
+ type: "prop",
49
+ },
50
+ {
51
+ v2Name: "theme",
52
+ v3Name: "brand",
53
+ componentsAffected: "ic-theme",
54
+ type: "prop",
55
+ },
56
+ {
57
+ v2Name: "step-type",
58
+ v3Name: "type",
59
+ componentsAffected: "ic-step",
60
+ type: "prop",
61
+ },
62
+ {
63
+ v2Name: "step-title",
64
+ v3Name: "heading",
65
+ componentsAffected: "ic-step",
66
+ type: "prop",
67
+ },
68
+ {
69
+ v2Name: "step-subtitle",
70
+ v3Name: "subheading",
71
+ componentsAffected: "ic-step",
72
+ type: "prop",
73
+ },
74
+ {
75
+ v2Name: "step-status",
76
+ v3Name: "status",
77
+ componentsAffected: "ic-step",
78
+ type: "prop",
79
+ },
80
+ {
81
+ v2Name: "adjacent-count",
82
+ v3Name: "adjacent-page-count",
83
+ componentsAffected: "ic-pagination",
84
+ type: "prop",
85
+ },
86
+ {
87
+ v2Name: "boundary-count",
88
+ v3Name: "boundary-page-count",
89
+ componentsAffected: "ic-pagination",
90
+ type: "prop",
91
+ },
92
+ {
93
+ v2Name: "keyboard-shortcut",
94
+ v3Name: "keyboard-shortcut-label",
95
+ componentsAffected: "ic-menu-item",
96
+ type: "prop",
97
+ },
98
+ {
99
+ v2Name: "disable-filter",
100
+ v3Name: "disable-auto-filtering",
101
+ componentsAffected: "ic-search-bar",
102
+ type: "prop",
103
+ },
104
+ {
105
+ v2Name: "disable-filter",
106
+ v3Name: "disable-auto-filtering",
107
+ componentsAffected: "ic-select",
108
+ type: "prop",
109
+ },
110
+ {
111
+ v2Name: "max-length",
112
+ v3Name: "max-characters",
113
+ componentsAffected: "ic-text-field",
114
+ type: "prop",
115
+ },
116
+ {
117
+ v2Name: "toggle-checked",
118
+ v3Name: "checked",
119
+ componentsAffected: "ic-menu-item",
120
+ type: "prop",
121
+ },
122
+ {
123
+ v2Name: "toggle-checked",
124
+ v3Name: "checked",
125
+ componentsAffected: "ic-toggle-button",
126
+ type: "prop",
127
+ },
128
+ {
129
+ v2Name: "hint-text",
130
+ v3Name: "assistive-hint-text",
131
+ componentsAffected: "ic-search-bar",
132
+ type: "prop",
133
+ },
134
+ {
135
+ v2Name: "side-nav-expanded",
136
+ v3Name: "ic-side-nav-expanded",
137
+ componentsAffected: "ic-side-navigation",
138
+ type: "prop",
139
+ },
140
+ {
141
+ v2Name: "top-nav-resized",
142
+ v3Name: "ic-top-nav-resized",
143
+ componentsAffected: "ic-top-navigation",
144
+ type: "prop",
145
+ },
146
+ {
147
+ v2Name: "ic-data-entity",
148
+ v3Name: "ic-data-list",
149
+ componentsAffected: "ic-data-entity",
150
+ type: "web-component",
151
+ },
152
+ {
153
+ v2Name: "HTMLIcDataEntityElement",
154
+ v3Name: "HTMLIcDataListElement",
155
+ componentsAffected: "HTMLIcDataEntityElement",
156
+ type: "component",
157
+ },
158
+ {
159
+ v2Name: "ic-card",
160
+ v3Name: "ic-card-vertical",
161
+ componentsAffected: "ic-card",
162
+ type: "web-component",
163
+ },
164
+ {
165
+ v2Name: "HTMLIcCardElement",
166
+ v3Name: "HTMLIcCardVerticalElement",
167
+ componentsAffected: "HTMLIcCardElement",
168
+ type: "component",
169
+ },
170
+ {
171
+ v2Name: "ic-select-with-multi",
172
+ v3Name: "ic-select",
173
+ componentsAffected: "ic-select-with-multi",
174
+ type: "web-component",
175
+ },
176
+ {
177
+ v2Name: "HTMLIcSelectWithMultiElement",
178
+ v3Name: "HTMLIcSelectElement",
179
+ componentsAffected: "HTMLIcSelectWithMultiElement",
180
+ type: "component",
181
+ },
182
+ ];
@@ -0,0 +1,14 @@
1
+ export default [
2
+ "node_modules",
3
+ ".json",
4
+ ".sh",
5
+ ".lock",
6
+ ".md",
7
+ "dist",
8
+ ".git",
9
+ ".cjs",
10
+ ".config",
11
+ ".png",
12
+ "LICENSE",
13
+ ".css",
14
+ ];
@@ -0,0 +1,200 @@
1
+ export default [
2
+ {
3
+ v2Name: "small={true}",
4
+ v3Name: "size={'small'}",
5
+ componentsAffected: "IcSelect",
6
+ type: "prop",
7
+ },
8
+ {
9
+ v2Name: "small",
10
+ v3Name: "size={'small'}",
11
+ componentsAffected: "IcSelect",
12
+ type: "absolute",
13
+ },
14
+ {
15
+ v2Name: "small={true}",
16
+ v3Name: "size={'small'}",
17
+ componentsAffected: "IcSelect",
18
+ type: "prop",
19
+ },
20
+ {
21
+ v2Name: "size={'default'}",
22
+ v3Name: "size={'medium'}",
23
+ componentsAffected: "IcStatusTag",
24
+ type: "prop",
25
+ },
26
+ {
27
+ v2Name: "size={'default'}",
28
+ v3Name: "size={'medium'}",
29
+ componentsAffected: "IcSwitch",
30
+ type: "prop",
31
+ },
32
+ {
33
+ v2Name: "groupTitle",
34
+ v3Name: "label",
35
+ componentsAffected: "IcAccordionGroup",
36
+ type: "prop",
37
+ },
38
+ {
39
+ v2Name: "groupTitle",
40
+ v3Name: "label",
41
+ componentsAffected: "IcFooterLinkGroup",
42
+ type: "prop",
43
+ },
44
+ {
45
+ v2Name: "textLabel",
46
+ v3Name: "label",
47
+ componentsAffected: "IcBadge",
48
+ type: "prop",
49
+ },
50
+ {
51
+ v2Name: "bodyMaxLines",
52
+ v3Name: "maxLines",
53
+ componentsAffected: "IcEmptyState",
54
+ type: "prop",
55
+ },
56
+ {
57
+ v2Name: "color",
58
+ v3Name: "brandColor",
59
+ componentsAffected: "IcTheme",
60
+ type: "prop",
61
+ },
62
+ {
63
+ v2Name: "stepType",
64
+ v3Name: "type",
65
+ componentsAffected: "IcStep",
66
+ type: "prop",
67
+ },
68
+ {
69
+ v2Name: "stepTitle",
70
+ v3Name: "heading",
71
+ componentsAffected: "IcStep",
72
+ type: "prop",
73
+ },
74
+ {
75
+ v2Name: "stepSubtitle",
76
+ v3Name: "subheading",
77
+ componentsAffected: "IcStep",
78
+ type: "prop",
79
+ },
80
+ {
81
+ v2Name: "stepStatus",
82
+ v3Name: "status",
83
+ componentsAffected: "IcStep",
84
+ type: "prop",
85
+ },
86
+ {
87
+ v2Name: "adjacentCount",
88
+ v3Name: "adjacentPageCount",
89
+ componentsAffected: "IcPagination",
90
+ type: "prop",
91
+ },
92
+ {
93
+ v2Name: "boundaryCount",
94
+ v3Name: "boundaryPageCount",
95
+ componentsAffected: "IcPagination",
96
+ type: "prop",
97
+ },
98
+ {
99
+ v2Name: "keyboardShortcut",
100
+ v3Name: "keyboardShortcutLabel",
101
+ componentsAffected: "IcMenuItem",
102
+ type: "prop",
103
+ },
104
+ {
105
+ v2Name: "disableFilter",
106
+ v3Name: "disableAutoFiltering",
107
+ componentsAffected: "IcSearchBar",
108
+ type: "prop",
109
+ },
110
+ {
111
+ v2Name: "disableFilter",
112
+ v3Name: "disableAutoFiltering",
113
+ componentsAffected: "IcSelect",
114
+ type: "prop",
115
+ },
116
+ {
117
+ v2Name: "maxLength",
118
+ v3Name: "maxCharacters",
119
+ componentsAffected: "IcTextField",
120
+ type: "prop",
121
+ },
122
+ {
123
+ v2Name: "toggleChecked",
124
+ v3Name: "checked",
125
+ componentsAffected: "IcMenuItem",
126
+ type: "prop",
127
+ },
128
+ {
129
+ v2Name: "toggleChecked",
130
+ v3Name: "checked",
131
+ componentsAffected: "IcToggleButton",
132
+ type: "prop",
133
+ },
134
+ {
135
+ v2Name: "hintText",
136
+ v3Name: "assistiveHintText",
137
+ componentsAffected: "IcSearchBar",
138
+ type: "prop",
139
+ },
140
+ {
141
+ v2Name: "sideNavExpanded",
142
+ v3Name: "icSideNavExpanded",
143
+ componentsAffected: "IcSideNavigation",
144
+ type: "prop",
145
+ },
146
+ {
147
+ v2Name: "onSideNavExpanded",
148
+ v3Name: "onIcSideNavExpanded",
149
+ componentsAffected: "IcSideNavigation",
150
+ type: "prop",
151
+ },
152
+ {
153
+ v2Name: "topNavResized",
154
+ v3Name: "icTopNavResized",
155
+ componentsAffected: "IcTopNavigation",
156
+ type: "prop",
157
+ },
158
+ {
159
+ v2Name: "onTopNavResized",
160
+ v3Name: "onIcTopNavResized",
161
+ componentsAffected: "IcTopNavigation",
162
+ type: "prop",
163
+ },
164
+ {
165
+ v2Name: "IcDataEntity",
166
+ v3Name: "IcDataList",
167
+ componentsAffected: "IcDataEntity",
168
+ type: "component",
169
+ },
170
+ {
171
+ v2Name: "HTMLIcDataEntityElement",
172
+ v3Name: "HTMLIcDataListElement",
173
+ componentsAffected: "HTMLIcDataEntityElement",
174
+ type: "component",
175
+ },
176
+ {
177
+ v2Name: "IcCard",
178
+ v3Name: "IcCardVertical",
179
+ componentsAffected: "IcCard",
180
+ type: "component",
181
+ },
182
+ {
183
+ v2Name: "HTMLIcCardElement",
184
+ v3Name: "HTMLIcCardVerticalElement",
185
+ componentsAffected: "HTMLIcCardElement",
186
+ type: "component",
187
+ },
188
+ {
189
+ v2Name: "IcSelectWithMulti",
190
+ v3Name: "IcSelect",
191
+ componentsAffected: "IcSelectWithMulti",
192
+ type: "component",
193
+ },
194
+ {
195
+ v2Name: "HTMLIcSelectWithMultiElement",
196
+ v3Name: "HTMLIcSelectElement",
197
+ componentsAffected: "HTMLIcSelectWithMultiElement",
198
+ type: "component",
199
+ },
200
+ ];
package/package.json ADDED
@@ -0,0 +1,23 @@
1
+ {
2
+ "name": "@ukic/codemod",
3
+ "version": "1.0.0-alpha.2",
4
+ "description": "Codemod tool to help assist users upgrading from v2 to v3 of the UKIC design system",
5
+ "bin": "./codemod.js",
6
+ "author": "mi6",
7
+ "type": "module",
8
+ "scripts": {
9
+ "test": "jest",
10
+ "example:cli": "node --no-warnings ./codemod.js --dir src"
11
+ },
12
+ "dependencies": {
13
+ "jest-cli": "^29.7.0",
14
+ "yargs": "^17.7.2"
15
+ },
16
+ "license": "MIT",
17
+ "private": false,
18
+ "publishConfig": {
19
+ "access": "public",
20
+ "registry": "https://registry.npmjs.org/"
21
+ },
22
+ "gitHead": "aadff337c20cb1dee5e221f7e88179dc8242789e"
23
+ }
@@ -0,0 +1,46 @@
1
+ import { generateRegex } from "./generate-regex.js";
2
+
3
+ /**
4
+ *
5
+ * @param {*} stringArray
6
+ * @param {*} jsonData
7
+ * @returns File string with changes made by comparing v2 and v3 names in predefined json file
8
+ */
9
+ export function compareComponent(stringArray, jsonData) {
10
+ let fileString = stringArray;
11
+ const componentsAffected = new Set(
12
+ jsonData.map((entry) => entry.componentsAffected)
13
+ );
14
+
15
+ componentsAffected.forEach((component) => {
16
+ const regex = generateRegex(
17
+ component,
18
+ jsonData.filter((field) => field.componentsAffected === component)[0].type
19
+ );
20
+ const match = String(fileString).match(regex);
21
+ if (match) {
22
+ const oldProps = jsonData.reduce((total, field) => {
23
+ if (field.componentsAffected === component) total.push(field.v2Name);
24
+ return total;
25
+ }, []);
26
+ match.forEach((component) => {
27
+ oldProps.filter((prop) => {
28
+ component.includes(prop);
29
+ });
30
+ let newComponent = component;
31
+ oldProps.forEach((prop) => {
32
+ const newProp = jsonData.find((field) => field.v2Name === prop);
33
+ if (newProp.type === "absolute") {
34
+ const regex = generateRegex(newProp.v2Name, "absolute-props");
35
+ const exactMatch = String(newComponent).match(regex);
36
+ if (exactMatch)
37
+ newComponent = newComponent.replace(prop, newProp.v3Name);
38
+ } else newComponent = newComponent.replace(prop, newProp.v3Name);
39
+ });
40
+ fileString = fileString.replace(component, newComponent);
41
+ });
42
+ }
43
+ });
44
+
45
+ return fileString;
46
+ }
@@ -0,0 +1,36 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import ignoreData from "../data/ignoreList.js";
4
+ /**
5
+ *
6
+ * @param {*} directoryPath
7
+ * @returns a list of files relevant to the code mod changes from a given directory
8
+ */
9
+ export async function searchDirectory(directoryPath) {
10
+ let foundFiles = [];
11
+ async function search(directoryPath) {
12
+ try {
13
+ const files = await fs.promises.readdir(directoryPath);
14
+ for (const file of files) {
15
+ const filePath = path.join(directoryPath, file);
16
+ const stats = await fs.promises.stat(filePath);
17
+
18
+ const ignored = ignoreData.reduce((acc, curr) => {
19
+ if (filePath.includes(curr)) {
20
+ return true;
21
+ }
22
+ return acc;
23
+ }, false);
24
+
25
+ if (stats.isDirectory() && !ignored) {
26
+ await search(filePath);
27
+ } else if (!ignored) foundFiles.push(filePath);
28
+ }
29
+ } catch (err) {
30
+ console.error(`Error reading directory: ${err.message}`);
31
+ }
32
+ }
33
+
34
+ await search(directoryPath);
35
+ return foundFiles;
36
+ }
@@ -0,0 +1,25 @@
1
+ /**
2
+ *
3
+ * @param {*} component
4
+ * @param {*} style
5
+ * @returns correct regex expression for the change based of predefined types in json files
6
+ */
7
+
8
+ function generateRegex(component, style) {
9
+ switch (style) {
10
+ case "prop":
11
+ return new RegExp(`<${component}([^>]+)>`, "gm");
12
+ case "component":
13
+ return new RegExp(`${component}\\W`, "gm");
14
+ case "web-component":
15
+ return new RegExp(`${component}\\b[^-]`, "gm");
16
+ case "absolute":
17
+ return new RegExp(`<${component}([^>]+)>`, "gm");
18
+ case "absolute-props":
19
+ return new RegExp(`${component}\\b[^'"]`, "gm");
20
+ case "test-simple":
21
+ return new RegExp(`[[.]${component}\\b`, "gm");
22
+ }
23
+ }
24
+
25
+ export { generateRegex };
@@ -0,0 +1,26 @@
1
+ import { generateRegex } from "./generate-regex.js";
2
+
3
+ /**
4
+ *
5
+ * @param {*} stringArray
6
+ * @param {*} jsonData
7
+ * @returns file string with changes made by comparing v2 and v3 names in predefined json file
8
+ */
9
+ export function simpleTestComparison(stringArray, jsonData) {
10
+ let fileString = stringArray;
11
+
12
+ const componentsToChange = new Set(jsonData.map((entry) => entry.v2Name));
13
+
14
+ componentsToChange.forEach((component) => {
15
+ const regex = generateRegex(component, "test-simple");
16
+ const match = fileString.match(regex);
17
+ if (match) {
18
+ const newProp = jsonData.find((field) => match[0].includes(field.v2Name));
19
+ if (newProp) {
20
+ fileString = fileString.replaceAll(component, newProp.v3Name);
21
+ }
22
+ }
23
+ });
24
+
25
+ return fileString;
26
+ }
@@ -0,0 +1,56 @@
1
+ /**
2
+ *
3
+ * @param {*} stringArray
4
+ * @param {*} jsonData
5
+ * @param {*} jsonData
6
+ * @returns File string with changes
7
+ *
8
+ * This function is used to compare prop name changes for components and if they are required to change in the file
9
+ *
10
+ * This is currently not in use, it will be kept here for future use as can be refactored to work with styled components if the need arises
11
+ */
12
+ export function testComparison(stringArray, jsonData) {
13
+ let fileString = stringArray;
14
+
15
+ const componentsAffected = new Set(
16
+ jsonData.map((entry) => entry.componentsAffected)
17
+ );
18
+
19
+ componentsAffected.forEach((component) => {
20
+ const regex = new RegExp(
21
+ `const .*=.*querySelector[(]([^'"]+?).*${component}.*'`,
22
+ "gm"
23
+ );
24
+ const match = fileString.match(regex);
25
+ if (match !== null) {
26
+ const variableNames = match.map((line) => {
27
+ const split = line.split("=");
28
+ const variableName = split[0].replace("const", "").trim();
29
+ return variableName;
30
+ });
31
+
32
+ const variableNamesArray = Array.from(new Set(variableNames));
33
+ variableNamesArray.forEach((variable) => {
34
+ const regex = new RegExp(`${variable}[.].*?[)]`, "gm");
35
+ const match = fileString.match(regex);
36
+ if (match !== null) {
37
+ match.map((line) => {
38
+ const split = line.split(".");
39
+ const prop = split[1].replace(")", "");
40
+ const newProp = jsonData.find(
41
+ (field) => field.v2Name === prop
42
+ ).v3Name;
43
+
44
+ const newFile = fileString.replaceAll(
45
+ `${variable}.${prop}`,
46
+ `${variable}.${newProp}`
47
+ );
48
+ fileString = newFile;
49
+ });
50
+ }
51
+ });
52
+ }
53
+ });
54
+
55
+ return fileString;
56
+ }