@emulsify/core 4.2.1 → 4.3.1
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/.storybook/main-vite.js +102 -37
- package/.storybook/main.js +20 -0
- package/.storybook/ready-reporter.js +230 -0
- package/README.md +186 -56
- package/config/a11y.config.js +2 -1
- package/config/vite/entries.js +4 -4
- package/config/vite/plugins/assets/copy-src-assets.js +120 -0
- package/config/vite/plugins/assets/copy-twig-files.js +130 -0
- package/config/vite/plugins/{css-asset-relativizer.js → assets/css-asset-relativizer.js} +9 -0
- package/config/vite/plugins/{mirror-components.js → assets/mirror-components.js} +2 -28
- package/config/vite/plugins/{source-file-index.js → assets/source-file-index.js} +1 -1
- package/config/vite/plugins/{svg-sprite.js → assets/svg-sprite.js} +2 -2
- package/config/vite/plugins/index.js +23 -14
- package/config/vite/plugins/reporter/asset-resolver.js +541 -0
- package/config/vite/plugins/reporter/build-errors.js +284 -0
- package/config/vite/plugins/reporter/diagnostics.js +367 -0
- package/config/vite/plugins/reporter/format.js +240 -0
- package/config/vite/plugins/reporter/index.js +417 -0
- package/config/vite/plugins/reporter/render.js +1332 -0
- package/config/vite/plugins/reporter/sass-logger.js +237 -0
- package/config/vite/plugins/reporter/source-roots.js +561 -0
- package/config/vite/plugins/reporter/verbosity.js +119 -0
- package/config/vite/plugins/reporter/vite-logger.js +249 -0
- package/config/vite/plugins/reporter/watch-mode.js +40 -0
- package/config/vite/plugins/{twig-extension-installers.js → twig/extension-installers.js} +1 -1
- package/config/vite/{twig-extensions.js → plugins/twig/extensions.js} +2 -2
- package/config/vite/plugins/{twig-module.js → twig/twig-module.js} +197 -85
- package/config/vite/plugins/{virtual-twig-asset-sources.js → twig/virtual-twig-asset-sources.js} +37 -136
- package/config/vite/plugins/{virtual-twig-globs.js → twig/virtual-twig-globs.js} +3 -32
- package/config/vite/plugins/{vituum-patch.js → twig/vituum-patch.js} +3 -3
- package/config/vite/plugins.js +1 -1
- package/config/vite/project-config.js +1 -1
- package/config/vite/project-structure.js +1 -1
- package/config/vite/utils/lru.js +77 -0
- package/config/vite/utils/package-version.js +42 -0
- package/config/vite/utils/paths.js +1 -9
- package/config/vite/utils/react-singleton.js +1 -1
- package/config/vite/vite.config.js +91 -5
- package/package.json +95 -57
- package/scripts/a11y.js +115 -23
- package/scripts/audit/checks/core-imports.js +78 -0
- package/scripts/audit/checks/css-asset-references.js +99 -0
- package/scripts/audit/checks/drupal-assumptions.js +48 -0
- package/scripts/audit/checks/files-outside-roots.js +53 -0
- package/scripts/audit/checks/generated-package-scripts.js +113 -0
- package/scripts/audit/checks/legacy-twig-stories.js +33 -0
- package/scripts/audit/checks/package-overrides.js +91 -0
- package/scripts/audit/checks/project-config.js +71 -0
- package/scripts/audit/checks/story-discovery.js +35 -0
- package/scripts/audit/checks/twig-references.js +69 -0
- package/scripts/audit/checks/twig-volume.js +54 -0
- package/scripts/audit/checks/webpack-patterns.js +86 -0
- package/scripts/audit/index.js +177 -0
- package/scripts/audit/lib/css.js +165 -0
- package/scripts/audit/lib/files.js +168 -0
- package/scripts/audit/lib/findings.js +31 -0
- package/scripts/audit/lib/package-json.js +65 -0
- package/scripts/audit/lib/twig.js +227 -0
- package/scripts/audit/report.js +273 -0
- package/scripts/audit-twig-stories.js +115 -78
- package/scripts/audit.js +150 -1632
- package/scripts/check-node-version.js +136 -10
- package/scripts/inspect-components.js +456 -0
- package/scripts/lib/cli.js +179 -0
- package/scripts/lib/fs.js +31 -0
- package/scripts/lib/proc.js +78 -0
- package/scripts/lib/text.js +14 -0
- package/scripts/loadYaml.js +2 -2
- package/src/extensions/shared/attributes.js +3 -3
- package/src/extensions/shared/lists.js +2 -6
- package/src/extensions/shared/root-relative.js +38 -0
- package/src/storybook/index.js +4 -0
- package/src/storybook/render-twig.js +1 -1
- package/src/storybook/render-web-component.js +459 -0
- package/src/storybook/twig/asset-source-runtime.js +193 -0
- package/src/storybook/twig/{source-extensions.js → constants.js} +3 -1
- package/src/storybook/twig/reference-paths.js +2 -13
- package/src/storybook/twig/setup.js +12 -2
- package/src/storybook/twig/source-function.js +5 -2
- package/config/vite/plugins/copy-src-assets.js +0 -76
- package/config/vite/plugins/copy-twig-files.js +0 -84
- package/config/vite/utils/unique.js +0 -36
- package/src/storybook/twig/include.js +0 -28
- package/src/storybook/twig/source-events.js +0 -5
package/package.json
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@emulsify/core",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.3.1",
|
|
4
4
|
"description": "Bundled tooling for Storybook development + Vite Build",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"component library",
|
|
7
7
|
"craftcms",
|
|
8
|
+
"custom elements",
|
|
8
9
|
"design system",
|
|
9
10
|
"drupal",
|
|
10
11
|
"pattern library",
|
|
@@ -12,17 +13,19 @@
|
|
|
12
13
|
"styleguide",
|
|
13
14
|
"twig",
|
|
14
15
|
"vite",
|
|
16
|
+
"web components",
|
|
15
17
|
"wordpress"
|
|
16
18
|
],
|
|
17
19
|
"author": "Four Kitchens <shout@fourkitchens.com>",
|
|
18
20
|
"license": "GPL-2.0",
|
|
19
21
|
"engines": {
|
|
20
|
-
"node": ">=24"
|
|
22
|
+
"node": ">=24.13.0"
|
|
21
23
|
},
|
|
22
24
|
"type": "module",
|
|
23
25
|
"bin": {
|
|
24
26
|
"emulsify-audit": "./scripts/audit.js",
|
|
25
|
-
"emulsify-audit-twig-stories": "./scripts/audit-twig-stories.js"
|
|
27
|
+
"emulsify-audit-twig-stories": "./scripts/audit-twig-stories.js",
|
|
28
|
+
"emulsify-inspect-components": "./scripts/inspect-components.js"
|
|
26
29
|
},
|
|
27
30
|
"repository": {
|
|
28
31
|
"type": "git",
|
|
@@ -44,6 +47,7 @@
|
|
|
44
47
|
".storybook/manager-head.css",
|
|
45
48
|
".storybook/manager.js",
|
|
46
49
|
".storybook/preview.js",
|
|
50
|
+
".storybook/ready-reporter.js",
|
|
47
51
|
".storybook/utils.js",
|
|
48
52
|
"assets/**/*",
|
|
49
53
|
"config/.prettierrc.json",
|
|
@@ -55,34 +59,55 @@
|
|
|
55
59
|
"config/vite/entries.js",
|
|
56
60
|
"config/vite/environment.js",
|
|
57
61
|
"config/vite/platforms.js",
|
|
58
|
-
"config/vite/plugins/copy-src-assets.js",
|
|
59
|
-
"config/vite/plugins/copy-twig-files.js",
|
|
60
|
-
"config/vite/plugins/css-asset-relativizer.js",
|
|
62
|
+
"config/vite/plugins/assets/copy-src-assets.js",
|
|
63
|
+
"config/vite/plugins/assets/copy-twig-files.js",
|
|
64
|
+
"config/vite/plugins/assets/css-asset-relativizer.js",
|
|
65
|
+
"config/vite/plugins/assets/mirror-components.js",
|
|
66
|
+
"config/vite/plugins/assets/source-file-index.js",
|
|
67
|
+
"config/vite/plugins/assets/svg-sprite.js",
|
|
61
68
|
"config/vite/plugins/index.js",
|
|
62
|
-
"config/vite/plugins/
|
|
69
|
+
"config/vite/plugins/reporter/asset-resolver.js",
|
|
70
|
+
"config/vite/plugins/reporter/build-errors.js",
|
|
71
|
+
"config/vite/plugins/reporter/diagnostics.js",
|
|
72
|
+
"config/vite/plugins/reporter/format.js",
|
|
73
|
+
"config/vite/plugins/reporter/index.js",
|
|
74
|
+
"config/vite/plugins/reporter/render.js",
|
|
75
|
+
"config/vite/plugins/reporter/sass-logger.js",
|
|
76
|
+
"config/vite/plugins/reporter/source-roots.js",
|
|
77
|
+
"config/vite/plugins/reporter/verbosity.js",
|
|
78
|
+
"config/vite/plugins/reporter/vite-logger.js",
|
|
79
|
+
"config/vite/plugins/reporter/watch-mode.js",
|
|
63
80
|
"config/vite/plugins/require-context.js",
|
|
64
|
-
"config/vite/plugins/
|
|
65
|
-
"config/vite/plugins/
|
|
66
|
-
"config/vite/plugins/twig-
|
|
67
|
-
"config/vite/plugins/twig-
|
|
68
|
-
"config/vite/plugins/virtual-twig-
|
|
69
|
-
"config/vite/plugins/
|
|
70
|
-
"config/vite/plugins/vituum-patch.js",
|
|
81
|
+
"config/vite/plugins/twig/extension-installers.js",
|
|
82
|
+
"config/vite/plugins/twig/extensions.js",
|
|
83
|
+
"config/vite/plugins/twig/twig-module.js",
|
|
84
|
+
"config/vite/plugins/twig/virtual-twig-asset-sources.js",
|
|
85
|
+
"config/vite/plugins/twig/virtual-twig-globs.js",
|
|
86
|
+
"config/vite/plugins/twig/vituum-patch.js",
|
|
71
87
|
"config/vite/plugins/yaml-module.js",
|
|
72
88
|
"config/vite/plugins.js",
|
|
73
89
|
"config/vite/project-config.js",
|
|
74
90
|
"config/vite/project-extensions.js",
|
|
75
91
|
"config/vite/project-structure.js",
|
|
76
|
-
"config/vite/twig-extensions.js",
|
|
77
92
|
"config/vite/utils/fs-safe.js",
|
|
93
|
+
"config/vite/utils/lru.js",
|
|
94
|
+
"config/vite/utils/package-version.js",
|
|
78
95
|
"config/vite/utils/paths.js",
|
|
79
96
|
"config/vite/utils/react-singleton.js",
|
|
80
|
-
"config/vite/utils/unique.js",
|
|
81
97
|
"config/vite/vite.config.js",
|
|
82
98
|
"scripts/a11y.js",
|
|
83
99
|
"scripts/audit.js",
|
|
100
|
+
"scripts/audit/**/*.js",
|
|
101
|
+
"!scripts/audit/**/*.test.js",
|
|
102
|
+
"!scripts/audit/**/__snapshots__/**",
|
|
103
|
+
"!scripts/audit/test-utils.js",
|
|
84
104
|
"scripts/audit-twig-stories.js",
|
|
85
105
|
"scripts/check-node-version.js",
|
|
106
|
+
"scripts/inspect-components.js",
|
|
107
|
+
"scripts/lib/cli.js",
|
|
108
|
+
"scripts/lib/fs.js",
|
|
109
|
+
"scripts/lib/proc.js",
|
|
110
|
+
"scripts/lib/text.js",
|
|
86
111
|
"scripts/loadYaml.js",
|
|
87
112
|
"src/extensions/index.js",
|
|
88
113
|
"src/extensions/react/index.js",
|
|
@@ -91,6 +116,7 @@
|
|
|
91
116
|
"src/extensions/shared/html.js",
|
|
92
117
|
"src/extensions/shared/lists.js",
|
|
93
118
|
"src/extensions/shared/object.js",
|
|
119
|
+
"src/extensions/shared/root-relative.js",
|
|
94
120
|
"src/extensions/twig/function-map.js",
|
|
95
121
|
"src/extensions/twig/functions/add-attributes.js",
|
|
96
122
|
"src/extensions/twig/functions/bem.js",
|
|
@@ -104,14 +130,14 @@
|
|
|
104
130
|
"src/storybook/preview-decorator.js",
|
|
105
131
|
"src/storybook/preview-parameters.js",
|
|
106
132
|
"src/storybook/render-twig.js",
|
|
107
|
-
"src/storybook/
|
|
108
|
-
"src/storybook/twig/
|
|
133
|
+
"src/storybook/render-web-component.js",
|
|
134
|
+
"src/storybook/twig/asset-source-runtime.js",
|
|
135
|
+
"src/storybook/twig/constants.js",
|
|
109
136
|
"src/storybook/twig/drupal-filters.js",
|
|
137
|
+
"src/storybook/twig/include-function.js",
|
|
110
138
|
"src/storybook/twig/reference-paths.js",
|
|
111
139
|
"src/storybook/twig/resolver.js",
|
|
112
140
|
"src/storybook/twig/setup.js",
|
|
113
|
-
"src/storybook/twig/source-events.js",
|
|
114
|
-
"src/storybook/twig/source-extensions.js",
|
|
115
141
|
"src/storybook/twig/source-function.js",
|
|
116
142
|
"src/storybook/twig/source.js"
|
|
117
143
|
],
|
|
@@ -142,17 +168,23 @@
|
|
|
142
168
|
"audit:twig-stories": "npm run check-node-version && node scripts/audit-twig-stories.js",
|
|
143
169
|
"coverage": "npm run check-node-version && npm run test && open-cli .coverage/lcov-report/index.html",
|
|
144
170
|
"format": "npm run check-node-version && npm run lint-fix && npm run prettier-fix",
|
|
171
|
+
"fixtures:consumer": "npm run check-node-version && node scripts/consumer-fixtures.js",
|
|
172
|
+
"fixtures:consumer:list": "npm run check-node-version && node scripts/consumer-fixtures.js --list",
|
|
145
173
|
"fixtures:release": "npm run check-node-version && node scripts/release-fixtures.js",
|
|
146
174
|
"fixtures:release:list": "npm run check-node-version && node scripts/release-fixtures.js --list",
|
|
147
175
|
"husky:commit-msg": "npm run check-node-version && commitlint --edit $1",
|
|
148
176
|
"husky:pre-commit": "npm run check-node-version && npm run lint",
|
|
177
|
+
"inspect:components": "npm run check-node-version && node scripts/inspect-components.js",
|
|
149
178
|
"lint": "npm run check-node-version && npm run lint-js && npm run prettier",
|
|
150
179
|
"lint-fix": "npm run check-node-version && npm run lint-js -- --fix",
|
|
151
180
|
"lint-js": "npm run check-node-version && eslint --config config/eslint.config.js ./.cli ./.storybook ./config ./scripts ./src",
|
|
152
181
|
"lint-staged": "npm run check-node-version && lint-staged",
|
|
182
|
+
"pack:dry-run": "npm run check-node-version && npm pack --dry-run --ignore-scripts --json",
|
|
153
183
|
"prepare": "npm run check-node-version && husky",
|
|
154
184
|
"prettier": "npm run check-node-version && prettier --check --config config/.prettierrc.json --ignore-unknown \"**/*.{js,mjs,cjs,jsx,json,yml,yaml,scss,md,twig}\"",
|
|
155
185
|
"prettier-fix": "npm run check-node-version && prettier --config config/.prettierrc.json --write --ignore-unknown \"**/*.{js,mjs,cjs,jsx,json,yml,yaml,scss,md,twig}\"",
|
|
186
|
+
"release:analyze": "npm run check-node-version && node scripts/verify-release-analysis.js",
|
|
187
|
+
"release:verify": "npm run check-node-version && npm run lint && npm test && npm run storybook-build && npm run fixtures:release && npm run fixtures:consumer && npm run pack:dry-run && npm run smoke:pack && npm run release:analyze",
|
|
156
188
|
"semantic-release": "npm run check-node-version && semantic-release --config ./release.config.cjs",
|
|
157
189
|
"smoke:pack": "npm run check-node-version && node scripts/smoke-pack.js",
|
|
158
190
|
"version:develop": "npm run check-node-version && node scripts/bump-version-from-commits.js",
|
|
@@ -164,64 +196,62 @@
|
|
|
164
196
|
},
|
|
165
197
|
"dependencies": {
|
|
166
198
|
"@babel/core": "^7.29.7",
|
|
167
|
-
"@babel/eslint-parser": "^7.
|
|
168
|
-
"@babel/preset-env": "^7.
|
|
199
|
+
"@babel/eslint-parser": "^7.29.7",
|
|
200
|
+
"@babel/preset-env": "^7.29.7",
|
|
169
201
|
"@emulsify/cli": "^2.2.0",
|
|
170
|
-
"@eslint/js": "^9.39.
|
|
171
|
-
"@
|
|
172
|
-
"@storybook/addon-
|
|
173
|
-
"@storybook/addon-
|
|
174
|
-
"@storybook/
|
|
175
|
-
"@storybook/react
|
|
176
|
-
"@
|
|
177
|
-
"
|
|
178
|
-
"
|
|
202
|
+
"@eslint/js": "^9.39.5",
|
|
203
|
+
"@mlnop/vite-plugin-sass-glob-import": "^6.2.0",
|
|
204
|
+
"@storybook/addon-a11y": "^10.5.4",
|
|
205
|
+
"@storybook/addon-links": "^10.5.4",
|
|
206
|
+
"@storybook/addon-themes": "^10.5.4",
|
|
207
|
+
"@storybook/react": "^10.5.4",
|
|
208
|
+
"@storybook/react-vite": "^10.5.4",
|
|
209
|
+
"@vituum/vite-plugin-twig": "^2.0.1",
|
|
210
|
+
"autoprefixer": "^10.5.4",
|
|
211
|
+
"axe-core": "^4.12.1",
|
|
179
212
|
"babel-preset-minify": "^0.5.2",
|
|
180
|
-
"concurrently": "^
|
|
181
|
-
"eslint": "^9.39.
|
|
213
|
+
"concurrently": "^10.0.4",
|
|
214
|
+
"eslint": "^9.39.5",
|
|
182
215
|
"eslint-config-prettier": "^10.1.8",
|
|
183
216
|
"eslint-plugin-import": "^2.32.0",
|
|
184
|
-
"eslint-plugin-jest": "^29.
|
|
185
|
-
"eslint-plugin-prettier": "^5.5.
|
|
217
|
+
"eslint-plugin-jest": "^29.16.0",
|
|
218
|
+
"eslint-plugin-prettier": "^5.5.6",
|
|
186
219
|
"eslint-plugin-security": "^4.0.1",
|
|
187
|
-
"eslint-plugin-storybook": "^10.4
|
|
220
|
+
"eslint-plugin-storybook": "^10.5.4",
|
|
188
221
|
"glob": "^13.0.6",
|
|
189
|
-
"jest": "^30.2
|
|
190
|
-
"jest-environment-jsdom": "^30.
|
|
191
|
-
"js-yaml": "^
|
|
222
|
+
"jest": "^30.4.2",
|
|
223
|
+
"jest-environment-jsdom": "^30.4.1",
|
|
224
|
+
"js-yaml": "^5.2.2",
|
|
192
225
|
"normalize.css": "^8.0.1",
|
|
193
226
|
"open-cli": "^9.0.0",
|
|
194
|
-
"pa11y": "^9.
|
|
195
|
-
"postcss": "^8.5.
|
|
227
|
+
"pa11y": "^9.1.1",
|
|
228
|
+
"postcss": "^8.5.23",
|
|
196
229
|
"postcss-scss": "^4.0.9",
|
|
197
|
-
"sass": "^1.
|
|
198
|
-
"storybook": "^10.
|
|
199
|
-
"stylelint": "^17.
|
|
230
|
+
"sass": "^1.102.0",
|
|
231
|
+
"storybook": "^10.5.4",
|
|
232
|
+
"stylelint": "^17.14.1",
|
|
200
233
|
"stylelint-config-standard-scss": "^17.0.0",
|
|
201
234
|
"stylelint-prettier": "^5.0.3",
|
|
202
235
|
"stylelint-selector-bem-pattern": "^5.0.0",
|
|
203
236
|
"twig": "^3.0.0",
|
|
204
237
|
"twig-drupal-filters": "^3.2.0",
|
|
205
|
-
"vite": "^
|
|
206
|
-
"vite-plugin-sass-glob-import": "^6.0.0",
|
|
207
|
-
"vite-plugin-static-copy": "^4.1.0",
|
|
208
|
-
"vite-plugin-svg-sprite": "^0.7.0",
|
|
209
|
-
"yaml": "^2.8.1"
|
|
238
|
+
"vite": "^8.1.5"
|
|
210
239
|
},
|
|
211
240
|
"devDependencies": {
|
|
212
|
-
"@commitlint/cli": "^21.2.
|
|
241
|
+
"@commitlint/cli": "^21.2.1",
|
|
213
242
|
"@commitlint/config-conventional": "^21.2.0",
|
|
214
|
-
"@semantic-release/changelog": "^
|
|
243
|
+
"@semantic-release/changelog": "^7.0.0",
|
|
215
244
|
"@semantic-release/commit-analyzer": "^13.0.1",
|
|
216
|
-
"@semantic-release/git": "^
|
|
245
|
+
"@semantic-release/git": "^11.0.1",
|
|
217
246
|
"@semantic-release/github": "^12.0.9",
|
|
218
247
|
"@semantic-release/npm": "^13.1.5",
|
|
219
|
-
"@semantic-release/release-notes-generator": "^14.1.
|
|
248
|
+
"@semantic-release/release-notes-generator": "^14.1.1",
|
|
220
249
|
"husky": "^9.1.7",
|
|
221
|
-
"lint-staged": "^17.0
|
|
222
|
-
"
|
|
223
|
-
"react
|
|
224
|
-
"
|
|
250
|
+
"lint-staged": "^17.2.0",
|
|
251
|
+
"puppeteer": "^25.3.0",
|
|
252
|
+
"react": "^19.2.8",
|
|
253
|
+
"react-dom": "^19.2.8",
|
|
254
|
+
"semantic-release": "^25.0.8"
|
|
225
255
|
},
|
|
226
256
|
"peerDependencies": {
|
|
227
257
|
"react": "^18.0.0 || ^19.0.0",
|
|
@@ -231,5 +261,13 @@
|
|
|
231
261
|
"glob": "^13.0.6",
|
|
232
262
|
"locutus": "^3.0.36",
|
|
233
263
|
"minimatch@3.0.x": "^3.1.5"
|
|
264
|
+
},
|
|
265
|
+
"allowScripts": {
|
|
266
|
+
"@parcel/watcher@2.6.0": true,
|
|
267
|
+
"esbuild@0.28.1": true,
|
|
268
|
+
"fsevents@2.3.3": true,
|
|
269
|
+
"puppeteer@24.43.1": true,
|
|
270
|
+
"unrs-resolver@1.12.2": true,
|
|
271
|
+
"puppeteer@25.4.0": true
|
|
234
272
|
}
|
|
235
273
|
}
|
package/scripts/a11y.js
CHANGED
|
@@ -5,16 +5,14 @@
|
|
|
5
5
|
* and reports issues.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import { existsSync, readFileSync } from 'fs';
|
|
8
|
+
import { createReadStream, existsSync, readFileSync, statSync } from 'fs';
|
|
9
|
+
import { createServer } from 'http';
|
|
9
10
|
import path from 'path';
|
|
10
|
-
import {
|
|
11
|
+
import { pathToFileURL } from 'url';
|
|
11
12
|
import pa11y from 'pa11y';
|
|
12
13
|
|
|
13
14
|
import a11yConfig from '../config/a11y.config.js';
|
|
14
15
|
|
|
15
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
16
|
-
const __dirname = path.dirname(__filename);
|
|
17
|
-
|
|
18
16
|
// Project-specific configuration.
|
|
19
17
|
let {
|
|
20
18
|
ignore = {},
|
|
@@ -24,23 +22,27 @@ let {
|
|
|
24
22
|
pa11y: pa11yConfig = {},
|
|
25
23
|
} = a11yConfig;
|
|
26
24
|
|
|
27
|
-
/**
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
25
|
+
/**
|
|
26
|
+
* Resolve the project-specific accessibility config used by generated themes.
|
|
27
|
+
*
|
|
28
|
+
* @param {string} [projectDir=process.cwd()] - Consuming project root.
|
|
29
|
+
* @returns {string} Absolute project config path.
|
|
30
|
+
*/
|
|
31
|
+
const resolveProjectA11yConfig = (projectDir = process.cwd()) =>
|
|
32
|
+
path.resolve(projectDir, 'config/emulsify-core/a11y.config.js');
|
|
32
33
|
|
|
33
34
|
/**
|
|
34
35
|
* Load project-specific accessibility config when a consuming project provides one.
|
|
35
36
|
*
|
|
36
37
|
* @returns {Promise<object>} Project accessibility config, when present.
|
|
37
38
|
*/
|
|
38
|
-
const loadProjectA11yConfig = async () => {
|
|
39
|
-
|
|
39
|
+
const loadProjectA11yConfig = async (projectDir = process.cwd()) => {
|
|
40
|
+
const configPath = resolveProjectA11yConfig(projectDir);
|
|
41
|
+
if (!existsSync(configPath)) {
|
|
40
42
|
return {};
|
|
41
43
|
}
|
|
42
44
|
|
|
43
|
-
const configModule = await import(pathToFileURL(
|
|
45
|
+
const configModule = await import(pathToFileURL(configPath).href);
|
|
44
46
|
return configModule.default || configModule;
|
|
45
47
|
};
|
|
46
48
|
|
|
@@ -90,10 +92,13 @@ const printHelp = () => {
|
|
|
90
92
|
* Resolve the configured Storybook build directory.
|
|
91
93
|
*
|
|
92
94
|
* @param {string} [buildDir=storybookBuildDir] - Configured build directory.
|
|
95
|
+
* @param {string} [projectDir=process.cwd()] - Consuming project root.
|
|
93
96
|
* @returns {string} Absolute Storybook build directory.
|
|
94
97
|
*/
|
|
95
|
-
const resolveStorybookBuildDir = (
|
|
96
|
-
|
|
98
|
+
const resolveStorybookBuildDir = (
|
|
99
|
+
buildDir = storybookBuildDir,
|
|
100
|
+
projectDir = process.cwd(),
|
|
101
|
+
) => path.resolve(projectDir, buildDir);
|
|
97
102
|
|
|
98
103
|
/**
|
|
99
104
|
* Resolve Storybook's iframe file used for per-story rendering.
|
|
@@ -104,6 +109,75 @@ const resolveStorybookBuildDir = (buildDir = storybookBuildDir) =>
|
|
|
104
109
|
const resolveStorybookIframe = (buildDir = storybookBuildDir) =>
|
|
105
110
|
path.join(resolveStorybookBuildDir(buildDir), 'iframe.html');
|
|
106
111
|
|
|
112
|
+
const contentTypes = {
|
|
113
|
+
'.css': 'text/css; charset=utf-8',
|
|
114
|
+
'.html': 'text/html; charset=utf-8',
|
|
115
|
+
'.js': 'text/javascript; charset=utf-8',
|
|
116
|
+
'.json': 'application/json; charset=utf-8',
|
|
117
|
+
'.map': 'application/json; charset=utf-8',
|
|
118
|
+
'.svg': 'image/svg+xml',
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Start a loopback-only server for a built Storybook.
|
|
123
|
+
*
|
|
124
|
+
* Storybook's module graph cannot run reliably from a file URL, so generated
|
|
125
|
+
* consumers need an HTTP origin before Pa11y evaluates a story.
|
|
126
|
+
*
|
|
127
|
+
* @param {string} [buildDir=storybookBuildDir] - Storybook build directory.
|
|
128
|
+
* @returns {Promise<{baseUrl: string, close: Function}>} Server controls.
|
|
129
|
+
*/
|
|
130
|
+
const startStorybookServer = (buildDir = storybookBuildDir) =>
|
|
131
|
+
new Promise((resolve, reject) => {
|
|
132
|
+
const root = resolveStorybookBuildDir(buildDir);
|
|
133
|
+
const server = createServer((request, response) => {
|
|
134
|
+
try {
|
|
135
|
+
const requestUrl = new URL(request.url || '/', 'http://127.0.0.1');
|
|
136
|
+
const requestedPath =
|
|
137
|
+
decodeURIComponent(requestUrl.pathname) === '/'
|
|
138
|
+
? 'index.html'
|
|
139
|
+
: decodeURIComponent(requestUrl.pathname).replace(/^\/+/, '');
|
|
140
|
+
const filePath = path.resolve(root, requestedPath);
|
|
141
|
+
const relativePath = path.relative(root, filePath);
|
|
142
|
+
|
|
143
|
+
if (
|
|
144
|
+
!relativePath ||
|
|
145
|
+
relativePath.startsWith('..') ||
|
|
146
|
+
path.isAbsolute(relativePath) ||
|
|
147
|
+
!existsSync(filePath) ||
|
|
148
|
+
!statSync(filePath).isFile()
|
|
149
|
+
) {
|
|
150
|
+
response.writeHead(404);
|
|
151
|
+
response.end('Not found');
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
response.writeHead(200, {
|
|
156
|
+
'Content-Type':
|
|
157
|
+
contentTypes[path.extname(filePath)] || 'application/octet-stream',
|
|
158
|
+
});
|
|
159
|
+
createReadStream(filePath).pipe(response);
|
|
160
|
+
} catch {
|
|
161
|
+
response.writeHead(400);
|
|
162
|
+
response.end('Bad request');
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
server.once('error', reject);
|
|
167
|
+
server.listen(0, '127.0.0.1', () => {
|
|
168
|
+
const address = server.address();
|
|
169
|
+
resolve({
|
|
170
|
+
baseUrl: `http://127.0.0.1:${address.port}`,
|
|
171
|
+
close: () =>
|
|
172
|
+
new Promise((closeResolve, closeReject) => {
|
|
173
|
+
server.close((error) =>
|
|
174
|
+
error ? closeReject(error) : closeResolve(),
|
|
175
|
+
);
|
|
176
|
+
}),
|
|
177
|
+
});
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
|
|
107
181
|
/**
|
|
108
182
|
* Return unique non-empty Storybook IDs in first-seen order.
|
|
109
183
|
*
|
|
@@ -282,10 +356,11 @@ const logReport = ({ issues, pageUrl }) => {
|
|
|
282
356
|
/**
|
|
283
357
|
* Run pa11y on a single Storybook story by its ID.
|
|
284
358
|
* @param {string} name - Story ID (e.g., "components-button--primary").
|
|
359
|
+
* @param {{baseUrl?: string}} [options={}] - Storybook origin options.
|
|
285
360
|
* @returns {Promise<{ issues: Pa11yIssue[], pageUrl: string }>} Pa11y result.
|
|
286
361
|
*/
|
|
287
|
-
const lintComponent = async (name) =>
|
|
288
|
-
pa11y(`${resolveStorybookIframe()}?id=${name}`, {
|
|
362
|
+
const lintComponent = async (name, { baseUrl } = {}) =>
|
|
363
|
+
pa11y(`${baseUrl || resolveStorybookIframe()}?id=${name}`, {
|
|
289
364
|
includeNotices: true,
|
|
290
365
|
includeWarnings: true,
|
|
291
366
|
runners: ['axe'],
|
|
@@ -295,10 +370,13 @@ const lintComponent = async (name) =>
|
|
|
295
370
|
/**
|
|
296
371
|
* Lint a list of components, log reports, and exit(1) if any have issues.
|
|
297
372
|
* @param {string[]} names - List of Storybook story IDs.
|
|
373
|
+
* @param {{baseUrl?: string}} [options={}] - Storybook origin options.
|
|
298
374
|
* @returns {Promise<void>}
|
|
299
375
|
*/
|
|
300
|
-
const lintReportAndExit = async (names) => {
|
|
301
|
-
const results = await Promise.all(
|
|
376
|
+
const lintReportAndExit = async (names, options = {}) => {
|
|
377
|
+
const results = await Promise.all(
|
|
378
|
+
names.map((name) => lintComponent(name, options)),
|
|
379
|
+
);
|
|
302
380
|
const hasIssues = results.map(logReport).some(Boolean);
|
|
303
381
|
|
|
304
382
|
if (hasIssues) {
|
|
@@ -311,10 +389,22 @@ const lintReportAndExit = async (names) => {
|
|
|
311
389
|
if (['-h', '--help'].includes(process.argv[2])) {
|
|
312
390
|
printHelp();
|
|
313
391
|
} else if (process.argv[2] === '-r') {
|
|
314
|
-
loadProjectA11yConfig()
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
392
|
+
loadProjectA11yConfig()
|
|
393
|
+
.then(async (projectConfig) => {
|
|
394
|
+
applyProjectA11yConfig(projectConfig);
|
|
395
|
+
const storybookServer = await startStorybookServer();
|
|
396
|
+
try {
|
|
397
|
+
await lintReportAndExit(resolvePa11yStoryIds(), {
|
|
398
|
+
baseUrl: `${storybookServer.baseUrl}/iframe.html`,
|
|
399
|
+
});
|
|
400
|
+
} finally {
|
|
401
|
+
await storybookServer.close();
|
|
402
|
+
}
|
|
403
|
+
})
|
|
404
|
+
.catch((error) => {
|
|
405
|
+
console.error(error?.stack || error);
|
|
406
|
+
process.exitCode = 1;
|
|
407
|
+
});
|
|
318
408
|
}
|
|
319
409
|
|
|
320
410
|
export {
|
|
@@ -328,7 +418,9 @@ export {
|
|
|
328
418
|
lintReportAndExit,
|
|
329
419
|
normalizeStoryIds,
|
|
330
420
|
resolvePa11yStoryIds,
|
|
421
|
+
resolveProjectA11yConfig,
|
|
331
422
|
resolveStorybookBuildDir,
|
|
332
423
|
resolveStorybookIframe,
|
|
424
|
+
startStorybookServer,
|
|
333
425
|
storyIdsFromStorybookIndex,
|
|
334
426
|
};
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Core import audit check.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { lineNumberAt } from '../../lib/text.js';
|
|
6
|
+
import { makeFinding } from '../lib/findings.js';
|
|
7
|
+
import { cachedReadFile } from '../lib/files.js';
|
|
8
|
+
|
|
9
|
+
const PUBLIC_CORE_IMPORTS = new Set([
|
|
10
|
+
'@emulsify/core',
|
|
11
|
+
'@emulsify/core/extensions',
|
|
12
|
+
'@emulsify/core/extensions/react',
|
|
13
|
+
'@emulsify/core/extensions/twig',
|
|
14
|
+
'@emulsify/core/package.json',
|
|
15
|
+
'@emulsify/core/storybook',
|
|
16
|
+
'@emulsify/core/vite',
|
|
17
|
+
'@emulsify/core/vite/plugins',
|
|
18
|
+
]);
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Extract import specifiers from JavaScript source.
|
|
22
|
+
*
|
|
23
|
+
* @param {string} source - JavaScript source.
|
|
24
|
+
* @returns {{specifier: string, index: number}[]} Import specifiers.
|
|
25
|
+
*/
|
|
26
|
+
function findImportSpecifiers(source) {
|
|
27
|
+
const imports = [];
|
|
28
|
+
const patterns = [
|
|
29
|
+
/(?:import|export)\s+(?:[^'"]+\s+from\s+)?['"]([^'"]+)['"]/g,
|
|
30
|
+
/import\s*\(\s*['"]([^'"]+)['"]\s*\)/g,
|
|
31
|
+
/require\s*\(\s*['"]([^'"]+)['"]\s*\)/g,
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
for (const pattern of patterns) {
|
|
35
|
+
for (const match of source.matchAll(pattern)) {
|
|
36
|
+
imports.push({
|
|
37
|
+
specifier: match[1],
|
|
38
|
+
index: match.index || 0,
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return imports;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Audit direct imports of Emulsify Core internals.
|
|
48
|
+
*
|
|
49
|
+
* @param {object} context - Audit context.
|
|
50
|
+
* @returns {object[]} Findings.
|
|
51
|
+
*/
|
|
52
|
+
export function auditCoreImports(context) {
|
|
53
|
+
const { codeFiles } = context;
|
|
54
|
+
const findings = [];
|
|
55
|
+
|
|
56
|
+
for (const filePath of codeFiles) {
|
|
57
|
+
const source = cachedReadFile(filePath);
|
|
58
|
+
|
|
59
|
+
for (const item of findImportSpecifiers(source)) {
|
|
60
|
+
const { specifier } = item;
|
|
61
|
+
if (!specifier.startsWith('@emulsify/core/')) continue;
|
|
62
|
+
if (PUBLIC_CORE_IMPORTS.has(specifier)) continue;
|
|
63
|
+
|
|
64
|
+
findings.push(
|
|
65
|
+
makeFinding({
|
|
66
|
+
id: 'internal-core-import',
|
|
67
|
+
severity: 'warn',
|
|
68
|
+
filePath,
|
|
69
|
+
line: lineNumberAt(source, item.index),
|
|
70
|
+
message: `Import "${specifier}" uses an internal Emulsify Core path. Prefer a public package export.`,
|
|
71
|
+
docs: 'https://github.com/emulsify-ds/emulsify-core/blob/4.x/README.md#public-imports',
|
|
72
|
+
}),
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return findings;
|
|
78
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file CSS asset reference audit check.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { dirname, resolve } from 'node:path';
|
|
6
|
+
import { firstExistingPath } from '../../../config/vite/utils/fs-safe.js';
|
|
7
|
+
import { displayPath, makeFinding } from '../lib/findings.js';
|
|
8
|
+
import {
|
|
9
|
+
cachedReadFile,
|
|
10
|
+
isInsideAnyRoot,
|
|
11
|
+
isSameOrInside,
|
|
12
|
+
safeIsDirectory,
|
|
13
|
+
} from '../lib/files.js';
|
|
14
|
+
import { auditAssetRoots } from '../lib/twig.js';
|
|
15
|
+
import {
|
|
16
|
+
cssUrlPath,
|
|
17
|
+
findCssUrlReferences,
|
|
18
|
+
isNonFilesystemCssUrl,
|
|
19
|
+
styleRuntimeDirectories,
|
|
20
|
+
} from '../lib/css.js';
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Audit local CSS/Sass asset URLs that Vite may leave to runtime resolution.
|
|
24
|
+
*
|
|
25
|
+
* @param {object} context - Audit context.
|
|
26
|
+
* @returns {object[]} Findings.
|
|
27
|
+
*/
|
|
28
|
+
export function auditCssAssetReferences(context) {
|
|
29
|
+
const { env, projectDir, styleFiles } = context;
|
|
30
|
+
const findings = [];
|
|
31
|
+
const projectAssetRoots = auditAssetRoots(env).filter(safeIsDirectory);
|
|
32
|
+
const styleSourceRoots = env.projectStructure?.sourceRoots || [];
|
|
33
|
+
|
|
34
|
+
for (const filePath of styleFiles) {
|
|
35
|
+
if (
|
|
36
|
+
styleSourceRoots.length &&
|
|
37
|
+
!isInsideAnyRoot(filePath, styleSourceRoots)
|
|
38
|
+
) {
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const source = cachedReadFile(filePath);
|
|
43
|
+
const runtimeDirs = styleRuntimeDirectories(filePath, env, projectDir);
|
|
44
|
+
|
|
45
|
+
for (const ref of findCssUrlReferences(source)) {
|
|
46
|
+
if (isNonFilesystemCssUrl(ref.value)) continue;
|
|
47
|
+
|
|
48
|
+
const assetPath = cssUrlPath(ref.value);
|
|
49
|
+
if (!assetPath) continue;
|
|
50
|
+
|
|
51
|
+
const sourceAsset = firstExistingPath([
|
|
52
|
+
resolve(dirname(filePath), assetPath),
|
|
53
|
+
]);
|
|
54
|
+
const runtimeAsset = firstExistingPath(
|
|
55
|
+
runtimeDirs.map((directory) => resolve(directory, assetPath)),
|
|
56
|
+
);
|
|
57
|
+
const resolvedAsset = sourceAsset || runtimeAsset;
|
|
58
|
+
|
|
59
|
+
if (!resolvedAsset) {
|
|
60
|
+
findings.push(
|
|
61
|
+
makeFinding({
|
|
62
|
+
id: 'unresolved-css-asset-reference',
|
|
63
|
+
severity: 'warn',
|
|
64
|
+
filePath,
|
|
65
|
+
line: ref.line,
|
|
66
|
+
message: `CSS asset URL "${ref.raw}" could not be resolved from the source file or expected emitted CSS location.`,
|
|
67
|
+
details: [
|
|
68
|
+
'Check for a typo, move the asset into a source-root-relative location Vite can resolve, or rewrite the URL to a stable Drupal/theme public path.',
|
|
69
|
+
],
|
|
70
|
+
docs: 'https://github.com/emulsify-ds/emulsify-core/blob/4.x/docs/migration-4x.md#css-asset-urls',
|
|
71
|
+
}),
|
|
72
|
+
);
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (
|
|
77
|
+
projectAssetRoots.some((root) => isSameOrInside(resolvedAsset, root)) &&
|
|
78
|
+
(!sourceAsset || runtimeAsset || assetPath.startsWith('..'))
|
|
79
|
+
) {
|
|
80
|
+
findings.push(
|
|
81
|
+
makeFinding({
|
|
82
|
+
id: 'css-runtime-asset-reference',
|
|
83
|
+
severity: 'info',
|
|
84
|
+
filePath,
|
|
85
|
+
line: ref.line,
|
|
86
|
+
message: `CSS asset URL "${ref.raw}" resolves to project-level assets and may be left unchanged by Vite for runtime resolution.`,
|
|
87
|
+
details: [
|
|
88
|
+
`Resolved asset: ${displayPath(projectDir, resolvedAsset)}.`,
|
|
89
|
+
'This is acceptable when Drupal serves the asset at that runtime URL. To make Vite bundle or rebase it, move the asset under a source root and reference it from the authored stylesheet.',
|
|
90
|
+
],
|
|
91
|
+
docs: 'https://github.com/emulsify-ds/emulsify-core/blob/4.x/docs/migration-4x.md#css-asset-urls',
|
|
92
|
+
}),
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return findings;
|
|
99
|
+
}
|