@justeattakeaway/pie-webc 0.3.1 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/README.md +28 -7
  2. package/components/assistive-text.d.ts +1 -0
  3. package/components/assistive-text.js +1 -0
  4. package/components/card.d.ts +1 -0
  5. package/components/card.js +1 -0
  6. package/components/checkbox.d.ts +1 -0
  7. package/components/checkbox.js +1 -0
  8. package/components/chip.d.ts +1 -0
  9. package/components/chip.js +1 -0
  10. package/components/cookie-banner.d.ts +1 -0
  11. package/components/cookie-banner.js +1 -0
  12. package/components/divider.d.ts +1 -0
  13. package/components/divider.js +1 -0
  14. package/components/form-label.d.ts +1 -0
  15. package/components/form-label.js +1 -0
  16. package/components/icon-button.d.ts +1 -0
  17. package/components/icon-button.js +1 -0
  18. package/components/input.d.ts +1 -0
  19. package/components/input.js +1 -0
  20. package/components/link.d.ts +1 -0
  21. package/components/link.js +1 -0
  22. package/components/notification.d.ts +1 -0
  23. package/components/notification.js +1 -0
  24. package/components/spinner.d.ts +1 -0
  25. package/components/spinner.js +1 -0
  26. package/components/switch.d.ts +1 -0
  27. package/components/switch.js +1 -0
  28. package/components/tag.d.ts +1 -0
  29. package/components/tag.js +1 -0
  30. package/package.json +163 -5
  31. package/react/assistive-text.d.ts +1 -0
  32. package/react/assistive-text.js +1 -0
  33. package/react/card.d.ts +1 -0
  34. package/react/card.js +1 -0
  35. package/react/checkbox.d.ts +1 -0
  36. package/react/checkbox.js +1 -0
  37. package/react/chip.d.ts +1 -0
  38. package/react/chip.js +1 -0
  39. package/react/cookie-banner.d.ts +1 -0
  40. package/react/cookie-banner.js +1 -0
  41. package/react/divider.d.ts +1 -0
  42. package/react/divider.js +1 -0
  43. package/react/form-label.d.ts +1 -0
  44. package/react/form-label.js +1 -0
  45. package/react/icon-button.d.ts +1 -0
  46. package/react/icon-button.js +1 -0
  47. package/react/input.d.ts +1 -0
  48. package/react/input.js +1 -0
  49. package/react/link.d.ts +1 -0
  50. package/react/link.js +1 -0
  51. package/react/notification.d.ts +1 -0
  52. package/react/notification.js +1 -0
  53. package/react/spinner.d.ts +1 -0
  54. package/react/spinner.js +1 -0
  55. package/react/switch.d.ts +1 -0
  56. package/react/switch.js +1 -0
  57. package/react/tag.d.ts +1 -0
  58. package/react/tag.js +1 -0
  59. package/src/componentService.js +182 -0
  60. package/src/index.js +33 -0
  61. package/test/componentService.spec.js +309 -0
  62. package/vite.config.js +12 -0
package/README.md CHANGED
@@ -8,17 +8,20 @@
8
8
  </a>
9
9
  </p>
10
10
 
11
+
11
12
  # Table of Contents
12
13
 
13
14
  1. [Introduction](#pie-webc)
14
15
  2. [Installation](#installation)
15
16
  3. [Contributing](#contributing)
16
17
 
18
+
17
19
  ## pie-webc
18
20
 
19
- `pie-webc` is a bundle that contains all PIE web components built using the Lit library.
21
+ `pie-webc` is a wrapper package which contains **all** PIE web components.
22
+
23
+ This means that after installing this package as a dependency, you can use as many PIE web components as you like, without bloating your application with unused code, or slowing it down with unnecessary component registrations in the browser.
20
24
 
21
- This package can be easily integrated into various frontend frameworks and customized through a set of properties.
22
25
 
23
26
  ## Installation
24
27
 
@@ -32,25 +35,43 @@ $ npm i @justeattakeaway/pie-webc
32
35
  $ yarn add @justeattakeaway/pie-webc
33
36
  ```
34
37
 
38
+
35
39
  ## Importing components
36
40
 
37
- Simply import each one individually using its specific entrypoint.
41
+ Simply import each component individually using its specific entrypoint.
38
42
 
39
43
  ```js
40
44
  import '@justeattakeaway/pie-webc/components/button.js';
41
45
  import '@justeattakeaway/pie-webc/components/modal.js';
42
46
  ```
43
47
 
44
- And for React applications:
48
+ Or for React applications:
45
49
 
46
- ```jsx
50
+ ```js
47
51
  import { PieButton } from '@justeattakeaway/pie-webc/react/button.js';
48
52
  import { PieModal } from '@justeattakeaway/pie-webc/react/modal.js';
49
53
  ```
50
54
 
51
-
52
55
  For full information on using PIE components as part of an application, check out the [Getting Started Guide](https://github.com/justeattakeaway/pie/wiki/Getting-started-with-PIE-Web-Components).
53
56
 
57
+
58
+ ## For maintainers
59
+
60
+ There is a command that can be run (from the root of the monorepo) which adds all PIE components to this package:
61
+
62
+ ```npx add-components```
63
+
64
+ This does the following:
65
+ 1. Loops through the (root) `packages/components` folder to find all of the PIE components, ignoring non-component folders, helper packages, and this package itself.
66
+ 2. Adds a `.js` and `.d.ts` file for each component to both the `components` and `react` directories (inside `pie-webc`).
67
+ 3. Adds entries for each component to the `exports` field in `pie-webc/package.json`.
68
+ 4. Adds entries for each component to the `dependencies` field in `pie-webc/package.json`, using the current (latest) version.
69
+
70
+ **Generally, there should be no need to run this script.** The only time it should be run is when a new component is created using the [component generator](../../tools/generator-pie-component/README.md). The generator runs this script automatically after creating a new component.
71
+
72
+ `changeset` should also make sure that the versions of components are up-to-date.
73
+
74
+
54
75
  ## Contributing
55
76
 
56
- Check out our [contributing guide](https://github.com/justeattakeaway/pie/wiki/Contributing-Guide) for more information on [local development](https://github.com/justeattakeaway/pie/wiki/Contributing-Guide#local-development) and how to run specific [component tests](https://github.com/justeattakeaway/pie/wiki/Contributing-Guide#testing).
77
+ Check out our [contributing guide](https://github.com/justeattakeaway/pie/wiki/Contributing-Guide) for more information on [local development](https://github.com/justeattakeaway/pie/wiki/Contributing-Guide#local-development).
@@ -0,0 +1 @@
1
+ export * from '@justeattakeaway/pie-assistive-text';
@@ -0,0 +1 @@
1
+ export * from '@justeattakeaway/pie-assistive-text';
@@ -0,0 +1 @@
1
+ export * from '@justeattakeaway/pie-card';
@@ -0,0 +1 @@
1
+ export * from '@justeattakeaway/pie-card';
@@ -0,0 +1 @@
1
+ export * from '@justeattakeaway/pie-checkbox';
@@ -0,0 +1 @@
1
+ export * from '@justeattakeaway/pie-checkbox';
@@ -0,0 +1 @@
1
+ export * from '@justeattakeaway/pie-chip';
@@ -0,0 +1 @@
1
+ export * from '@justeattakeaway/pie-chip';
@@ -0,0 +1 @@
1
+ export * from '@justeattakeaway/pie-cookie-banner';
@@ -0,0 +1 @@
1
+ export * from '@justeattakeaway/pie-cookie-banner';
@@ -0,0 +1 @@
1
+ export * from '@justeattakeaway/pie-divider';
@@ -0,0 +1 @@
1
+ export * from '@justeattakeaway/pie-divider';
@@ -0,0 +1 @@
1
+ export * from '@justeattakeaway/pie-form-label';
@@ -0,0 +1 @@
1
+ export * from '@justeattakeaway/pie-form-label';
@@ -0,0 +1 @@
1
+ export * from '@justeattakeaway/pie-icon-button';
@@ -0,0 +1 @@
1
+ export * from '@justeattakeaway/pie-icon-button';
@@ -0,0 +1 @@
1
+ export * from '@justeattakeaway/pie-input';
@@ -0,0 +1 @@
1
+ export * from '@justeattakeaway/pie-input';
@@ -0,0 +1 @@
1
+ export * from '@justeattakeaway/pie-link';
@@ -0,0 +1 @@
1
+ export * from '@justeattakeaway/pie-link';
@@ -0,0 +1 @@
1
+ export * from '@justeattakeaway/pie-notification';
@@ -0,0 +1 @@
1
+ export * from '@justeattakeaway/pie-notification';
@@ -0,0 +1 @@
1
+ export * from '@justeattakeaway/pie-spinner';
@@ -0,0 +1 @@
1
+ export * from '@justeattakeaway/pie-spinner';
@@ -0,0 +1 @@
1
+ export * from '@justeattakeaway/pie-switch';
@@ -0,0 +1 @@
1
+ export * from '@justeattakeaway/pie-switch';
@@ -0,0 +1 @@
1
+ export * from '@justeattakeaway/pie-tag';
@@ -0,0 +1 @@
1
+ export * from '@justeattakeaway/pie-tag';
package/package.json CHANGED
@@ -1,13 +1,23 @@
1
1
  {
2
2
  "name": "@justeattakeaway/pie-webc",
3
3
  "description": "Component bundle containing all PIE web components",
4
- "version": "0.3.1",
4
+ "version": "0.4.0",
5
5
  "type": "module",
6
6
  "files": [
7
7
  "**/*.js",
8
8
  "**/*.d.ts"
9
9
  ],
10
10
  "exports": {
11
+ "./components/assistive-text.js": {
12
+ "import": "./components/assistive-text.js",
13
+ "require": "./components/assistive-text.js",
14
+ "types": "./components/assistive-text.d.ts"
15
+ },
16
+ "./react/assistive-text.js": {
17
+ "import": "./react/assistive-text.js",
18
+ "require": "./react/assistive-text.js",
19
+ "types": "./react/assistive-text.d.ts"
20
+ },
11
21
  "./components/button.js": {
12
22
  "import": "./components/button.js",
13
23
  "require": "./components/button.js",
@@ -18,6 +28,96 @@
18
28
  "require": "./react/button.js",
19
29
  "types": "./react/button.d.ts"
20
30
  },
31
+ "./components/card.js": {
32
+ "import": "./components/card.js",
33
+ "require": "./components/card.js",
34
+ "types": "./components/card.d.ts"
35
+ },
36
+ "./react/card.js": {
37
+ "import": "./react/card.js",
38
+ "require": "./react/card.js",
39
+ "types": "./react/card.d.ts"
40
+ },
41
+ "./components/checkbox.js": {
42
+ "import": "./components/checkbox.js",
43
+ "require": "./components/checkbox.js",
44
+ "types": "./components/checkbox.d.ts"
45
+ },
46
+ "./react/checkbox.js": {
47
+ "import": "./react/checkbox.js",
48
+ "require": "./react/checkbox.js",
49
+ "types": "./react/checkbox.d.ts"
50
+ },
51
+ "./components/chip.js": {
52
+ "import": "./components/chip.js",
53
+ "require": "./components/chip.js",
54
+ "types": "./components/chip.d.ts"
55
+ },
56
+ "./react/chip.js": {
57
+ "import": "./react/chip.js",
58
+ "require": "./react/chip.js",
59
+ "types": "./react/chip.d.ts"
60
+ },
61
+ "./components/cookie-banner.js": {
62
+ "import": "./components/cookie-banner.js",
63
+ "require": "./components/cookie-banner.js",
64
+ "types": "./components/cookie-banner.d.ts"
65
+ },
66
+ "./react/cookie-banner.js": {
67
+ "import": "./react/cookie-banner.js",
68
+ "require": "./react/cookie-banner.js",
69
+ "types": "./react/cookie-banner.d.ts"
70
+ },
71
+ "./components/divider.js": {
72
+ "import": "./components/divider.js",
73
+ "require": "./components/divider.js",
74
+ "types": "./components/divider.d.ts"
75
+ },
76
+ "./react/divider.js": {
77
+ "import": "./react/divider.js",
78
+ "require": "./react/divider.js",
79
+ "types": "./react/divider.d.ts"
80
+ },
81
+ "./components/form-label.js": {
82
+ "import": "./components/form-label.js",
83
+ "require": "./components/form-label.js",
84
+ "types": "./components/form-label.d.ts"
85
+ },
86
+ "./react/form-label.js": {
87
+ "import": "./react/form-label.js",
88
+ "require": "./react/form-label.js",
89
+ "types": "./react/form-label.d.ts"
90
+ },
91
+ "./components/icon-button.js": {
92
+ "import": "./components/icon-button.js",
93
+ "require": "./components/icon-button.js",
94
+ "types": "./components/icon-button.d.ts"
95
+ },
96
+ "./react/icon-button.js": {
97
+ "import": "./react/icon-button.js",
98
+ "require": "./react/icon-button.js",
99
+ "types": "./react/icon-button.d.ts"
100
+ },
101
+ "./components/input.js": {
102
+ "import": "./components/input.js",
103
+ "require": "./components/input.js",
104
+ "types": "./components/input.d.ts"
105
+ },
106
+ "./react/input.js": {
107
+ "import": "./react/input.js",
108
+ "require": "./react/input.js",
109
+ "types": "./react/input.d.ts"
110
+ },
111
+ "./components/link.js": {
112
+ "import": "./components/link.js",
113
+ "require": "./components/link.js",
114
+ "types": "./components/link.d.ts"
115
+ },
116
+ "./react/link.js": {
117
+ "import": "./react/link.js",
118
+ "require": "./react/link.js",
119
+ "types": "./react/link.d.ts"
120
+ },
21
121
  "./components/modal.js": {
22
122
  "import": "./components/modal.js",
23
123
  "require": "./components/modal.js",
@@ -27,24 +127,82 @@
27
127
  "import": "./react/modal.js",
28
128
  "require": "./react/modal.js",
29
129
  "types": "./react/modal.d.ts"
130
+ },
131
+ "./components/notification.js": {
132
+ "import": "./components/notification.js",
133
+ "require": "./components/notification.js",
134
+ "types": "./components/notification.d.ts"
135
+ },
136
+ "./react/notification.js": {
137
+ "import": "./react/notification.js",
138
+ "require": "./react/notification.js",
139
+ "types": "./react/notification.d.ts"
140
+ },
141
+ "./components/spinner.js": {
142
+ "import": "./components/spinner.js",
143
+ "require": "./components/spinner.js",
144
+ "types": "./components/spinner.d.ts"
145
+ },
146
+ "./react/spinner.js": {
147
+ "import": "./react/spinner.js",
148
+ "require": "./react/spinner.js",
149
+ "types": "./react/spinner.d.ts"
150
+ },
151
+ "./components/switch.js": {
152
+ "import": "./components/switch.js",
153
+ "require": "./components/switch.js",
154
+ "types": "./components/switch.d.ts"
155
+ },
156
+ "./react/switch.js": {
157
+ "import": "./react/switch.js",
158
+ "require": "./react/switch.js",
159
+ "types": "./react/switch.d.ts"
160
+ },
161
+ "./components/tag.js": {
162
+ "import": "./components/tag.js",
163
+ "require": "./components/tag.js",
164
+ "types": "./components/tag.d.ts"
165
+ },
166
+ "./react/tag.js": {
167
+ "import": "./react/tag.js",
168
+ "require": "./react/tag.js",
169
+ "types": "./react/tag.d.ts"
30
170
  }
31
171
  },
172
+ "bin": {
173
+ "add-components": "./src/index.js"
174
+ },
32
175
  "scripts": {
33
176
  "lint:scripts": "run -T eslint .",
34
177
  "lint:scripts:fix": "yarn lint:scripts --fix",
35
178
  "lint:style": "echo \"Error: no scss / css to lint\" && exit 0",
36
179
  "lint:style:fix": "yarn lint:style --fix",
37
- "test": "echo \"Error: no test specified\" && exit 0",
180
+ "test": "run -T vitest run --config ./vite.config.js",
38
181
  "test:ci": "yarn test"
39
182
  },
40
183
  "author": "Just Eat Takeaway.com - Design System Team",
41
184
  "license": "Apache-2.0",
42
185
  "devDependencies": {
43
- "@justeattakeaway/pie-components-config": "0.16.0"
186
+ "@justeattakeaway/pie-components-config": "0.16.0",
187
+ "chalk": "5.3.0"
44
188
  },
45
189
  "dependencies": {
46
- "@justeattakeaway/pie-button": "0.47.3",
47
- "@justeattakeaway/pie-modal": "0.42.4"
190
+ "@justeattakeaway/pie-assistive-text": "0.3.6",
191
+ "@justeattakeaway/pie-button": "0.47.4",
192
+ "@justeattakeaway/pie-card": "0.19.4",
193
+ "@justeattakeaway/pie-checkbox": "0.2.0",
194
+ "@justeattakeaway/pie-chip": "0.6.2",
195
+ "@justeattakeaway/pie-cookie-banner": "0.19.6",
196
+ "@justeattakeaway/pie-divider": "0.13.4",
197
+ "@justeattakeaway/pie-form-label": "0.13.4",
198
+ "@justeattakeaway/pie-icon-button": "0.28.5",
199
+ "@justeattakeaway/pie-input": "0.19.0",
200
+ "@justeattakeaway/pie-link": "0.17.4",
201
+ "@justeattakeaway/pie-modal": "0.42.5",
202
+ "@justeattakeaway/pie-notification": "0.6.0",
203
+ "@justeattakeaway/pie-spinner": "0.6.4",
204
+ "@justeattakeaway/pie-switch": "0.29.4",
205
+ "@justeattakeaway/pie-tag": "0.9.5"
48
206
  },
49
207
  "volta": {
50
208
  "extends": "../../../package.json"
@@ -0,0 +1 @@
1
+ export * from '@justeattakeaway/pie-assistive-text/dist/react.js';
@@ -0,0 +1 @@
1
+ export * from '@justeattakeaway/pie-assistive-text/dist/react.js';
@@ -0,0 +1 @@
1
+ export * from '@justeattakeaway/pie-card/dist/react.js';
package/react/card.js ADDED
@@ -0,0 +1 @@
1
+ export * from '@justeattakeaway/pie-card/dist/react.js';
@@ -0,0 +1 @@
1
+ export * from '@justeattakeaway/pie-checkbox/dist/react.js';
@@ -0,0 +1 @@
1
+ export * from '@justeattakeaway/pie-checkbox/dist/react.js';
@@ -0,0 +1 @@
1
+ export * from '@justeattakeaway/pie-chip/dist/react.js';
package/react/chip.js ADDED
@@ -0,0 +1 @@
1
+ export * from '@justeattakeaway/pie-chip/dist/react.js';
@@ -0,0 +1 @@
1
+ export * from '@justeattakeaway/pie-cookie-banner/dist/react.js';
@@ -0,0 +1 @@
1
+ export * from '@justeattakeaway/pie-cookie-banner/dist/react.js';
@@ -0,0 +1 @@
1
+ export * from '@justeattakeaway/pie-divider/dist/react.js';
@@ -0,0 +1 @@
1
+ export * from '@justeattakeaway/pie-divider/dist/react.js';
@@ -0,0 +1 @@
1
+ export * from '@justeattakeaway/pie-form-label/dist/react.js';
@@ -0,0 +1 @@
1
+ export * from '@justeattakeaway/pie-form-label/dist/react.js';
@@ -0,0 +1 @@
1
+ export * from '@justeattakeaway/pie-icon-button/dist/react.js';
@@ -0,0 +1 @@
1
+ export * from '@justeattakeaway/pie-icon-button/dist/react.js';
@@ -0,0 +1 @@
1
+ export * from '@justeattakeaway/pie-input/dist/react.js';
package/react/input.js ADDED
@@ -0,0 +1 @@
1
+ export * from '@justeattakeaway/pie-input/dist/react.js';
@@ -0,0 +1 @@
1
+ export * from '@justeattakeaway/pie-link/dist/react.js';
package/react/link.js ADDED
@@ -0,0 +1 @@
1
+ export * from '@justeattakeaway/pie-link/dist/react.js';
@@ -0,0 +1 @@
1
+ export * from '@justeattakeaway/pie-notification/dist/react.js';
@@ -0,0 +1 @@
1
+ export * from '@justeattakeaway/pie-notification/dist/react.js';
@@ -0,0 +1 @@
1
+ export * from '@justeattakeaway/pie-spinner/dist/react.js';
@@ -0,0 +1 @@
1
+ export * from '@justeattakeaway/pie-spinner/dist/react.js';
@@ -0,0 +1 @@
1
+ export * from '@justeattakeaway/pie-switch/dist/react.js';
@@ -0,0 +1 @@
1
+ export * from '@justeattakeaway/pie-switch/dist/react.js';
package/react/tag.d.ts ADDED
@@ -0,0 +1 @@
1
+ export * from '@justeattakeaway/pie-tag/dist/react.js';
package/react/tag.js ADDED
@@ -0,0 +1 @@
1
+ export * from '@justeattakeaway/pie-tag/dist/react.js';
@@ -0,0 +1,182 @@
1
+ import chalk from 'chalk';
2
+
3
+ export class ComponentService {
4
+ constructor (fs, path) {
5
+ this.fs = fs;
6
+ this.path = path;
7
+ }
8
+
9
+ /**
10
+ * Helper function to get some frequently-used paths for the script.
11
+ * @param {string} workingDir - The current working directory.
12
+ * @returns {Object} - An object containing useful paths for the script.
13
+ */
14
+ getPathShortcuts (workingDir) {
15
+ const componentsSourceDir = this.path.resolve(workingDir, 'packages/components');
16
+ const pieWebcDir = this.path.join(componentsSourceDir, 'pie-webc');
17
+ const componentsTargetDir = this.path.join(pieWebcDir, 'components');
18
+ const reactTargetDir = this.path.join(pieWebcDir, 'react');
19
+ const pieWebcPackageJsonPath = this.path.join(pieWebcDir, 'package.json');
20
+
21
+ return {
22
+ componentsSourceDir,
23
+ componentsTargetDir,
24
+ reactTargetDir,
25
+ pieWebcPackageJsonPath,
26
+ };
27
+ }
28
+
29
+ /**
30
+ * Checks if a directory exists and creates it if it doesn't.
31
+ * @param {string} dir - The directory to create if it doesn't exist.
32
+ */
33
+ ensureDirectoryExists (dir) {
34
+ if (!this.fs.existsSync(dir)) {
35
+ this.fs.mkdirSync(dir, { recursive: true });
36
+ }
37
+ }
38
+
39
+ /**
40
+ * Reads and returns the package.json file at the given path,
41
+ * making sure that the `exports` and `dependencies` fields are present.
42
+ * @param {string} packageJsonPath - Path to the package.json file, including the file name.
43
+ * @returns - The prepared package.json object.
44
+ */
45
+ readAndPreparePackageJson (packageJsonPath) {
46
+ const packageJsonData = this.fs.readFileSync(packageJsonPath, 'utf-8');
47
+ const packageJson = JSON.parse(packageJsonData);
48
+ packageJson.exports = packageJson.exports || {};
49
+ packageJson.dependencies = packageJson.dependencies || {};
50
+
51
+ return packageJson;
52
+ }
53
+
54
+ /**
55
+ * Verifies that the script is run from the root of the monorepo
56
+ * and that the package name matches the expected package name.
57
+ * @param {string} workingDir - The working directory from which the script is being run.
58
+ * @param {*} expectedPackageName - The expected package name for the directory the script should be run from.
59
+ */
60
+ verifyRootDirectory (workingDir, expectedPackageName) {
61
+ const packageJsonPath = this.path.join(workingDir, 'package.json');
62
+
63
+ if (!this.fs.existsSync(packageJsonPath)) {
64
+ throw new Error(chalk.redBright('Please run this script from the root of the monorepo.'));
65
+ }
66
+
67
+ const packageJson = JSON.parse(this.fs.readFileSync(packageJsonPath, 'utf8'));
68
+
69
+ if (packageJson.name !== expectedPackageName) {
70
+ throw new Error(chalk.redBright('Incorrect package: Please run this script from the root of the monorepo.'));
71
+ }
72
+ }
73
+
74
+ /**
75
+ * Creates the exports for a component to be added to the pie-webc package.json.
76
+ * @param {string} componentName - The name of the component to create exports for, omitting the `'pie-'` prefix.
77
+ * @returns {Object} - An object containing the exports for the component.
78
+ */
79
+ createPackageJsonExports (componentName) {
80
+ const exports = {
81
+ [`./components/${componentName}.js`]: {
82
+ import: `./components/${componentName}.js`,
83
+ require: `./components/${componentName}.js`,
84
+ types: `./components/${componentName}.d.ts`,
85
+ },
86
+ [`./react/${componentName}.js`]: {
87
+ import: `./react/${componentName}.js`,
88
+ require: `./react/${componentName}.js`,
89
+ types: `./react/${componentName}.d.ts`,
90
+ },
91
+ };
92
+
93
+ return exports;
94
+ }
95
+
96
+ /**
97
+ * Writes a `.js` and `.d.ts` file for the given component to the target directory.
98
+ * @param {*} componentName - The name of the component to write files for, omitting the `'pie-'` prefix.
99
+ * @param {*} target - An object containing the target directory and the export path.
100
+ * @param {*} target.dir - The target directory to write the files to.
101
+ * Either `'components'` or `'react'`.
102
+ * @param {*} target.exportPath - The export path for the component.
103
+ * For react components, this should be the path to the react.js file including the package name, e.g., `'@justeattakeaway/pie-button/dist/react.js'`.
104
+ * Otherwise, this should be the package name, e.g., `'@justeattakeaway/pie-button'`.
105
+ */
106
+ writeFilesForComponent (componentName, target) {
107
+ const jsFilePath = this.path.join(target.dir, `${componentName}.js`);
108
+ const tsFilePath = this.path.join(target.dir, `${componentName}.d.ts`);
109
+
110
+ const fileContent = `export * from '${target.exportPath}';\n`;
111
+ this.fs.writeFileSync(jsFilePath, fileContent);
112
+ this.fs.writeFileSync(tsFilePath, fileContent);
113
+ }
114
+
115
+ /**
116
+ * Writes the package.json file to the given path.
117
+ * @param {string} path - The path to write the package.json file to.
118
+ * @param {Object} content - The content to write to the package.json file.
119
+ */
120
+ writePackageJson (path, content) {
121
+ this.fs.writeFileSync(path, `${JSON.stringify(content, null, 2)}\n`);
122
+ }
123
+
124
+ /**
125
+ * Processes all components in the components directory, adding them to the pie-webc package.
126
+ * @param {*} workingDir - The working directory from which the script is run.
127
+ * @param {*} excludedFolders - An array of folder names to exclude from the processing.
128
+ * By default, any folder starting with 'pie-' will be processed, unless excluded.
129
+ * @param {*} packageJson - The package.json object to update with the new dependencies and exports.
130
+ * @returns - The updated package.json object.
131
+ */
132
+ processComponents (workingDir, excludedFolders, packageJson) {
133
+ const newPackageJson = { ...packageJson };
134
+ const {
135
+ componentsSourceDir, componentsTargetDir, reactTargetDir,
136
+ } = this.getPathShortcuts(workingDir);
137
+
138
+ this.fs.readdirSync(componentsSourceDir).forEach((folder) => {
139
+ if (!folder.startsWith('pie-')) {
140
+ return;
141
+ }
142
+ if (excludedFolders.includes(folder)) {
143
+ console.info(chalk.yellow(`Excluding: ${chalk.white(folder)}`));
144
+ return;
145
+ }
146
+
147
+ const fullFolderPath = this.path.join(componentsSourceDir, folder);
148
+ const componentName = folder.replace('pie-', '');
149
+ const packageName = `@justeattakeaway/${folder}`;
150
+ const componentPackageJsonPath = this.path.join(fullFolderPath, 'package.json');
151
+ const componentPackageJsonData = this.fs.readFileSync(componentPackageJsonPath, 'utf-8');
152
+ const componentPackageJson = JSON.parse(componentPackageJsonData);
153
+
154
+ // Add the component to dependencies
155
+ newPackageJson.dependencies[packageName] = componentPackageJson.version;
156
+
157
+ const targets = [
158
+ {
159
+ dir: componentsTargetDir,
160
+ exportPath: packageName,
161
+ },
162
+ {
163
+ dir: reactTargetDir,
164
+ exportPath: `${packageName}/dist/react.js`,
165
+ }
166
+ ];
167
+
168
+ console.info(chalk.gray(`Adding: ${chalk.white(folder)}`));
169
+
170
+ targets.forEach((target) => {
171
+ this.writeFilesForComponent(componentName, target);
172
+ });
173
+
174
+ newPackageJson.exports = {
175
+ ...newPackageJson.exports,
176
+ ...this.createPackageJsonExports(componentName),
177
+ };
178
+ });
179
+
180
+ return newPackageJson;
181
+ }
182
+ }
package/src/index.js ADDED
@@ -0,0 +1,33 @@
1
+ #!/usr/bin/env node
2
+ import fs from 'fs';
3
+ import path from 'path';
4
+ import chalk from 'chalk';
5
+
6
+ import { ComponentService } from './componentService.js';
7
+
8
+ /**
9
+ * The main entry point of the script which adds all components to the pie-webc package.
10
+ * @param {*} fs - Node.js file system module
11
+ * @param {*} path - Node.js path module
12
+ */
13
+ const addComponents = (fs, path) => {
14
+ const workingDir = process.cwd();
15
+ const componentService = new ComponentService(fs, path);
16
+ componentService.verifyRootDirectory(workingDir, 'pie-monorepo');
17
+
18
+ const { componentsTargetDir, reactTargetDir, pieWebcPackageJsonPath } = componentService.getPathShortcuts(workingDir);
19
+
20
+ componentService.ensureDirectoryExists(componentsTargetDir);
21
+ componentService.ensureDirectoryExists(reactTargetDir);
22
+
23
+ const pieWebcPackageJson = componentService.readAndPreparePackageJson(pieWebcPackageJsonPath);
24
+
25
+ const excludedFolders = ['pie-webc', 'pie-webc-core', 'pie-webc-testing'];
26
+ const updatedPackageJson = componentService.processComponents(workingDir, excludedFolders, pieWebcPackageJson);
27
+
28
+ componentService.writePackageJson(pieWebcPackageJsonPath, updatedPackageJson);
29
+ console.info(chalk.green('\nAll components added to pie-webc!'));
30
+ };
31
+
32
+ // Run the script
33
+ addComponents(fs, path);
@@ -0,0 +1,309 @@
1
+ import { ComponentService } from '../src/componentService';
2
+
3
+ describe('ComponentService', () => {
4
+ let fsMock;
5
+ let pathMock;
6
+ let componentService;
7
+
8
+ beforeEach(() => {
9
+ fsMock = {
10
+ existsSync: vi.fn(),
11
+ mkdirSync: vi.fn(),
12
+ readdirSync: vi.fn(),
13
+ readFileSync: vi.fn(),
14
+ writeFileSync: vi.fn(),
15
+ };
16
+
17
+ pathMock = {
18
+ join: vi.fn(),
19
+ resolve: vi.fn(),
20
+ };
21
+
22
+ pathMock.join.mockImplementation((...args) => args.join('/'));
23
+ pathMock.resolve.mockImplementation((...args) => args.join('/'));
24
+
25
+ // Suppress console output
26
+ vi.spyOn(console, 'info').mockImplementation(() => {}); // eslint-disable-line @typescript-eslint/no-empty-function
27
+ });
28
+
29
+ describe('ensureDirectoryExists', () => {
30
+ it('should create a directory if it does not exist', () => {
31
+ // Arrange
32
+ fsMock.existsSync.mockReturnValue(false);
33
+ componentService = new ComponentService(fsMock, pathMock);
34
+
35
+ // Act
36
+ componentService.ensureDirectoryExists('dir');
37
+
38
+ // Assert
39
+ expect(fsMock.mkdirSync).toHaveBeenCalledWith('dir', { recursive: true });
40
+ });
41
+
42
+ it('should not create a directory if it already exists', () => {
43
+ // Arrange
44
+ fsMock.existsSync.mockReturnValue(true);
45
+ componentService = new ComponentService(fsMock, pathMock);
46
+
47
+ // Act
48
+ componentService.ensureDirectoryExists('dir');
49
+
50
+ // Assert
51
+ expect(fsMock.mkdirSync).not.toHaveBeenCalled();
52
+ });
53
+ });
54
+
55
+ describe('readAndPreparePackageJson', () => {
56
+ it('should return the packageJson with empty exports and dependencies objects if they do not exist', () => {
57
+ // Arrange
58
+ fsMock.readFileSync.mockReturnValue('{}');
59
+ componentService = new ComponentService(fsMock, pathMock);
60
+
61
+ // Act
62
+ const result = componentService.readAndPreparePackageJson('packageJsonPath');
63
+
64
+ // Assert
65
+ expect(result.exports).toEqual({});
66
+ expect(result.dependencies).toEqual({});
67
+ });
68
+
69
+ it('should return the packageJson with existing exports and dependencies objects if they exist', () => {
70
+ // Arrange
71
+ fsMock.readFileSync.mockReturnValue('{"exports": {"exportKey": "exportValue"}, "dependencies": {"depKey": "depValue"}}');
72
+ componentService = new ComponentService(fsMock, pathMock);
73
+
74
+ // Act
75
+ const result = componentService.readAndPreparePackageJson('/path/to/package.json');
76
+
77
+ // Assert
78
+ expect(result.exports).toEqual({ exportKey: 'exportValue' });
79
+ expect(result.dependencies).toEqual({ depKey: 'depValue' });
80
+ });
81
+ });
82
+
83
+ describe('verifyRootDirectory', () => {
84
+ beforeEach(() => {
85
+ pathMock.join.mockReturnValue('');
86
+ });
87
+
88
+ it('should throw an error if there is no package.json file in the working directory', () => {
89
+ // Arrange
90
+ fsMock.existsSync.mockReturnValue(false);
91
+ componentService = new ComponentService(fsMock, pathMock);
92
+
93
+ // Act & Assert
94
+ const testFn = () => componentService.verifyRootDirectory('workingDir', 'expectedPackageName');
95
+ expect(testFn).toThrowError('Please run this script from the root of the monorepo.');
96
+ });
97
+
98
+ it('should throw an error if the package.json does not match the expected name', () => {
99
+ // Arrange
100
+ fsMock.existsSync.mockReturnValue(true);
101
+ fsMock.readFileSync.mockReturnValue('{"name": "wrongPackageName"}');
102
+ componentService = new ComponentService(fsMock, pathMock);
103
+
104
+ // Act & Assert
105
+ const testFn = () => componentService.verifyRootDirectory('workingDir', 'expectedPackageName');
106
+ expect(testFn).toThrowError('Incorrect package: Please run this script from the root of the monorepo.');
107
+ });
108
+
109
+ it('should not throw any errors if the package.json name matches the expected name', () => {
110
+ // Arrange
111
+ fsMock.existsSync.mockReturnValue(true);
112
+ fsMock.readFileSync.mockReturnValue('{"name": "expectedPackageName"}');
113
+ componentService = new ComponentService(fsMock, pathMock);
114
+
115
+ // Act & Assert
116
+ const testFn = () => componentService.verifyRootDirectory('workingDir', 'expectedPackageName');
117
+ expect(testFn).not.toThrow();
118
+ });
119
+ });
120
+
121
+ describe('createPackageJsonExports', () => {
122
+ it('should return an object with exports for the component', () => {
123
+ // Arrange
124
+ componentService = new ComponentService(fsMock, pathMock);
125
+
126
+ // Act
127
+ const result = componentService.createPackageJsonExports('component-name');
128
+
129
+ // Assert
130
+ expect(result).toEqual({
131
+ './components/component-name.js': {
132
+ import: './components/component-name.js',
133
+ require: './components/component-name.js',
134
+ types: './components/component-name.d.ts',
135
+ },
136
+ './react/component-name.js': {
137
+ import: './react/component-name.js',
138
+ require: './react/component-name.js',
139
+ types: './react/component-name.d.ts',
140
+ },
141
+ });
142
+ });
143
+ });
144
+
145
+ describe('writeFilesForComponent', () => {
146
+ it('should write the component files to the target directory', () => {
147
+ // Arrange
148
+ componentService = new ComponentService(fsMock, pathMock);
149
+ const target = {
150
+ dir: 'componentsTargetDir',
151
+ exportPath: 'packageName',
152
+ };
153
+
154
+ // Act
155
+ componentService.writeFilesForComponent('component-name', target);
156
+
157
+ // Assert
158
+ expect(fsMock.writeFileSync).toHaveBeenCalledWith('componentsTargetDir/component-name.js', "export * from 'packageName';\n");
159
+ expect(fsMock.writeFileSync).toHaveBeenCalledWith('componentsTargetDir/component-name.d.ts', "export * from 'packageName';\n");
160
+ });
161
+ });
162
+
163
+ describe('writePackageJson', () => {
164
+ it('should write the stringified content to the specified path', () => {
165
+ // Arrange
166
+ componentService = new ComponentService(fsMock, pathMock);
167
+ const packageJson = { name: 'package-name', version: '1.0.0' };
168
+
169
+ // Act
170
+ componentService.writePackageJson('path/to/package.json', packageJson);
171
+
172
+ // Assert
173
+ expect(fsMock.writeFileSync).toHaveBeenCalledWith('path/to/package.json', '{\n "name": "package-name",\n "version": "1.0.0"\n}\n');
174
+ });
175
+ });
176
+
177
+ describe('processComponents', () => {
178
+ let excludedFolders;
179
+ let workingDir;
180
+
181
+ beforeEach(() => {
182
+ excludedFolders = [];
183
+ workingDir = 'workingDir';
184
+
185
+ fsMock.readFileSync.mockReturnValue('{"version": "1.1.1"}'); // Component package.json
186
+ fsMock.readdirSync.mockReturnValue(['pie-component-name']); // Default, overridden by some tests
187
+ });
188
+
189
+ it('should ignore non-component folders', () => {
190
+ // Arrange
191
+ fsMock.readdirSync.mockReturnValue(['not-a-component']);
192
+ componentService = new ComponentService(fsMock, pathMock);
193
+
194
+ // Act
195
+ componentService.processComponents(workingDir, excludedFolders, {});
196
+
197
+ // Assert
198
+ expect(fsMock.writeFileSync).not.toHaveBeenCalled();
199
+ });
200
+
201
+ it('should ignore excluded folders', () => {
202
+ // Arrange
203
+ fsMock.readdirSync.mockReturnValue(['pie-excluded']);
204
+ componentService = new ComponentService(fsMock, pathMock);
205
+
206
+ excludedFolders = ['pie-excluded'];
207
+
208
+ // Act
209
+ componentService.processComponents(workingDir, excludedFolders, {});
210
+
211
+ // Assert
212
+ expect(fsMock.writeFileSync).not.toHaveBeenCalled();
213
+ });
214
+
215
+ it('should add components as dependencies to the package.json', () => {
216
+ // Arrange
217
+ componentService = new ComponentService(fsMock, pathMock);
218
+ const spy = vi.spyOn(componentService, 'writeFilesForComponent');
219
+
220
+ // Act
221
+ componentService.processComponents(workingDir, excludedFolders, { dependencies: {} });
222
+
223
+ // Assert
224
+ expect(spy).toHaveBeenCalledWith('component-name', {
225
+ dir: 'workingDir/packages/components/pie-webc/components',
226
+ exportPath: '@justeattakeaway/pie-component-name',
227
+ });
228
+
229
+ expect(spy).toHaveBeenCalledWith('component-name', {
230
+ dir: 'workingDir/packages/components/pie-webc/react',
231
+ exportPath: '@justeattakeaway/pie-component-name/dist/react.js',
232
+ });
233
+ });
234
+
235
+ it('should add exports for each component to the package.json', () => {
236
+ // Arrange
237
+ componentService = new ComponentService(fsMock, pathMock);
238
+ const rootPackageJson = { dependencies: {} };
239
+
240
+ // Act
241
+ const { exports } = componentService.processComponents(workingDir, excludedFolders, rootPackageJson);
242
+
243
+ // Assert
244
+ expect(Object.keys(exports)).toHaveLength(2);
245
+ expect(Object.keys(exports)).toContain('./components/component-name.js');
246
+ expect(Object.keys(exports)).toContain('./react/component-name.js');
247
+ // Content of the exports object is tested in createPackageJsonExports
248
+ });
249
+
250
+ it('should preserve existing exports in the package.json', () => {
251
+ // Arrange
252
+ componentService = new ComponentService(fsMock, pathMock);
253
+ const rootPackageJson = {
254
+ dependencies: {},
255
+ exports: {
256
+ './components/existing-component.js': {},
257
+ './react/existing-component.js': {},
258
+ },
259
+ };
260
+
261
+ // Act
262
+ const { exports } = componentService.processComponents(workingDir, excludedFolders, rootPackageJson);
263
+
264
+ // Assert
265
+ expect(Object.keys(exports)).toContain('./components/existing-component.js');
266
+ expect(Object.keys(exports)).toContain('./react/existing-component.js');
267
+ expect(Object.keys(exports)).toHaveLength(4);
268
+ });
269
+
270
+ it('should override existing exports in the package.json', () => {
271
+ // Arrange
272
+ componentService = new ComponentService(fsMock, pathMock);
273
+ const rootPackageJson = {
274
+ dependencies: {},
275
+ exports: {
276
+ './components/component-name.js': {},
277
+ './react/component-name.js': {},
278
+ },
279
+ };
280
+
281
+ // Act
282
+ const { exports } = componentService.processComponents(workingDir, excludedFolders, rootPackageJson);
283
+
284
+ // Assert
285
+ expect(Object.keys(exports)).toContain('./components/component-name.js');
286
+ expect(Object.keys(exports)).toContain('./react/component-name.js');
287
+ expect(Object.keys(exports)).toHaveLength(2);
288
+ });
289
+
290
+ it('should preserve existing dependencies in the package.json', () => {
291
+ // Arrange
292
+ fsMock.readdirSync.mockReturnValue(['pie-new-component']);
293
+
294
+ componentService = new ComponentService(fsMock, pathMock);
295
+ const rootPackageJson = {
296
+ dependencies: { '@justeattakeaway/pie-existing-component': '1.0.0' },
297
+ };
298
+
299
+ // Act
300
+ const { dependencies } = componentService.processComponents(workingDir, excludedFolders, rootPackageJson);
301
+
302
+ // Assert
303
+ expect(dependencies).toEqual({
304
+ '@justeattakeaway/pie-existing-component': '1.0.0',
305
+ '@justeattakeaway/pie-new-component': '1.1.1',
306
+ });
307
+ });
308
+ });
309
+ });
package/vite.config.js ADDED
@@ -0,0 +1,12 @@
1
+ import { defineConfig } from 'vitest/config';
2
+
3
+ export default defineConfig({
4
+ test: {
5
+ dir: '.',
6
+ environment: 'node',
7
+ globals: true,
8
+ exclude: [
9
+ '**/node_modules/**/*'
10
+ ],
11
+ },
12
+ });