@entur/typography 1.9.13 → 1.10.0-beta.10

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 (43) hide show
  1. package/README.md +101 -4
  2. package/dist/BaseHeading.d.ts +18 -0
  3. package/dist/Blockquote.d.ts +12 -0
  4. package/dist/CodeText.d.ts +16 -0
  5. package/dist/EmphasizedText.d.ts +20 -0
  6. package/dist/Heading1.d.ts +20 -0
  7. package/dist/Heading2.d.ts +20 -0
  8. package/dist/Heading3.d.ts +20 -0
  9. package/dist/Heading4.d.ts +20 -0
  10. package/dist/Heading5.d.ts +20 -0
  11. package/dist/Heading6.d.ts +20 -0
  12. package/dist/Label.d.ts +20 -0
  13. package/dist/LeadParagraph.d.ts +20 -0
  14. package/dist/Link.d.ts +22 -0
  15. package/dist/ListItem.d.ts +11 -0
  16. package/dist/NumberedList.d.ts +8 -0
  17. package/dist/Paragraph.d.ts +20 -0
  18. package/dist/PreformattedText.d.ts +16 -0
  19. package/dist/SmallText.d.ts +20 -0
  20. package/dist/StrongText.d.ts +20 -0
  21. package/dist/SubLabel.d.ts +20 -0
  22. package/dist/SubParagraph.d.ts +20 -0
  23. package/dist/UnorderedList.d.ts +8 -0
  24. package/dist/beta/BlockquoteBeta.d.ts +12 -0
  25. package/dist/beta/Heading.d.ts +21 -0
  26. package/dist/beta/LinkBeta.d.ts +16 -0
  27. package/dist/beta/Text.d.ts +21 -0
  28. package/dist/beta/index.d.ts +6 -0
  29. package/dist/beta/types.d.ts +5 -0
  30. package/dist/beta/utils.d.ts +10 -0
  31. package/dist/index.d.ts +26 -426
  32. package/dist/index.js +8 -0
  33. package/dist/styles.css +838 -2
  34. package/dist/typography.cjs.development.js +528 -0
  35. package/dist/typography.cjs.development.js.map +1 -0
  36. package/dist/typography.cjs.production.min.js +2 -0
  37. package/dist/typography.cjs.production.min.js.map +1 -0
  38. package/dist/typography.esm.js +474 -392
  39. package/dist/typography.esm.js.map +1 -1
  40. package/package.json +21 -25
  41. package/scripts/migrate-typography.js +1259 -0
  42. package/dist/typography.cjs.js +0 -416
  43. package/dist/typography.cjs.js.map +0 -1
@@ -0,0 +1,1259 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Typography Migration Script
5
+ *
6
+ * This script helps you migrate from old typography components to new beta typography.
7
+ *
8
+ * MIGRATION MODES:
9
+ *
10
+ * šŸš€ Complete Mode (default):
11
+ * - Updates import paths AND component usage
12
+ * - Replaces old components with beta components
13
+ * - CONSEQUENCES:
14
+ * * <Heading1> becomes <Heading as="h1" variant="title-1">
15
+ * * <Paragraph> becomes <Text variant="paragraph">
16
+ * * <Link> becomes <LinkBeta>
17
+ * * Props may need updates (e.g., different prop names)
18
+ * * Styling classes may change
19
+ * * Test thoroughly after migration!
20
+ *
21
+
22
+ *
23
+ * Usage:
24
+ * 1. Run this script in your project root
25
+ * 2. Choose your migration mode (complete)
26
+ * 3. Update your styles as needed
27
+ * 4. Test your application thoroughly
28
+ *
29
+ * Options:
30
+ * --dry-run Show what would be changed without modifying files
31
+ *
32
+ * Environment Variables:
33
+ * TYPOGRAPHY_MIGRATION_DIRS Comma-separated list of directories to scan
34
+ * Example: "src/**,app/**"
35
+ *
36
+ * Security Features:
37
+ * - Only scans allowed directories (src/**, app/**, etc.)
38
+ * - Never scans node_modules, dist, build, .git, etc.
39
+ * - Dry-run mode for safe testing
40
+ * - Path validation prevents directory traversal attacks
41
+ *
42
+ */
43
+
44
+ const fs = require('fs');
45
+ const path = require('path');
46
+
47
+ // Check if glob is available
48
+ let glob;
49
+ try {
50
+ glob = require('glob');
51
+ } catch (error) {
52
+ console.error(
53
+ 'āŒ Error: The "glob" package is required to run this migration script.',
54
+ );
55
+ console.error('');
56
+ console.error('Please install it:');
57
+ console.error(' npm install glob');
58
+ console.error(' yarn add glob');
59
+ console.error('');
60
+ console.error('Or use npx which will handle dependencies automatically:');
61
+ console.error(' npx @entur/typography@latest migrate');
62
+ console.error('');
63
+ process.exit(1);
64
+ }
65
+
66
+ // Configuration
67
+ const OLD_IMPORT = '@entur/typography';
68
+ const BETA_IMPORT = '@entur/typography';
69
+
70
+ // Enhanced warning detection patterns - only truly problematic patterns
71
+ const PROBLEMATIC_PATTERNS = {
72
+ // Style conflicts that will cause issues
73
+ styleMarginConflict: /style=.*margin=/g,
74
+ styleSpacingConflict: /style=.*spacing=/g,
75
+
76
+ // Invalid HTML structure
77
+ nestedTypography: /<Text[^>]*>.*<Text[^>]*>/g,
78
+
79
+ // Accessibility issues
80
+ missingAsProps: /<Heading[^>]*>(?!.*\bas=)/g,
81
+
82
+ // Semantic HTML mismatches
83
+ semanticMismatch: /<Heading[^>]*as="([^"]*)"[^>]*variant="([^"]*)"/g,
84
+ };
85
+
86
+ // Warning severity levels
87
+ const WARNING_CATEGORIES = {
88
+ CRITICAL: 'critical', // Will break functionality
89
+ HIGH: 'high', // Likely to cause issues
90
+ MEDIUM: 'medium', // May cause styling issues
91
+ LOW: 'low', // Best practice suggestions
92
+ INFO: 'info', // Informational only
93
+ };
94
+
95
+ // =============================================================================
96
+ // šŸŽÆ MIGRATION FOLDERS CONFIGURATION
97
+ // =============================================================================
98
+ //
99
+ // EDIT THIS SECTION TO CONTROL WHICH FOLDERS ARE SCANNED
100
+ //
101
+ // ADD FOLDERS: Add new patterns to scan additional directories
102
+ // REMOVE FOLDERS: Delete patterns you don't want to scan
103
+ // CLEAR ALL: Remove all patterns to scan only what you add
104
+ //
105
+ // Examples:
106
+ // 'src/**' - Scan src folder and all subdirectories
107
+ // 'app/**' - Scan app folder and all subdirectories
108
+ // 'packages/my-app/**' - Scan specific package
109
+ // 'frontend/**' - Scan frontend directory
110
+ // 'shared/**' - Scan shared components
111
+ // 'components/**' - Scan components folder
112
+ //
113
+ // =============================================================================
114
+
115
+ const MIGRATION_FOLDERS = [
116
+ // šŸ‘‡ ADD YOUR FOLDERS HERE šŸ‘‡
117
+ 'src/**',
118
+ 'app/**',
119
+ 'apps/**',
120
+ 'components/**',
121
+ 'pages/**',
122
+ 'lib/**',
123
+ 'utils/**',
124
+ 'styles/**',
125
+ 'css/**',
126
+ 'scss/**',
127
+ // šŸ‘† ADD YOUR FOLDERS ABOVE šŸ‘†
128
+ ];
129
+
130
+ // =============================================================================
131
+
132
+ // Validate and sanitize directory input for security
133
+ function validateDirectoryPath(dir) {
134
+ return !path.isAbsolute(dir) && !dir.includes('..') && !dir.includes('~');
135
+ }
136
+
137
+ // Enhanced file analysis for better warning detection - only truly problematic patterns
138
+ function analyzeFile(filePath, content) {
139
+ const analysis = {
140
+ hasStyleConflicts: false,
141
+ hasNestedTypography: false,
142
+ hasAccessibilityIssues: false,
143
+ hasSemanticMismatches: false,
144
+ lineNumbers: {},
145
+ suggestions: [],
146
+ warnings: [],
147
+ };
148
+
149
+ // Line-by-line analysis for better context
150
+ content.split('\n').forEach((line, index) => {
151
+ const lineNum = index + 1;
152
+
153
+ // Check for style conflicts (style + margin/spacing)
154
+ if (
155
+ line.match(PROBLEMATIC_PATTERNS.styleMarginConflict) ||
156
+ line.match(PROBLEMATIC_PATTERNS.styleSpacingConflict)
157
+ ) {
158
+ analysis.hasStyleConflicts = true;
159
+ analysis.lineNumbers.styleConflicts = (
160
+ analysis.lineNumbers.styleConflicts || []
161
+ ).concat(lineNum);
162
+
163
+ // Generate warning message
164
+ analysis.warnings.push(
165
+ `Line ${lineNum}: Style conflicts detected - component has both style and margin/spacing props`,
166
+ );
167
+ }
168
+
169
+ // Check for nested typography components (invalid HTML)
170
+ if (line.match(PROBLEMATIC_PATTERNS.nestedTypography)) {
171
+ analysis.hasNestedTypography = true;
172
+ analysis.lineNumbers.nestedTypography = (
173
+ analysis.lineNumbers.nestedTypography || []
174
+ ).concat(lineNum);
175
+
176
+ // Generate warning message
177
+ analysis.warnings.push(
178
+ `Line ${lineNum}: Nested typography components detected - invalid HTML structure`,
179
+ );
180
+ }
181
+
182
+ // Check for missing as props (accessibility issue)
183
+ if (line.match(PROBLEMATIC_PATTERNS.missingAsProps)) {
184
+ analysis.hasAccessibilityIssues = true;
185
+ analysis.lineNumbers.missingAsProps = (
186
+ analysis.lineNumbers.missingAsProps || []
187
+ ).concat(lineNum);
188
+
189
+ // Generate warning message
190
+ analysis.warnings.push(
191
+ `Line ${lineNum}: Missing 'as' prop - accessibility issue for Heading component`,
192
+ );
193
+ }
194
+
195
+ // Check for semantic mismatches (e.g., h1 with subtitle variant)
196
+ if (line.match(PROBLEMATIC_PATTERNS.semanticMismatch)) {
197
+ analysis.hasSemanticMismatches = true;
198
+ analysis.lineNumbers.semanticMismatches = (
199
+ analysis.lineNumbers.semanticMismatches || []
200
+ ).concat(lineNum);
201
+
202
+ // Generate warning message
203
+ analysis.warnings.push(
204
+ `Line ${lineNum}: Semantic mismatch detected - heading level and variant combination may be incorrect`,
205
+ );
206
+ }
207
+ });
208
+
209
+ return analysis;
210
+ }
211
+
212
+ // Generate enhanced warnings with context and solutions
213
+ function generateWarningWithSolution(warning, context, filePath, lineNumber) {
214
+ const severity = determineSeverity(warning);
215
+ const suggestion = generateSuggestion(warning, context);
216
+ const codeExample = generateCodeExample(warning);
217
+
218
+ return {
219
+ message: warning,
220
+ severity,
221
+ suggestion,
222
+ codeExample,
223
+ file: filePath,
224
+ line: lineNumber,
225
+ documentation: getRelevantDocs(warning),
226
+ };
227
+ }
228
+
229
+ // Determine warning severity based on content
230
+ function determineSeverity(warning) {
231
+ if (warning.includes('will break') || warning.includes('fatal'))
232
+ return WARNING_CATEGORIES.CRITICAL;
233
+ if (warning.includes('conflict') || warning.includes('override'))
234
+ return WARNING_CATEGORIES.HIGH;
235
+ if (warning.includes('may cause') || warning.includes('styling'))
236
+ return WARNING_CATEGORIES.MEDIUM;
237
+ if (warning.includes('best practice') || warning.includes('consider'))
238
+ return WARNING_CATEGORIES.LOW;
239
+ return WARNING_CATEGORIES.INFO;
240
+ }
241
+
242
+ // Generate actionable suggestions
243
+ function generateSuggestion(warning, context) {
244
+ if (warning.includes('style and margin')) {
245
+ return 'Remove the margin prop as it will be overridden by inline styles. Use spacing prop instead.';
246
+ }
247
+ if (warning.includes('missing variant')) {
248
+ return 'Add a variant prop to ensure consistent styling. Example: variant="title-1"';
249
+ }
250
+ if (warning.includes('nested typography')) {
251
+ return 'Avoid nesting Text components. Use spans or other inline elements for emphasis.';
252
+ }
253
+ if (warning.includes('deprecated margin')) {
254
+ return 'Replace margin prop with spacing prop for better consistency.';
255
+ }
256
+ return 'Review the component for potential styling conflicts.';
257
+ }
258
+
259
+ // Generate code examples for fixes
260
+ function generateCodeExample(warning) {
261
+ if (warning.includes('style and margin')) {
262
+ return '// Before: <Text style={{color: "red"}} margin="bottom">\n// After: <Text style={{color: "red"}} spacing="bottom">';
263
+ }
264
+ if (warning.includes('missing variant')) {
265
+ return '// Before: <Heading as="h1">Title</Heading>\n// After: <Heading as="h1" variant="title-1">Title</Heading>';
266
+ }
267
+ if (warning.includes('nested typography')) {
268
+ return '// Before: <Text>Hello <Text>World</Text></Text>\n// After: <Text>Hello <span>World</span></Text>';
269
+ }
270
+ return '';
271
+ }
272
+
273
+ // Get relevant documentation links
274
+ function getRelevantDocs(warning) {
275
+ if (warning.includes('variant'))
276
+ return 'https://linje.entur.no/komponenter/ressurser/typography-beta#heading-variants';
277
+ if (warning.includes('spacing'))
278
+ return 'https://linje.entur.no/komponenter/ressurser/typography-beta#spacing';
279
+ if (warning.includes('semantic'))
280
+ return 'https://linje.entur.no/komponenter/ressurser/typography-beta#semantic-html';
281
+ return 'https://linje.entur.no/komponenter/ressurser/typography-beta';
282
+ }
283
+
284
+ let ALLOWED_DIRECTORIES = process.env.TYPOGRAPHY_MIGRATION_DIRS
285
+ ? process.env.TYPOGRAPHY_MIGRATION_DIRS.split(',')
286
+ : MIGRATION_FOLDERS;
287
+
288
+ // Filter out potentially dangerous paths
289
+ ALLOWED_DIRECTORIES = ALLOWED_DIRECTORIES.filter(validateDirectoryPath);
290
+
291
+ if (ALLOWED_DIRECTORIES.length === 0) {
292
+ console.error(
293
+ 'āŒ Error: No valid migration directories found after security validation.',
294
+ );
295
+ console.error(
296
+ 'All directory paths must be relative and not contain ".." or "~".',
297
+ );
298
+ console.error('');
299
+ console.error('Valid examples:');
300
+ console.error(' src/**');
301
+ console.error(' app/**');
302
+ console.error(' components/**');
303
+ console.error('');
304
+ console.error('Invalid examples:');
305
+ console.error(' /absolute/path');
306
+ console.error(' ../parent/directory');
307
+ console.error(' ~/home/directory');
308
+ process.exit(1);
309
+ }
310
+
311
+ // Security: Block-list of directories to never scan
312
+ const BLOCKED_DIRECTORIES = [
313
+ '**/node_modules/**',
314
+ '**/dist/**',
315
+ '**/build/**',
316
+ '**/.git/**',
317
+ '**/coverage/**',
318
+ '**/.next/**',
319
+ '**/.nuxt/**',
320
+ '**/public/**',
321
+ '**/static/**',
322
+ '**/assets/**',
323
+ '**/images/**',
324
+ '**/fonts/**',
325
+ '**/vendor/**',
326
+ '**/temp/**',
327
+ '**/tmp/**',
328
+ ];
329
+
330
+ // Component mapping for complete migration
331
+ const COMPONENT_MAPPING = {
332
+ Heading1: { component: 'Heading', as: 'h1', variant: 'title-1' },
333
+ Heading2: { component: 'Heading', as: 'h2', variant: 'title-2' },
334
+ Heading3: { component: 'Heading', as: 'h3', variant: 'subtitle-1' },
335
+ Heading4: { component: 'Heading', as: 'h4', variant: 'subtitle-2' },
336
+ Heading5: { component: 'Heading', as: 'h5', variant: 'section-1' },
337
+ Heading6: { component: 'Heading', as: 'h6', variant: 'section-2' },
338
+ Paragraph: { component: 'Text', variant: 'paragraph' },
339
+ LeadParagraph: { component: 'Text', variant: 'leading' },
340
+ SmallText: { component: 'Text', variant: 'subparagraph' },
341
+ StrongText: { component: 'Text', as: 'strong', weight: 'bold' },
342
+ SubLabel: { component: 'Text', variant: 'sublabel' },
343
+ SubParagraph: { component: 'Text', variant: 'subparagraph' },
344
+ Label: { component: 'Text', variant: 'label' },
345
+ EmphasizedText: { component: 'Text', variant: 'emphasized' },
346
+ CodeText: { component: 'Text', variant: 'code-text' },
347
+ Link: { component: 'LinkBeta' }, // Convert Link to LinkBeta
348
+ };
349
+
350
+ // Props mapping for migration
351
+ const PROPS_MAPPING = {
352
+ margin: 'spacing',
353
+ };
354
+
355
+ // Spacing value mapping from old margin to new spacing
356
+ // Based on the actual CSS classes in src/beta/styles.scss
357
+ // and the old margin prop values: "top" | "bottom" | "both" | "none"
358
+ const SPACING_MAPPING = {
359
+ // Old margin values mapped to new spacing values
360
+ none: 'none', // No spacing
361
+ top: 'md-top', // Top margin only (medium size)
362
+ bottom: 'md-bottom', // Bottom margin only (medium size)
363
+ both: 'md', // Both top and bottom margins (medium size)
364
+
365
+ // Additional spacing values for more granular control
366
+ // These weren't in the old margin prop but are available in new spacing
367
+ left: 'md-left', // Left margin (medium size)
368
+ right: 'md-right', // Right margin (medium size)
369
+
370
+ // Size-based spacing (applies to both top and bottom)
371
+ xs: 'xs',
372
+ sm: 'sm',
373
+ md: 'md',
374
+ lg: 'lg',
375
+ xl: 'xl',
376
+
377
+ // Specific directional spacing with sizes
378
+ 'xs-top': 'xs-top',
379
+ 'xs-bottom': 'xs-bottom',
380
+ 'sm-top': 'sm-top',
381
+ 'sm-bottom': 'sm-bottom',
382
+ 'md-top': 'md-top',
383
+ 'md-bottom': 'md-bottom',
384
+ 'lg-top': 'lg-top',
385
+ 'lg-bottom': 'lg-bottom',
386
+ 'xl-top': 'xl-top',
387
+ 'xl-bottom': 'xl-bottom',
388
+
389
+ // Extra small variants
390
+ xs2: 'xs2',
391
+ 'xs2-top': 'xs2-top',
392
+ 'xs2-bottom': 'xs2-bottom',
393
+ };
394
+
395
+ // Import patterns to handle
396
+ const IMPORT_PATTERNS = [
397
+ /from\s+['"`]@entur\/typography['"`]/g,
398
+ /from\s+['"`]@entur\/typography\/dist['"`]/g,
399
+ /from\s+['"`]@entur\/typography\/dist\/index['"`]/g,
400
+ /from\s+['"`]@entur\/typography\/dist\/styles\.css['"`]/g,
401
+ ];
402
+
403
+ // Parse JSX props more robustly
404
+ function parseJSXProps(propsString) {
405
+ if (!propsString || !propsString.trim()) {
406
+ return { props: {}, warnings: [], spreadProps: [] };
407
+ }
408
+
409
+ const props = {};
410
+ const warnings = [];
411
+ const spreadProps = []; // Track spread props separately
412
+
413
+ try {
414
+ // Parse props manually to handle complex cases
415
+ let remaining = propsString.trim();
416
+
417
+ // First, extract all spread props
418
+ const spreadRegex = /\.\.\.\{?(\w+)\}?/g;
419
+ let spreadMatch;
420
+ while ((spreadMatch = spreadRegex.exec(remaining)) !== null) {
421
+ spreadProps.push(spreadMatch[1]);
422
+ }
423
+
424
+ // Remove spread props from the string to parse regular props
425
+ remaining = remaining.replace(/\.\.\.\{?(\w+)\}?/g, '');
426
+
427
+ // Now parse regular props
428
+ while (remaining.trim().length > 0) {
429
+ // Skip whitespace
430
+ remaining = remaining.replace(/^\s+/, '');
431
+
432
+ // Match prop name
433
+ const nameMatch = remaining.match(/^(\w+)=/);
434
+ if (!nameMatch) break;
435
+
436
+ const propName = nameMatch[1];
437
+ const matchLength = nameMatch[0].length;
438
+ remaining = remaining.substring(matchLength);
439
+
440
+ // Match prop value
441
+ if (remaining.startsWith('"') || remaining.startsWith("'")) {
442
+ // String value
443
+ const quote = remaining[0];
444
+ const endQuoteIndex = remaining.indexOf(quote, 1);
445
+ if (endQuoteIndex === -1) {
446
+ warnings.push(`Unterminated string in prop ${propName}`);
447
+ break;
448
+ }
449
+
450
+ const propValue = remaining.substring(1, endQuoteIndex);
451
+ props[propName] = propValue;
452
+ remaining = remaining.substring(endQuoteIndex + 1);
453
+ } else if (remaining.startsWith('{')) {
454
+ // Object value - find matching closing brace
455
+ let braceCount = 0;
456
+ let endIndex = -1;
457
+
458
+ for (let i = 0; i < remaining.length; i++) {
459
+ if (remaining[i] === '{') braceCount++;
460
+ if (remaining[i] === '}') {
461
+ braceCount--;
462
+ if (braceCount === 0) {
463
+ endIndex = i;
464
+ break;
465
+ }
466
+ }
467
+ }
468
+
469
+ if (endIndex === -1) {
470
+ warnings.push(`Unterminated object in prop ${propName}`);
471
+ break;
472
+ }
473
+
474
+ const propValue = remaining.substring(1, endIndex);
475
+ props[propName] = propValue;
476
+ remaining = remaining.substring(endIndex + 1);
477
+ } else {
478
+ // Boolean prop
479
+ props[propName] = true;
480
+ break;
481
+ }
482
+
483
+ // Skip whitespace
484
+ remaining = remaining.replace(/^\s+/, '');
485
+ }
486
+ } catch (error) {
487
+ warnings.push(`Failed to parse props: ${error.message}`);
488
+ }
489
+
490
+ return { props, warnings, spreadProps };
491
+ }
492
+
493
+ // Migrate props from old to new format
494
+ function migrateProps(props, oldComponent) {
495
+ const migratedProps = { ...props };
496
+ const warnings = [];
497
+
498
+ // Handle margin prop migration
499
+ if (props.margin) {
500
+ const newSpacing = SPACING_MAPPING[props.margin];
501
+ if (newSpacing) {
502
+ migratedProps.spacing = newSpacing;
503
+ delete migratedProps.margin;
504
+ warnings.push(
505
+ `Migrated 'margin="${props.margin}"' to 'spacing="${newSpacing}"'`,
506
+ );
507
+ } else {
508
+ // Unknown margin value - suggest alternatives
509
+ const suggestions = getSpacingSuggestions(props.margin);
510
+ migratedProps.spacing = props.margin; // Keep original value for now
511
+ delete migratedProps.margin;
512
+ warnings.push(
513
+ `Migrated 'margin="${props.margin}"' to 'spacing="${props.margin}"' (unknown value). ${suggestions}`,
514
+ );
515
+ }
516
+ }
517
+
518
+ // Handle Heading components with existing 'as' prop
519
+ if (oldComponent.startsWith('Heading') && props.as) {
520
+ const headingNumber = oldComponent.replace('Heading', '');
521
+ const expectedAs = `h${headingNumber}`;
522
+
523
+ if (props.as !== expectedAs) {
524
+ warnings.push(
525
+ `Heading component has 'as="${props.as}"' but expected 'as="${expectedAs}"' - review semantic HTML structure`,
526
+ );
527
+ }
528
+ }
529
+
530
+ // Handle style prop conflicts
531
+ if (props.style && props.margin) {
532
+ warnings.push(
533
+ `Component has both 'style' and 'margin' props - check for conflicts`,
534
+ );
535
+ }
536
+
537
+ return { props: migratedProps, warnings };
538
+ }
539
+
540
+ // Helper function to suggest spacing alternatives for unknown margin values
541
+ function getSpacingSuggestions(unknownMargin) {
542
+ const suggestions = [];
543
+
544
+ // Check if it might be one of the old margin values
545
+ if (['top', 'bottom', 'both', 'none'].includes(unknownMargin)) {
546
+ suggestions.push(
547
+ `"${unknownMargin}" is a valid old margin value and will be migrated correctly.`,
548
+ );
549
+ return suggestions.join(' ');
550
+ }
551
+
552
+ // Check if it might be a directional value
553
+ if (
554
+ unknownMargin.includes('top') ||
555
+ unknownMargin.includes('bottom') ||
556
+ unknownMargin.includes('left') ||
557
+ unknownMargin.includes('right')
558
+ ) {
559
+ suggestions.push(
560
+ 'Consider using directional spacing like "md-top", "sm-bottom", etc.',
561
+ );
562
+ }
563
+
564
+ // Check if it might be a size value
565
+ if (['xs', 'sm', 'md', 'lg', 'xl'].includes(unknownMargin)) {
566
+ suggestions.push(
567
+ 'Consider using size-based spacing like "xs", "sm", "md", "lg", "xl".',
568
+ );
569
+ }
570
+
571
+ // Check if it might be a specific variant
572
+ if (unknownMargin.includes('xs2')) {
573
+ suggestions.push(
574
+ 'Consider using "xs2", "xs2-top", or "xs2-bottom" for extra small spacing.',
575
+ );
576
+ }
577
+
578
+ if (suggestions.length === 0) {
579
+ suggestions.push(
580
+ 'Old margin values: "none", "top", "bottom", "both". New spacing values: "xs", "sm", "md", "lg", "xl", and directional variants.',
581
+ );
582
+ }
583
+
584
+ return suggestions.join(' ');
585
+ }
586
+
587
+ // Convert props object back to JSX string
588
+ function propsToString(props) {
589
+ if (!props || Object.keys(props).length === 0) {
590
+ return '';
591
+ }
592
+
593
+ return (
594
+ ' ' +
595
+ Object.entries(props)
596
+ .map(([key, value]) => {
597
+ // Handle different value types
598
+ if (typeof value === 'string' && !value.includes('{')) {
599
+ return `${key}="${value}"`;
600
+ } else if (
601
+ typeof value === 'string' &&
602
+ value.startsWith('{') &&
603
+ value.endsWith('}')
604
+ ) {
605
+ // Already a JSX object, don't add extra braces
606
+ return `${key}={${value}}`;
607
+ } else {
608
+ return `${key}={${value}}`;
609
+ }
610
+ })
611
+ .join(' ')
612
+ );
613
+ }
614
+
615
+ // Update imports in content
616
+ function updateImports(content) {
617
+ let updatedContent = content;
618
+ let changes = 0;
619
+
620
+ // First, update import paths
621
+ IMPORT_PATTERNS.forEach(pattern => {
622
+ const matches = content.match(pattern) || [];
623
+ changes += matches.length;
624
+ updatedContent = updatedContent.replace(pattern, `from '${BETA_IMPORT}'`);
625
+ });
626
+
627
+ // Then, update destructured import names - only within @entur/typography imports
628
+ // Find all import statements from @entur/typography and update component names
629
+ const importRegex =
630
+ /import\s*{([^}]+)}\s*from\s*['"']@entur\/typography['"']/g;
631
+
632
+ updatedContent = updatedContent.replace(importRegex, (match, importList) => {
633
+ let updatedImportList = importList;
634
+ let hasChanges = false;
635
+ const uniqueComponents = new Set();
636
+
637
+ // Check each component in the import list
638
+ Object.entries(COMPONENT_MAPPING).forEach(([oldComponent, mapping]) => {
639
+ const componentRegex = new RegExp(`\\b${oldComponent}\\b`, 'g');
640
+ if (componentRegex.test(updatedImportList)) {
641
+ updatedImportList = updatedImportList.replace(
642
+ componentRegex,
643
+ mapping.component,
644
+ );
645
+ uniqueComponents.add(mapping.component);
646
+ hasChanges = true;
647
+ }
648
+ });
649
+
650
+ if (hasChanges) {
651
+ changes++;
652
+ // Deduplicate components and create clean import statement
653
+ const finalImportList = Array.from(uniqueComponents).join(', ');
654
+ return `import {${finalImportList}} from '${BETA_IMPORT}'`;
655
+ }
656
+
657
+ return match;
658
+ });
659
+
660
+ return { content: updatedContent, changes };
661
+ }
662
+
663
+ // Update component usage with better prop handling
664
+ function updateComponents(content) {
665
+ let updatedContent = content;
666
+ let changes = 0;
667
+ let warnings = [];
668
+
669
+ Object.entries(COMPONENT_MAPPING).forEach(([oldComponent, mapping]) => {
670
+ // More robust regex to handle complex JSX
671
+ const componentRegex = new RegExp(`<${oldComponent}(\\s+[^>]*?)?>`, 'g');
672
+
673
+ updatedContent = updatedContent.replace(
674
+ componentRegex,
675
+ (match, propsString) => {
676
+ changes++;
677
+
678
+ // Parse existing props
679
+ const {
680
+ props: existingProps,
681
+ warnings: parseWarnings,
682
+ spreadProps,
683
+ } = parseJSXProps(propsString);
684
+ warnings.push(...parseWarnings);
685
+
686
+ // Migrate props
687
+ const { props: migratedProps, warnings: migrateWarnings } =
688
+ migrateProps(existingProps, oldComponent);
689
+ warnings.push(...migrateWarnings);
690
+
691
+ // Build new props from mapping
692
+ const newProps = { ...migratedProps };
693
+
694
+ // Add mapping props (but don't override existing ones)
695
+ Object.entries(mapping).forEach(([key, value]) => {
696
+ if (key !== 'component' && !newProps[key]) {
697
+ newProps[key] = value;
698
+ }
699
+ });
700
+
701
+ // Handle Heading components
702
+ if (mapping.component === 'Heading') {
703
+ // Preserve existing 'as' prop if it exists, otherwise use mapping default
704
+ const asValue = existingProps.as || mapping.as;
705
+ // Preserve existing 'variant' prop if it exists, otherwise use mapping default
706
+ const variantValue = existingProps.variant || mapping.variant;
707
+
708
+ // Remove as and variant from props since we'll add them separately
709
+ delete newProps.as;
710
+ delete newProps.variant;
711
+
712
+ // Ensure mapping props come first
713
+ const orderedProps = {};
714
+ if (newProps.spacing) {
715
+ orderedProps.spacing = newProps.spacing;
716
+ delete newProps.spacing;
717
+ }
718
+ Object.assign(orderedProps, newProps);
719
+
720
+ const propsString = propsToString(orderedProps);
721
+ const spreadPropsString =
722
+ spreadProps.length > 0 ? ` {...${spreadProps.join(', ...')}}` : '';
723
+ return `<Heading as="${asValue}" variant="${variantValue}"${propsString}${spreadPropsString}>`;
724
+ }
725
+
726
+ // Handle other components
727
+ const componentName = mapping.component;
728
+
729
+ // Remove mapping props from newProps since they're already set
730
+ Object.keys(mapping).forEach(key => {
731
+ if (key !== 'component') {
732
+ delete newProps[key];
733
+ }
734
+ });
735
+
736
+ // Add mapping props in the correct order
737
+ const finalProps = {};
738
+ Object.entries(mapping).forEach(([key, value]) => {
739
+ if (key !== 'component') {
740
+ finalProps[key] = value;
741
+ }
742
+ });
743
+ Object.assign(finalProps, newProps);
744
+
745
+ const otherPropsString = propsToString(finalProps);
746
+ const spreadPropsString =
747
+ spreadProps.length > 0 ? ` {...${spreadProps.join(', ...')}}` : '';
748
+ return `<${componentName}${otherPropsString}${spreadPropsString}>`;
749
+ },
750
+ );
751
+
752
+ // Update closing tags
753
+ const closingTagRegex = new RegExp(`</${oldComponent}>`, 'g');
754
+ const componentName = mapping.component;
755
+ updatedContent = updatedContent.replace(
756
+ closingTagRegex,
757
+ `</${componentName}>`,
758
+ );
759
+ });
760
+
761
+ return { content: updatedContent, changes, warnings };
762
+ }
763
+
764
+ /**
765
+ * Find files matching the given pattern in allowed directories
766
+ *
767
+ * This function uses efficient glob patterns and data structures:
768
+ * - Single glob call with brace expansion instead of multiple calls
769
+ * - Set-based extension filtering for O(1) lookups
770
+ * - No array concatenation in loops
771
+ *
772
+ * @param {string} pattern - Glob pattern to match (e.g., '*.{ts,tsx,js,jsx}')
773
+ * @returns {string[]} Array of matching file paths
774
+ */
775
+ function findFiles(pattern) {
776
+ const allFiles = [];
777
+
778
+ // Process directory patterns
779
+ const directoryPatterns = ALLOWED_DIRECTORIES.filter(dir =>
780
+ dir.includes('**'),
781
+ );
782
+ const filePatterns = ALLOWED_DIRECTORIES.filter(dir => !dir.includes('**'));
783
+
784
+ // Handle directory patterns (e.g., src/**, app/**)
785
+ if (directoryPatterns.length > 0) {
786
+ const combinedDirPattern = `{${directoryPatterns.join(',')}}/${pattern}`;
787
+ const dirFiles = glob.sync(combinedDirPattern, {
788
+ ignore: BLOCKED_DIRECTORIES,
789
+ nodir: true,
790
+ absolute: false,
791
+ });
792
+ allFiles.push(...dirFiles);
793
+ }
794
+
795
+ // Handle file patterns (e.g., *.jsx, *.tsx)
796
+ filePatterns.forEach(filePattern => {
797
+ const files = glob.sync(filePattern, {
798
+ ignore: BLOCKED_DIRECTORIES,
799
+ nodir: true,
800
+ absolute: false,
801
+ });
802
+ allFiles.push(...files);
803
+ });
804
+
805
+ // Use Set for efficient deduplication and filtering
806
+ const fileExtensions = new Set([
807
+ '.ts',
808
+ '.tsx',
809
+ '.js',
810
+ '.jsx',
811
+ '.scss',
812
+ '.css',
813
+ ]);
814
+
815
+ const uniqueFiles = allFiles.filter(file => {
816
+ const ext = path.extname(file).toLowerCase();
817
+ return fileExtensions.has(ext);
818
+ });
819
+
820
+ return uniqueFiles;
821
+ }
822
+
823
+ function updateImportsAndComponents(content) {
824
+ let updatedContent = content;
825
+ let changes = 0;
826
+ let warnings = [];
827
+
828
+ // Update both imports and components
829
+ const { content: newContent, changes: importChanges } =
830
+ updateImports(content);
831
+ const {
832
+ content: finalContent,
833
+ changes: componentChanges,
834
+ warnings: componentWarnings,
835
+ } = updateComponents(newContent);
836
+ updatedContent = finalContent;
837
+ changes = importChanges + componentChanges;
838
+ warnings = componentWarnings;
839
+
840
+ return { content: updatedContent, changes, warnings };
841
+ }
842
+
843
+ function generateMigrationReport(files, isDryRun = false) {
844
+ const report = {
845
+ strategy: 'complete',
846
+ totalFiles: files.length,
847
+ migratedFiles: 0,
848
+ totalChanges: 0,
849
+ totalWarnings: 0,
850
+ files: [],
851
+ warnings: [],
852
+ isDryRun,
853
+ };
854
+
855
+ files.forEach(file => {
856
+ try {
857
+ const content = fs.readFileSync(file, 'utf8');
858
+
859
+ // Analyze file for problematic patterns BEFORE migration
860
+ const fileAnalysis = analyzeFile(file, content);
861
+
862
+ const {
863
+ content: updatedContent,
864
+ changes,
865
+ warnings,
866
+ } = updateImportsAndComponents(content);
867
+
868
+ // Combine migration warnings with file analysis warnings
869
+ const allWarnings = [...warnings, ...fileAnalysis.warnings];
870
+
871
+ if (changes > 0 || fileAnalysis.warnings.length > 0) {
872
+ if (!isDryRun) {
873
+ fs.writeFileSync(file, updatedContent, 'utf8');
874
+ }
875
+ if (changes > 0) {
876
+ report.migratedFiles++;
877
+ report.totalChanges += changes;
878
+ }
879
+ report.totalWarnings += allWarnings.length;
880
+ report.files.push({ file, changes, warnings: allWarnings });
881
+ report.warnings.push(
882
+ ...allWarnings.map(warning => `${file}: ${warning}`),
883
+ );
884
+ }
885
+ } catch (error) {
886
+ report.warnings.push(`${file}: Error processing file - ${error.message}`);
887
+ }
888
+ });
889
+
890
+ return report;
891
+ }
892
+
893
+ function printReport(report) {
894
+ console.log('\nšŸŽ‰ Migration Report');
895
+ console.log('==================');
896
+ console.log(`Strategy: ${report.strategy}`);
897
+ console.log(`Total files scanned: ${report.totalFiles}`);
898
+ console.log(`Files migrated: ${report.migratedFiles}`);
899
+ console.log(`Total changes: ${report.totalChanges}`);
900
+ console.log(`Total warnings: ${report.totalWarnings}`);
901
+
902
+ if (report.files.length > 0) {
903
+ console.log('\nMigrated files:');
904
+ report.files.forEach(({ file, changes, warnings }) => {
905
+ console.log(
906
+ ` āœ… ${file} (${changes} changes${
907
+ warnings.length > 0 ? `, ${warnings.length} warnings` : ''
908
+ })`,
909
+ );
910
+ });
911
+ }
912
+
913
+ if (report.warnings.length > 0) {
914
+ console.log('\nāš ļø Warnings:');
915
+
916
+ // Group warnings by type
917
+ const marginWarnings = report.warnings.filter(w => w.includes('Migrated'));
918
+ const semanticWarnings = report.warnings.filter(w =>
919
+ w.includes('expected'),
920
+ );
921
+ const conflictWarnings = report.warnings.filter(w =>
922
+ w.includes('check for conflicts'),
923
+ );
924
+
925
+ // New warning types from file analysis
926
+ const styleConflictWarnings = report.warnings.filter(
927
+ w => w.includes('style conflicts') || w.includes('style and margin'),
928
+ );
929
+ const nestedTypographyWarnings = report.warnings.filter(w =>
930
+ w.includes('nested typography'),
931
+ );
932
+ const accessibilityWarnings = report.warnings.filter(
933
+ w => w.includes('missing as prop') || w.includes('accessibility'),
934
+ );
935
+ const semanticMismatchWarnings = report.warnings.filter(w =>
936
+ w.includes('semantic mismatch'),
937
+ );
938
+
939
+ if (marginWarnings.length > 0) {
940
+ console.log(
941
+ `\n šŸ”„ Margin → Spacing Migrations (${marginWarnings.length}):`,
942
+ );
943
+ // Show first 5 warnings, then summarize the rest
944
+ marginWarnings
945
+ .slice(0, 5)
946
+ .forEach(warning => console.log(` ${warning}`));
947
+ if (marginWarnings.length > 5) {
948
+ console.log(
949
+ ` ... and ${marginWarnings.length - 5} more similar warnings`,
950
+ );
951
+ }
952
+ }
953
+
954
+ if (semanticWarnings.length > 0) {
955
+ console.log(`\n šŸŽÆ Semantic HTML Issues (${semanticWarnings.length}):`);
956
+ // Show first 5 warnings, then summarize the rest
957
+ semanticWarnings
958
+ .slice(0, 5)
959
+ .forEach(warning => console.log(` ${warning}`));
960
+ if (semanticWarnings.length > 5) {
961
+ console.log(
962
+ ` ... and ${semanticWarnings.length - 5} more similar warnings`,
963
+ );
964
+ }
965
+ }
966
+
967
+ if (conflictWarnings.length > 0) {
968
+ console.log(`\n 🚨 Style Conflicts (${conflictWarnings.length}):`);
969
+ // Show first 5 warnings, then summarize the rest
970
+ conflictWarnings
971
+ .slice(0, 5)
972
+ .forEach(warning => console.log(` ${warning}`));
973
+ if (conflictWarnings.length > 5) {
974
+ console.log(
975
+ ` ... and ${conflictWarnings.length - 5} more similar warnings`,
976
+ );
977
+ }
978
+ console.log(` → Review these components for styling conflicts`);
979
+ }
980
+
981
+ // Display new warning types
982
+ if (styleConflictWarnings.length > 0) {
983
+ console.log(
984
+ `\n šŸŽØ Style + Margin Conflicts (${styleConflictWarnings.length}):`,
985
+ );
986
+ styleConflictWarnings
987
+ .slice(0, 5)
988
+ .forEach(warning => console.log(` ${warning}`));
989
+ if (styleConflictWarnings.length > 5) {
990
+ console.log(
991
+ ` ... and ${
992
+ styleConflictWarnings.length - 5
993
+ } more similar warnings`,
994
+ );
995
+ }
996
+ console.log(` → Remove margin prop when using inline styles`);
997
+ }
998
+
999
+ if (nestedTypographyWarnings.length > 0) {
1000
+ console.log(
1001
+ `\n 🚫 Nested Typography (${nestedTypographyWarnings.length}):`,
1002
+ );
1003
+ nestedTypographyWarnings
1004
+ .slice(0, 5)
1005
+ .forEach(warning => console.log(` ${warning}`));
1006
+ if (nestedTypographyWarnings.length > 5) {
1007
+ console.log(
1008
+ ` ... and ${
1009
+ nestedTypographyWarnings.length - 5
1010
+ } more similar warnings`,
1011
+ );
1012
+ }
1013
+ console.log(
1014
+ ` → Use spans or other inline elements instead of nested Text components`,
1015
+ );
1016
+ }
1017
+
1018
+ if (accessibilityWarnings.length > 0) {
1019
+ console.log(
1020
+ `\n ♿ Accessibility Issues (${accessibilityWarnings.length}):`,
1021
+ );
1022
+ accessibilityWarnings
1023
+ .slice(0, 5)
1024
+ .forEach(warning => console.log(` ${warning}`));
1025
+ if (accessibilityWarnings.length > 5) {
1026
+ console.log(
1027
+ ` ... and ${
1028
+ accessibilityWarnings.length - 5
1029
+ } more similar warnings`,
1030
+ );
1031
+ }
1032
+ console.log(
1033
+ ` → Add 'as' prop to Heading components for proper semantic HTML`,
1034
+ );
1035
+ }
1036
+
1037
+ if (semanticMismatchWarnings.length > 0) {
1038
+ console.log(
1039
+ `\n šŸ” Semantic Mismatches (${semanticMismatchWarnings.length}):`,
1040
+ );
1041
+ semanticMismatchWarnings
1042
+ .slice(0, 5)
1043
+ .forEach(warning => console.log(` ${warning}`));
1044
+ if (semanticMismatchWarnings.length > 5) {
1045
+ console.log(
1046
+ ` ... and ${
1047
+ semanticMismatchWarnings.length - 5
1048
+ } more similar warnings`,
1049
+ );
1050
+ }
1051
+ console.log(` → Review heading level and variant combinations`);
1052
+ }
1053
+
1054
+ console.log('\nšŸ“‹ Summary:');
1055
+ if (marginWarnings.length > 0)
1056
+ console.log(
1057
+ ` • ${marginWarnings.length} margin props migrated to spacing`,
1058
+ );
1059
+ if (semanticWarnings.length > 0)
1060
+ console.log(
1061
+ ` • ${semanticWarnings.length} semantic HTML issues need review`,
1062
+ );
1063
+ if (conflictWarnings.length > 0)
1064
+ console.log(
1065
+ ` • ${conflictWarnings.length} style conflicts need manual review`,
1066
+ );
1067
+ if (styleConflictWarnings.length > 0)
1068
+ console.log(
1069
+ ` • ${styleConflictWarnings.length} style + margin conflicts detected`,
1070
+ );
1071
+ if (nestedTypographyWarnings.length > 0)
1072
+ console.log(
1073
+ ` • ${nestedTypographyWarnings.length} nested typography components found`,
1074
+ );
1075
+ if (accessibilityWarnings.length > 0)
1076
+ console.log(
1077
+ ` • ${accessibilityWarnings.length} accessibility issues need attention`,
1078
+ );
1079
+ if (semanticMismatchWarnings.length > 0)
1080
+ console.log(
1081
+ ` • ${semanticMismatchWarnings.length} semantic mismatches detected`,
1082
+ );
1083
+
1084
+ // Add helpful note about warning limits
1085
+ if (report.warnings.length > 15) {
1086
+ console.log(
1087
+ '\nšŸ’” Note: Only showing first 5 warnings of each type to avoid overwhelming output.',
1088
+ );
1089
+ console.log(
1090
+ ' All warnings are still logged in the migration report above.',
1091
+ );
1092
+ }
1093
+ }
1094
+ }
1095
+
1096
+ function showNextSteps() {
1097
+ console.log('\nšŸ“ Next Steps');
1098
+ console.log('=============');
1099
+
1100
+ console.log('1. 🧪 Test your application thoroughly');
1101
+ console.log('2. šŸ”„ Review and adjust any component props if needed');
1102
+ console.log('3. šŸ“š Read the migration guide on our website');
1103
+
1104
+ console.log('\nāš ļø Important Notes:');
1105
+ console.log('- Check warnings above for potential issues');
1106
+ console.log('- Review migrated components for prop conflicts');
1107
+ console.log('- Test thoroughly, especially components with custom styling');
1108
+ }
1109
+
1110
+ function main() {
1111
+ // Show help if requested
1112
+ if (process.argv.includes('--help') || process.argv.includes('-h')) {
1113
+ console.log('šŸŽØ Typography Migration Script');
1114
+ console.log('==============================');
1115
+ console.log('');
1116
+ console.log('Usage:');
1117
+ console.log(' # From npm package (recommended)');
1118
+ console.log(' npx @entur/typography@latest migrate [options]');
1119
+ console.log(' yarn dlx @entur/typography@latest migrate [options]');
1120
+ console.log('');
1121
+ console.log(' # Direct execution (requires glob package)');
1122
+ console.log(' node scripts/migrate-typography.js [options]');
1123
+ console.log('');
1124
+ console.log(' # Local development');
1125
+ console.log(' npm run migrate');
1126
+ console.log('');
1127
+ console.log('Options:');
1128
+ console.log(
1129
+ ' --dry-run Show what would be changed without modifying files',
1130
+ );
1131
+ console.log(' --help, -h Show this help message');
1132
+ console.log('');
1133
+ console.log('Migration Mode:');
1134
+ console.log(' šŸš€ Complete Mode: Updates everything');
1135
+ console.log(' - Replaces old components with beta components');
1136
+ console.log(' - May require prop/styling updates');
1137
+ console.log(' - Test thoroughly after migration');
1138
+ console.log('');
1139
+ console.log('Examples:');
1140
+ console.log(' # See what would be changed');
1141
+ console.log(' npx @entur/typography@latest migrate --dry-run');
1142
+ console.log('');
1143
+ console.log(' # Complete migration: update everything (default)');
1144
+ console.log(' npx @entur/typography@latest migrate');
1145
+
1146
+ console.log('Environment Variables:');
1147
+ console.log(
1148
+ ' TYPOGRAPHY_MIGRATION_DIRS Comma-separated list of directories to scan',
1149
+ );
1150
+ console.log(' Example: "src/**,app/**"');
1151
+ console.log('');
1152
+ console.log('šŸŽÆ Customizing Scan Directories:');
1153
+ console.log(' Option 1: Edit MIGRATION_FOLDERS in the script (EASIEST)');
1154
+ console.log(
1155
+ ' Open the script and find the "MIGRATION FOLDERS CONFIGURATION" section',
1156
+ );
1157
+ console.log(' Add/remove folder patterns between the šŸ‘‡ and šŸ‘† markers');
1158
+ console.log(' Examples: "src/**", "app/**", "packages/my-app/**"');
1159
+ console.log('');
1160
+ console.log(' Option 2: Set environment variable (for CI/CD)');
1161
+ console.log(
1162
+ ' export TYPOGRAPHY_MIGRATION_DIRS="src/**,app/**,components/**"',
1163
+ );
1164
+ console.log(' node scripts/migrate-typography.js');
1165
+ console.log('');
1166
+ console.log('Security Features:');
1167
+ console.log(' - Only scans allowed directories (src/**, app/**, etc.)');
1168
+ console.log(' - Never scans node_modules, dist, build, .git, etc.)');
1169
+ console.log(' - Dry-run mode for safe testing');
1170
+ console.log('');
1171
+ process.exit(0);
1172
+ }
1173
+
1174
+ console.log('šŸŽØ Typography Migration Script');
1175
+ console.log('==============================');
1176
+ console.log('');
1177
+ console.log(
1178
+ 'This script helps you migrate from old typography to new beta typography.',
1179
+ );
1180
+ console.log('');
1181
+
1182
+ // Find files to migrate - use a single efficient pattern
1183
+ const allFiles = findFiles('*.{ts,tsx,js,jsx,scss,css}');
1184
+
1185
+ console.log(`Found ${allFiles.length} files to scan for typography imports.`);
1186
+ console.log('');
1187
+
1188
+ // Security check
1189
+ console.log('šŸ”’ Security: Only scanning allowed directories:');
1190
+ ALLOWED_DIRECTORIES.forEach(dir => {
1191
+ console.log(` āœ… ${dir}`);
1192
+ });
1193
+ console.log('');
1194
+
1195
+ // Safety check
1196
+ if (allFiles.length === 0) {
1197
+ console.log('āš ļø No files found to scan. This might mean:');
1198
+ console.log(" - You're not in the right directory");
1199
+ console.log(" - Your project structure doesn't match the allow-list");
1200
+ console.log(' - You need to run this from your project root');
1201
+ console.log('');
1202
+ console.log('Allowed directory patterns:');
1203
+ ALLOWED_DIRECTORIES.forEach(dir => console.log(` ${dir}`));
1204
+ process.exit(1);
1205
+ }
1206
+
1207
+ console.log('šŸ“ Files will be scanned in these locations:');
1208
+ const scannedDirs = [
1209
+ ...new Set(allFiles.map(file => path.dirname(file))),
1210
+ ].slice(0, 10);
1211
+
1212
+ // Show relative paths safely
1213
+ scannedDirs.forEach(dir => {
1214
+ // Ensure we don't show absolute paths
1215
+ const safeDir = path.isAbsolute(dir)
1216
+ ? path.relative(process.cwd(), dir)
1217
+ : dir;
1218
+ console.log(` šŸ“‚ ${safeDir}`);
1219
+ });
1220
+
1221
+ if (allFiles.length > 10) {
1222
+ console.log(` ... and ${allFiles.length - 10} more files`);
1223
+ }
1224
+ console.log('');
1225
+
1226
+ // Parse command line options
1227
+ const isDryRun = process.argv.includes('--dry-run');
1228
+
1229
+ if (isDryRun) {
1230
+ console.log('šŸ” DRY RUN MODE: No files will be modified');
1231
+ console.log('');
1232
+ }
1233
+
1234
+ console.log('šŸš€ COMPLETE MIGRATION: Updating imports + component usage');
1235
+ console.log('āš ļø WARNING: This will modify your component usage!');
1236
+ console.log(' - Old components will be replaced with beta components');
1237
+ console.log(' - You may need to update props and styling');
1238
+ console.log(' - Test thoroughly after migration');
1239
+
1240
+ console.log('');
1241
+
1242
+ // Perform migration
1243
+ const report = generateMigrationReport(allFiles, isDryRun);
1244
+ printReport(report);
1245
+ showNextSteps();
1246
+
1247
+ console.log('\nšŸŽÆ Migration complete!');
1248
+ }
1249
+
1250
+ if (require.main === module) {
1251
+ main();
1252
+ }
1253
+
1254
+ module.exports = {
1255
+ updateImportsAndComponents,
1256
+ generateMigrationReport,
1257
+ COMPONENT_MAPPING,
1258
+ PROPS_MAPPING,
1259
+ };