@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
@@ -9,9 +9,22 @@ import { relative, resolve } from 'node:path';
9
9
  import { globSync } from 'glob';
10
10
  import { resolveProjectConfig } from '../config/vite/project-config.js';
11
11
  import { toPosixPath } from '../config/vite/utils/paths.js';
12
+ import {
13
+ createUsage,
14
+ isCliEntrypoint,
15
+ parseArgs as parseCliArgs,
16
+ } from './lib/cli.js';
17
+ import {
18
+ formatAuditJsonErrorReport,
19
+ formatAuditJsonReport,
20
+ } from './audit/report.js';
21
+ import { lineNumberAt } from './lib/text.js';
12
22
 
13
23
  const STORY_GLOB = '**/*.stories.{js,jsx,ts,tsx}';
14
24
  const IDENTIFIER_PATTERN = '[A-Za-z_$][\\w$]*';
25
+ const LEGACY_TWIG_STORY_DOCS =
26
+ 'https://github.com/emulsify-ds/emulsify-core/blob/4.x/docs/storybook.md#legacy-twig-story-compatibility';
27
+ const cliFailureExitCode = 2;
15
28
  const DEFAULT_IGNORES = [
16
29
  '**/node_modules/**',
17
30
  '**/dist/**',
@@ -29,17 +42,6 @@ function escapeRegExp(value) {
29
42
  return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
30
43
  }
31
44
 
32
- /**
33
- * Find the 1-based line number for a character index.
34
- *
35
- * @param {string} source - File source.
36
- * @param {number} index - Character index.
37
- * @returns {number} 1-based line number.
38
- */
39
- function lineNumberAt(source, index) {
40
- return source.slice(0, index).split('\n').length;
41
- }
42
-
43
45
  /**
44
46
  * Find imported Twig template identifiers.
45
47
  *
@@ -226,6 +228,35 @@ export function auditTwigStories(options = {}) {
226
228
  };
227
229
  }
228
230
 
231
+ /**
232
+ * Adapt the focused Twig analysis to the shared machine-readable contract.
233
+ *
234
+ * @param {{projectDir: string, files: string[], findings: object[]}} result
235
+ * Twig story audit result.
236
+ * @returns {object} Combined audit-compatible result.
237
+ */
238
+ function createJsonAuditResult(result) {
239
+ return {
240
+ projectDir: result.projectDir,
241
+ files: {
242
+ stories: result.files.length,
243
+ twig: 0,
244
+ code: result.files.length,
245
+ styles: 0,
246
+ },
247
+ findings: result.findings.map((finding) => ({
248
+ id: 'legacy-twig-story',
249
+ severity: 'warn',
250
+ filePath: finding.filePath,
251
+ line: finding.directTemplateReturns[0]?.line,
252
+ message:
253
+ 'Twig story appears to return an HTML string directly. This remains compatible, but renderTwig() is preferred for active migrations.',
254
+ details: finding.reasons,
255
+ docs: LEGACY_TWIG_STORY_DOCS,
256
+ })),
257
+ };
258
+ }
259
+
229
260
  /**
230
261
  * Format audit findings for terminal output.
231
262
  *
@@ -280,46 +311,24 @@ export function formatAuditReport(result) {
280
311
  * Parsed options.
281
312
  */
282
313
  function parseArgs(argv) {
283
- const options = {
284
- projectDir: process.cwd(),
285
- failOnFound: false,
286
- json: false,
287
- help: false,
288
- };
289
-
290
- for (let index = 0; index < argv.length; index += 1) {
291
- const arg = argv[index];
292
-
293
- if (arg === '--help' || arg === '-h') {
294
- options.help = true;
295
- continue;
296
- }
297
- if (arg === '--fail-on-found') {
298
- options.failOnFound = true;
299
- continue;
300
- }
301
- if (arg === '--json') {
302
- options.json = true;
303
- continue;
304
- }
305
- if (arg === '--root') {
306
- const value = argv[index + 1];
307
- if (!value || value.startsWith('--')) {
308
- throw new Error('--root requires a project directory.');
309
- }
310
- options.projectDir = value;
311
- index += 1;
312
- continue;
313
- }
314
- if (arg.startsWith('--root=')) {
315
- options.projectDir = arg.slice('--root='.length);
316
- continue;
317
- }
318
-
319
- throw new Error(`Unknown option: ${arg}`);
320
- }
321
-
322
- return options;
314
+ return parseCliArgs(argv, {
315
+ defaults: {
316
+ projectDir: process.cwd(),
317
+ failOnFound: false,
318
+ json: false,
319
+ help: false,
320
+ },
321
+ flags: {
322
+ '--fail-on-found': 'failOnFound',
323
+ '--json': 'json',
324
+ },
325
+ options: {
326
+ '--root': {
327
+ key: 'projectDir',
328
+ missingMessage: '--root requires a project directory.',
329
+ },
330
+ },
331
+ });
323
332
  }
324
333
 
325
334
  /**
@@ -328,15 +337,15 @@ function parseArgs(argv) {
328
337
  * @returns {string} Usage text.
329
338
  */
330
339
  function usage() {
331
- return [
340
+ return createUsage(
332
341
  'Usage: emulsify-audit-twig-stories [--root <dir>] [--json] [--fail-on-found]',
333
- '',
334
- 'Options:',
335
- ' --root <dir> Project root to scan. Defaults to the current directory.',
336
- ' --json Print machine-readable JSON.',
337
- ' --fail-on-found Exit with code 1 when migration candidates are found.',
338
- ' --help Print this help text.',
339
- ].join('\n');
342
+ [
343
+ ' --root <dir> Project root to scan. Defaults to the current directory.',
344
+ ' --json Print machine-readable JSON.',
345
+ ' --fail-on-found Exit with code 1 when migration candidates are found.',
346
+ ' --help Print this help text.',
347
+ ],
348
+ );
340
349
  }
341
350
 
342
351
  /**
@@ -346,33 +355,61 @@ function usage() {
346
355
  * @returns {number} Process exit code.
347
356
  */
348
357
  export function runCli(argv = process.argv.slice(2)) {
349
- const options = parseArgs(argv);
358
+ const jsonRequested = argv.includes('--json');
359
+ let options;
360
+
361
+ try {
362
+ options = parseArgs(argv);
363
+ if (options.help && options.json) {
364
+ throw new Error('--json cannot be combined with --help.');
365
+ }
366
+ } catch (error) {
367
+ if (jsonRequested) {
368
+ console.log(
369
+ formatAuditJsonErrorReport(error, {
370
+ code: 'invalid-arguments',
371
+ }),
372
+ );
373
+ } else {
374
+ console.error(`${error.message || error}\n\n${usage()}`);
375
+ }
376
+
377
+ return cliFailureExitCode;
378
+ }
350
379
 
351
380
  if (options.help) {
352
381
  console.log(usage());
353
382
  return 0;
354
383
  }
355
384
 
356
- const result = auditTwigStories({
357
- projectDir: options.projectDir,
358
- });
385
+ try {
386
+ const result = auditTwigStories({
387
+ projectDir: options.projectDir,
388
+ });
389
+
390
+ if (options.json) {
391
+ console.log(formatAuditJsonReport(createJsonAuditResult(result)));
392
+ } else {
393
+ console.log(formatAuditReport(result));
394
+ }
359
395
 
360
- if (options.json) {
361
- console.log(JSON.stringify(result, null, 2));
362
- } else {
363
- console.log(formatAuditReport(result));
364
- }
396
+ return options.failOnFound && result.findings.length ? 1 : 0;
397
+ } catch (error) {
398
+ if (options.json) {
399
+ console.log(
400
+ formatAuditJsonErrorReport(error, {
401
+ code: 'audit-failed',
402
+ projectDir: resolve(options.projectDir),
403
+ }),
404
+ );
405
+ } else {
406
+ console.error(`Audit failed: ${error.message || error}`);
407
+ }
365
408
 
366
- return options.failOnFound && result.findings.length ? 1 : 0;
409
+ return cliFailureExitCode;
410
+ }
367
411
  }
368
412
 
369
- if (process.argv[1]?.split(/[\\/]/).pop() === 'audit-twig-stories.js') {
370
- try {
371
- process.exitCode = runCli();
372
- } catch (error) {
373
- console.error(error.message || error);
374
- console.error('');
375
- console.error(usage());
376
- process.exitCode = 1;
377
- }
413
+ if (isCliEntrypoint(['audit-twig-stories.js', 'emulsify-audit-twig-stories'])) {
414
+ process.exitCode = runCli();
378
415
  }