@wise/wds-codemods 1.1.2 → 1.1.3

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.
@@ -0,0 +1,194 @@
1
+ # Legacy Component → ListItem Migration Guide
2
+
3
+ ## Import Management
4
+
5
+ 1. Remove imports: `{ ActionOption, NavigationOption, NavigationOptionList, Summary, SwitchOption, CheckboxOption, RadioOption }` from `'@transferwise/components'`
6
+ 2. Add import: `{ ListItem }` from `'@transferwise/components'`
7
+ 3. For Summary with info: Add `{ Modal }` and/or `{ Popover }` from `'@transferwise/components'`
8
+ 4. For Summary with info: Add `{ QuestionMarkCircle }` from `'@transferwise/icons'`
9
+ 5. For Summary with info modal: Add `{ useState }` from `'react'`
10
+
11
+ ## Universal Rules
12
+
13
+ 1. `title` → `title` (direct)
14
+ 2. `content` or `description` → `subtitle`
15
+ 3. `disabled` stays on `ListItem` (not controls)
16
+ 4. `id` stays on `ListItem` (not controls)
17
+ 5. Remove deprecated props: `as`, `complex`, `showMediaAtAllSizes`, `showMediaCircle`, `isContainerAligned`
18
+ 6. Preserve string quotes exactly as they are (don't convert ``to`'`or`"`)
19
+
20
+ ---
21
+
22
+ ## ActionOption → ListItem.Button
23
+
24
+ - `action` → Button children
25
+ - `onClick` → Button `onClick`
26
+ - Priority mapping:
27
+ - default or `"primary"` → `"primary"`
28
+ - `"secondary"` → `"secondary-neutral"`
29
+ - `"secondary-send"` → `"secondary"`
30
+ - `"tertiary"` → `"tertiary"`
31
+
32
+ ```tsx
33
+ <ActionOption title="Title" content="Text" action="Click" priority="secondary" onClick={fn} />
34
+
35
+ <ListItem title="Title" subtitle="Text" control={<ListItem.Button priority="secondary-neutral" onClick={fn}>Click</ListItem.Button>} />
36
+ ```
37
+
38
+ ---
39
+
40
+ ## CheckboxOption → ListItem.Checkbox
41
+
42
+ - `id` stays on ListItem
43
+ - `name` is removed (not transferred)
44
+ - `onChange`: `(checked: boolean) => void` → `(event: ChangeEvent<HTMLInputElement>) => void`
45
+ - Update handler: `(checked) => setX(checked)` → `(event) => setX(event.target.checked)`
46
+
47
+ ```tsx
48
+ <CheckboxOption id="x" name="y" title="Title" content="Text" checked={v} onChange={(c) => set(c)} />
49
+
50
+ <ListItem id="x" title="Title" subtitle="Text" control={<ListItem.Checkbox checked={v} onChange={(e) => set(e.target.checked)} />} />
51
+ ```
52
+
53
+ ---
54
+
55
+ ## RadioOption → ListItem.Radio
56
+
57
+ - `id` stays on ListItem
58
+ - `name`, `value`, `checked`, `onChange` move to Radio
59
+
60
+ ```tsx
61
+ <RadioOption id="x" name="y" value="v" title="Title" content="Text" checked={v==='v'} onChange={set} />
62
+
63
+ <ListItem id="x" title="Title" subtitle="Text" control={<ListItem.Radio name="y" value="v" checked={v==='v'} onChange={set} />} />
64
+ ```
65
+
66
+ ---
67
+
68
+ ## SwitchOption → ListItem.Switch
69
+
70
+ - `id` stays on ListItem
71
+ - `aria-label` is moved to `ListItem.Switch`
72
+ - `onChange` → `onClick` with manual toggle: `onChange={set}` → `onClick={() => set(!v)}`
73
+
74
+ ```tsx
75
+ <SwitchOption id="x" title="Title" content="Text" checked={v} aria-label="Toggle" onChange={set} />
76
+
77
+ <ListItem id="x" title="Title" subtitle="Text" control={<ListItem.Switch checked={v} aria-label="Toggle" onClick={() => set(!v)} />} />
78
+ ```
79
+
80
+ ---
81
+
82
+ ## NavigationOption → ListItem.Navigation
83
+
84
+ - `onClick` or `href` move to Navigation
85
+
86
+ ```tsx
87
+ <NavigationOption title="Title" content="Text" onClick={fn} />
88
+
89
+ <ListItem title="Title" subtitle="Text" control={<ListItem.Navigation onClick={fn} />} />
90
+ ```
91
+
92
+ ---
93
+
94
+ ## NavigationOptionList → List
95
+
96
+ - Convert `NavigationOptionList` wrapper to `<List></List>`
97
+ - Convert each child `NavigationOption` to `ListItem` with `ListItem.Navigation`
98
+
99
+ ```tsx
100
+ <NavigationOptionList>
101
+ <NavigationOption title="T1" content="C1" onClick={fn1} />
102
+ <NavigationOption title="T2" content="C2" href="/link" />
103
+ </NavigationOptionList>
104
+
105
+ <List>
106
+ <ListItem title="T1" subtitle="C1" control={<ListItem.Navigation onClick={fn1} />} />
107
+ <ListItem title="T2" subtitle="C2" control={<ListItem.Navigation href="/link" />} />
108
+ </List>
109
+ ```
110
+
111
+ ---
112
+
113
+ ## Option → ListItem
114
+
115
+ - Wrap `media` in `ListItem.AvatarView`
116
+
117
+ ```tsx
118
+ <Option media={<Icon />} title="Title" />
119
+
120
+ <ListItem title="Title" media={<ListItem.AvatarView><Icon /></ListItem.AvatarView>} />
121
+ ```
122
+
123
+ ---
124
+
125
+ ## Summary → ListItem
126
+
127
+ ### Basic
128
+
129
+ - `icon` → wrap in `ListItem.AvatarView` with `size={32}` as `media`
130
+ - Remove `size` prop from child icon
131
+
132
+ ### Status mapping
133
+
134
+ - `Status.DONE` → `badge={{ status: 'positive' }}` on AvatarView
135
+ - `Status.PENDING` → `badge={{ status: 'pending' }}` on AvatarView
136
+ - `Status.NOT_DONE` → no badge
137
+
138
+ ### Action
139
+
140
+ - `action.text` → `action.label` in `ListItem.AdditionalInfo`
141
+ - Pass entire action object including `href`, `onClick`, `target`
142
+ - Remove `aria-label` from action (not supported)
143
+
144
+ ### Info with Modal (add state management)
145
+
146
+ - Add: `const [open, setOpen] = useState(false)`
147
+ - Create `ListItem.IconButton` with `partiallyInteractive` and `onClick`
148
+ - Render `Modal` separately with state
149
+ - Transfer `aria-label` to IconButton
150
+
151
+ ### Info with Popover
152
+
153
+ - `Popover` wraps `ListItem.IconButton` with `partiallyInteractive`
154
+ - Transfer `aria-label` to IconButton
155
+
156
+ ```tsx
157
+ // Basic
158
+ <Summary title="T" description="D" icon={<Icon size={24} />} />
159
+
160
+ <ListItem title="T" subtitle="D" media={<ListItem.AvatarView size={32}><Icon /></ListItem.AvatarView>} />
161
+
162
+ // Status
163
+ <Summary title="T" description="D" icon={<Icon />} status={Status.DONE} />
164
+
165
+ <ListItem title="T" subtitle="D" media={<ListItem.AvatarView size={32} badge={{status:'positive'}}><Icon /></ListItem.AvatarView>} />
166
+
167
+ // Action
168
+ <Summary title="T" description="D" icon={<Icon />} action={{text:'Go', href:'/go', target:'_blank'}} />
169
+
170
+ <ListItem title="T" subtitle="D" media={<ListItem.AvatarView size={32}><Icon /></ListItem.AvatarView>} additionalInfo={<ListItem.AdditionalInfo action={{label:'Go', href:'/go', target:'_blank'}} />} />
171
+
172
+ // Modal (requires state: const [open, setOpen] = useState(false))
173
+ <Summary title="T" description="D" icon={<Icon />} info={{title:'Help', content:'Text', presentation:'MODAL', 'aria-label':'Info'}} />
174
+
175
+ <>
176
+ <ListItem title="T" subtitle="D" media={<ListItem.AvatarView size={32}><Icon /></ListItem.AvatarView>} control={
177
+ <ListItem.IconButton partiallyInteractive aria-label="Info" onClick={()=>setOpen(!open)}>
178
+ <QuestionMarkCircle />
179
+ </ListItem.IconButton>
180
+ } />
181
+ <Modal open={open} title="Help" body="Text" onClose={()=>setOpen(false)} />
182
+ </>
183
+
184
+ // Popover
185
+ <Summary title="T" description="D" icon={<Icon />} info={{title:'Help', content:'Text', presentation:'POPOVER', 'aria-label':'Info'}} />
186
+
187
+ <ListItem title="T" subtitle="D" media={<ListItem.AvatarView size={32}><Icon /></ListItem.AvatarView>} control={
188
+ <Popover title="Help" content="Text" onClose={()=>setOpen(false)}>
189
+ <ListItem.IconButton partiallyInteractive aria-label="Info">
190
+ <QuestionMarkCircle />
191
+ </ListItem.IconButton>
192
+ </Popover>
193
+ } />
194
+ ```
@@ -1,6 +1,7 @@
1
1
  const require_common = require('../../common-DdEQmI2h.js');
2
2
  let node_child_process = require("node:child_process");
3
3
  let node_path = require("node:path");
4
+ let node_url = require("node:url");
4
5
  let node_fs = require("node:fs");
5
6
  let _anthropic_ai_claude_agent_sdk = require("@anthropic-ai/claude-agent-sdk");
6
7
  let node_https = require("node:https");
@@ -257,206 +258,19 @@ const DEPRECATED_COMPONENT_NAMES = [
257
258
  "RadioOption"
258
259
  ];
259
260
  const GREP_PATTERN = new RegExp(`import\\s*\\{[\\s\\S]*?(${DEPRECATED_COMPONENT_NAMES.join("|")})[\\s\\S]*?\\}\\s*from\\s*['"]@transferwise/components['"]`, "u");
260
- const MIGRATION_RULES = `Migration rules:
261
- # Legacy Component → ListItem Migration Guide
262
-
263
- ## Import Management
264
- 1. Remove imports: \`{ ActionOption, NavigationOption, NavigationOptionList, Summary, SwitchOption, CheckboxOption, RadioOption }\` from \`'@transferwise/components'\`
265
- 2. Add import: \`{ ListItem }\` from \`'@transferwise/components'\`
266
- 3. For Summary with info: Add \`{ Modal }\` and/or \`{ Popover }\` from \`'@transferwise/components'\`
267
- 4. For Summary with info: Add \`{ QuestionMarkCircle }\` from \`'@transferwise/icons'\`
268
- 5. For Summary with info modal: Add \`{ useState }\` from \`'react'\`
269
-
270
- ## Universal Rules
271
- 1. \`title\` → \`title\` (direct)
272
- 2. \`content\` or \`description\` → \`subtitle\`
273
- 3. \`disabled\` stays on \`ListItem\` (not controls)
274
- 4. \`id\` stays on \`ListItem\` (not controls)
275
- 5. Remove deprecated props: \`as\`, \`complex\`, \`showMediaAtAllSizes\`, \`showMediaCircle\`, \`isContainerAligned\`
276
- 6. Preserve string quotes exactly as they are (don't convert \`\` to \`'\` or \`"\`)
277
- 7. When wrapping icons in \`ListItem.AvatarView\`: move \`size\` from icon to AvatarView (except Summary always uses 32)
278
-
279
- ---
280
-
281
- ## ActionOption → ListItem.Button
282
-
283
- - \`action\` → Button children
284
- - \`onClick\` → Button \`onClick\`
285
- - Priority mapping:
286
- - default or \`"primary"\` → \`"primary"\`
287
- - \`"secondary"\` → \`"secondary-neutral"\`
288
- - \`"secondary-send"\` → \`"secondary"\`
289
- - \`"tertiary"\` → \`"tertiary"\`
290
-
291
- \`\`\`tsx
292
- <ActionOption title="Title" content="Text" action="Click" priority="secondary" onClick={fn} />
293
-
294
- <ListItem title="Title" subtitle="Text" control={<ListItem.Button priority="secondary-neutral" onClick={fn}>Click</ListItem.Button>} />
295
- \`\`\`
296
-
297
- ---
298
-
299
- ## CheckboxOption → ListItem.Checkbox
300
-
301
- - \`name\` is moved to \`ListItem.Checkbox\`
302
- - \`onChange\`: \`(checked: boolean) => void\` → \`(event: ChangeEvent<HTMLInputElement>) => void\`
303
- - Update handler: \`(checked) => setX(checked)\` → \`(event) => setX(event.target.checked)\`
304
-
305
- \`\`\`tsx
306
- <CheckboxOption id="x" name="y" title="Title" content="Text" checked={v} onChange={(c) => set(c)} />
307
-
308
- <ListItem id="x" title="Title" subtitle="Text" control={<ListItem.Checkbox name="y" checked={v} onChange={(e) => set(e.target.checked)} />} />
309
- \`\`\`
310
-
311
- ---
312
-
313
- ## RadioOption → ListItem.Radio
314
-
315
- - \`name\`, \`value\`, \`checked\`, \`onChange\` move to Radio
316
-
317
- \`\`\`tsx
318
- <RadioOption id="x" name="y" value="v" title="Title" content="Text" checked={v==='v'} onChange={set} />
319
-
320
- <ListItem id="x" title="Title" subtitle="Text" control={<ListItem.Radio name="y" value="v" checked={v==='v'} onChange={set} />} />
321
- \`\`\`
322
-
323
- ---
324
-
325
- ## SwitchOption → ListItem.Switch
326
-
327
- - \`aria-label\` is moved to \`ListItem.Switch\`
328
- - \`onChange\` → \`onClick\` with manual toggle: \`onChange={set}\` → \`onClick={() => set(!v)}\`
329
-
330
- \`\`\`tsx
331
- <SwitchOption id="x" title="Title" content="Text" checked={v} aria-label="Toggle" onChange={set} />
332
-
333
- <ListItem id="x" title="Title" subtitle="Text" control={<ListItem.Switch checked={v} aria-label="Toggle" onClick={() => set(!v)} />} />
334
- \`\`\`
335
-
336
- ---
337
-
338
- ## NavigationOption → ListItem.Navigation
339
-
340
- - \`onClick\` or \`href\` move to Navigation
341
-
342
- \`\`\`tsx
343
- <NavigationOption title="Title" content="Text" onClick={fn} />
344
-
345
- <ListItem title="Title" subtitle="Text" control={<ListItem.Navigation onClick={fn} />} />
346
- \`\`\`
347
-
348
- ---
349
-
350
- ## NavigationOptionList → (remove wrapper)
351
-
352
- - Convert \`NavigationOptionList\` wrapper to \`<List></List>\`
353
- - Convert each child \`NavigationOption\` to \`ListItem\` with \`ListItem.Navigation\`
354
-
355
- \`\`\`tsx
356
- <NavigationOptionList>
357
- <NavigationOption title="T1" content="C1" onClick={fn1} />
358
- <NavigationOption title="T2" content="C2" href="/link" />
359
- </NavigationOptionList>
360
-
361
- <>
362
- <ListItem title="T1" subtitle="C1" control={<ListItem.Navigation onClick={fn1} />} />
363
- <ListItem title="T2" subtitle="C2" control={<ListItem.Navigation href="/link" />} />
364
- </>
365
- \`\`\`
366
-
367
- ---
368
-
369
- ## Option → ListItem
370
-
371
- - Wrap \`media\` in \`ListItem.AvatarView\`
372
- - Move \`size\` prop from icon to AvatarView (keeping same value)
373
-
374
- \`\`\`tsx
375
- <Option media={<Icon size={48} />} title="Title" />
376
-
377
- <ListItem title="Title" media={<ListItem.AvatarView size={48}><Icon /></ListItem.AvatarView>} />
378
- \`\`\`
379
-
380
- ---
381
-
382
- ## Summary → ListItem
383
-
384
- ### Basic
385
- - \`icon\` prop → wrap in \`ListItem.AvatarView\` with \`size={32}\` as \`media\`
386
- - ALWAYS use \`size={32}\` on AvatarView (regardless of original icon size)
387
- - ALWAYS remove \`size\` prop from child icon
388
- - Summary-specific: always uses 32, while other components move size value from icon to AvatarView
389
-
390
- ### Status mapping
391
- - \`Status.DONE\` → \`badge={{ status: 'positive' }}\` on AvatarView
392
- - \`Status.PENDING\` → \`badge={{ status: 'pending' }}\` on AvatarView
393
- - \`Status.NOT_DONE\` → no badge
394
-
395
- ### Action
396
- - \`action.text\` → \`action.label\` in \`ListItem.AdditionalInfo\`
397
- - Pass entire action object including \`href\`, \`onClick\`, \`target\`
398
- - Remove \`aria-label\` from action (not supported)
399
-
400
- ### Info with Modal (add state management)
401
- - Add: \`const [open, setOpen] = useState(false)\`
402
- - Create \`ListItem.IconButton\` with \`partiallyInteractive\` and \`onClick\`
403
- - Render \`Modal\` separately with state
404
- - Transfer \`aria-label\` to IconButton
405
-
406
- ### Info with Popover
407
- - \`Popover\` wraps \`ListItem.IconButton\` with \`partiallyInteractive\`
408
- - Transfer \`aria-label\` to IconButton
409
-
410
- \`\`\`tsx
411
- // Basic (icon with or without size prop always becomes size={32} on AvatarView)
412
- <Summary title="T" description="D" icon={<Icon size={24} />} />
413
-
414
- <ListItem title="T" subtitle="D" media={<ListItem.AvatarView size={32}><Icon /></ListItem.AvatarView>} />
415
-
416
- // Status
417
- <Summary title="T" description="D" icon={<Icon />} status={Status.DONE} />
418
-
419
- <ListItem title="T" subtitle="D" media={<ListItem.AvatarView size={32} badge={{status:'positive'}}><Icon /></ListItem.AvatarView>} />
420
-
421
- // Action
422
- <Summary title="T" description="D" icon={<Icon />} action={{text:'Go', href:'/go', target:'_blank'}} />
423
-
424
- <ListItem title="T" subtitle="D" media={<ListItem.AvatarView size={32}><Icon /></ListItem.AvatarView>} additionalInfo={<ListItem.AdditionalInfo action={{label:'Go', href:'/go', target:'_blank'}} />} />
425
-
426
- // Modal (requires state: const [open, setOpen] = useState(false))
427
- <Summary title="T" description="D" icon={<Icon />} info={{title:'Help', content:'Text', presentation:'MODAL', 'aria-label':'Info'}} />
428
-
429
- <>
430
- <ListItem title="T" subtitle="D" media={<ListItem.AvatarView size={32}><Icon /></ListItem.AvatarView>} control={
431
- <ListItem.IconButton partiallyInteractive aria-label="Info" onClick={()=>setOpen(!open)}>
432
- <QuestionMarkCircle />
433
- </ListItem.IconButton>
434
- } />
435
- <Modal open={open} title="Help" body="Text" onClose={()=>setOpen(false)} />
436
- </>
437
-
438
- // Popover
439
- <Summary title="T" description="D" icon={<Icon />} info={{title:'Help', content:'Text', presentation:'POPOVER', 'aria-label':'Info'}} />
440
-
441
- <ListItem title="T" subtitle="D" media={<ListItem.AvatarView size={32}><Icon /></ListItem.AvatarView>} control={
442
- <Popover title="Help" content="Text" onClose={()=>setOpen(false)}>
443
- <ListItem.IconButton partiallyInteractive aria-label="Info">
444
- <QuestionMarkCircle />
445
- </ListItem.IconButton>
446
- </Popover>
447
- } />
448
- \`\`\`
449
-
450
- `;
451
261
 
452
262
  //#endregion
453
263
  //#region src/transforms/list-item/transformer.ts
454
- const transformer = async (targetDirectories, isDebug = false) => claudeTransformer({
455
- targetDirectories,
456
- isDebug,
457
- componentGrepPattern: GREP_PATTERN,
458
- additionalPromptContext: `Deprecated components: ${DEPRECATED_COMPONENT_NAMES.join(", ")}\n${MIGRATION_RULES}`
459
- });
264
+ const MIGRATION_RULES_PATH = (0, node_path.join)((0, node_path.dirname)((0, node_url.fileURLToPath)(require("url").pathToFileURL(__filename).href)), "MIGRATION_RULES.md");
265
+ const transformer = async (targetDirectories, isDebug = false) => {
266
+ const migrationRules = (0, node_fs.readFileSync)(MIGRATION_RULES_PATH, "utf-8");
267
+ return claudeTransformer({
268
+ targetDirectories,
269
+ isDebug,
270
+ componentGrepPattern: GREP_PATTERN,
271
+ additionalPromptContext: `Deprecated components: ${DEPRECATED_COMPONENT_NAMES.join(", ")}\n${migrationRules}`
272
+ });
273
+ };
460
274
  var transformer_default = transformer;
461
275
 
462
276
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"transformer.js","names":["https","options: Options","CONSOLE_ICONS","Manager","Listr"],"sources":["../../../src/constants/claude.ts","../../../src/helpers/common/pathUtils.ts","../../../src/helpers/common/timerUtils.ts","../../../src/helpers/common/vpnUtils.ts","../../../src/helpers/claude/query.ts","../../../src/helpers/claude/transformer.ts","../../../src/transforms/list-item/constants.ts","../../../src/transforms/list-item/transformer.ts"],"sourcesContent":["export const CLAUDE_SETTINGS_FILE = '.claude/settings.json';\nexport const VPN_COUNTDOWN_TIMEOUT = 5;\nexport const DIRECTORY_CONCURRENCY_LIMIT = 3;\nexport const FILE_CONCURRENCY_LIMIT = 10;\n\nexport const INITIAL_CLAUDE_PROMPT = `You are a code migration assistant that helps migrate TypeScript/JSX code of deprecated Wise Design System (WDS) components from '@transferwise/components', to their new replacement components as detailed in the relevant migration guide. The following is a list of rules that must always be followed throughout the migration process.\n\nRules:\n1. Only ever modify files via the Edit tool - do not use the Write tool\n2. When identifying what code to migrate within a file, explain how you identified it first.\n3. Migrate components per provided migration rules\n4. Maintain TypeScript type safety and update types to match new API\n5. Map props: handle renamed, deprecated, new required, and changed types\n6. Update imports to new WDS components and types\n7. Preserve code style, formatting, and calculated logic\n8. Handle conditional rendering, spread props, and complex expressions\n9. Note: New components may lack feature parity with legacy versions\n10. Only modify code requiring changes per migration rules, and any impacted surrounding code for context.\n11. Final result response should just be whether the migration was successful overall, or if any errors were encountered\n - Do not summarise or explain the changes made\n12. Explain your reasoning and justification before making changes, as you edit each file.\n - Keep it concise and succinct, as only bullet points\n13. After modifying the file, do not summarise the changes made.\n14. If you do not have permission to edit a file, still attempt to edit it and then move onto the next file.\n\nAdditionally you'll receive:\n- File paths to migrate in individual queries\n- Deprecated component names at the end of this prompt\n- Migration context and guide for each deprecated component`;\n","/** Split the path string to get the relative path after the directory, and wrap with ANSI color codes */\nexport function formatPathOutput(directory: string, path?: string, asDim?: boolean): string {\n const relativePath = path ? (path.split(directory.replace('.', ''))[1] ?? path) : directory;\n return asDim ? `\\x1b[2m${relativePath}\\x1b[0m` : `\\x1b[32m${relativePath}\\x1b[0m`;\n}\n","/** Generates a formatted string representing the total elapsed time since the given start time */\nexport function generateElapsedTime(startTime: number): string {\n const endTime = Date.now();\n const elapsedTime = Math.floor((endTime - startTime) / 1000);\n const hours = Math.floor(elapsedTime / 3600);\n const minutes = Math.floor((elapsedTime % 3600) / 60);\n const seconds = elapsedTime % 60;\n\n return `${hours ? `${hours}h ` : ''}${minutes ? `${minutes}m ` : ''}${seconds ? `${seconds}s` : ''}`;\n}\n","import https from 'node:https';\n\nimport type { DefaultRenderer, ListrTaskWrapper, SimpleRenderer } from 'listr2';\n\nimport { VPN_COUNTDOWN_TIMEOUT } from '../../constants/claude';\n\n/** Checks VPN connectivity by pinging the provided base URL's /health endpoint, with countdown retries */\nexport async function checkVPN(\n baseUrl: string | undefined,\n task: ListrTaskWrapper<never, typeof DefaultRenderer, typeof SimpleRenderer>,\n): Promise<void> {\n if (!baseUrl) return;\n\n const checkOnce = async (): Promise<boolean> =>\n new Promise<boolean>((resolveCheck) => {\n const url = new URL('/health', baseUrl);\n const req = https.get(url, { timeout: 2000, rejectUnauthorized: false }, (res) => {\n const ok = !!(res.statusCode && res.statusCode >= 200 && res.statusCode < 400);\n res.resume();\n resolveCheck(ok);\n });\n req.on('timeout', () => {\n req.destroy(new Error('timeout'));\n });\n req.on('error', () => resolveCheck(false));\n });\n\n while (true) {\n const ok = await checkOnce();\n if (ok) {\n // eslint-disable-next-line no-param-reassign\n task.title = 'Connected to VPN';\n break;\n }\n\n // Countdown from 5s\n for (let countdown = VPN_COUNTDOWN_TIMEOUT; countdown > 0; countdown -= 1) {\n // eslint-disable-next-line no-param-reassign\n task.output = `Please connect to VPN... retrying in ${countdown}s`;\n await new Promise<void>((response) => {\n setTimeout(response, 1000);\n });\n }\n }\n}\n","import { execSync } from 'node:child_process';\nimport { readFileSync } from 'node:fs';\nimport { resolve } from 'node:path';\n\nimport { type Options, query } from '@anthropic-ai/claude-agent-sdk';\nimport type { Manager } from '@listr2/manager';\nimport type { DefaultRenderer, ListrTaskWrapper, SimpleRenderer } from 'listr2';\n\nimport {\n CLAUDE_SETTINGS_FILE,\n DIRECTORY_CONCURRENCY_LIMIT,\n INITIAL_CLAUDE_PROMPT,\n} from '../../constants/claude';\nimport { CONSOLE_ICONS } from '../../constants/common';\nimport { checkVPN, formatPathOutput, generateElapsedTime } from '../common';\n\ninterface ClaudeSettings {\n apiKeyHelper?: string;\n env?: {\n ANTHROPIC_BASE_URL?: string;\n ANTHROPIC_CUSTOM_HEADERS?: string;\n ANTHROPIC_DEFAULT_SONNET_MODEL?: string;\n ANTHROPIC_DEFAULT_HAIKU_MODEL?: string;\n ANTHROPIC_DEFAULT_OPUS_MODEL?: string;\n API_TIMEOUT_MS?: string;\n [key: string]: unknown;\n };\n [key: string]: unknown;\n}\n\ninterface QueryOptionProps {\n additionalPromptContext?: string;\n}\n\n/**\n * Creates reusable query options for Claude session, including authentication and system prompt\n */\nfunction getQueryOptions({ additionalPromptContext }: QueryOptionProps): Options {\n // Read settings from ~/.claude/settings.json to get headers and apiKeyHelper\n const claudeSettingsPath = resolve(process.env.HOME || '', CLAUDE_SETTINGS_FILE);\n const settings = JSON.parse(readFileSync(claudeSettingsPath, 'utf-8')) as ClaudeSettings;\n\n // Get API key by executing the apiKeyHelper script, for authenticating with Okta via LLM Gateway\n let apiKey;\n try {\n apiKey = execSync(`bash ${settings.apiKeyHelper}`, {\n encoding: 'utf-8',\n }).trim();\n } catch {}\n\n if (!apiKey || !settings.env?.ANTHROPIC_BASE_URL) {\n throw new Error(\n 'Failed to retrieve Anthropic API key or Base URL. Please check your Claude Code x LLM Gateway configuration - https://transferwise.atlassian.net/wiki/x/_YUe3Q',\n );\n }\n\n const { ANTHROPIC_CUSTOM_HEADERS, ...restEnvVars } = settings?.env ?? {};\n\n const envVars = {\n ANTHROPIC_AUTH_TOKEN: apiKey,\n ANTHROPIC_CUSTOM_HEADERS,\n ...restEnvVars,\n PATH: process.env.PATH, // Specifying PATH, as Claude Agent SDK seems to struggle consuming the actual environment PATH\n };\n\n return {\n env: envVars,\n permissionMode: 'acceptEdits',\n systemPrompt: {\n type: 'preset',\n preset: 'claude_code',\n append: `${INITIAL_CLAUDE_PROMPT}\\n${additionalPromptContext}`,\n },\n allowedTools: ['Grep', 'Read'],\n settingSources: ['local', 'project', 'user'],\n };\n}\n\n/**\n * Initiate a new Claude session/conversation and return reusable options\n */\nexport async function initiateClaudeSessionOptions(\n manager: Manager,\n additionalPromptContext: string,\n): Promise<Options> {\n let options: Options = {};\n\n manager.add([\n {\n title: 'Configuring Claude connection and instance...',\n task: async (ctx, task) => {\n options = getQueryOptions({ additionalPromptContext });\n\n return task.newListr([\n {\n title: 'Checking VPN connection...',\n task: async (\n subCtx,\n subtask: ListrTaskWrapper<never, typeof DefaultRenderer, typeof SimpleRenderer>,\n ) => checkVPN(options.env?.ANTHROPIC_BASE_URL, subtask),\n },\n {\n title: 'Initialising Claude session',\n task: async (\n subCtx,\n subtask: ListrTaskWrapper<never, typeof DefaultRenderer, typeof SimpleRenderer>,\n ) => {\n // eslint-disable-next-line no-param-reassign\n subtask.output = 'Your browser may open for Okta authentication if required';\n\n const result = query({\n options,\n prompt: `This is an initialisation query to start a new Claude code migration session. No text response is needed.`,\n });\n\n for await (const message of result) {\n switch (message.type) {\n case 'system':\n if (message.subtype === 'init' && !options.resume) {\n // Set the session ID to resume the conversation in future queries\n options.resume = message.session_id;\n }\n break;\n default:\n if (message.type === 'result' && message.subtype !== 'success') {\n throw new Error(\n `Claude encountered an error when initialising: ${message.errors.join('\\n')}`,\n );\n }\n }\n }\n\n // eslint-disable-next-line no-param-reassign\n task.title = 'Successfully configured and initialised Claude\\n';\n },\n },\n ]);\n },\n },\n ]);\n\n // Set manager to run tasks concurrently, once initialisation steps are done\n await manager.runAll().finally(() => {\n // eslint-disable-next-line no-param-reassign\n manager.options = {\n concurrent: DIRECTORY_CONCURRENCY_LIMIT,\n };\n });\n\n return options;\n}\n\n/**\n * Queries Claude with the given path and handles logging of success/error messages\n */\nexport async function queryClaude(\n directory: string,\n filePath: string,\n options: Options,\n task: ListrTaskWrapper<never, typeof DefaultRenderer, typeof SimpleRenderer>,\n isDebug = false,\n) {\n const startTime = Date.now();\n const result = query({\n options,\n prompt: filePath,\n });\n\n for await (const message of result) {\n switch (message.type) {\n case 'result':\n if (message.subtype === 'success' && isDebug) {\n // eslint-disable-next-line no-param-reassign\n task.title = `\\x1b[2m${formatPathOutput(directory, filePath)}\\x1b[0m]`;\n // eslint-disable-next-line no-param-reassign\n task.output = `\\x1b[2mMigrated in ${generateElapsedTime(startTime)}\\x1b[0m`;\n } else if (message.is_error) {\n // eslint-disable-next-line no-param-reassign\n task.title = `\\x1b[2m${formatPathOutput(directory, filePath)}\\x1b[0m]`;\n // eslint-disable-next-line no-param-reassign\n task.output = `${CONSOLE_ICONS.error} Claude encountered an error: ${JSON.stringify(message)}`;\n }\n break;\n default:\n }\n }\n}\n","import { execSync } from 'node:child_process';\nimport { readFileSync } from 'node:fs';\n\nimport { Manager } from '@listr2/manager';\nimport {\n type DefaultRenderer,\n Listr,\n type ListrTask,\n type ListrTaskWrapper,\n type SimpleRenderer,\n} from 'listr2';\n\nimport { FILE_CONCURRENCY_LIMIT } from '../../constants/claude';\nimport { formatPathOutput, generateElapsedTime } from '../common';\nimport { initiateClaudeSessionOptions, queryClaude } from './query';\n\ninterface ClaudeTransformerOptions {\n targetDirectories: string[];\n componentGrepPattern: RegExp;\n additionalPromptContext: string;\n isDebug?: boolean;\n}\n\n/**\n * Performs code migration using Claude for the specified target directories, for the provided deprecated components and migration guides.\n * @param targetDirectories - Array of directory paths to process\n * @param componentGrepPattern - RegExp pattern to identify files needing migration (e.g. via deprecated component imports)\n * @param isDebug - Whether to enable debug logging\n * @param additionalPromptContext - Additional context to include in the initial Claude prompt before processing files\n */\nexport const claudeTransformer = async ({\n targetDirectories,\n componentGrepPattern,\n additionalPromptContext,\n isDebug = false,\n}: ClaudeTransformerOptions) => {\n process.setMaxListeners(20); // Resolves potential memory issues with how Claude handles its own event listeners\n const startTime = Date.now();\n // Create manager for handling multiple listr instances\n const manager = new Manager({\n concurrent: true,\n });\n const queryOptions = await initiateClaudeSessionOptions(manager, additionalPromptContext);\n\n manager.add([\n {\n title: 'Processing target directories for migration...',\n task: async (ctx, task) => {\n return task.newListr(\n targetDirectories.map((directory) => {\n // Find all .tsx files in the directory\n const allTsxFiles = execSync(`find \"${directory}\" -name \"*.tsx\" -type f`, {\n encoding: 'utf-8',\n })\n .trim()\n .split('\\n')\n .filter(Boolean);\n\n // Filter files that match the pattern by reading and testing each file\n const matchingFilePaths = allTsxFiles.filter((filePath) => {\n const content = readFileSync(filePath, 'utf-8');\n return componentGrepPattern.test(content);\n });\n\n // No files to process in this directory, so we add a task that's immediately skipped\n if (matchingFilePaths.length === 0) {\n return {\n title: `\\x1b[2m${formatPathOutput(directory)} - No files need migration\\x1b[0m`,\n task: (subCtx: ListrTask): void => {\n // eslint-disable-next-line no-param-reassign\n subCtx.skip = true;\n },\n };\n }\n\n return {\n title: `${formatPathOutput(directory)} - Found \\x1b[32m${matchingFilePaths.length}\\x1b[0m file(s) needing migration`,\n task: async (subCtx, parentTask) => {\n // eslint-disable-next-line no-param-reassign\n parentTask.title = `${formatPathOutput(directory)} - Migrating \\x1b[32m${matchingFilePaths.length}\\x1b[0m file(s)...`;\n const completedFilesInDirectory = { count: 0 };\n return parentTask\n .newListr(\n matchingFilePaths.map((filePath) => ({\n title: '', // No title so it runs in the background without any console output\n task: async (\n fileCtx,\n fileTask: ListrTaskWrapper<\n never,\n typeof DefaultRenderer,\n typeof SimpleRenderer\n >,\n ) => {\n await queryClaude(\n directory,\n filePath,\n queryOptions,\n fileTask,\n isDebug,\n ).finally(() => {\n // Update parent task title with progress for each completed file migration\n completedFilesInDirectory.count += 1;\n const isDim =\n completedFilesInDirectory.count === matchingFilePaths.length;\n // eslint-disable-next-line no-param-reassign\n parentTask.title = `${isDim ? '\\x1b[2m' : ''}${formatPathOutput(directory)} - Migrated \\x1b[32m${completedFilesInDirectory.count}\\x1b[0m/\\x1b[32m${matchingFilePaths.length}\\x1b[0m files${isDim ? '\\x1b[0m' : ''}`;\n });\n },\n })),\n { concurrent: FILE_CONCURRENCY_LIMIT },\n )\n .run();\n },\n };\n }),\n { rendererOptions: { suffixSkips: false, collapseSubtasks: false } },\n );\n },\n },\n ]);\n\n // Run all directory tasks concurrently, with final follow up/summary task\n await manager.runAll().finally(async () => {\n await new Listr([\n {\n title: `Finished migrating - elapsed time: \\x1b[32m${generateElapsedTime(startTime)}\\x1b[0m `,\n task: async () => {\n // Task completes immediately\n },\n },\n ]).run();\n });\n};\n","export const DEPRECATED_COMPONENT_NAMES = [\n 'ActionOption',\n 'NavigationOption',\n 'NavigationOptionList',\n 'Summary',\n 'SwitchOption',\n 'CheckboxOption',\n 'RadioOption',\n];\nexport const GREP_PATTERN = new RegExp(\n `import\\\\s*\\\\{[\\\\s\\\\S]*?(${DEPRECATED_COMPONENT_NAMES.join('|')})[\\\\s\\\\S]*?\\\\}\\\\s*from\\\\s*['\"]@transferwise/components['\"]`,\n 'u',\n);\n\nexport const MIGRATION_RULES = `Migration rules:\n# Legacy Component → ListItem Migration Guide\n\n## Import Management\n1. Remove imports: \\`{ ActionOption, NavigationOption, NavigationOptionList, Summary, SwitchOption, CheckboxOption, RadioOption }\\` from \\`'@transferwise/components'\\`\n2. Add import: \\`{ ListItem }\\` from \\`'@transferwise/components'\\`\n3. For Summary with info: Add \\`{ Modal }\\` and/or \\`{ Popover }\\` from \\`'@transferwise/components'\\`\n4. For Summary with info: Add \\`{ QuestionMarkCircle }\\` from \\`'@transferwise/icons'\\`\n5. For Summary with info modal: Add \\`{ useState }\\` from \\`'react'\\`\n\n## Universal Rules\n1. \\`title\\` → \\`title\\` (direct)\n2. \\`content\\` or \\`description\\` → \\`subtitle\\`\n3. \\`disabled\\` stays on \\`ListItem\\` (not controls)\n4. \\`id\\` stays on \\`ListItem\\` (not controls)\n5. Remove deprecated props: \\`as\\`, \\`complex\\`, \\`showMediaAtAllSizes\\`, \\`showMediaCircle\\`, \\`isContainerAligned\\`\n6. Preserve string quotes exactly as they are (don't convert \\`\\` to \\`'\\` or \\`\"\\`)\n7. When wrapping icons in \\`ListItem.AvatarView\\`: move \\`size\\` from icon to AvatarView (except Summary always uses 32)\n\n---\n\n## ActionOption → ListItem.Button\n\n- \\`action\\` → Button children\n- \\`onClick\\` → Button \\`onClick\\`\n- Priority mapping:\n - default or \\`\"primary\"\\` → \\`\"primary\"\\`\n - \\`\"secondary\"\\` → \\`\"secondary-neutral\"\\`\n - \\`\"secondary-send\"\\` → \\`\"secondary\"\\`\n - \\`\"tertiary\"\\` → \\`\"tertiary\"\\`\n\n\\`\\`\\`tsx\n<ActionOption title=\"Title\" content=\"Text\" action=\"Click\" priority=\"secondary\" onClick={fn} />\n→\n<ListItem title=\"Title\" subtitle=\"Text\" control={<ListItem.Button priority=\"secondary-neutral\" onClick={fn}>Click</ListItem.Button>} />\n\\`\\`\\`\n\n---\n\n## CheckboxOption → ListItem.Checkbox\n\n- \\`name\\` is moved to \\`ListItem.Checkbox\\`\n- \\`onChange\\`: \\`(checked: boolean) => void\\` → \\`(event: ChangeEvent<HTMLInputElement>) => void\\`\n - Update handler: \\`(checked) => setX(checked)\\` → \\`(event) => setX(event.target.checked)\\`\n\n\\`\\`\\`tsx\n<CheckboxOption id=\"x\" name=\"y\" title=\"Title\" content=\"Text\" checked={v} onChange={(c) => set(c)} />\n→\n<ListItem id=\"x\" title=\"Title\" subtitle=\"Text\" control={<ListItem.Checkbox name=\"y\" checked={v} onChange={(e) => set(e.target.checked)} />} />\n\\`\\`\\`\n\n---\n\n## RadioOption → ListItem.Radio\n\n- \\`name\\`, \\`value\\`, \\`checked\\`, \\`onChange\\` move to Radio\n\n\\`\\`\\`tsx\n<RadioOption id=\"x\" name=\"y\" value=\"v\" title=\"Title\" content=\"Text\" checked={v==='v'} onChange={set} />\n→\n<ListItem id=\"x\" title=\"Title\" subtitle=\"Text\" control={<ListItem.Radio name=\"y\" value=\"v\" checked={v==='v'} onChange={set} />} />\n\\`\\`\\`\n\n---\n\n## SwitchOption → ListItem.Switch\n\n- \\`aria-label\\` is moved to \\`ListItem.Switch\\`\n- \\`onChange\\` → \\`onClick\\` with manual toggle: \\`onChange={set}\\` → \\`onClick={() => set(!v)}\\`\n\n\\`\\`\\`tsx\n<SwitchOption id=\"x\" title=\"Title\" content=\"Text\" checked={v} aria-label=\"Toggle\" onChange={set} />\n→\n<ListItem id=\"x\" title=\"Title\" subtitle=\"Text\" control={<ListItem.Switch checked={v} aria-label=\"Toggle\" onClick={() => set(!v)} />} />\n\\`\\`\\`\n\n---\n\n## NavigationOption → ListItem.Navigation\n\n- \\`onClick\\` or \\`href\\` move to Navigation\n\n\\`\\`\\`tsx\n<NavigationOption title=\"Title\" content=\"Text\" onClick={fn} />\n→\n<ListItem title=\"Title\" subtitle=\"Text\" control={<ListItem.Navigation onClick={fn} />} />\n\\`\\`\\`\n\n---\n\n## NavigationOptionList → (remove wrapper)\n\n- Convert \\`NavigationOptionList\\` wrapper to \\`<List></List>\\`\n- Convert each child \\`NavigationOption\\` to \\`ListItem\\` with \\`ListItem.Navigation\\`\n\n\\`\\`\\`tsx\n<NavigationOptionList>\n <NavigationOption title=\"T1\" content=\"C1\" onClick={fn1} />\n <NavigationOption title=\"T2\" content=\"C2\" href=\"/link\" />\n</NavigationOptionList>\n→\n<>\n <ListItem title=\"T1\" subtitle=\"C1\" control={<ListItem.Navigation onClick={fn1} />} />\n <ListItem title=\"T2\" subtitle=\"C2\" control={<ListItem.Navigation href=\"/link\" />} />\n</>\n\\`\\`\\`\n\n---\n\n## Option → ListItem\n\n- Wrap \\`media\\` in \\`ListItem.AvatarView\\`\n- Move \\`size\\` prop from icon to AvatarView (keeping same value)\n\n\\`\\`\\`tsx\n<Option media={<Icon size={48} />} title=\"Title\" />\n→\n<ListItem title=\"Title\" media={<ListItem.AvatarView size={48}><Icon /></ListItem.AvatarView>} />\n\\`\\`\\`\n\n---\n\n## Summary → ListItem\n\n### Basic\n- \\`icon\\` prop → wrap in \\`ListItem.AvatarView\\` with \\`size={32}\\` as \\`media\\`\n- ALWAYS use \\`size={32}\\` on AvatarView (regardless of original icon size)\n- ALWAYS remove \\`size\\` prop from child icon\n- Summary-specific: always uses 32, while other components move size value from icon to AvatarView\n\n### Status mapping\n- \\`Status.DONE\\` → \\`badge={{ status: 'positive' }}\\` on AvatarView\n- \\`Status.PENDING\\` → \\`badge={{ status: 'pending' }}\\` on AvatarView\n- \\`Status.NOT_DONE\\` → no badge\n\n### Action\n- \\`action.text\\` → \\`action.label\\` in \\`ListItem.AdditionalInfo\\`\n- Pass entire action object including \\`href\\`, \\`onClick\\`, \\`target\\`\n- Remove \\`aria-label\\` from action (not supported)\n\n### Info with Modal (add state management)\n- Add: \\`const [open, setOpen] = useState(false)\\`\n- Create \\`ListItem.IconButton\\` with \\`partiallyInteractive\\` and \\`onClick\\`\n- Render \\`Modal\\` separately with state\n- Transfer \\`aria-label\\` to IconButton\n\n### Info with Popover\n- \\`Popover\\` wraps \\`ListItem.IconButton\\` with \\`partiallyInteractive\\`\n- Transfer \\`aria-label\\` to IconButton\n\n\\`\\`\\`tsx\n// Basic (icon with or without size prop always becomes size={32} on AvatarView)\n<Summary title=\"T\" description=\"D\" icon={<Icon size={24} />} />\n→\n<ListItem title=\"T\" subtitle=\"D\" media={<ListItem.AvatarView size={32}><Icon /></ListItem.AvatarView>} />\n\n// Status\n<Summary title=\"T\" description=\"D\" icon={<Icon />} status={Status.DONE} />\n→\n<ListItem title=\"T\" subtitle=\"D\" media={<ListItem.AvatarView size={32} badge={{status:'positive'}}><Icon /></ListItem.AvatarView>} />\n\n// Action\n<Summary title=\"T\" description=\"D\" icon={<Icon />} action={{text:'Go', href:'/go', target:'_blank'}} />\n→\n<ListItem title=\"T\" subtitle=\"D\" media={<ListItem.AvatarView size={32}><Icon /></ListItem.AvatarView>} additionalInfo={<ListItem.AdditionalInfo action={{label:'Go', href:'/go', target:'_blank'}} />} />\n\n// Modal (requires state: const [open, setOpen] = useState(false))\n<Summary title=\"T\" description=\"D\" icon={<Icon />} info={{title:'Help', content:'Text', presentation:'MODAL', 'aria-label':'Info'}} />\n→\n<>\n <ListItem title=\"T\" subtitle=\"D\" media={<ListItem.AvatarView size={32}><Icon /></ListItem.AvatarView>} control={\n <ListItem.IconButton partiallyInteractive aria-label=\"Info\" onClick={()=>setOpen(!open)}>\n <QuestionMarkCircle />\n </ListItem.IconButton>\n } />\n <Modal open={open} title=\"Help\" body=\"Text\" onClose={()=>setOpen(false)} />\n</>\n\n// Popover\n<Summary title=\"T\" description=\"D\" icon={<Icon />} info={{title:'Help', content:'Text', presentation:'POPOVER', 'aria-label':'Info'}} />\n→\n<ListItem title=\"T\" subtitle=\"D\" media={<ListItem.AvatarView size={32}><Icon /></ListItem.AvatarView>} control={\n <Popover title=\"Help\" content=\"Text\" onClose={()=>setOpen(false)}>\n <ListItem.IconButton partiallyInteractive aria-label=\"Info\">\n <QuestionMarkCircle />\n </ListItem.IconButton>\n </Popover>\n} />\n\\`\\`\\`\n\n`;\n","import { claudeTransformer } from '../../helpers/claude';\nimport { DEPRECATED_COMPONENT_NAMES, GREP_PATTERN, MIGRATION_RULES } from './constants';\n\nconst transformer = async (targetDirectories: string[], isDebug = false) =>\n claudeTransformer({\n targetDirectories,\n isDebug,\n componentGrepPattern: GREP_PATTERN,\n additionalPromptContext: `Deprecated components: ${DEPRECATED_COMPONENT_NAMES.join(', ')}\\n${MIGRATION_RULES}`,\n });\n\nexport default transformer;\n"],"mappings":"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"}
1
+ {"version":3,"file":"transformer.js","names":["https","options: Options","CONSOLE_ICONS","Manager","Listr"],"sources":["../../../src/constants/claude.ts","../../../src/helpers/common/pathUtils.ts","../../../src/helpers/common/timerUtils.ts","../../../src/helpers/common/vpnUtils.ts","../../../src/helpers/claude/query.ts","../../../src/helpers/claude/transformer.ts","../../../src/transforms/list-item/constants.ts","../../../src/transforms/list-item/transformer.ts"],"sourcesContent":["export const CLAUDE_SETTINGS_FILE = '.claude/settings.json';\nexport const VPN_COUNTDOWN_TIMEOUT = 5;\nexport const DIRECTORY_CONCURRENCY_LIMIT = 3;\nexport const FILE_CONCURRENCY_LIMIT = 10;\n\nexport const INITIAL_CLAUDE_PROMPT = `You are a code migration assistant that helps migrate TypeScript/JSX code of deprecated Wise Design System (WDS) components from '@transferwise/components', to their new replacement components as detailed in the relevant migration guide. The following is a list of rules that must always be followed throughout the migration process.\n\nRules:\n1. Only ever modify files via the Edit tool - do not use the Write tool\n2. When identifying what code to migrate within a file, explain how you identified it first.\n3. Migrate components per provided migration rules\n4. Maintain TypeScript type safety and update types to match new API\n5. Map props: handle renamed, deprecated, new required, and changed types\n6. Update imports to new WDS components and types\n7. Preserve code style, formatting, and calculated logic\n8. Handle conditional rendering, spread props, and complex expressions\n9. Note: New components may lack feature parity with legacy versions\n10. Only modify code requiring changes per migration rules, and any impacted surrounding code for context.\n11. Final result response should just be whether the migration was successful overall, or if any errors were encountered\n - Do not summarise or explain the changes made\n12. Explain your reasoning and justification before making changes, as you edit each file.\n - Keep it concise and succinct, as only bullet points\n13. After modifying the file, do not summarise the changes made.\n14. If you do not have permission to edit a file, still attempt to edit it and then move onto the next file.\n\nAdditionally you'll receive:\n- File paths to migrate in individual queries\n- Deprecated component names at the end of this prompt\n- Migration context and guide for each deprecated component`;\n","/** Split the path string to get the relative path after the directory, and wrap with ANSI color codes */\nexport function formatPathOutput(directory: string, path?: string, asDim?: boolean): string {\n const relativePath = path ? (path.split(directory.replace('.', ''))[1] ?? path) : directory;\n return asDim ? `\\x1b[2m${relativePath}\\x1b[0m` : `\\x1b[32m${relativePath}\\x1b[0m`;\n}\n","/** Generates a formatted string representing the total elapsed time since the given start time */\nexport function generateElapsedTime(startTime: number): string {\n const endTime = Date.now();\n const elapsedTime = Math.floor((endTime - startTime) / 1000);\n const hours = Math.floor(elapsedTime / 3600);\n const minutes = Math.floor((elapsedTime % 3600) / 60);\n const seconds = elapsedTime % 60;\n\n return `${hours ? `${hours}h ` : ''}${minutes ? `${minutes}m ` : ''}${seconds ? `${seconds}s` : ''}`;\n}\n","import https from 'node:https';\n\nimport type { DefaultRenderer, ListrTaskWrapper, SimpleRenderer } from 'listr2';\n\nimport { VPN_COUNTDOWN_TIMEOUT } from '../../constants/claude';\n\n/** Checks VPN connectivity by pinging the provided base URL's /health endpoint, with countdown retries */\nexport async function checkVPN(\n baseUrl: string | undefined,\n task: ListrTaskWrapper<never, typeof DefaultRenderer, typeof SimpleRenderer>,\n): Promise<void> {\n if (!baseUrl) return;\n\n const checkOnce = async (): Promise<boolean> =>\n new Promise<boolean>((resolveCheck) => {\n const url = new URL('/health', baseUrl);\n const req = https.get(url, { timeout: 2000, rejectUnauthorized: false }, (res) => {\n const ok = !!(res.statusCode && res.statusCode >= 200 && res.statusCode < 400);\n res.resume();\n resolveCheck(ok);\n });\n req.on('timeout', () => {\n req.destroy(new Error('timeout'));\n });\n req.on('error', () => resolveCheck(false));\n });\n\n while (true) {\n const ok = await checkOnce();\n if (ok) {\n // eslint-disable-next-line no-param-reassign\n task.title = 'Connected to VPN';\n break;\n }\n\n // Countdown from 5s\n for (let countdown = VPN_COUNTDOWN_TIMEOUT; countdown > 0; countdown -= 1) {\n // eslint-disable-next-line no-param-reassign\n task.output = `Please connect to VPN... retrying in ${countdown}s`;\n await new Promise<void>((response) => {\n setTimeout(response, 1000);\n });\n }\n }\n}\n","import { execSync } from 'node:child_process';\nimport { readFileSync } from 'node:fs';\nimport { resolve } from 'node:path';\n\nimport { type Options, query } from '@anthropic-ai/claude-agent-sdk';\nimport type { Manager } from '@listr2/manager';\nimport type { DefaultRenderer, ListrTaskWrapper, SimpleRenderer } from 'listr2';\n\nimport {\n CLAUDE_SETTINGS_FILE,\n DIRECTORY_CONCURRENCY_LIMIT,\n INITIAL_CLAUDE_PROMPT,\n} from '../../constants/claude';\nimport { CONSOLE_ICONS } from '../../constants/common';\nimport { checkVPN, formatPathOutput, generateElapsedTime } from '../common';\n\ninterface ClaudeSettings {\n apiKeyHelper?: string;\n env?: {\n ANTHROPIC_BASE_URL?: string;\n ANTHROPIC_CUSTOM_HEADERS?: string;\n ANTHROPIC_DEFAULT_SONNET_MODEL?: string;\n ANTHROPIC_DEFAULT_HAIKU_MODEL?: string;\n ANTHROPIC_DEFAULT_OPUS_MODEL?: string;\n API_TIMEOUT_MS?: string;\n [key: string]: unknown;\n };\n [key: string]: unknown;\n}\n\ninterface QueryOptionProps {\n additionalPromptContext?: string;\n}\n\n/**\n * Creates reusable query options for Claude session, including authentication and system prompt\n */\nfunction getQueryOptions({ additionalPromptContext }: QueryOptionProps): Options {\n // Read settings from ~/.claude/settings.json to get headers and apiKeyHelper\n const claudeSettingsPath = resolve(process.env.HOME || '', CLAUDE_SETTINGS_FILE);\n const settings = JSON.parse(readFileSync(claudeSettingsPath, 'utf-8')) as ClaudeSettings;\n\n // Get API key by executing the apiKeyHelper script, for authenticating with Okta via LLM Gateway\n let apiKey;\n try {\n apiKey = execSync(`bash ${settings.apiKeyHelper}`, {\n encoding: 'utf-8',\n }).trim();\n } catch {}\n\n if (!apiKey || !settings.env?.ANTHROPIC_BASE_URL) {\n throw new Error(\n 'Failed to retrieve Anthropic API key or Base URL. Please check your Claude Code x LLM Gateway configuration - https://transferwise.atlassian.net/wiki/x/_YUe3Q',\n );\n }\n\n const { ANTHROPIC_CUSTOM_HEADERS, ...restEnvVars } = settings?.env ?? {};\n\n const envVars = {\n ANTHROPIC_AUTH_TOKEN: apiKey,\n ANTHROPIC_CUSTOM_HEADERS,\n ...restEnvVars,\n PATH: process.env.PATH, // Specifying PATH, as Claude Agent SDK seems to struggle consuming the actual environment PATH\n };\n\n return {\n env: envVars,\n permissionMode: 'acceptEdits',\n systemPrompt: {\n type: 'preset',\n preset: 'claude_code',\n append: `${INITIAL_CLAUDE_PROMPT}\\n${additionalPromptContext}`,\n },\n allowedTools: ['Grep', 'Read'],\n settingSources: ['local', 'project', 'user'],\n };\n}\n\n/**\n * Initiate a new Claude session/conversation and return reusable options\n */\nexport async function initiateClaudeSessionOptions(\n manager: Manager,\n additionalPromptContext: string,\n): Promise<Options> {\n let options: Options = {};\n\n manager.add([\n {\n title: 'Configuring Claude connection and instance...',\n task: async (ctx, task) => {\n options = getQueryOptions({ additionalPromptContext });\n\n return task.newListr([\n {\n title: 'Checking VPN connection...',\n task: async (\n subCtx,\n subtask: ListrTaskWrapper<never, typeof DefaultRenderer, typeof SimpleRenderer>,\n ) => checkVPN(options.env?.ANTHROPIC_BASE_URL, subtask),\n },\n {\n title: 'Initialising Claude session',\n task: async (\n subCtx,\n subtask: ListrTaskWrapper<never, typeof DefaultRenderer, typeof SimpleRenderer>,\n ) => {\n // eslint-disable-next-line no-param-reassign\n subtask.output = 'Your browser may open for Okta authentication if required';\n\n const result = query({\n options,\n prompt: `This is an initialisation query to start a new Claude code migration session. No text response is needed.`,\n });\n\n for await (const message of result) {\n switch (message.type) {\n case 'system':\n if (message.subtype === 'init' && !options.resume) {\n // Set the session ID to resume the conversation in future queries\n options.resume = message.session_id;\n }\n break;\n default:\n if (message.type === 'result' && message.subtype !== 'success') {\n throw new Error(\n `Claude encountered an error when initialising: ${message.errors.join('\\n')}`,\n );\n }\n }\n }\n\n // eslint-disable-next-line no-param-reassign\n task.title = 'Successfully configured and initialised Claude\\n';\n },\n },\n ]);\n },\n },\n ]);\n\n // Set manager to run tasks concurrently, once initialisation steps are done\n await manager.runAll().finally(() => {\n // eslint-disable-next-line no-param-reassign\n manager.options = {\n concurrent: DIRECTORY_CONCURRENCY_LIMIT,\n };\n });\n\n return options;\n}\n\n/**\n * Queries Claude with the given path and handles logging of success/error messages\n */\nexport async function queryClaude(\n directory: string,\n filePath: string,\n options: Options,\n task: ListrTaskWrapper<never, typeof DefaultRenderer, typeof SimpleRenderer>,\n isDebug = false,\n) {\n const startTime = Date.now();\n const result = query({\n options,\n prompt: filePath,\n });\n\n for await (const message of result) {\n switch (message.type) {\n case 'result':\n if (message.subtype === 'success' && isDebug) {\n // eslint-disable-next-line no-param-reassign\n task.title = `\\x1b[2m${formatPathOutput(directory, filePath)}\\x1b[0m]`;\n // eslint-disable-next-line no-param-reassign\n task.output = `\\x1b[2mMigrated in ${generateElapsedTime(startTime)}\\x1b[0m`;\n } else if (message.is_error) {\n // eslint-disable-next-line no-param-reassign\n task.title = `\\x1b[2m${formatPathOutput(directory, filePath)}\\x1b[0m]`;\n // eslint-disable-next-line no-param-reassign\n task.output = `${CONSOLE_ICONS.error} Claude encountered an error: ${JSON.stringify(message)}`;\n }\n break;\n default:\n }\n }\n}\n","import { execSync } from 'node:child_process';\nimport { readFileSync } from 'node:fs';\n\nimport { Manager } from '@listr2/manager';\nimport {\n type DefaultRenderer,\n Listr,\n type ListrTask,\n type ListrTaskWrapper,\n type SimpleRenderer,\n} from 'listr2';\n\nimport { FILE_CONCURRENCY_LIMIT } from '../../constants/claude';\nimport { formatPathOutput, generateElapsedTime } from '../common';\nimport { initiateClaudeSessionOptions, queryClaude } from './query';\n\ninterface ClaudeTransformerOptions {\n targetDirectories: string[];\n componentGrepPattern: RegExp;\n additionalPromptContext: string;\n isDebug?: boolean;\n}\n\n/**\n * Performs code migration using Claude for the specified target directories, for the provided deprecated components and migration guides.\n * @param targetDirectories - Array of directory paths to process\n * @param componentGrepPattern - RegExp pattern to identify files needing migration (e.g. via deprecated component imports)\n * @param isDebug - Whether to enable debug logging\n * @param additionalPromptContext - Additional context to include in the initial Claude prompt before processing files\n */\nexport const claudeTransformer = async ({\n targetDirectories,\n componentGrepPattern,\n additionalPromptContext,\n isDebug = false,\n}: ClaudeTransformerOptions) => {\n process.setMaxListeners(20); // Resolves potential memory issues with how Claude handles its own event listeners\n const startTime = Date.now();\n // Create manager for handling multiple listr instances\n const manager = new Manager({\n concurrent: true,\n });\n const queryOptions = await initiateClaudeSessionOptions(manager, additionalPromptContext);\n\n manager.add([\n {\n title: 'Processing target directories for migration...',\n task: async (ctx, task) => {\n return task.newListr(\n targetDirectories.map((directory) => {\n // Find all .tsx files in the directory\n const allTsxFiles = execSync(`find \"${directory}\" -name \"*.tsx\" -type f`, {\n encoding: 'utf-8',\n })\n .trim()\n .split('\\n')\n .filter(Boolean);\n\n // Filter files that match the pattern by reading and testing each file\n const matchingFilePaths = allTsxFiles.filter((filePath) => {\n const content = readFileSync(filePath, 'utf-8');\n return componentGrepPattern.test(content);\n });\n\n // No files to process in this directory, so we add a task that's immediately skipped\n if (matchingFilePaths.length === 0) {\n return {\n title: `\\x1b[2m${formatPathOutput(directory)} - No files need migration\\x1b[0m`,\n task: (subCtx: ListrTask): void => {\n // eslint-disable-next-line no-param-reassign\n subCtx.skip = true;\n },\n };\n }\n\n return {\n title: `${formatPathOutput(directory)} - Found \\x1b[32m${matchingFilePaths.length}\\x1b[0m file(s) needing migration`,\n task: async (subCtx, parentTask) => {\n // eslint-disable-next-line no-param-reassign\n parentTask.title = `${formatPathOutput(directory)} - Migrating \\x1b[32m${matchingFilePaths.length}\\x1b[0m file(s)...`;\n const completedFilesInDirectory = { count: 0 };\n return parentTask\n .newListr(\n matchingFilePaths.map((filePath) => ({\n title: '', // No title so it runs in the background without any console output\n task: async (\n fileCtx,\n fileTask: ListrTaskWrapper<\n never,\n typeof DefaultRenderer,\n typeof SimpleRenderer\n >,\n ) => {\n await queryClaude(\n directory,\n filePath,\n queryOptions,\n fileTask,\n isDebug,\n ).finally(() => {\n // Update parent task title with progress for each completed file migration\n completedFilesInDirectory.count += 1;\n const isDim =\n completedFilesInDirectory.count === matchingFilePaths.length;\n // eslint-disable-next-line no-param-reassign\n parentTask.title = `${isDim ? '\\x1b[2m' : ''}${formatPathOutput(directory)} - Migrated \\x1b[32m${completedFilesInDirectory.count}\\x1b[0m/\\x1b[32m${matchingFilePaths.length}\\x1b[0m files${isDim ? '\\x1b[0m' : ''}`;\n });\n },\n })),\n { concurrent: FILE_CONCURRENCY_LIMIT },\n )\n .run();\n },\n };\n }),\n { rendererOptions: { suffixSkips: false, collapseSubtasks: false } },\n );\n },\n },\n ]);\n\n // Run all directory tasks concurrently, with final follow up/summary task\n await manager.runAll().finally(async () => {\n await new Listr([\n {\n title: `Finished migrating - elapsed time: \\x1b[32m${generateElapsedTime(startTime)}\\x1b[0m `,\n task: async () => {\n // Task completes immediately\n },\n },\n ]).run();\n });\n};\n","export const DEPRECATED_COMPONENT_NAMES = [\n 'ActionOption',\n 'NavigationOption',\n 'NavigationOptionList',\n 'Summary',\n 'SwitchOption',\n 'CheckboxOption',\n 'RadioOption',\n];\nexport const GREP_PATTERN = new RegExp(\n `import\\\\s*\\\\{[\\\\s\\\\S]*?(${DEPRECATED_COMPONENT_NAMES.join('|')})[\\\\s\\\\S]*?\\\\}\\\\s*from\\\\s*['\"]@transferwise/components['\"]`,\n 'u',\n);\n","import { readFileSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nimport { claudeTransformer } from '../../helpers/claude';\nimport { DEPRECATED_COMPONENT_NAMES, GREP_PATTERN } from './constants';\n\nconst MIGRATION_RULES_DIRECTORY = dirname(fileURLToPath(import.meta.url));\nconst MIGRATION_RULES_PATH = join(MIGRATION_RULES_DIRECTORY, 'MIGRATION_RULES.md');\n\nconst transformer = async (targetDirectories: string[], isDebug = false) => {\n const migrationRules = readFileSync(MIGRATION_RULES_PATH, 'utf-8');\n\n return claudeTransformer({\n targetDirectories,\n isDebug,\n componentGrepPattern: GREP_PATTERN,\n additionalPromptContext: `Deprecated components: ${DEPRECATED_COMPONENT_NAMES.join(', ')}\\n${migrationRules}`,\n });\n};\n\nexport default transformer;\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wise/wds-codemods",
3
- "version": "1.1.2",
3
+ "version": "1.1.3",
4
4
  "license": "UNLICENSED",
5
5
  "author": "Wise Payments Ltd.",
6
6
  "repository": {