@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.
Files changed (84) hide show
  1. package/.storybook/main-vite.js +102 -37
  2. package/.storybook/main.js +20 -0
  3. package/.storybook/ready-reporter.js +230 -0
  4. package/README.md +186 -56
  5. package/config/a11y.config.js +2 -1
  6. package/config/vite/entries.js +4 -4
  7. package/config/vite/plugins/assets/copy-src-assets.js +120 -0
  8. package/config/vite/plugins/assets/copy-twig-files.js +130 -0
  9. package/config/vite/plugins/{css-asset-relativizer.js → assets/css-asset-relativizer.js} +9 -0
  10. package/config/vite/plugins/{mirror-components.js → assets/mirror-components.js} +2 -28
  11. package/config/vite/plugins/{source-file-index.js → assets/source-file-index.js} +1 -1
  12. package/config/vite/plugins/{svg-sprite.js → assets/svg-sprite.js} +2 -2
  13. package/config/vite/plugins/index.js +23 -14
  14. package/config/vite/plugins/reporter/asset-resolver.js +541 -0
  15. package/config/vite/plugins/reporter/build-errors.js +284 -0
  16. package/config/vite/plugins/reporter/diagnostics.js +367 -0
  17. package/config/vite/plugins/reporter/format.js +240 -0
  18. package/config/vite/plugins/reporter/index.js +417 -0
  19. package/config/vite/plugins/reporter/render.js +1332 -0
  20. package/config/vite/plugins/reporter/sass-logger.js +237 -0
  21. package/config/vite/plugins/reporter/source-roots.js +561 -0
  22. package/config/vite/plugins/reporter/verbosity.js +119 -0
  23. package/config/vite/plugins/reporter/vite-logger.js +249 -0
  24. package/config/vite/plugins/reporter/watch-mode.js +40 -0
  25. package/config/vite/plugins/{twig-extension-installers.js → twig/extension-installers.js} +1 -1
  26. package/config/vite/{twig-extensions.js → plugins/twig/extensions.js} +2 -2
  27. package/config/vite/plugins/{twig-module.js → twig/twig-module.js} +197 -85
  28. package/config/vite/plugins/{virtual-twig-asset-sources.js → twig/virtual-twig-asset-sources.js} +37 -136
  29. package/config/vite/plugins/{virtual-twig-globs.js → twig/virtual-twig-globs.js} +3 -32
  30. package/config/vite/plugins/{vituum-patch.js → twig/vituum-patch.js} +3 -3
  31. package/config/vite/plugins.js +1 -1
  32. package/config/vite/project-config.js +1 -1
  33. package/config/vite/project-structure.js +1 -1
  34. package/config/vite/utils/lru.js +77 -0
  35. package/config/vite/utils/package-version.js +42 -0
  36. package/config/vite/utils/paths.js +1 -9
  37. package/config/vite/utils/react-singleton.js +1 -1
  38. package/config/vite/vite.config.js +91 -5
  39. package/package.json +95 -57
  40. package/scripts/a11y.js +115 -23
  41. package/scripts/audit/checks/core-imports.js +78 -0
  42. package/scripts/audit/checks/css-asset-references.js +99 -0
  43. package/scripts/audit/checks/drupal-assumptions.js +48 -0
  44. package/scripts/audit/checks/files-outside-roots.js +53 -0
  45. package/scripts/audit/checks/generated-package-scripts.js +113 -0
  46. package/scripts/audit/checks/legacy-twig-stories.js +33 -0
  47. package/scripts/audit/checks/package-overrides.js +91 -0
  48. package/scripts/audit/checks/project-config.js +71 -0
  49. package/scripts/audit/checks/story-discovery.js +35 -0
  50. package/scripts/audit/checks/twig-references.js +69 -0
  51. package/scripts/audit/checks/twig-volume.js +54 -0
  52. package/scripts/audit/checks/webpack-patterns.js +86 -0
  53. package/scripts/audit/index.js +177 -0
  54. package/scripts/audit/lib/css.js +165 -0
  55. package/scripts/audit/lib/files.js +168 -0
  56. package/scripts/audit/lib/findings.js +31 -0
  57. package/scripts/audit/lib/package-json.js +65 -0
  58. package/scripts/audit/lib/twig.js +227 -0
  59. package/scripts/audit/report.js +273 -0
  60. package/scripts/audit-twig-stories.js +115 -78
  61. package/scripts/audit.js +150 -1632
  62. package/scripts/check-node-version.js +136 -10
  63. package/scripts/inspect-components.js +456 -0
  64. package/scripts/lib/cli.js +179 -0
  65. package/scripts/lib/fs.js +31 -0
  66. package/scripts/lib/proc.js +78 -0
  67. package/scripts/lib/text.js +14 -0
  68. package/scripts/loadYaml.js +2 -2
  69. package/src/extensions/shared/attributes.js +3 -3
  70. package/src/extensions/shared/lists.js +2 -6
  71. package/src/extensions/shared/root-relative.js +38 -0
  72. package/src/storybook/index.js +4 -0
  73. package/src/storybook/render-twig.js +1 -1
  74. package/src/storybook/render-web-component.js +459 -0
  75. package/src/storybook/twig/asset-source-runtime.js +193 -0
  76. package/src/storybook/twig/{source-extensions.js → constants.js} +3 -1
  77. package/src/storybook/twig/reference-paths.js +2 -13
  78. package/src/storybook/twig/setup.js +12 -2
  79. package/src/storybook/twig/source-function.js +5 -2
  80. package/config/vite/plugins/copy-src-assets.js +0 -76
  81. package/config/vite/plugins/copy-twig-files.js +0 -84
  82. package/config/vite/utils/unique.js +0 -36
  83. package/src/storybook/twig/include.js +0 -28
  84. package/src/storybook/twig/source-events.js +0 -5
@@ -0,0 +1,237 @@
1
+ /**
2
+ * @file Sass logger that feeds the Emulsify develop reporter.
3
+ *
4
+ * Dart Sass prints a fully formatted warning — message, source excerpt, caret,
5
+ * and import chain — for every deprecation it encounters, every time it
6
+ * encounters it. A project with a handful of legacy `$space/2` divisions in a
7
+ * shared partial can emit several hundred lines per build, because each entry
8
+ * that imports the partial re-triggers the same warning.
9
+ *
10
+ * Supplying a custom logger replaces that output entirely. Rather than calling
11
+ * `silenceDeprecations`, which hides the warnings and the fact that they exist,
12
+ * this logger routes each report into the shared diagnostics collector so the
13
+ * reporter can print one deduplicated summary with a migration hint. The debt
14
+ * stays visible; only the repetition goes away.
15
+ *
16
+ * @see https://sass-lang.com/documentation/js-api/interfaces/logger/
17
+ */
18
+
19
+ import { fileURLToPath } from 'node:url';
20
+
21
+ /**
22
+ * What each Sass deprecation means and how to resolve it.
23
+ *
24
+ * `fix` is the substitution to make, written as `before → after` so the row
25
+ * reads as an instruction rather than an identifier. A deprecation ID like
26
+ * `slash-div` tells a themer nothing on its own; `$a/$b → math.div($a, $b)`
27
+ * tells them exactly what to type.
28
+ *
29
+ * `migrator` names the `sass-migrator` migration that rewrites the code
30
+ * automatically, and is omitted for deprecations that have to be fixed by hand.
31
+ * The migrator only ships five migrations, so most IDs here have no entry.
32
+ *
33
+ * @type {Record<string, {fix: string, migrator?: string}>}
34
+ * @see https://sass-lang.com/documentation/cli/migrator/
35
+ */
36
+ export const DEPRECATION_GUIDE = {
37
+ 'slash-div': {
38
+ fix: '$a/$b → math.div($a, $b)',
39
+ migrator: 'division',
40
+ },
41
+ 'global-builtin': {
42
+ fix: 'map-get() → map.get()',
43
+ migrator: 'module',
44
+ },
45
+ import: {
46
+ fix: '@import → @use',
47
+ migrator: 'module',
48
+ },
49
+ 'color-functions': {
50
+ fix: 'lighten()/darken() → color.adjust()',
51
+ migrator: 'color',
52
+ },
53
+ 'if-function': {
54
+ fix: 'if() → CSS if()',
55
+ migrator: 'if',
56
+ },
57
+ 'color-4-api': { fix: 'color.red($c) → color.channel($c, "red")' },
58
+ 'legacy-js-api': { fix: 'render() → compile()' },
59
+ 'mixed-decls': { fix: 'move declarations above nested rules' },
60
+ 'strict-unary': { fix: '$a -$b → $a - $b' },
61
+ 'abs-percent': { fix: 'abs(10%) → math.abs(10%)' },
62
+ 'duplicate-var-flags': { fix: 'remove the repeated !default or !global' },
63
+ 'null-alpha': { fix: 'rgb($c, null) → rgb($c)' },
64
+ 'feature-exists': { fix: 'remove meta.feature-exists()' },
65
+ 'moz-document': { fix: 'remove @-moz-document' },
66
+ 'bogus-combinators': { fix: 'remove the dangling combinator' },
67
+ elseif: { fix: '@elseif → @else if' },
68
+ 'call-string': { fix: 'call($name) → call(get-function($name))' },
69
+ 'new-global': { fix: 'declare the variable before assigning it !global' },
70
+ };
71
+
72
+ /**
73
+ * Look up the human-readable fix for a deprecation ID.
74
+ *
75
+ * @param {string} id - Sass deprecation ID.
76
+ * @returns {string|undefined} Fix instruction, when one is known.
77
+ */
78
+ export function deprecationFix(id) {
79
+ return DEPRECATION_GUIDE[id]?.fix;
80
+ }
81
+
82
+ /**
83
+ * Look up the `sass-migrator` migration that resolves a deprecation ID.
84
+ *
85
+ * @param {string} id - Sass deprecation ID.
86
+ * @returns {string|undefined} Migration name, when one exists.
87
+ */
88
+ export function deprecationMigrator(id) {
89
+ return DEPRECATION_GUIDE[id]?.migrator;
90
+ }
91
+
92
+ /**
93
+ * Convert a Sass span URL into a readable filesystem path.
94
+ *
95
+ * Spans carry a `URL` for on-disk stylesheets, but string URLs and custom
96
+ * importer schemes both appear in practice, so every branch is guarded.
97
+ *
98
+ * @param {URL|string|undefined} url - Span URL.
99
+ * @returns {string|undefined} Filesystem path, or undefined when unavailable.
100
+ */
101
+ export function spanUrlToPath(url) {
102
+ if (!url) return undefined;
103
+
104
+ const href = typeof url === 'string' ? url : url.href;
105
+ if (!href) return undefined;
106
+ if (!href.startsWith('file:')) return href;
107
+
108
+ try {
109
+ return fileURLToPath(href);
110
+ } catch {
111
+ return href;
112
+ }
113
+ }
114
+
115
+ /**
116
+ * Read the 1-based line number from a Sass span.
117
+ *
118
+ * Sass reports zero-based line numbers; editors and the rest of the Emulsify
119
+ * tooling are one-based.
120
+ *
121
+ * @param {{start?: {line?: number}}|undefined} span - Sass source span.
122
+ * @returns {number|undefined} 1-based line number, when available.
123
+ */
124
+ export function spanLineNumber(span) {
125
+ const line = span?.start?.line;
126
+ return typeof line === 'number' ? line + 1 : undefined;
127
+ }
128
+
129
+ /**
130
+ * Reduce a multi-line Sass message to its first meaningful line.
131
+ *
132
+ * @param {string|undefined} message - Raw Sass message.
133
+ * @returns {string|undefined} Condensed message.
134
+ */
135
+ export function condenseMessage(message) {
136
+ if (typeof message !== 'string') return undefined;
137
+ const [firstLine] = message.split('\n');
138
+ return firstLine?.trim() || undefined;
139
+ }
140
+
141
+ /**
142
+ * Detect Dart Sass's own truncation notice.
143
+ *
144
+ * Without `verbose`, Sass prints at most five instances of each deprecation and
145
+ * then emits `"N repetitive deprecation warnings omitted."` as a warning with no
146
+ * span attached. That notice is not a problem in the stylesheet — it is Sass
147
+ * reporting that it hid some. Recording it would put a locationless row in the
148
+ * summary and, worse, imply the reporter's totals are complete when they are
149
+ * short by exactly the number Sass suppressed.
150
+ *
151
+ * {@link createSassOptions} sets `verbose` so the notice should never appear,
152
+ * but a project that overrides `preprocessorOptions` could reintroduce it.
153
+ *
154
+ * @param {string|undefined} message - Raw Sass message.
155
+ * @returns {boolean} TRUE when the message is a truncation notice.
156
+ */
157
+ export function isRepetitionNotice(message) {
158
+ if (typeof message !== 'string') return false;
159
+ return /^\s*\d+\s+repetitive\s+deprecation\s+warnings?\s+omitted\b/i.test(
160
+ message,
161
+ );
162
+ }
163
+
164
+ /**
165
+ * Create a Sass logger that records into a diagnostics collector.
166
+ *
167
+ * @param {ReturnType<import('./diagnostics.js').createDiagnosticsCollector>} collector - Shared collector.
168
+ * @param {{passthrough?: (message: string, options: object) => void}} [options] - Logger options.
169
+ * @returns {{warn: Function, debug: Function}} Sass logger.
170
+ */
171
+ export function createSassLogger(collector, { passthrough } = {}) {
172
+ return {
173
+ /**
174
+ * Record a Sass warning instead of printing it.
175
+ *
176
+ * @param {string} message - Warning message.
177
+ * @param {object} [options] - Sass warning metadata.
178
+ * @returns {void}
179
+ */
180
+ warn(message, options = {}) {
181
+ // Sass counting its own suppressed warnings is not a finding to report.
182
+ if (isRepetitionNotice(message)) return;
183
+
184
+ const file = spanUrlToPath(options.span?.url);
185
+ const line = spanLineNumber(options.span);
186
+
187
+ if (options.deprecation) {
188
+ collector.recordDeprecation({
189
+ id: options.deprecationType?.id,
190
+ file,
191
+ line,
192
+ });
193
+ } else {
194
+ collector.recordWarning({
195
+ message: condenseMessage(message),
196
+ file,
197
+ line,
198
+ });
199
+ }
200
+
201
+ // Projects that want the original firehose back can opt in per build.
202
+ if (typeof passthrough === 'function') {
203
+ passthrough(message, options);
204
+ }
205
+ },
206
+
207
+ /**
208
+ * Discard Sass debug output.
209
+ *
210
+ * `@debug` is a authoring aid and has no place in the develop summary.
211
+ *
212
+ * @returns {void}
213
+ */
214
+ debug() {},
215
+ };
216
+ }
217
+
218
+ /**
219
+ * Build the Sass preprocessor options used by the develop watcher.
220
+ *
221
+ * `verbose` and the custom logger have to travel together. Dart Sass caps each
222
+ * deprecation at five reported instances by default, so a reporter that counts
223
+ * what the logger receives would silently undercount — a project with 135 real
224
+ * occurrences would be told it had 85. Turning `verbose` on hands every
225
+ * occurrence to the logger, which then deduplicates them properly. The console
226
+ * stays quiet either way, because the logger prints nothing.
227
+ *
228
+ * @param {ReturnType<import('./diagnostics.js').createDiagnosticsCollector>} collector - Shared collector.
229
+ * @param {{passthrough?: (message: string, options: object) => void}} [options] - Logger options.
230
+ * @returns {{logger: {warn: Function, debug: Function}, verbose: boolean}} Sass options.
231
+ */
232
+ export function createSassOptions(collector, options = {}) {
233
+ return {
234
+ logger: createSassLogger(collector, options),
235
+ verbose: true,
236
+ };
237
+ }