@reapit/elements 5.0.0-beta.71 → 5.0.0-beta.72

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 (32) hide show
  1. package/bin/elements.cjs +3 -5
  2. package/dist/codemods/at-a-glance-article-card/transform.d.ts +4 -0
  3. package/dist/codemods/at-a-glance-article-card/transform.d.ts.map +1 -0
  4. package/dist/codemods/at-a-glance-article-card/transform.js +223 -0
  5. package/dist/codemods/at-a-glance-article-card/transform.js.map +1 -0
  6. package/dist/codemods/bin.d.ts +2 -0
  7. package/dist/codemods/bin.d.ts.map +1 -0
  8. package/dist/codemods/bin.js +140 -0
  9. package/dist/codemods/bin.js.map +1 -0
  10. package/dist/codemods/codemods.d.ts +29 -0
  11. package/dist/codemods/codemods.d.ts.map +1 -0
  12. package/dist/codemods/codemods.js +32 -0
  13. package/dist/codemods/codemods.js.map +1 -0
  14. package/{codemods → dist/codemods}/manifest.json +1 -1
  15. package/dist/codemods/runner.d.ts +21 -0
  16. package/dist/codemods/runner.d.ts.map +1 -0
  17. package/dist/codemods/runner.js +165 -0
  18. package/dist/codemods/runner.js.map +1 -0
  19. package/package.json +6 -5
  20. package/codemods/__tests__/codemods.test.ts +0 -178
  21. package/codemods/__tests__/generate-manifest.test.ts +0 -240
  22. package/codemods/__tests__/readme-parser.test.ts +0 -218
  23. package/codemods/__tests__/runner.test.ts +0 -530
  24. package/codemods/at-a-glance-article-card/README.md +0 -122
  25. package/codemods/at-a-glance-article-card/__tests__/transform.test.ts +0 -390
  26. package/codemods/at-a-glance-article-card/transform.ts +0 -291
  27. package/codemods/bin.ts +0 -205
  28. package/codemods/codemods.ts +0 -75
  29. package/codemods/generate-manifest.ts +0 -120
  30. package/codemods/manifest.schema.json +0 -39
  31. package/codemods/readme-parser.ts +0 -37
  32. package/codemods/runner.ts +0 -196
@@ -1,530 +0,0 @@
1
- import { existsSync, mkdirSync, writeFileSync, rmSync, readFileSync } from 'node:fs'
2
- import { join } from 'node:path'
3
- import { tmpdir } from 'node:os'
4
- import { findFiles, matchesPatterns, run } from '../runner'
5
- import type { Transform } from '../runner'
6
-
7
- let testDir: string
8
-
9
- beforeEach(() => {
10
- // Create a temporary test directory
11
- testDir = join(tmpdir(), `runner-test-${Date.now()}`)
12
- mkdirSync(testDir, { recursive: true })
13
- })
14
-
15
- afterEach(() => {
16
- // Clean up test directory
17
- if (existsSync(testDir)) {
18
- rmSync(testDir, { recursive: true, force: true })
19
- }
20
- })
21
-
22
- describe('matchesPatterns', () => {
23
- test('matches files with *.ext pattern', () => {
24
- expect(matchesPatterns('file.ts', ['*.ts'])).toBe(true)
25
- expect(matchesPatterns('file.tsx', ['*.tsx'])).toBe(true)
26
- expect(matchesPatterns('file.js', ['*.js'])).toBe(true)
27
- })
28
-
29
- test('does not match files with wrong extension', () => {
30
- expect(matchesPatterns('file.ts', ['*.tsx'])).toBe(false)
31
- expect(matchesPatterns('file.js', ['*.ts'])).toBe(false)
32
- })
33
-
34
- test('matches with multiple patterns', () => {
35
- const patterns = ['*.ts', '*.tsx']
36
-
37
- expect(matchesPatterns('file.ts', patterns)).toBe(true)
38
- expect(matchesPatterns('file.tsx', patterns)).toBe(true)
39
- expect(matchesPatterns('file.js', patterns)).toBe(false)
40
- })
41
-
42
- test('matches exact filename', () => {
43
- expect(matchesPatterns('specific-file.ts', ['specific-file.ts'])).toBe(true)
44
- expect(matchesPatterns('other-file.ts', ['specific-file.ts'])).toBe(false)
45
- })
46
-
47
- test('matches with wildcard in middle of pattern', () => {
48
- expect(matchesPatterns('test.spec.ts', ['*.spec.ts'])).toBe(true)
49
- expect(matchesPatterns('file.test.ts', ['*.spec.ts'])).toBe(false)
50
- })
51
-
52
- test('rejects patterns that are too long (ReDoS protection)', () => {
53
- const longPattern = '*'.repeat(200)
54
- const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
55
-
56
- const result = matchesPatterns('file.ts', [longPattern])
57
-
58
- expect(result).toBe(false)
59
- expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('potentially unsafe pattern'))
60
-
61
- consoleSpy.mockRestore()
62
- })
63
-
64
- test('rejects patterns with excessive wildcards (ReDoS protection)', () => {
65
- const pattern = '*.*.*.*.*.*.ts'
66
- const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
67
-
68
- const result = matchesPatterns('file.ts', [pattern])
69
-
70
- expect(result).toBe(false)
71
- expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('potentially unsafe pattern'))
72
-
73
- consoleSpy.mockRestore()
74
- })
75
-
76
- test('rejects filenames that are too long', () => {
77
- const longFilename = 'a'.repeat(600) + '.ts'
78
- const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
79
-
80
- const result = matchesPatterns(longFilename, ['*test*.ts'])
81
-
82
- expect(result).toBe(false)
83
- expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('Filename too long'))
84
-
85
- consoleSpy.mockRestore()
86
- })
87
-
88
- test('matches common file extensions', () => {
89
- expect(matchesPatterns('Component.tsx', ['*.tsx', '*.ts', '*.jsx', '*.js'])).toBe(true)
90
- expect(matchesPatterns('utils.ts', ['*.tsx', '*.ts', '*.jsx', '*.js'])).toBe(true)
91
- expect(matchesPatterns('App.jsx', ['*.tsx', '*.ts', '*.jsx', '*.js'])).toBe(true)
92
- expect(matchesPatterns('index.js', ['*.tsx', '*.ts', '*.jsx', '*.js'])).toBe(true)
93
- })
94
-
95
- test('returns false for empty pattern array', () => {
96
- expect(matchesPatterns('file.ts', [])).toBe(false)
97
- })
98
-
99
- test('matches first matching pattern', () => {
100
- const patterns = ['*.js', '*.ts', '*.tsx']
101
-
102
- expect(matchesPatterns('file.ts', patterns)).toBe(true)
103
- })
104
- })
105
-
106
- describe('findFiles', () => {
107
- test('finds files matching patterns in directory', () => {
108
- writeFileSync(join(testDir, 'file1.ts'), 'content')
109
- writeFileSync(join(testDir, 'file2.tsx'), 'content')
110
- writeFileSync(join(testDir, 'file3.js'), 'content')
111
-
112
- const files = findFiles(testDir, ['*.ts', '*.tsx'])
113
-
114
- expect(files).toHaveLength(2)
115
- expect(files).toContainEqual(join(testDir, 'file1.ts'))
116
- expect(files).toContainEqual(join(testDir, 'file2.tsx'))
117
- })
118
-
119
- test('recursively finds files in subdirectories', () => {
120
- const subDir = join(testDir, 'subdir')
121
- mkdirSync(subDir)
122
-
123
- writeFileSync(join(testDir, 'root.ts'), 'content')
124
- writeFileSync(join(subDir, 'nested.ts'), 'content')
125
-
126
- const files = findFiles(testDir, ['*.ts'])
127
-
128
- expect(files).toHaveLength(2)
129
- expect(files).toContainEqual(join(testDir, 'root.ts'))
130
- expect(files).toContainEqual(join(subDir, 'nested.ts'))
131
- })
132
-
133
- test('skips node_modules directory', () => {
134
- const nodeModules = join(testDir, 'node_modules')
135
- mkdirSync(nodeModules)
136
-
137
- writeFileSync(join(testDir, 'app.ts'), 'content')
138
- writeFileSync(join(nodeModules, 'package.ts'), 'content')
139
-
140
- const files = findFiles(testDir, ['*.ts'])
141
-
142
- expect(files).toHaveLength(1)
143
- expect(files).toContainEqual(join(testDir, 'app.ts'))
144
- expect(files).not.toContainEqual(join(nodeModules, 'package.ts'))
145
- })
146
-
147
- test('skips dist directory', () => {
148
- const dist = join(testDir, 'dist')
149
- mkdirSync(dist)
150
-
151
- writeFileSync(join(testDir, 'source.ts'), 'content')
152
- writeFileSync(join(dist, 'compiled.js'), 'content')
153
-
154
- const files = findFiles(testDir, ['*.ts', '*.js'])
155
-
156
- expect(files).toHaveLength(1)
157
- expect(files).toContainEqual(join(testDir, 'source.ts'))
158
- })
159
-
160
- test('returns empty array when no matching files exist', () => {
161
- writeFileSync(join(testDir, 'file.txt'), 'content')
162
- writeFileSync(join(testDir, 'file.md'), 'content')
163
-
164
- const files = findFiles(testDir, ['*.ts'])
165
-
166
- expect(files).toEqual([])
167
- })
168
-
169
- test('finds files in deeply nested directories', () => {
170
- const level1 = join(testDir, 'level1')
171
- const level2 = join(level1, 'level2')
172
- const level3 = join(level2, 'level3')
173
-
174
- mkdirSync(level3, { recursive: true })
175
-
176
- writeFileSync(join(level3, 'deep.ts'), 'content')
177
-
178
- const files = findFiles(testDir, ['*.ts'])
179
-
180
- expect(files).toHaveLength(1)
181
- expect(files).toContainEqual(join(level3, 'deep.ts'))
182
- })
183
-
184
- test('handles multiple file types in nested structure', () => {
185
- const src = join(testDir, 'src')
186
- const components = join(src, 'components')
187
- const utils = join(src, 'utils')
188
-
189
- mkdirSync(components, { recursive: true })
190
- mkdirSync(utils, { recursive: true })
191
-
192
- writeFileSync(join(components, 'Button.tsx'), 'content')
193
- writeFileSync(join(components, 'Input.tsx'), 'content')
194
- writeFileSync(join(utils, 'helpers.ts'), 'content')
195
- writeFileSync(join(src, 'index.ts'), 'content')
196
-
197
- const files = findFiles(testDir, ['*.ts', '*.tsx'])
198
-
199
- expect(files).toHaveLength(4)
200
- })
201
-
202
- test('uses accumulator parameter correctly', () => {
203
- writeFileSync(join(testDir, 'file1.ts'), 'content')
204
- writeFileSync(join(testDir, 'file2.ts'), 'content')
205
-
206
- const existingResults = ['/existing/file.ts']
207
- const files = findFiles(testDir, ['*.ts'], existingResults)
208
-
209
- expect(files).toHaveLength(3)
210
- expect(files).toContain('/existing/file.ts')
211
- })
212
-
213
- test('prevents path traversal attacks', () => {
214
- // This test ensures that symlinks or malicious paths don't escape the base directory
215
- writeFileSync(join(testDir, 'safe.ts'), 'content')
216
-
217
- const files = findFiles(testDir, ['*.ts'])
218
-
219
- // All returned files should be within testDir
220
- files.forEach((file) => {
221
- expect(file.startsWith(testDir)).toBe(true)
222
- })
223
- })
224
- })
225
-
226
- describe('run', () => {
227
- let originalCwd: string
228
- let originalExit: typeof process.exit
229
- let exitCode: number | null
230
- let consoleOutput: string[]
231
-
232
- beforeEach(() => {
233
- originalCwd = process.cwd()
234
- originalExit = process.exit
235
- exitCode = null
236
- consoleOutput = []
237
-
238
- // Mock process.exit to capture exit codes
239
- process.exit = ((code?: number) => {
240
- exitCode = code ?? 0
241
- throw new Error(`process.exit(${code})`)
242
- }) as typeof process.exit
243
-
244
- // Mock console methods
245
- vi.spyOn(console, 'log').mockImplementation((msg) => {
246
- consoleOutput.push(String(msg))
247
- })
248
- vi.spyOn(console, 'error').mockImplementation((msg) => {
249
- consoleOutput.push(String(msg))
250
- })
251
- })
252
-
253
- afterEach(() => {
254
- process.exit = originalExit
255
- process.chdir(originalCwd)
256
- vi.restoreAllMocks()
257
- })
258
-
259
- test('exits with help when no args provided', async () => {
260
- const transform: Transform = (source) => source
261
-
262
- await expect(
263
- run({
264
- transform,
265
- codemodName: 'test-codemod',
266
- args: [],
267
- }),
268
- ).rejects.toThrow('process.exit(0)')
269
-
270
- expect(exitCode).toBe(0)
271
- expect(consoleOutput.join('\n')).toContain('Usage:')
272
- })
273
-
274
- test('exits with help when --help flag provided', async () => {
275
- const transform: Transform = (source) => source
276
-
277
- await expect(
278
- run({
279
- transform,
280
- codemodName: 'test-codemod',
281
- args: ['--help'],
282
- }),
283
- ).rejects.toThrow('process.exit(0)')
284
-
285
- expect(exitCode).toBe(0)
286
- expect(consoleOutput.join('\n')).toContain('Usage:')
287
- })
288
-
289
- test('transforms files in directory', async () => {
290
- process.chdir(testDir)
291
-
292
- const srcDir = join(testDir, 'src')
293
- mkdirSync(srcDir)
294
-
295
- writeFileSync(join(srcDir, 'file1.ts'), 'OLD_CODE')
296
- writeFileSync(join(srcDir, 'file2.ts'), 'OLD_CODE')
297
-
298
- const transform: Transform = (source) => source.replace('OLD_CODE', 'NEW_CODE')
299
-
300
- await run({
301
- transform,
302
- codemodName: 'test-codemod',
303
- args: ['src'],
304
- })
305
-
306
- const file1Content = readFileSync(join(srcDir, 'file1.ts'), 'utf-8')
307
- const file2Content = readFileSync(join(srcDir, 'file2.ts'), 'utf-8')
308
-
309
- expect(file1Content).toBe('NEW_CODE')
310
- expect(file2Content).toBe('NEW_CODE')
311
- })
312
-
313
- test('performs dry run without modifying files', async () => {
314
- process.chdir(testDir)
315
-
316
- const srcDir = join(testDir, 'src')
317
- mkdirSync(srcDir)
318
-
319
- writeFileSync(join(srcDir, 'file.ts'), 'OLD_CODE')
320
-
321
- const transform: Transform = (source) => source.replace('OLD_CODE', 'NEW_CODE')
322
-
323
- await run({
324
- transform,
325
- codemodName: 'test-codemod',
326
- args: ['src', '--dry-run'],
327
- })
328
-
329
- const fileContent = readFileSync(join(srcDir, 'file.ts'), 'utf-8')
330
-
331
- expect(fileContent).toBe('OLD_CODE')
332
- expect(consoleOutput.join('\n')).toContain('Would transform')
333
- })
334
-
335
- test('respects custom file extensions', async () => {
336
- process.chdir(testDir)
337
-
338
- const srcDir = join(testDir, 'src')
339
- mkdirSync(srcDir)
340
-
341
- writeFileSync(join(srcDir, 'file.tsx'), 'OLD_CODE')
342
- writeFileSync(join(srcDir, 'file.ts'), 'OLD_CODE')
343
- writeFileSync(join(srcDir, 'file.js'), 'OLD_CODE')
344
-
345
- const transform: Transform = (source) => source.replace('OLD_CODE', 'NEW_CODE')
346
-
347
- await run({
348
- transform,
349
- codemodName: 'test-codemod',
350
- args: ['src', '--ext', '.tsx,.jsx'],
351
- })
352
-
353
- const tsxContent = readFileSync(join(srcDir, 'file.tsx'), 'utf-8')
354
- const tsContent = readFileSync(join(srcDir, 'file.ts'), 'utf-8')
355
-
356
- expect(tsxContent).toBe('NEW_CODE')
357
- expect(tsContent).toBe('OLD_CODE') // Not transformed
358
- })
359
-
360
- test('exits with error when directory not found', async () => {
361
- process.chdir(testDir)
362
-
363
- const transform: Transform = (source) => source
364
-
365
- await expect(
366
- run({
367
- transform,
368
- codemodName: 'test-codemod',
369
- args: ['nonexistent'],
370
- }),
371
- ).rejects.toThrow('process.exit(1)')
372
-
373
- expect(exitCode).toBe(1)
374
- expect(consoleOutput.join('\n')).toContain('Directory not found')
375
- })
376
-
377
- test('exits with error when no directory provided', async () => {
378
- process.chdir(testDir)
379
-
380
- const transform: Transform = (source) => source
381
-
382
- await expect(
383
- run({
384
- transform,
385
- codemodName: 'test-codemod',
386
- args: ['--dry-run'],
387
- }),
388
- ).rejects.toThrow('process.exit(1)')
389
-
390
- expect(exitCode).toBe(1)
391
- expect(consoleOutput.join('\n')).toContain('No directory provided')
392
- })
393
-
394
- test('prevents directory traversal attacks', async () => {
395
- process.chdir(testDir)
396
-
397
- const transform: Transform = (source) => source
398
-
399
- await expect(
400
- run({
401
- transform,
402
- codemodName: 'test-codemod',
403
- args: ['../../../etc'],
404
- }),
405
- ).rejects.toThrow('process.exit(1)')
406
-
407
- expect(exitCode).toBe(1)
408
- expect(consoleOutput.join('\n')).toContain('outside the current working directory')
409
- })
410
-
411
- test('only transforms files that change', async () => {
412
- process.chdir(testDir)
413
-
414
- const srcDir = join(testDir, 'src')
415
- mkdirSync(srcDir)
416
-
417
- writeFileSync(join(srcDir, 'changed.ts'), 'OLD_CODE')
418
- writeFileSync(join(srcDir, 'unchanged.ts'), 'KEEP_CODE')
419
-
420
- const transform: Transform = (source) => source.replace('OLD_CODE', 'NEW_CODE')
421
-
422
- await run({
423
- transform,
424
- codemodName: 'test-codemod',
425
- args: ['src'],
426
- })
427
-
428
- const output = consoleOutput.join('\n')
429
- expect(output).toContain('Transformed: src/changed.ts')
430
- expect(output).not.toContain('unchanged.ts')
431
- expect(output).toContain('Transformed 1 file(s)')
432
- })
433
-
434
- test('handles transform errors gracefully', async () => {
435
- process.chdir(testDir)
436
-
437
- const srcDir = join(testDir, 'src')
438
- mkdirSync(srcDir)
439
-
440
- writeFileSync(join(srcDir, 'file1.ts'), 'content')
441
- writeFileSync(join(srcDir, 'file2.ts'), 'content')
442
-
443
- const transform: Transform = (source, filePath) => {
444
- if (filePath.includes('file1')) {
445
- throw new Error('Transform failed')
446
- }
447
- return source.replace('content', 'transformed')
448
- }
449
-
450
- await run({
451
- transform,
452
- codemodName: 'test-codemod',
453
- args: ['src'],
454
- })
455
-
456
- const output = consoleOutput.join('\n')
457
- expect(output).toContain('Error processing')
458
- expect(output).toContain('Transform failed')
459
-
460
- // Second file should still be transformed
461
- const file2Content = readFileSync(join(srcDir, 'file2.ts'), 'utf-8')
462
- expect(file2Content).toBe('transformed')
463
- })
464
-
465
- test('reports correct file count when no files match', async () => {
466
- process.chdir(testDir)
467
-
468
- const srcDir = join(testDir, 'src')
469
- mkdirSync(srcDir)
470
-
471
- writeFileSync(join(srcDir, 'file.txt'), 'content')
472
-
473
- const transform: Transform = (source) => source
474
-
475
- await expect(
476
- run({
477
- transform,
478
- codemodName: 'test-codemod',
479
- args: ['src'],
480
- }),
481
- ).rejects.toThrow('process.exit(0)')
482
-
483
- expect(consoleOutput.join('\n')).toContain('No matching files found')
484
- })
485
-
486
- test('handles -d shorthand for dry-run', async () => {
487
- process.chdir(testDir)
488
-
489
- const srcDir = join(testDir, 'src')
490
- mkdirSync(srcDir)
491
-
492
- writeFileSync(join(srcDir, 'file.ts'), 'OLD_CODE')
493
-
494
- const transform: Transform = (source) => source.replace('OLD_CODE', 'NEW_CODE')
495
-
496
- await run({
497
- transform,
498
- codemodName: 'test-codemod',
499
- args: ['src', '-d'],
500
- })
501
-
502
- const fileContent = readFileSync(join(srcDir, 'file.ts'), 'utf-8')
503
-
504
- expect(fileContent).toBe('OLD_CODE')
505
- expect(consoleOutput.join('\n')).toContain('dry run')
506
- })
507
-
508
- test('passes file path to transform function', async () => {
509
- process.chdir(testDir)
510
-
511
- const srcDir = join(testDir, 'src')
512
- mkdirSync(srcDir)
513
-
514
- writeFileSync(join(srcDir, 'file.ts'), 'content')
515
-
516
- let receivedPath = ''
517
- const transform: Transform = (source, filePath) => {
518
- receivedPath = filePath
519
- return source
520
- }
521
-
522
- await run({
523
- transform,
524
- codemodName: 'test-codemod',
525
- args: ['src'],
526
- })
527
-
528
- expect(receivedPath).toContain('file.ts')
529
- })
530
- })
@@ -1,122 +0,0 @@
1
- ---
2
- description: Migrates AtAGlance.Card to AtAGlance.ArticleCard
3
- ---
4
-
5
- # AtAGlance ArticleCard Codemod
6
-
7
- Migrates old `AtAGlance.Card` usage to the new `AtAGlance.ArticleCard` component.
8
-
9
- ## Usage
10
-
11
- ```bash
12
- # List available codemods
13
- yarn dlx @reapit/elements@beta codemod list
14
-
15
- # Show detailed info about this codemod
16
- yarn dlx @reapit/elements@beta codemod info at-a-glance-article-card
17
-
18
- # Run on a directory
19
- yarn dlx @reapit/elements@beta codemod apply at-a-glance-article-card src/
20
-
21
- # Preview changes without writing files
22
- yarn dlx @reapit/elements@beta codemod apply at-a-glance-article-card src/ --dry-run
23
-
24
- # Specify file extensions
25
- yarn dlx @reapit/elements@beta codemod apply at-a-glance-article-card src/ --ext .tsx,.jsx
26
- ```
27
-
28
- ### Facade Package Support
29
-
30
- If your project re-exports `@reapit/elements` through an internal facade package, use the `--facade-package` flag:
31
-
32
- ```bash
33
- yarn dlx @reapit/elements@beta codemod apply at-a-glance-article-card src/ --facade-package @company/ui-components
34
- ```
35
-
36
- The codemod uses **prefix matching**, so specifying a base package will match all its subpaths:
37
-
38
- ```bash
39
- # This will match:
40
- # - @company/design-system/elements
41
- # - @company/design-system/core
42
- # - @company/design-system/utils
43
- # - etc.
44
- yarn dlx @reapit/elements@beta codemod apply at-a-glance-article-card src/ --facade-package @company/design-system
45
- ```
46
-
47
- If you have multiple unrelated facade packages, run the codemod once for each package:
48
-
49
- ```bash
50
- yarn dlx @reapit/elements@beta codemod apply at-a-glance-article-card src/ --facade-package @company/ui
51
- yarn dlx @reapit/elements@beta codemod apply at-a-glance-article-card src/ --facade-package @another/design-lib
52
- ```
53
-
54
- **Example with facade package:**
55
-
56
- ```tsx
57
- // Before (with facade package @habio/design-system)
58
- import { AtAGlanceCard } from '@habio/design-system/elements'
59
- ;<AtAGlanceCard displayValue="42" label="Total" />
60
-
61
- // After running with --facade-package @habio/design-system
62
- import { AtAGlance } from '@habio/design-system/elements'
63
- ;<AtAGlance.ArticleCard displayValue="42" label="Total" />
64
- ```
65
-
66
- ## Background
67
-
68
- The `AtAGlance.Card` component API has changed:
69
-
70
- - **Old API**: `AtAGlance.Card` accepted props like `displayValue`, `label`, `description`, and `icon` directly
71
- - **New API**: `AtAGlance.Card` is now a primitive for custom layouts using the `grid` prop and subcomponents (`CardIcon`, `CardLabel`, `CardDescription`, `CardValue`)
72
-
73
- For standard layouts, use the new element-specific card components:
74
-
75
- - `AtAGlance.ArticleCard` - static, non-interactive cards
76
- - `AtAGlance.AnchorCard` - navigable link cards
77
- - `AtAGlance.ButtonCard` - interactive button cards
78
-
79
- ## Transformations
80
-
81
- Both namespaced and direct component usage are converted to the namespaced `AtAGlance.ArticleCard`:
82
-
83
- | Before | After |
84
- | --------------------------------------------------- | ---------------------------------------------------------- |
85
- | `<AtAGlance.Card displayValue="42" label="Total"/>` | `<AtAGlance.ArticleCard displayValue="42" label="Total"/>` |
86
- | `<AtAGlanceCard displayValue="42" label="Total"/>` | `<AtAGlance.ArticleCard displayValue="42" label="Total"/>` |
87
- | `<AtAGlance.Card grid="..." />` | No change (already using new primitive API) |
88
- | `<AtAGlanceCard grid="..." />` | No change (already using new primitive API) |
89
- | `<AtAGlance.Card>{children}</AtAGlance.Card>` | No change (already using new primitive API) |
90
- | `<AtAGlanceCard>{children}</AtAGlanceCard>` | No change (already using new primitive API) |
91
- | `<AtAGlance.AnchorCard {...} />` | No change (API unchanged) |
92
- | `<AtAGlanceAnchorCard {...} />` | No change (API unchanged) |
93
- | `<AtAGlance.ButtonCard {...} />` | No change (API unchanged) |
94
- | `<AtAGlanceButtonCard {...} />` | No change (API unchanged) |
95
-
96
- ## Import Handling
97
-
98
- The codemod removes `AtAGlanceCard` from imports when it is no longer used and adds the `AtAGlance` import. This works for direct `@reapit/elements` imports and facade packages (when specified via `--facade-package`):
99
-
100
- ```tsx
101
- // Before
102
- import { AtAGlanceCard } from '@reapit/elements'
103
- ;<AtAGlanceCard displayValue="42" label="Total" />
104
-
105
- // After
106
- import { AtAGlance } from '@reapit/elements'
107
- ;<AtAGlance.ArticleCard displayValue="42" label="Total" />
108
- ```
109
-
110
- When `AtAGlanceCard` is still used with the new primitive API, the import is kept:
111
-
112
- ```tsx
113
- // Before
114
- import { AtAGlanceCard } from '@reapit/elements'
115
- <AtAGlanceCard displayValue="42" label="Old" />
116
- <AtAGlanceCard grid="auto"><span>New</span></AtAGlanceCard>
117
-
118
- // After
119
- import { AtAGlanceCard } from '@reapit/elements'
120
- <AtAGlance.ArticleCard displayValue="42" label="Old" />
121
- <AtAGlanceCard grid="auto"><span>New</span></AtAGlanceCard>
122
- ```