@entur/typography 1.10.0-beta.8 → 2.0.0-beta.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 (112) hide show
  1. package/README.md +37 -17
  2. package/dist/BaseHeading.d.ts +1 -1
  3. package/dist/Blockquote.d.ts +3 -4
  4. package/dist/CodeText.d.ts +1 -1
  5. package/dist/EmphasizedText.d.ts +1 -1
  6. package/dist/Heading1.d.ts +1 -1
  7. package/dist/Heading2.d.ts +1 -1
  8. package/dist/Heading3.d.ts +1 -1
  9. package/dist/Heading4.d.ts +1 -1
  10. package/dist/Heading5.d.ts +1 -1
  11. package/dist/Heading6.d.ts +1 -1
  12. package/dist/Label.d.ts +1 -1
  13. package/dist/LeadParagraph.d.ts +1 -1
  14. package/dist/Link.d.ts +1 -1
  15. package/dist/ListItem.d.ts +1 -1
  16. package/dist/NumberedList.d.ts +1 -1
  17. package/dist/Paragraph.d.ts +1 -1
  18. package/dist/PreformattedText.d.ts +1 -1
  19. package/dist/SmallText.d.ts +1 -1
  20. package/dist/StrongText.d.ts +1 -1
  21. package/dist/SubLabel.d.ts +1 -1
  22. package/dist/SubParagraph.d.ts +1 -1
  23. package/dist/UnorderedList.d.ts +1 -1
  24. package/dist/beta/cjs/components/Blockquote.cjs +29 -0
  25. package/dist/beta/cjs/components/Blockquote.cjs.map +1 -0
  26. package/dist/beta/cjs/components/Heading.cjs +37 -0
  27. package/dist/beta/cjs/components/Heading.cjs.map +1 -0
  28. package/dist/beta/cjs/components/Link.cjs +41 -0
  29. package/dist/beta/cjs/components/Link.cjs.map +1 -0
  30. package/dist/beta/cjs/components/ListItem.cjs +33 -0
  31. package/dist/beta/cjs/components/ListItem.cjs.map +1 -0
  32. package/dist/beta/cjs/components/NumberedList.cjs +32 -0
  33. package/dist/beta/cjs/components/NumberedList.cjs.map +1 -0
  34. package/dist/beta/cjs/components/Text.cjs +36 -0
  35. package/dist/beta/cjs/components/Text.cjs.map +1 -0
  36. package/dist/beta/cjs/components/UnorderedList.cjs +29 -0
  37. package/dist/beta/cjs/components/UnorderedList.cjs.map +1 -0
  38. package/dist/beta/cjs/index.cjs +18 -0
  39. package/dist/beta/cjs/index.cjs.map +1 -0
  40. package/dist/beta/cjs/utils/utils.cjs +77 -0
  41. package/dist/beta/cjs/utils/utils.cjs.map +1 -0
  42. package/dist/beta/esm/components/Blockquote.mjs +29 -0
  43. package/dist/beta/esm/components/Blockquote.mjs.map +1 -0
  44. package/dist/beta/esm/components/Heading.mjs +37 -0
  45. package/dist/beta/esm/components/Heading.mjs.map +1 -0
  46. package/dist/beta/esm/components/Link.mjs +41 -0
  47. package/dist/beta/esm/components/Link.mjs.map +1 -0
  48. package/dist/beta/esm/components/ListItem.mjs +33 -0
  49. package/dist/beta/esm/components/ListItem.mjs.map +1 -0
  50. package/dist/beta/esm/components/NumberedList.mjs +32 -0
  51. package/dist/beta/esm/components/NumberedList.mjs.map +1 -0
  52. package/dist/beta/esm/components/Text.mjs +36 -0
  53. package/dist/beta/esm/components/Text.mjs.map +1 -0
  54. package/dist/beta/esm/components/UnorderedList.mjs +29 -0
  55. package/dist/beta/esm/components/UnorderedList.mjs.map +1 -0
  56. package/dist/beta/esm/index.mjs +18 -0
  57. package/dist/beta/esm/index.mjs.map +1 -0
  58. package/dist/beta/esm/utils/utils.mjs +77 -0
  59. package/dist/beta/esm/utils/utils.mjs.map +1 -0
  60. package/dist/beta/styles/components/heading.css +458 -0
  61. package/dist/beta/styles/components/text.css +959 -0
  62. package/dist/beta/types/components/Blockquote.d.ts +12 -0
  63. package/dist/beta/{Heading.d.ts → types/components/Heading.d.ts} +2 -3
  64. package/dist/beta/types/components/Link.d.ts +22 -0
  65. package/dist/beta/types/components/ListItem.d.ts +22 -0
  66. package/dist/beta/types/components/NumberedList.d.ts +22 -0
  67. package/dist/beta/{Text.d.ts → types/components/Text.d.ts} +2 -3
  68. package/dist/beta/types/components/UnorderedList.d.ts +20 -0
  69. package/dist/beta/types/index.d.ts +8 -0
  70. package/dist/beta/types/utils/utils.d.ts +13 -0
  71. package/dist/index.d.ts +0 -5
  72. package/dist/styles.css +162 -820
  73. package/dist/typography.cjs.js +416 -0
  74. package/dist/typography.cjs.js.map +1 -0
  75. package/dist/typography.esm.js +392 -454
  76. package/dist/typography.esm.js.map +1 -1
  77. package/fonts/Entur-Nationale-Demibold.eot +0 -0
  78. package/fonts/Entur-Nationale-Demibold.woff +0 -0
  79. package/fonts/Entur-Nationale-Demibold.woff2 +0 -0
  80. package/fonts/Entur-Nationale-DemiboldItalic.eot +0 -0
  81. package/fonts/Entur-Nationale-DemiboldItalic.woff +0 -0
  82. package/fonts/Entur-Nationale-DemiboldItalic.woff2 +0 -0
  83. package/fonts/Entur-Nationale-Italic.eot +0 -0
  84. package/fonts/Entur-Nationale-Italic.woff +0 -0
  85. package/fonts/Entur-Nationale-Italic.woff2 +0 -0
  86. package/fonts/Entur-Nationale-Light.eot +0 -0
  87. package/fonts/Entur-Nationale-Light.woff +0 -0
  88. package/fonts/Entur-Nationale-Light.woff2 +0 -0
  89. package/fonts/Entur-Nationale-LightItalic.eot +0 -0
  90. package/fonts/Entur-Nationale-LightItalic.woff +0 -0
  91. package/fonts/Entur-Nationale-LightItalic.woff2 +0 -0
  92. package/fonts/Entur-Nationale-Medium.eot +0 -0
  93. package/fonts/Entur-Nationale-Medium.woff +0 -0
  94. package/fonts/Entur-Nationale-Medium.woff2 +0 -0
  95. package/fonts/Entur-Nationale-MediumItalic.eot +0 -0
  96. package/fonts/Entur-Nationale-MediumItalic.woff +0 -0
  97. package/fonts/Entur-Nationale-MediumItalic.woff2 +0 -0
  98. package/fonts/Entur-Nationale-Regular.eot +0 -0
  99. package/fonts/Entur-Nationale-Regular.woff +0 -0
  100. package/fonts/Entur-Nationale-Regular.woff2 +0 -0
  101. package/package.json +59 -19
  102. package/scripts/migrate-typography.js +415 -178
  103. package/dist/beta/BlockquoteBeta.d.ts +0 -12
  104. package/dist/beta/LinkBeta.d.ts +0 -16
  105. package/dist/beta/index.d.ts +0 -6
  106. package/dist/beta/utils.d.ts +0 -9
  107. package/dist/index.js +0 -8
  108. package/dist/typography.cjs.development.js +0 -508
  109. package/dist/typography.cjs.development.js.map +0 -1
  110. package/dist/typography.cjs.production.min.js +0 -2
  111. package/dist/typography.cjs.production.min.js.map +0 -1
  112. /package/dist/beta/{types.d.ts → types/types.d.ts} +0 -0
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  /**
4
+ * NOTICE: This script is generated using AI with human guidance.
4
5
  * Typography Migration Script
5
6
  *
6
7
  * This script helps you migrate from old typography components to new beta typography.
@@ -11,28 +12,38 @@
11
12
  * - Updates import paths AND component usage
12
13
  * - Replaces old components with beta components
13
14
  * - CONSEQUENCES:
14
- * * <Heading1> becomes <Heading as="h1" variant="title-1">
15
+ * * <Heading1-6> becomes <Heading as="h1-6" variant="title-1|title-2|subtitle-1|subtitle-2|section-1|section-2">
15
16
  * * <Paragraph> becomes <Text variant="paragraph">
16
- * * <Link> becomes <LinkBeta>
17
- * * Props may need updates (e.g., different prop names)
17
+ * * <LeadParagraph> becomes <Text variant="leading">
18
+ * * <SmallText> becomes <Text variant="subparagraph">
19
+ * * <StrongText> becomes <Text as="strong" weight="bold">
20
+ * * <SubLabel> becomes <Text variant="sublabel">
21
+ * * <SubParagraph> becomes <Text variant="subparagraph">
22
+ * * <Label> becomes <Text variant="label"> (or <Text as="label" variant="label"> if htmlFor prop exists)
23
+ * * <EmphasizedText> becomes <Text variant="emphasized">
24
+ * * <CodeText> becomes <Text variant="code-text">
25
+ * * <PreformattedText> becomes <Text variant="preformatted-text">
26
+ * * <Link> becomes <Link> (from @entur/typography/beta)
27
+ * * <Blockquote> becomes <Blockquote> (from @entur/typography/beta)
28
+ * * <BlockquoteFooter> becomes <BlockquoteFooter> (from @entur/typography/beta)
29
+ * * <UnorderedList> becomes <UnorderedList> (from @entur/typography/beta)
30
+ * * <NumberedList> becomes <NumberedList> (from @entur/typography/beta)
31
+ * * <ListItem> becomes <ListItem> (from @entur/typography/beta)
32
+ * * Import paths change from @entur/typography to @entur/typography/beta
33
+ * * Props may need updates (e.g., margin becomes spacing)
18
34
  * * Styling classes may change
19
35
  * * Test thoroughly after migration!
20
36
  *
21
- * 📝 Import-Only Mode (--import-only):
22
- * - Only updates import paths from '@entur/typography' to '@entur/typography'
23
- * - Keeps your existing component usage unchanged
24
- * - Minimal risk, allows gradual migration
25
- * - You can manually update components later
37
+
26
38
  *
27
39
  * Usage:
28
40
  * 1. Run this script in your project root
29
- * 2. Choose your migration mode (complete or import-only)
41
+ * 2. Choose your migration mode (complete)
30
42
  * 3. Update your styles as needed
31
43
  * 4. Test your application thoroughly
32
44
  *
33
45
  * Options:
34
46
  * --dry-run Show what would be changed without modifying files
35
- * --import-only Import-only migration: update import paths only
36
47
  *
37
48
  * Environment Variables:
38
49
  * TYPOGRAPHY_MIGRATION_DIRS Comma-separated list of directories to scan
@@ -46,13 +57,19 @@
46
57
  *
47
58
  */
48
59
 
49
- const fs = require('fs');
50
- const path = require('path');
60
+ import fs from 'fs';
61
+ import path from 'path';
62
+ import { fileURLToPath } from 'url';
63
+
64
+ // Get __dirname equivalent for ES modules
65
+ const __filename = fileURLToPath(import.meta.url);
66
+ const __dirname = path.dirname(__filename);
51
67
 
52
68
  // Check if glob is available
53
69
  let glob;
54
70
  try {
55
- glob = require('glob');
71
+ const globModule = await import('glob');
72
+ glob = globModule.default || globModule;
56
73
  } catch (error) {
57
74
  console.error(
58
75
  '❌ Error: The "glob" package is required to run this migration script.',
@@ -70,7 +87,7 @@ try {
70
87
 
71
88
  // Configuration
72
89
  const OLD_IMPORT = '@entur/typography';
73
- const BETA_IMPORT = '@entur/typography';
90
+ const BETA_IMPORT = '@entur/typography/beta';
74
91
 
75
92
  // Enhanced warning detection patterns - only truly problematic patterns
76
93
  const PROBLEMATIC_PATTERNS = {
@@ -123,6 +140,7 @@ const MIGRATION_FOLDERS = [
123
140
  'app/**',
124
141
  'apps/**',
125
142
  'components/**',
143
+ 'packages/**',
126
144
  'pages/**',
127
145
  'lib/**',
128
146
  'utils/**',
@@ -342,15 +360,20 @@ const COMPONENT_MAPPING = {
342
360
  Heading6: { component: 'Heading', as: 'h6', variant: 'section-2' },
343
361
  Paragraph: { component: 'Text', variant: 'paragraph' },
344
362
  LeadParagraph: { component: 'Text', variant: 'leading' },
345
- SmallText: { component: 'Text', variant: 'subparagraph', size: 's' },
346
- StrongText: { component: 'Text', variant: 'emphasized', weight: 'semibold' },
347
- SubLabel: { component: 'Text', variant: 'sublabel', size: 'xs' },
363
+ SmallText: { component: 'Text', variant: 'subparagraph' },
364
+ StrongText: { component: 'Text', as: 'strong', weight: 'bold' },
365
+ SubLabel: { component: 'Text', variant: 'sublabel' },
348
366
  SubParagraph: { component: 'Text', variant: 'subparagraph' },
349
367
  Label: { component: 'Text', variant: 'label' },
350
368
  EmphasizedText: { component: 'Text', variant: 'emphasized' },
351
369
  CodeText: { component: 'Text', variant: 'code-text' },
352
- Link: { component: 'LinkBeta' },
353
- Blockquote: { component: 'BlockquoteBeta' },
370
+ PreformattedText: { component: 'Text', variant: 'preformatted-text' },
371
+ Link: { component: 'Link' }, // Convert Link to beta Link
372
+ Blockquote: { component: 'Blockquote' }, // Convert Blockquote to beta Blockquote
373
+ BlockquoteFooter: { component: 'BlockquoteFooter' }, // Convert BlockquoteFooter to beta BlockquoteFooter
374
+ UnorderedList: { component: 'UnorderedList' },
375
+ NumberedList: { component: 'NumberedList' },
376
+ ListItem: { component: 'ListItem' },
354
377
  };
355
378
 
356
379
  // Props mapping for migration
@@ -359,17 +382,43 @@ const PROPS_MAPPING = {
359
382
  };
360
383
 
361
384
  // Spacing value mapping from old margin to new spacing
385
+ // Based on the actual CSS classes in src/beta/styles.scss
386
+ // and the old margin prop values: "top" | "bottom" | "both" | "none"
362
387
  const SPACING_MAPPING = {
363
- none: 'none',
364
- top: 'md-top',
365
- bottom: 'md-bottom',
366
- left: 'md-left',
367
- right: 'md-right',
388
+ // Old margin values mapped to new spacing values
389
+ none: 'none', // No spacing
390
+ top: 'md-top', // Top margin only (medium size)
391
+ bottom: 'md-bottom', // Bottom margin only (medium size)
392
+ both: 'md', // Both top and bottom margins (medium size)
393
+
394
+ // Additional spacing values for more granular control
395
+ // These weren't in the old margin prop but are available in new spacing
396
+ left: 'md-left', // Left margin (medium size)
397
+ right: 'md-right', // Right margin (medium size)
398
+
399
+ // Size-based spacing (applies to both top and bottom)
368
400
  xs: 'xs',
369
401
  sm: 'sm',
370
402
  md: 'md',
371
403
  lg: 'lg',
372
404
  xl: 'xl',
405
+
406
+ // Specific directional spacing with sizes
407
+ 'xs-top': 'xs-top',
408
+ 'xs-bottom': 'xs-bottom',
409
+ 'sm-top': 'sm-top',
410
+ 'sm-bottom': 'sm-bottom',
411
+ 'md-top': 'md-top',
412
+ 'md-bottom': 'md-bottom',
413
+ 'lg-top': 'lg-top',
414
+ 'lg-bottom': 'lg-bottom',
415
+ 'xl-top': 'xl-top',
416
+ 'xl-bottom': 'xl-bottom',
417
+
418
+ // Extra small variants
419
+ xs2: 'xs2',
420
+ 'xs2-top': 'xs2-top',
421
+ 'xs2-bottom': 'xs2-bottom',
373
422
  };
374
423
 
375
424
  // Import patterns to handle
@@ -378,35 +427,40 @@ const IMPORT_PATTERNS = [
378
427
  /from\s+['"`]@entur\/typography\/dist['"`]/g,
379
428
  /from\s+['"`]@entur\/typography\/dist\/index['"`]/g,
380
429
  /from\s+['"`]@entur\/typography\/dist\/styles\.css['"`]/g,
430
+ /from\s+['"`]@entur\/typography\/styles['"`]/g,
381
431
  ];
382
432
 
383
433
  // Parse JSX props more robustly
384
434
  function parseJSXProps(propsString) {
385
435
  if (!propsString || !propsString.trim()) {
386
- return { props: {}, warnings: [] };
436
+ return { props: {}, warnings: [], spreadProps: [] };
387
437
  }
388
438
 
389
439
  const props = {};
390
440
  const warnings = [];
391
- const MAX_ITERATIONS = 100; // Prevent infinite loops
392
- let iterationCount = 0;
441
+ const spreadProps = []; // Track spread props separately
442
+ const originalSyntax = {}; // Track original JSX syntax for each prop
393
443
 
394
444
  try {
395
445
  // Parse props manually to handle complex cases
396
446
  let remaining = propsString.trim();
397
- let lastRemainingLength = remaining.length;
398
447
 
399
- while (remaining.length > 0 && iterationCount < MAX_ITERATIONS) {
400
- iterationCount++;
448
+ // First, extract all spread props
449
+ const spreadRegex = /\.\.\.\{?(\w+)\}?/g;
450
+ let spreadMatch;
451
+ while ((spreadMatch = spreadRegex.exec(remaining)) !== null) {
452
+ spreadProps.push(spreadMatch[1]);
453
+ }
401
454
 
402
- // Safety check: if we're not making progress, break
403
- if (remaining.length >= lastRemainingLength) {
404
- warnings.push(`Parser stuck at iteration ${iterationCount}, breaking`);
405
- break;
406
- }
407
- lastRemainingLength = remaining.length;
455
+ // Remove spread props from the string to parse regular props
456
+ remaining = remaining.replace(/\.\.\.\{?(\w+)\}?/g, '');
457
+
458
+ // Now parse regular props
459
+ while (remaining.trim().length > 0) {
460
+ // Skip whitespace
461
+ remaining = remaining.replace(/^\s+/, '');
408
462
 
409
- // Match prop name - more efficient regex
463
+ // Match prop name
410
464
  const nameMatch = remaining.match(/^(\w+)=/);
411
465
  if (!nameMatch) break;
412
466
 
@@ -416,7 +470,7 @@ function parseJSXProps(propsString) {
416
470
 
417
471
  // Match prop value
418
472
  if (remaining.startsWith('"') || remaining.startsWith("'")) {
419
- // String value - use indexOf for better performance
473
+ // String value
420
474
  const quote = remaining[0];
421
475
  const endQuoteIndex = remaining.indexOf(quote, 1);
422
476
  if (endQuoteIndex === -1) {
@@ -426,14 +480,14 @@ function parseJSXProps(propsString) {
426
480
 
427
481
  const propValue = remaining.substring(1, endQuoteIndex);
428
482
  props[propName] = propValue;
483
+ originalSyntax[propName] = 'string'; // Mark as string literal
429
484
  remaining = remaining.substring(endQuoteIndex + 1);
430
485
  } else if (remaining.startsWith('{')) {
431
- // Object value - find matching closing brace with bounds checking
486
+ // Object value - find matching closing brace
432
487
  let braceCount = 0;
433
488
  let endIndex = -1;
434
- const maxSearchLength = Math.min(remaining.length, 1000); // Limit search length
435
489
 
436
- for (let i = 0; i < maxSearchLength; i++) {
490
+ for (let i = 0; i < remaining.length; i++) {
437
491
  if (remaining[i] === '{') braceCount++;
438
492
  if (remaining[i] === '}') {
439
493
  braceCount--;
@@ -451,25 +505,23 @@ function parseJSXProps(propsString) {
451
505
 
452
506
  const propValue = remaining.substring(1, endIndex);
453
507
  props[propName] = propValue;
508
+ originalSyntax[propName] = 'jsx'; // Mark as JSX expression
454
509
  remaining = remaining.substring(endIndex + 1);
455
510
  } else {
456
- // Boolean prop (e.g., disabled) or invalid syntax
511
+ // Boolean prop
457
512
  props[propName] = true;
513
+ originalSyntax[propName] = 'boolean'; // Mark as boolean
458
514
  break;
459
515
  }
460
516
 
461
- // Skip whitespace more efficiently
517
+ // Skip whitespace
462
518
  remaining = remaining.replace(/^\s+/, '');
463
519
  }
464
-
465
- if (iterationCount >= MAX_ITERATIONS) {
466
- warnings.push(`Maximum parsing iterations (${MAX_ITERATIONS}) reached`);
467
- }
468
520
  } catch (error) {
469
521
  warnings.push(`Failed to parse props: ${error.message}`);
470
522
  }
471
523
 
472
- return { props, warnings };
524
+ return { props, warnings, spreadProps, originalSyntax };
473
525
  }
474
526
 
475
527
  // Migrate props from old to new format
@@ -487,11 +539,12 @@ function migrateProps(props, oldComponent) {
487
539
  `Migrated 'margin="${props.margin}"' to 'spacing="${newSpacing}"'`,
488
540
  );
489
541
  } else {
490
- // Unknown margin value - keep as is but warn
491
- migratedProps.spacing = props.margin;
542
+ // Unknown margin value - suggest alternatives
543
+ const suggestions = getSpacingSuggestions(props.margin);
544
+ migratedProps.spacing = props.margin; // Keep original value for now
492
545
  delete migratedProps.margin;
493
546
  warnings.push(
494
- `Migrated 'margin="${props.margin}"' to 'spacing="${props.margin}"' (unknown value - may need manual review)`,
547
+ `Migrated 'margin="${props.margin}"' to 'spacing="${props.margin}"' (unknown value). ${suggestions}`,
495
548
  );
496
549
  }
497
550
  }
@@ -518,8 +571,55 @@ function migrateProps(props, oldComponent) {
518
571
  return { props: migratedProps, warnings };
519
572
  }
520
573
 
574
+ // Helper function to suggest spacing alternatives for unknown margin values
575
+ function getSpacingSuggestions(unknownMargin) {
576
+ const suggestions = [];
577
+
578
+ // Check if it might be one of the old margin values
579
+ if (['top', 'bottom', 'both', 'none'].includes(unknownMargin)) {
580
+ suggestions.push(
581
+ `"${unknownMargin}" is a valid old margin value and will be migrated correctly.`,
582
+ );
583
+ return suggestions.join(' ');
584
+ }
585
+
586
+ // Check if it might be a directional value
587
+ if (
588
+ unknownMargin.includes('top') ||
589
+ unknownMargin.includes('bottom') ||
590
+ unknownMargin.includes('left') ||
591
+ unknownMargin.includes('right')
592
+ ) {
593
+ suggestions.push(
594
+ 'Consider using directional spacing like "md-top", "sm-bottom", etc.',
595
+ );
596
+ }
597
+
598
+ // Check if it might be a size value
599
+ if (['xs', 'sm', 'md', 'lg', 'xl'].includes(unknownMargin)) {
600
+ suggestions.push(
601
+ 'Consider using size-based spacing like "xs", "sm", "md", "lg", "xl".',
602
+ );
603
+ }
604
+
605
+ // Check if it might be a specific variant
606
+ if (unknownMargin.includes('xs2')) {
607
+ suggestions.push(
608
+ 'Consider using "xs2", "xs2-top", or "xs2-bottom" for extra small spacing.',
609
+ );
610
+ }
611
+
612
+ if (suggestions.length === 0) {
613
+ suggestions.push(
614
+ 'Old margin values: "none", "top", "bottom", "both". New spacing values: "xs", "sm", "md", "lg", "xl", and directional variants.',
615
+ );
616
+ }
617
+
618
+ return suggestions.join(' ');
619
+ }
620
+
521
621
  // Convert props object back to JSX string
522
- function propsToString(props) {
622
+ function propsToString(props, originalSyntax = {}) {
523
623
  if (!props || Object.keys(props).length === 0) {
524
624
  return '';
525
625
  }
@@ -528,20 +628,46 @@ function propsToString(props) {
528
628
  ' ' +
529
629
  Object.entries(props)
530
630
  .map(([key, value]) => {
531
- // Handle different value types
532
- if (typeof value === 'string' && !value.includes('{')) {
631
+ // Use original syntax information if available
632
+ if (originalSyntax[key] === 'string') {
533
633
  return `${key}="${value}"`;
534
- } else if (
535
- typeof value === 'string' &&
536
- value.startsWith('{') &&
537
- value.endsWith('}')
538
- ) {
539
- // Already a JSX object, don't add extra braces
540
- return `${key}={${value}}`;
634
+ } else if (originalSyntax[key] === 'jsx') {
635
+ // Check if the JSX expression is actually a string literal (e.g., 'a' or "a")
636
+ if (
637
+ typeof value === 'string' &&
638
+ ((value.startsWith("'") &&
639
+ value.endsWith("'") &&
640
+ value.length > 1) ||
641
+ (value.startsWith('"') &&
642
+ value.endsWith('"') &&
643
+ value.length > 1))
644
+ ) {
645
+ // It's a string literal in JSX, convert to string prop
646
+ const stringValue = value.slice(1, -1); // Remove quotes
647
+ return `${key}="${stringValue}"`;
648
+ } else {
649
+ // It's a real JSX expression, keep as is
650
+ return `${key}={${value}}`;
651
+ }
652
+ } else if (originalSyntax[key] === 'boolean') {
653
+ return value ? key : '';
541
654
  } else {
542
- return `${key}={${value}}`;
655
+ // Fallback logic for when originalSyntax is not available
656
+ if (typeof value === 'string' && !value.includes('{')) {
657
+ return `${key}="${value}"`;
658
+ } else if (
659
+ typeof value === 'string' &&
660
+ value.startsWith('{') &&
661
+ value.endsWith('}')
662
+ ) {
663
+ // Already a JSX object, don't add extra braces
664
+ return `${key}={${value}}`;
665
+ } else {
666
+ return `${key}={${value}}`;
667
+ }
543
668
  }
544
669
  })
670
+ .filter(prop => prop.length > 0) // Remove empty props (like false booleans)
545
671
  .join(' ')
546
672
  );
547
673
  }
@@ -551,40 +677,113 @@ function updateImports(content) {
551
677
  let updatedContent = content;
552
678
  let changes = 0;
553
679
 
554
- // First, update import paths
555
- IMPORT_PATTERNS.forEach(pattern => {
556
- const matches = content.match(pattern) || [];
557
- changes += matches.length;
558
- updatedContent = updatedContent.replace(pattern, `from '${BETA_IMPORT}'`);
559
- });
680
+ // Handle CSS imports separately - these should stay with the main package
681
+ const cssImportPattern = /from\s+['"`]@entur\/typography\/styles['"`]/g;
682
+ if (cssImportPattern.test(content)) {
683
+ // CSS imports should remain unchanged as they're still in the main package
684
+ // No changes needed for CSS imports
685
+ }
560
686
 
561
- // Then, update destructured import names - only within @entur/typography imports
562
- // Find all import statements from @entur/typography and update component names
687
+ // Handle component imports - only migrate if they contain components that need migration
563
688
  const importRegex =
564
689
  /import\s*{([^}]+)}\s*from\s*['"']@entur\/typography['"']/g;
565
690
 
566
691
  updatedContent = updatedContent.replace(importRegex, (match, importList) => {
692
+ const components = importList
693
+ .split(',')
694
+ .map(comp => comp.trim())
695
+ .filter(comp => comp);
696
+
697
+ // Check if any of the imported components need migration
698
+ // Need to check both the full import (e.g., "Link as Li") and just the component name (e.g., "Link")
699
+ const needsMigration = components.some(comp => {
700
+ // Extract the actual component name from aliased imports
701
+ const componentName = comp.includes(' as ')
702
+ ? comp.split(' as ')[0].trim()
703
+ : comp;
704
+ return Object.keys(COMPONENT_MAPPING).includes(componentName);
705
+ });
706
+
707
+ if (!needsMigration) {
708
+ // No migration needed, keep the import as is
709
+ return match;
710
+ }
711
+
567
712
  let updatedImportList = importList;
568
713
  let hasChanges = false;
569
714
  const uniqueComponents = new Set();
715
+ const processedComponents = new Set(); // Track which components we've already processed
716
+
717
+ // First, collect all existing components that should be preserved
718
+ const existingComponents = components.filter(comp => {
719
+ // Extract the actual component name from aliased imports
720
+ const componentName = comp.includes(' as ')
721
+ ? comp.split(' as ')[0].trim()
722
+ : comp;
723
+
724
+ // Keep components that are:
725
+ // 1. Not in the migration mapping (old components), OR
726
+ // 2. Are the target components (new beta components)
727
+ const isOldComponent =
728
+ Object.keys(COMPONENT_MAPPING).includes(componentName);
729
+ const isTargetComponent = Object.values(COMPONENT_MAPPING).some(
730
+ mapping => mapping.component === componentName,
731
+ );
570
732
 
571
- // Check each component in the import list
733
+ return !isOldComponent || isTargetComponent;
734
+ });
735
+
736
+ // Then, update components that need migration
572
737
  Object.entries(COMPONENT_MAPPING).forEach(([oldComponent, mapping]) => {
573
- const componentRegex = new RegExp(`\\b${oldComponent}\\b`, 'g');
574
- if (componentRegex.test(updatedImportList)) {
575
- updatedImportList = updatedImportList.replace(
576
- componentRegex,
577
- mapping.component,
578
- );
579
- uniqueComponents.add(mapping.component);
580
- hasChanges = true;
738
+ // Check if this old component exists in any form (aliased or not)
739
+ const hasComponent = components.some(comp => {
740
+ const componentName = comp.includes(' as ')
741
+ ? comp.split(' as ')[0].trim()
742
+ : comp;
743
+ return componentName === oldComponent;
744
+ });
745
+
746
+ if (hasComponent && !processedComponents.has(oldComponent)) {
747
+ // Replace the old component with the new one, preserving aliases
748
+ const componentRegex = new RegExp(`\\b${oldComponent}\\b`, 'g');
749
+ if (componentRegex.test(updatedImportList)) {
750
+ updatedImportList = updatedImportList.replace(
751
+ componentRegex,
752
+ mapping.component,
753
+ );
754
+
755
+ // Only add to uniqueComponents if the component isn't already present in aliased form
756
+ const componentAlreadyPresent = components.some(comp => {
757
+ const componentName = comp.includes(' as ')
758
+ ? comp.split(' as ')[0].trim()
759
+ : comp;
760
+ return componentName === mapping.component;
761
+ });
762
+
763
+ if (!componentAlreadyPresent) {
764
+ uniqueComponents.add(mapping.component);
765
+ }
766
+
767
+ processedComponents.add(oldComponent);
768
+ hasChanges = true;
769
+ }
581
770
  }
582
771
  });
583
772
 
584
773
  if (hasChanges) {
585
774
  changes++;
586
- // Deduplicate components and create clean import statement
587
- const finalImportList = Array.from(uniqueComponents).join(', ');
775
+ // Combine existing components with migrated components
776
+ const allComponents = [
777
+ ...existingComponents,
778
+ ...Array.from(uniqueComponents),
779
+ ];
780
+
781
+ // Filter out any empty components and deduplicate
782
+ const finalComponents = allComponents
783
+ .filter(comp => comp && comp.trim())
784
+ .filter((comp, index, arr) => arr.indexOf(comp) === index); // Remove duplicates
785
+
786
+ const finalImportList = finalComponents.join(', ');
588
787
  return `import {${finalImportList}} from '${BETA_IMPORT}'`;
589
788
  }
590
789
 
@@ -610,8 +809,12 @@ function updateComponents(content) {
610
809
  changes++;
611
810
 
612
811
  // Parse existing props
613
- const { props: existingProps, warnings: parseWarnings } =
614
- parseJSXProps(propsString);
812
+ const {
813
+ props: existingProps,
814
+ warnings: parseWarnings,
815
+ spreadProps,
816
+ originalSyntax,
817
+ } = parseJSXProps(propsString);
615
818
  warnings.push(...parseWarnings);
616
819
 
617
820
  // Migrate props
@@ -631,8 +834,46 @@ function updateComponents(content) {
631
834
 
632
835
  // Handle Heading components
633
836
  if (mapping.component === 'Heading') {
634
- const asValue = newProps.as || mapping.as;
635
- const variantValue = newProps.variant || mapping.variant;
837
+ // Preserve existing 'as' prop if it exists, otherwise use mapping default
838
+ const asValue = existingProps.as || mapping.as;
839
+ // Preserve existing 'variant' prop if it exists, otherwise use mapping default
840
+ const variantValue = existingProps.variant || mapping.variant;
841
+
842
+ // Remove as and variant from props since we'll add them separately
843
+ delete newProps.as;
844
+ delete newProps.variant;
845
+
846
+ // Create props object with as and variant, preserving original syntax
847
+ const headingProps = {
848
+ as: asValue,
849
+ variant: variantValue,
850
+ ...newProps,
851
+ };
852
+
853
+ // Create original syntax object for as and variant
854
+ const headingOriginalSyntax = {
855
+ as: originalSyntax.as || 'string',
856
+ variant: originalSyntax.variant || 'string',
857
+ ...originalSyntax,
858
+ };
859
+
860
+ const propsString = propsToString(
861
+ headingProps,
862
+ headingOriginalSyntax,
863
+ );
864
+ const spreadPropsString =
865
+ spreadProps.length > 0 ? ` {...${spreadProps.join(', ...')}}` : '';
866
+ return `<Heading${propsString}${spreadPropsString}>`;
867
+ }
868
+
869
+ // Handle Label components with special case for htmlFor prop
870
+ if (oldComponent === 'Label' && mapping.component === 'Text') {
871
+ // If htmlFor prop exists, add as="label" to ensure proper semantic HTML
872
+ // Otherwise, use existing as prop or default to undefined (no as prop)
873
+ const asValue = existingProps.htmlFor
874
+ ? 'label'
875
+ : existingProps.as || undefined;
876
+ const variantValue = existingProps.variant || mapping.variant;
636
877
 
637
878
  // Remove as and variant from props since we'll add them separately
638
879
  delete newProps.as;
@@ -646,8 +887,13 @@ function updateComponents(content) {
646
887
  }
647
888
  Object.assign(orderedProps, newProps);
648
889
 
649
- const propsString = propsToString(orderedProps);
650
- return `<Heading as="${asValue}" variant="${variantValue}"${propsString}>`;
890
+ const propsString = propsToString(orderedProps, originalSyntax);
891
+ const spreadPropsString =
892
+ spreadProps.length > 0 ? ` {...${spreadProps.join(', ...')}}` : '';
893
+
894
+ // Only add as prop if it has a value
895
+ const asProp = asValue ? ` as="${asValue}"` : '';
896
+ return `<Text${asProp} variant="${variantValue}"${propsString}${spreadPropsString}>`;
651
897
  }
652
898
 
653
899
  // Handle other components
@@ -669,8 +915,10 @@ function updateComponents(content) {
669
915
  });
670
916
  Object.assign(finalProps, newProps);
671
917
 
672
- const otherPropsString = propsToString(finalProps);
673
- return `<${componentName}${otherPropsString}>`;
918
+ const otherPropsString = propsToString(finalProps, originalSyntax);
919
+ const spreadPropsString =
920
+ spreadProps.length > 0 ? ` {...${spreadProps.join(', ...')}}` : '';
921
+ return `<${componentName}${otherPropsString}${spreadPropsString}>`;
674
922
  },
675
923
  );
676
924
 
@@ -698,15 +946,33 @@ function updateComponents(content) {
698
946
  * @returns {string[]} Array of matching file paths
699
947
  */
700
948
  function findFiles(pattern) {
701
- // Create a single glob pattern that covers all allowed directories
702
- // Uses brace expansion: {src,app,components}/**/*.{ts,tsx,js,jsx}
703
- const combinedPattern = `{${ALLOWED_DIRECTORIES.join(',')}}/${pattern}`;
704
-
705
- // Use a single glob call instead of multiple calls
706
- const allFiles = glob.sync(combinedPattern, {
707
- ignore: BLOCKED_DIRECTORIES,
708
- nodir: true,
709
- absolute: false,
949
+ const allFiles = [];
950
+
951
+ // Process directory patterns
952
+ const directoryPatterns = ALLOWED_DIRECTORIES.filter(dir =>
953
+ dir.includes('**'),
954
+ );
955
+ const filePatterns = ALLOWED_DIRECTORIES.filter(dir => !dir.includes('**'));
956
+
957
+ // Handle directory patterns (e.g., src/**, app/**)
958
+ if (directoryPatterns.length > 0) {
959
+ const combinedDirPattern = `{${directoryPatterns.join(',')}}/${pattern}`;
960
+ const dirFiles = glob.sync(combinedDirPattern, {
961
+ ignore: BLOCKED_DIRECTORIES,
962
+ nodir: true,
963
+ absolute: false,
964
+ });
965
+ allFiles.push(...dirFiles);
966
+ }
967
+
968
+ // Handle file patterns (e.g., *.jsx, *.tsx)
969
+ filePatterns.forEach(filePattern => {
970
+ const files = glob.sync(filePattern, {
971
+ ignore: BLOCKED_DIRECTORIES,
972
+ nodir: true,
973
+ absolute: false,
974
+ });
975
+ allFiles.push(...files);
710
976
  });
711
977
 
712
978
  // Use Set for efficient deduplication and filtering
@@ -727,37 +993,29 @@ function findFiles(pattern) {
727
993
  return uniqueFiles;
728
994
  }
729
995
 
730
- function updateImportsAndComponents(content, strategy) {
996
+ function updateImportsAndComponents(content) {
731
997
  let updatedContent = content;
732
998
  let changes = 0;
733
999
  let warnings = [];
734
1000
 
735
- if (strategy === 'import-only') {
736
- // Only update imports
737
- const { content: newContent, changes: importChanges } =
738
- updateImports(content);
739
- updatedContent = newContent;
740
- changes = importChanges;
741
- } else if (strategy === 'complete') {
742
- // Update both imports and components
743
- const { content: newContent, changes: importChanges } =
744
- updateImports(content);
745
- const {
746
- content: finalContent,
747
- changes: componentChanges,
748
- warnings: componentWarnings,
749
- } = updateComponents(newContent);
750
- updatedContent = finalContent;
751
- changes = importChanges + componentChanges;
752
- warnings = componentWarnings;
753
- }
1001
+ // Update both imports and components
1002
+ const { content: newContent, changes: importChanges } =
1003
+ updateImports(content);
1004
+ const {
1005
+ content: finalContent,
1006
+ changes: componentChanges,
1007
+ warnings: componentWarnings,
1008
+ } = updateComponents(newContent);
1009
+ updatedContent = finalContent;
1010
+ changes = importChanges + componentChanges;
1011
+ warnings = componentWarnings;
754
1012
 
755
1013
  return { content: updatedContent, changes, warnings };
756
1014
  }
757
1015
 
758
- function generateMigrationReport(files, strategy, isDryRun = false) {
1016
+ function generateMigrationReport(files, isDryRun = false) {
759
1017
  const report = {
760
- strategy,
1018
+ strategy: 'complete',
761
1019
  totalFiles: files.length,
762
1020
  migratedFiles: 0,
763
1021
  totalChanges: 0,
@@ -778,7 +1036,7 @@ function generateMigrationReport(files, strategy, isDryRun = false) {
778
1036
  content: updatedContent,
779
1037
  changes,
780
1038
  warnings,
781
- } = updateImportsAndComponents(content, strategy);
1039
+ } = updateImportsAndComponents(content);
782
1040
 
783
1041
  // Combine migration warnings with file analysis warnings
784
1042
  const allWarnings = [...warnings, ...fileAnalysis.warnings];
@@ -1008,23 +1266,13 @@ function printReport(report) {
1008
1266
  }
1009
1267
  }
1010
1268
 
1011
- function showNextSteps(strategy) {
1269
+ function showNextSteps() {
1012
1270
  console.log('\n📝 Next Steps');
1013
1271
  console.log('=============');
1014
1272
 
1015
- if (strategy === 'import-only') {
1016
- console.log('1. Import statements updated');
1017
- console.log('2. 🔄 Update component usage manually when ready:');
1018
- Object.entries(COMPONENT_MAPPING).forEach(([old, new_]) => {
1019
- console.log(` ${old} → ${new_}`);
1020
- });
1021
- console.log('3. 🧪 Test your application');
1022
- console.log('4. 📚 Read the migration guide on our website');
1023
- } else if (strategy === 'complete') {
1024
- console.log('1. 🧪 Test your application thoroughly');
1025
- console.log('2. 🔄 Review and adjust any component props if needed');
1026
- console.log('3. 📚 Read the migration guide on our website');
1027
- }
1273
+ console.log('1. 🧪 Test your application thoroughly');
1274
+ console.log('2. 🔄 Review and adjust any component props if needed');
1275
+ console.log('3. 📚 Read the migration guide on our website');
1028
1276
 
1029
1277
  console.log('\n⚠️ Important Notes:');
1030
1278
  console.log('- Check warnings above for potential issues');
@@ -1032,7 +1280,7 @@ function showNextSteps(strategy) {
1032
1280
  console.log('- Test thoroughly, especially components with custom styling');
1033
1281
  }
1034
1282
 
1035
- function main() {
1283
+ async function main() {
1036
1284
  // Show help if requested
1037
1285
  if (process.argv.includes('--help') || process.argv.includes('-h')) {
1038
1286
  console.log('🎨 Typography Migration Script');
@@ -1053,36 +1301,24 @@ function main() {
1053
1301
  console.log(
1054
1302
  ' --dry-run Show what would be changed without modifying files',
1055
1303
  );
1056
- console.log(
1057
- ' --import-only Import-only migration: update import paths only',
1058
- );
1059
-
1060
1304
  console.log(' --help, -h Show this help message');
1061
1305
  console.log('');
1062
- console.log('Migration Modes:');
1063
- console.log(' 🚀 Complete Mode (default): Updates everything');
1306
+ console.log('Migration Mode:');
1307
+ console.log(' 🚀 Complete Mode: Updates everything');
1064
1308
  console.log(' - Replaces old components with beta components');
1065
- console.log(' - May require prop/styling updates');
1309
+ console.log(' - Heading1-6 Heading with as/variant props');
1310
+ console.log(' - Text components → Text with variant props');
1311
+ console.log(' - All typography components migrate to beta versions');
1312
+ console.log(' - Import paths change to @entur/typography/beta');
1313
+ console.log(' - May require prop/styling updates (margin → spacing)');
1066
1314
  console.log(' - Test thoroughly after migration');
1067
1315
  console.log('');
1068
- console.log(
1069
- ' 📝 Import-Only Mode (--import-only): Only updates import paths',
1070
- );
1071
- console.log(' - Keeps your existing component usage unchanged');
1072
- console.log(' - Minimal risk, gradual migration');
1073
- console.log('');
1074
1316
  console.log('Examples:');
1075
1317
  console.log(' # See what would be changed');
1076
1318
  console.log(' npx @entur/typography@latest migrate --dry-run');
1077
1319
  console.log('');
1078
1320
  console.log(' # Complete migration: update everything (default)');
1079
1321
  console.log(' npx @entur/typography@latest migrate');
1080
- console.log('');
1081
- console.log(' # Import-only migration: update import paths only');
1082
- console.log(' npx @entur/typography@latest migrate --import-only');
1083
- console.log('');
1084
-
1085
- console.log('');
1086
1322
 
1087
1323
  console.log('Environment Variables:');
1088
1324
  console.log(
@@ -1098,7 +1334,7 @@ function main() {
1098
1334
  console.log(' Add/remove folder patterns between the 👇 and 👆 markers');
1099
1335
  console.log(' Examples: "src/**", "app/**", "packages/my-app/**"');
1100
1336
  console.log('');
1101
- console.log(' Option 2: Set environment variable (for CI/CD)');
1337
+ console.log(' Option 2: Set environment variable');
1102
1338
  console.log(
1103
1339
  ' export TYPOGRAPHY_MIGRATION_DIRS="src/**,app/**,components/**"',
1104
1340
  );
@@ -1166,47 +1402,48 @@ function main() {
1166
1402
 
1167
1403
  // Parse command line options
1168
1404
  const isDryRun = process.argv.includes('--dry-run');
1169
- const isImportOnly = process.argv.includes('--import-only');
1170
1405
 
1171
1406
  if (isDryRun) {
1172
1407
  console.log('🔍 DRY RUN MODE: No files will be modified');
1173
1408
  console.log('');
1174
1409
  }
1175
1410
 
1176
- if (isImportOnly) {
1177
- console.log('📝 IMPORT-ONLY MIGRATION: Updating import paths only');
1178
- console.log(' - Your component usage will remain unchanged');
1179
- console.log(' - You can update components manually later');
1180
- } else {
1181
- console.log('🚀 COMPLETE MIGRATION: Updating imports + component usage');
1182
- console.log('⚠️ WARNING: This will modify your component usage!');
1183
- console.log(' - Old components will be replaced with beta components');
1184
- console.log(' - You may need to update props and styling');
1185
- console.log(' - Test thoroughly after migration');
1186
- console.log(' (Use --import-only for import-only migration)');
1187
- }
1411
+ console.log('🚀 COMPLETE MIGRATION: Updating imports + component usage');
1412
+ console.log('⚠️ WARNING: This will modify your component usage!');
1413
+ console.log(' - Old components will be replaced with beta components');
1414
+ console.log(' - Import paths will change to @entur/typography/beta');
1415
+ console.log(
1416
+ ' - Link Link, Blockquote Blockquote, Lists → List components (from beta)',
1417
+ );
1418
+ console.log(
1419
+ ' - List components UnorderedList, NumberedList, ListItem (from beta)',
1420
+ );
1421
+ console.log(' - Props may change (margin → spacing)');
1422
+ console.log(' - Test thoroughly after migration');
1188
1423
 
1189
1424
  console.log('');
1190
1425
 
1191
1426
  // Perform migration
1192
- const report = generateMigrationReport(
1193
- allFiles,
1194
- isImportOnly ? 'import-only' : 'complete',
1195
- isDryRun,
1196
- );
1427
+ const report = generateMigrationReport(allFiles, isDryRun);
1197
1428
  printReport(report);
1198
- showNextSteps(isImportOnly ? 'import-only' : 'complete');
1429
+ showNextSteps();
1199
1430
 
1200
1431
  console.log('\n🎯 Migration complete!');
1201
1432
  }
1202
1433
 
1203
- if (require.main === module) {
1204
- main();
1434
+ // Check if this module is being run directly
1435
+ if (import.meta.url === `file://${process.argv[1]}`) {
1436
+ main().catch(error => {
1437
+ console.error('❌ Migration failed:', error);
1438
+ process.exit(1);
1439
+ });
1205
1440
  }
1206
1441
 
1207
- module.exports = {
1442
+ export {
1208
1443
  updateImportsAndComponents,
1209
1444
  generateMigrationReport,
1210
1445
  COMPONENT_MAPPING,
1211
1446
  PROPS_MAPPING,
1447
+ parseJSXProps,
1448
+ propsToString,
1212
1449
  };