@wise/wds-codemods 1.0.0-experimental-0f5c549 → 1.0.0-experimental-dfd0a93

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.
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  const require_helpers = require('./helpers-Cca1yIbM.js');
3
- const require_transformer = require('./transformer-BqBEjGcV.js');
3
+ const require_transformer = require('./transformer-C2vxxgYT.js');
4
4
  let node_child_process = require("node:child_process");
5
5
  let node_fs_promises = require("node:fs/promises");
6
6
  node_fs_promises = require_helpers.__toESM(node_fs_promises);
@@ -71,7 +71,7 @@ async function runCodemod(transformsDir) {
71
71
  transformFiles: resolvedTransformNames,
72
72
  preselectedTransformFile: transformFile
73
73
  });
74
- if (transformFile === "list-item") await require_transformer.transformer_default(options, codemodPath, isDebug);
74
+ if (transformFile === "list-item") await require_transformer.transformer_default(options.targetPaths, codemodPath, isDebug);
75
75
  else await Promise.all(options.targetPaths.map(async (targetPath) => {
76
76
  console.info(`${require_transformer.CONSOLE_ICONS.focus} \x1b[1mProcessing:\x1b[0m \x1b[32m${targetPath}\x1b[0m`);
77
77
  if (require_helpers.assessPrerequisites(targetPath, codemodPath)) {
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["path","fs","CONSOLE_ICONS","logToInquirer","findPackages","findProjectRoot","loadTransformModules","transformFile: string","runTransformPrompts","getOptions","transformer","assessPrerequisites","error: unknown"],"sources":["../src/controller/index.ts","../src/index.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport { execSync } from 'node:child_process';\nimport fs from 'node:fs/promises';\nimport path from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nimport { select as list } from '@inquirer/prompts';\n\nimport { CONSOLE_ICONS } from '../constants';\nimport transformer from '../transforms/list-item/transformer';\nimport {\n assessPrerequisites,\n findPackages,\n findProjectRoot,\n getOptions,\n loadTransformModules,\n logToInquirer,\n runTransformPrompts,\n} from './helpers';\n\nlet isDebug = false;\nconst currentFilePath = fileURLToPath(import.meta.url);\nconst currentDirPath = path.dirname(currentFilePath);\n\nconst resetReportFile = async (reportPath: string) => {\n try {\n await fs.access(reportPath);\n await fs.rm(reportPath);\n console.debug(\n `${CONSOLE_ICONS.info} Removed existing report file${isDebug ? `: ${reportPath}` : '.'}`,\n );\n } catch {\n console.debug(\n `${CONSOLE_ICONS.info} No existing report file to remove${isDebug ? `: ${reportPath}` : '.'}`,\n );\n }\n};\n\nconst summariseReportFile = async (reportPath: string) => {\n try {\n const reportContent = await fs.readFile(reportPath, 'utf8');\n const lines = reportContent.split('\\n').filter(Boolean);\n if (lines.length) {\n console.debug(\n `\\n${CONSOLE_ICONS.warning} ${lines.length} manual review${lines.length > 1 ? 's are' : ' is'} required. See ${reportPath} for details.`,\n );\n } else {\n console.debug(\n `${CONSOLE_ICONS.info} Report file exists but is empty${isDebug ? `: ${reportPath}` : '.'}`,\n );\n }\n } catch {\n console.debug(`${CONSOLE_ICONS.info} No report file generated - no manual reviews needed`);\n }\n};\n\nconst log = (label: string, value?: string): void => {\n if (typeof logToInquirer === 'function') {\n logToInquirer(label, value || '');\n } else {\n console.info(label, value || '');\n }\n};\n\nasync function runCodemod(transformsDir?: string) {\n const args = process.argv.slice(2);\n const candidate = args[0];\n isDebug = args.includes('--debug');\n\n try {\n const packages = findPackages();\n const reportPath = path.resolve(findProjectRoot(), 'codemod-report.txt');\n const resolvedTransformsDir =\n transformsDir ?? path.resolve(currentDirPath, '../dist/transforms');\n\n if (isDebug) {\n console.debug(\n `${CONSOLE_ICONS.info} Resolved transforms directory: ${resolvedTransformsDir}`,\n );\n }\n\n await resetReportFile(reportPath);\n\n const { transformFiles } = await loadTransformModules(resolvedTransformsDir);\n\n const resolvedTransformNames = await Promise.all(transformFiles);\n if (resolvedTransformNames.length === 0) {\n throw new Error(\n `${CONSOLE_ICONS.error} No transform scripts found${isDebug ? ` in: ${resolvedTransformsDir}` : '.'}`,\n );\n }\n\n let transformFile: string;\n\n if (candidate && resolvedTransformNames.includes(candidate)) {\n log('Select codemod to run:', candidate);\n transformFile = candidate;\n } else {\n transformFile = await list({\n message: 'Select codemod to run:',\n choices: resolvedTransformNames.map((name) => ({ name, value: name })),\n });\n log('Selected codemod:', transformFile);\n }\n\n const codemodPath = path.resolve(resolvedTransformsDir, transformFile, 'transformer.js');\n if (isDebug) {\n console.debug(`${CONSOLE_ICONS.info} Resolved codemod path: ${codemodPath}`);\n }\n\n const promptAnswers = await runTransformPrompts(codemodPath);\n\n const options = await getOptions({\n packages,\n root: findProjectRoot(),\n transformFiles: resolvedTransformNames,\n preselectedTransformFile: transformFile,\n });\n\n // Handle ListItem transform differently as it uses Claude, not jscodeshift\n if (transformFile === 'list-item') {\n // TODO: Handle ALL args and options properly - isDry, isPrint, ignorePatterns, useGitIgnore, etc\n await transformer(options, codemodPath, isDebug);\n } else {\n await Promise.all(\n options.targetPaths.map(async (targetPath) => {\n console.info(\n `${CONSOLE_ICONS.focus} \\x1b[1mProcessing:\\x1b[0m \\x1b[32m${targetPath}\\x1b[0m`,\n );\n\n const isCompliant = assessPrerequisites(targetPath, codemodPath);\n if (isCompliant) {\n const answerArgs = Object.entries(promptAnswers).map(\n ([promptName, answerValue]) => `--${promptName}=${String(answerValue)}`,\n );\n\n const argsList = [\n '-t',\n codemodPath,\n targetPath,\n options.isDry ? '--dry' : '',\n options.isPrint ? '--print' : '',\n options.ignorePatterns\n ? options.ignorePatterns\n .split(',')\n .map((pattern) => `--ignore-pattern=${pattern.trim()}`)\n .join(' ')\n : '',\n options.useGitIgnore ? '--gitignore' : '',\n ...answerArgs,\n ].filter(Boolean);\n const command = `npx jscodeshift ${argsList.join(' ')}`;\n\n if (isDebug) {\n console.debug(`${CONSOLE_ICONS.info} Running: ${command}`);\n }\n\n return execSync(command, { stdio: 'inherit' });\n }\n return undefined;\n }),\n );\n }\n\n await summariseReportFile(reportPath);\n } catch (error: unknown) {\n if (error instanceof Error) {\n console.error(`${CONSOLE_ICONS.error} Error running ${candidate} codemod:`, error.message);\n } else {\n console.error(`${CONSOLE_ICONS.error} Error running ${candidate} codemod:`, error);\n }\n if (process.env.NODE_ENV !== 'test') {\n process.exit(1);\n }\n }\n}\n\nexport { runCodemod };\n","#!/usr/bin/env node\nimport { runCodemod } from './controller';\n\nvoid runCodemod();\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":["path","fs","CONSOLE_ICONS","logToInquirer","findPackages","findProjectRoot","loadTransformModules","transformFile: string","runTransformPrompts","getOptions","transformer","assessPrerequisites","error: unknown"],"sources":["../src/controller/index.ts","../src/index.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport { execSync } from 'node:child_process';\nimport fs from 'node:fs/promises';\nimport path from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nimport { select as list } from '@inquirer/prompts';\n\nimport { CONSOLE_ICONS } from '../constants';\nimport transformer from '../transforms/list-item/transformer';\nimport {\n assessPrerequisites,\n findPackages,\n findProjectRoot,\n getOptions,\n loadTransformModules,\n logToInquirer,\n runTransformPrompts,\n} from './helpers';\n\nlet isDebug = false;\nconst currentFilePath = fileURLToPath(import.meta.url);\nconst currentDirPath = path.dirname(currentFilePath);\n\nconst resetReportFile = async (reportPath: string) => {\n try {\n await fs.access(reportPath);\n await fs.rm(reportPath);\n console.debug(\n `${CONSOLE_ICONS.info} Removed existing report file${isDebug ? `: ${reportPath}` : '.'}`,\n );\n } catch {\n console.debug(\n `${CONSOLE_ICONS.info} No existing report file to remove${isDebug ? `: ${reportPath}` : '.'}`,\n );\n }\n};\n\nconst summariseReportFile = async (reportPath: string) => {\n try {\n const reportContent = await fs.readFile(reportPath, 'utf8');\n const lines = reportContent.split('\\n').filter(Boolean);\n if (lines.length) {\n console.debug(\n `\\n${CONSOLE_ICONS.warning} ${lines.length} manual review${lines.length > 1 ? 's are' : ' is'} required. See ${reportPath} for details.`,\n );\n } else {\n console.debug(\n `${CONSOLE_ICONS.info} Report file exists but is empty${isDebug ? `: ${reportPath}` : '.'}`,\n );\n }\n } catch {\n console.debug(`${CONSOLE_ICONS.info} No report file generated - no manual reviews needed`);\n }\n};\n\nconst log = (label: string, value?: string): void => {\n if (typeof logToInquirer === 'function') {\n logToInquirer(label, value || '');\n } else {\n console.info(label, value || '');\n }\n};\n\nasync function runCodemod(transformsDir?: string) {\n const args = process.argv.slice(2);\n const candidate = args[0];\n isDebug = args.includes('--debug');\n\n try {\n const packages = findPackages();\n const reportPath = path.resolve(findProjectRoot(), 'codemod-report.txt');\n const resolvedTransformsDir =\n transformsDir ?? path.resolve(currentDirPath, '../dist/transforms');\n\n if (isDebug) {\n console.debug(\n `${CONSOLE_ICONS.info} Resolved transforms directory: ${resolvedTransformsDir}`,\n );\n }\n\n await resetReportFile(reportPath);\n\n const { transformFiles } = await loadTransformModules(resolvedTransformsDir);\n\n const resolvedTransformNames = await Promise.all(transformFiles);\n if (resolvedTransformNames.length === 0) {\n throw new Error(\n `${CONSOLE_ICONS.error} No transform scripts found${isDebug ? ` in: ${resolvedTransformsDir}` : '.'}`,\n );\n }\n\n let transformFile: string;\n\n if (candidate && resolvedTransformNames.includes(candidate)) {\n log('Select codemod to run:', candidate);\n transformFile = candidate;\n } else {\n transformFile = await list({\n message: 'Select codemod to run:',\n choices: resolvedTransformNames.map((name) => ({ name, value: name })),\n });\n log('Selected codemod:', transformFile);\n }\n\n const codemodPath = path.resolve(resolvedTransformsDir, transformFile, 'transformer.js');\n if (isDebug) {\n console.debug(`${CONSOLE_ICONS.info} Resolved codemod path: ${codemodPath}`);\n }\n\n const promptAnswers = await runTransformPrompts(codemodPath);\n\n const options = await getOptions({\n packages,\n root: findProjectRoot(),\n transformFiles: resolvedTransformNames,\n preselectedTransformFile: transformFile,\n });\n\n // Handle ListItem transform differently as it uses Claude, not jscodeshift\n if (transformFile === 'list-item') {\n // TODO: Handle ALL args and options properly - isDry, isPrint, ignorePatterns, useGitIgnore, etc\n await transformer(options.targetPaths, codemodPath, isDebug);\n } else {\n await Promise.all(\n options.targetPaths.map(async (targetPath) => {\n console.info(\n `${CONSOLE_ICONS.focus} \\x1b[1mProcessing:\\x1b[0m \\x1b[32m${targetPath}\\x1b[0m`,\n );\n\n const isCompliant = assessPrerequisites(targetPath, codemodPath);\n if (isCompliant) {\n const answerArgs = Object.entries(promptAnswers).map(\n ([promptName, answerValue]) => `--${promptName}=${String(answerValue)}`,\n );\n\n const argsList = [\n '-t',\n codemodPath,\n targetPath,\n options.isDry ? '--dry' : '',\n options.isPrint ? '--print' : '',\n options.ignorePatterns\n ? options.ignorePatterns\n .split(',')\n .map((pattern) => `--ignore-pattern=${pattern.trim()}`)\n .join(' ')\n : '',\n options.useGitIgnore ? '--gitignore' : '',\n ...answerArgs,\n ].filter(Boolean);\n const command = `npx jscodeshift ${argsList.join(' ')}`;\n\n if (isDebug) {\n console.debug(`${CONSOLE_ICONS.info} Running: ${command}`);\n }\n\n return execSync(command, { stdio: 'inherit' });\n }\n return undefined;\n }),\n );\n }\n\n await summariseReportFile(reportPath);\n } catch (error: unknown) {\n if (error instanceof Error) {\n console.error(`${CONSOLE_ICONS.error} Error running ${candidate} codemod:`, error.message);\n } else {\n console.error(`${CONSOLE_ICONS.error} Error running ${candidate} codemod:`, error);\n }\n if (process.env.NODE_ENV !== 'test') {\n process.exit(1);\n }\n }\n}\n\nexport { runCodemod };\n","#!/usr/bin/env node\nimport { runCodemod } from './controller';\n\nvoid runCodemod();\n"],"mappings":"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"}
@@ -1,9 +1,12 @@
1
1
  const require_helpers = require('./helpers-Cca1yIbM.js');
2
2
  let node_child_process = require("node:child_process");
3
3
  let node_path = require("node:path");
4
- let __anthropic_ai_claude_agent_sdk = require("@anthropic-ai/claude-agent-sdk");
5
4
  let node_fs = require("node:fs");
6
- let diff = require("diff");
5
+ let __anthropic_ai_claude_agent_sdk = require("@anthropic-ai/claude-agent-sdk");
6
+ let node_https = require("node:https");
7
+ node_https = require_helpers.__toESM(node_https);
8
+ let ora = require("ora");
9
+ ora = require_helpers.__toESM(ora);
7
10
 
8
11
  //#region src/constants.ts
9
12
  const CONSOLE_ICONS = {
@@ -11,8 +14,7 @@ const CONSOLE_ICONS = {
11
14
  focus: "\x1B[34m➙\x1B[0m",
12
15
  success: "\x1B[32m✔\x1B[0m",
13
16
  warning: "\x1B[33m⚠\x1B[0m",
14
- error: "\x1B[31m✖\x1B[0m",
15
- claude: "\x1B[35m💬\x1B[0m"
17
+ error: "\x1B[31m✖\x1B[0m"
16
18
  };
17
19
 
18
20
  //#endregion
@@ -31,11 +33,11 @@ const MIGRATION_RULES = `Migration rules:
31
33
 
32
34
  ## Universal Rules
33
35
 
34
- 1. Wrap all \`ListItem\` in \`<List>\`
35
- 2. \`title\` → \`title\` (direct)
36
- 3. \`content\` or \`description\` \`subtitle\`
37
- 4. \`disabled\` stays on \`ListItem\` (not controls)
38
- 5. Keep HTML attributes (\`id\`, \`name\`, \`aria-label\`), remove: \`as\`, \`complex\`, \`showMediaAtAllSizes\`, \`showMediaCircle\`, \`isContainerAligned\`
36
+ 1. \`title\` \`title\` (direct)
37
+ 2. \`content\` or \`description\` → \`subtitle\`
38
+ 3. \`disabled\` stays on \`ListItem\` (not controls)
39
+ 4. Keep HTML attributes (\`id\`, \`name\`, \`aria-label\`), remove: \`as\`, \`complex\`, \`showMediaAtAllSizes\`, \`showMediaCircle\`, \`isContainerAligned\`
40
+ 5. In strings, don't convert \`\`to\`'\`or\`"\`. Preserve what is there.
39
41
 
40
42
  ---
41
43
 
@@ -48,7 +50,7 @@ const MIGRATION_RULES = `Migration rules:
48
50
  \`\`\`tsx
49
51
  <ActionOption title="Title" content="Text" action="Click" priority="secondary" onClick={fn} />
50
52
 
51
- <List><ListItem title="Title" subtitle="Text" control={<ListItem.Button priority="secondary-neutral" onClick={fn}>Click</ListItem.Button>} /></List>
53
+ <ListItem title="Title" subtitle="Text" control={<ListItem.Button priority="secondary-neutral" onClick={fn}>Click</ListItem.Button>} />
52
54
  \`\`\`
53
55
 
54
56
  ---
@@ -56,24 +58,26 @@ const MIGRATION_RULES = `Migration rules:
56
58
  ## CheckboxOption → ListItem.Checkbox
57
59
 
58
60
  - \`onChange\`: \`(checked: boolean)\` → \`(event: ChangeEvent)\` use \`event.target.checked\`
59
- - \`id\`, \`name\` move to Checkbox
61
+ - \`name\` move to Checkbox
62
+ - Don't move \`id\` to Checkbox
60
63
 
61
64
  \`\`\`tsx
62
65
  <CheckboxOption id="x" name="y" title="Title" content="Text" checked={v} onChange={(c) => set(c)} />
63
66
 
64
- <List><ListItem title="Title" subtitle="Text" control={<ListItem.Checkbox id="x" name="y" checked={v} onChange={(e) => set(e.target.checked)} />} /></List>
67
+ <ListItem title="Title" subtitle="Text" control={<ListItem.Checkbox name="y" checked={v} onChange={(e) => set(e.target.checked)} />} />
65
68
  \`\`\`
66
69
 
67
70
  ---
68
71
 
69
72
  ## RadioOption → ListItem.Radio
70
73
 
71
- - \`id\`, \`name\`, \`value\`, \`checked\`, \`onChange\` move to Radio
74
+ - \`name\`, \`value\`, \`checked\`, \`onChange\` move to Radio
75
+ - Don't move \`id\` to Radio
72
76
 
73
77
  \`\`\`tsx
74
78
  <RadioOption id="x" name="y" value="v" title="Title" content="Text" checked={v==='v'} onChange={set} />
75
79
 
76
- <List><ListItem title="Title" subtitle="Text" control={<ListItem.Radio id="x" name="y" value="v" checked={v==='v'} onChange={set} />} /></List>
80
+ <ListItem title="Title" subtitle="Text" control={<ListItem.Radio name="y" value="v" checked={v==='v'} onChange={set} />} />
77
81
  \`\`\`
78
82
 
79
83
  ---
@@ -86,7 +90,7 @@ const MIGRATION_RULES = `Migration rules:
86
90
  \`\`\`tsx
87
91
  <SwitchOption title="Title" content="Text" checked={v} aria-label="Toggle" onChange={set} />
88
92
 
89
- <List><ListItem title="Title" subtitle="Text" control={<ListItem.Switch checked={v} aria-label="Toggle" onClick={() => set(!v)} />} /></List>
93
+ <ListItem title="Title" subtitle="Text" control={<ListItem.Switch checked={v} aria-label="Toggle" onClick={() => set(!v)} />} />
90
94
  \`\`\`
91
95
 
92
96
  ---
@@ -98,7 +102,7 @@ const MIGRATION_RULES = `Migration rules:
98
102
  \`\`\`tsx
99
103
  <NavigationOption title="Title" content="Text" onClick={fn} />
100
104
 
101
- <List><ListItem title="Title" subtitle="Text" control={<ListItem.Navigation onClick={fn} />} /></List>
105
+ <ListItem title="Title" subtitle="Text" control={<ListItem.Navigation onClick={fn} />} />
102
106
  \`\`\`
103
107
 
104
108
  ---
@@ -110,7 +114,7 @@ const MIGRATION_RULES = `Migration rules:
110
114
  \`\`\`tsx
111
115
  <Option media={<Icon />} title="Title" />
112
116
 
113
- <List><ListItem title="Title" media={<ListItem.AvatarView><Icon /></ListItem.AvatarView>} /></List>
117
+ <ListItem title="Title" media={<ListItem.AvatarView><Icon /></ListItem.AvatarView>} />
114
118
  \`\`\`
115
119
 
116
120
  ---
@@ -120,6 +124,7 @@ const MIGRATION_RULES = `Migration rules:
120
124
  **Basic:**
121
125
 
122
126
  - \`icon\` → wrap in \`ListItem.AvatarView\` with \`size={32}\` as \`media\`
127
+ - Remove \`size\` from child \`<Icon />\`
123
128
 
124
129
  **Status:**
125
130
 
@@ -135,33 +140,33 @@ const MIGRATION_RULES = `Migration rules:
135
140
 
136
141
  - \`MODAL\` → \`ListItem.IconButton partiallyInteractive\` + \`<Modal>\` in \`control\`
137
142
  - \`POPOVER\` → \`<Popover>\` wrapping \`ListItem.IconButton partiallyInteractive\` in \`control\`
138
- - Use \`QuestionMarkCircle\` icon
143
+ - Use \`QuestionMarkCircle\` icon (import from \`@transferwise/icons\`)
139
144
 
140
145
  \`\`\`tsx
141
146
  // Basic
142
147
  <Summary title="T" description="D" icon={<Icon />} />
143
148
 
144
- <List><ListItem title="T" subtitle="D" media={<ListItem.AvatarView size={32}><Icon /></ListItem.AvatarView>} /></List>
149
+ <ListItem title="T" subtitle="D" media={<ListItem.AvatarView size={32}><Icon /></ListItem.AvatarView>} />
145
150
 
146
151
  // Status
147
152
  <Summary title="T" description="D" icon={<Icon />} status={Status.DONE} />
148
153
 
149
- <List><ListItem title="T" subtitle="D" media={<ListItem.AvatarView size={32} badge={{status:'positive'}}><Icon /></ListItem.AvatarView>} /></List>
154
+ <ListItem title="T" subtitle="D" media={<ListItem.AvatarView size={32} badge={{status:'positive'}}><Icon /></ListItem.AvatarView>} />
150
155
 
151
156
  // Action
152
157
  <Summary title="T" description="D" icon={<Icon />} action={{text:'Go', href:'/go'}} />
153
158
 
154
- <List><ListItem title="T" subtitle="D" media={<ListItem.AvatarView size={32}><Icon /></ListItem.AvatarView>} additionalInfo={<ListItem.AdditionalInfo action={{label:'Go', href:'/go'}} />} /></List>
159
+ <ListItem title="T" subtitle="D" media={<ListItem.AvatarView size={32}><Icon /></ListItem.AvatarView>} additionalInfo={<ListItem.AdditionalInfo action={{label:'Go', href:'/go'}} />} />
155
160
 
156
161
  // Modal (add: const [open, setOpen] = useState(false))
157
162
  <Summary title="T" description="D" icon={<Icon />} info={{title:'Help', content:'Text', presentation:'MODAL', 'aria-label':'Info'}} />
158
163
 
159
- <List><ListItem title="T" subtitle="D" media={<ListItem.AvatarView size={32}><Icon /></ListItem.AvatarView>} control={<ListItem.IconButton partiallyInteractive aria-label="Info" onClick={()=>setOpen(!open)}><QuestionMarkCircle /><Modal open={open} title="Help" body="Text" onClose={()=>setOpen(false)} /></ListItem.IconButton>} /></List>
164
+ <ListItem title="T" subtitle="D" media={<ListItem.AvatarView size={32}><Icon /></ListItem.AvatarView>} control={<ListItem.IconButton partiallyInteractive aria-label="Info" onClick={()=>setOpen(!open)}><QuestionMarkCircle /><Modal open={open} title="Help" body="Text" onClose={()=>setOpen(false)} /></ListItem.IconButton>} />
160
165
 
161
166
  // Popover
162
167
  <Summary title="T" description="D" icon={<Icon />} info={{title:'Help', content:'Text', presentation:'POPOVER', 'aria-label':'Info'}} />
163
168
 
164
- <List><ListItem title="T" subtitle="D" media={<ListItem.AvatarView size={32}><Icon /></ListItem.AvatarView>} control={<Popover title="Help" content="Text" onClose={()=>setOpen(false)}><ListItem.IconButton partiallyInteractive aria-label="Info"><QuestionMarkCircle /></ListItem.IconButton></Popover>} /></List>
169
+ <ListItem title="T" subtitle="D" media={<ListItem.AvatarView size={32}><Icon /></ListItem.AvatarView>} control={<Popover title="Help" content="Text" onClose={()=>setOpen(false)}><ListItem.IconButton partiallyInteractive aria-label="Info"><QuestionMarkCircle /></ListItem.IconButton></Popover>} />
165
170
  \`\`\`
166
171
 
167
172
  ---
@@ -179,10 +184,10 @@ const MIGRATION_RULES = `Migration rules:
179
184
  {title:'T2', value:'V2', key:'k2', action:{label:'Edit', onClick:fn}}
180
185
  ]} />
181
186
 
182
- <List>
187
+
183
188
  <ListItem key="k1" title="T1" subtitle="V1" />
184
189
  <ListItem key="k2" title="T2" subtitle="V2" control={<ListItem.Button priority="secondary-neutral" onClick={fn}>Edit</ListItem.Button>} />
185
- </List>
190
+
186
191
  \`\`\`
187
192
  `;
188
193
  const SYSTEM_PROMPT = `Transform TypeScript/JSX code from legacy Wise Design System (WDS) components to the new ListItem component and ListItem subcomponents from '@transferwise/components'.
@@ -199,13 +204,12 @@ Rules:
199
204
  7. Handle conditional rendering, spread props, and complex expressions
200
205
  8. Note: New components may lack feature parity with legacy versions
201
206
  9. Only modify code requiring changes per migration rules, and any impacted surrounding code for context.
202
- 10. Do not summarise the initial user request in a response.
203
- 11. Use glob to find files in the directory and then grep to identify files with deprecated imports.
204
- 12. Final result response should just be whether the migration was successful overall, or if any errors were encountered
205
- 13. Explain your reasoning and justification before making changes, as you edit each file. Keep it concise and succinct as only bullet points
206
- 14. Do not summarise the changes made after modifying a file.
207
+ 10. Provide only the transformed code as output, without explanations or additional text
208
+ 11. Do not summarise the initial user request in a response.
209
+ 12. Use glob or grep tool usage to find the files with deprecated components.
210
+ 13. Final result response should just be whether the migration was successful overall, or if any errors were encountered
207
211
 
208
- Make the necessary updates to the files.
212
+ Make the necessary updates to the files and do not respond with any explanations or reasoning.
209
213
 
210
214
  You'll receive:
211
215
  - File paths/directories to search in individual queries
@@ -231,45 +235,42 @@ function generateElapsedTime(startTime) {
231
235
  const seconds = elapsedTime % 60;
232
236
  return `${hours ? `${hours}h ` : ""}${minutes ? `${minutes}m ` : ""}${seconds ? `${seconds}s` : ""}`;
233
237
  }
234
- function formatClaudeResponseContent(content) {
235
- return `\x1b[2m${content.replace(/\*\*(.+?)\*\*/gu, "\x1B[1m$1\x1B[0m\x1B[2m").replace(/`(.+?)`/gu, "\x1B[32m$1\x1B[0m\x1B[2m").split("\n").map((line, index) => index === 0 ? line : ` ${line}`).join("\n")}\x1b[0m`;
236
- }
237
- function generateDiff(original, modified) {
238
- const lines = (0, diff.createPatch)("", original, modified, "", "").trim().split("\n").slice(4).filter((line) => !line.startsWith("\"));
239
- let oldLineNumber = 0;
240
- let newLineNumber = 0;
241
- return lines.map((line) => {
242
- const trimmedLine = line.trimEnd();
243
- if (trimmedLine.startsWith("@@")) {
244
- const match = /@@ -(\d+)(?:,\d+)? \+(\d+)(?:,\d+)? @@/u.exec(trimmedLine);
245
- if (match) {
246
- oldLineNumber = Number.parseInt(match[1], 10);
247
- newLineNumber = Number.parseInt(match[2], 10);
248
- }
249
- return `\x1b[36m${trimmedLine}\x1b[0m`;
250
- }
251
- let linePrefix = "";
252
- if (trimmedLine.startsWith("+")) {
253
- linePrefix = `${newLineNumber.toString().padStart(4, " ")} `;
254
- newLineNumber += 1;
255
- return `\x1b[32m${linePrefix}${trimmedLine}\x1b[0m`;
256
- }
257
- if (trimmedLine.startsWith("-")) {
258
- linePrefix = `${oldLineNumber.toString().padStart(4, " ")} `;
259
- oldLineNumber += 1;
260
- return `\x1b[31m${linePrefix}${trimmedLine}\x1b[0m`;
261
- }
262
- linePrefix = `${oldLineNumber.toString().padStart(4, " ")} `;
263
- oldLineNumber += 1;
264
- newLineNumber += 1;
265
- return `${linePrefix}${trimmedLine}`;
266
- }).join("\n");
267
- }
268
238
 
269
239
  //#endregion
270
240
  //#region src/transforms/list-item/claude.ts
271
241
  const CLAUDE_SETTINGS_FILE = ".claude/settings.json";
272
- function getQueryOptions(sessionId, codemodOptions, isDebug) {
242
+ async function checkVPN(baseUrl) {
243
+ if (baseUrl) {
244
+ const vpnSpinner = (0, ora.default)("Checking VPN connection...").start();
245
+ const checkOnce = async () => await new Promise((resolveCheck) => {
246
+ const url = new URL("/health", baseUrl);
247
+ const req = node_https.default.get(url, {
248
+ timeout: 2e3,
249
+ rejectUnauthorized: false
250
+ }, (res) => {
251
+ const ok = !!(res.statusCode && res.statusCode >= 200 && res.statusCode < 400);
252
+ res.resume();
253
+ resolveCheck(ok);
254
+ });
255
+ req.on("timeout", () => {
256
+ req.destroy(/* @__PURE__ */ new Error("timeout"));
257
+ });
258
+ req.on("error", () => resolveCheck(false));
259
+ });
260
+ while (true) {
261
+ if (await checkOnce()) {
262
+ vpnSpinner.succeed("Connected to VPN");
263
+ break;
264
+ }
265
+ vpnSpinner.text = "Please connect to VPN... retrying in 3s";
266
+ await new Promise((response) => {
267
+ setTimeout(response, 3e3);
268
+ });
269
+ }
270
+ }
271
+ return true;
272
+ }
273
+ function getQueryOptions(sessionId, isDebug) {
273
274
  const claudeSettingsPath = (0, node_path.resolve)(process.env.HOME || "", CLAUDE_SETTINGS_FILE);
274
275
  const settings = JSON.parse((0, node_fs.readFileSync)(claudeSettingsPath, "utf-8"));
275
276
  let apiKey;
@@ -277,33 +278,29 @@ function getQueryOptions(sessionId, codemodOptions, isDebug) {
277
278
  apiKey = (0, node_child_process.execSync)(`bash ${settings.apiKeyHelper}`, { encoding: "utf-8" }).trim();
278
279
  } catch {}
279
280
  if (!apiKey) throw new Error("Failed to retrieve Anthropic API key. Please check your Claude Code x LLM Gateway configuration - https://transferwise.atlassian.net/wiki/x/_YUe3Q");
280
- const { ANTHROPIC_CUSTOM_HEADERS, ...restEnvVars } = settings?.env || {};
281
281
  const envVars = {
282
282
  ANTHROPIC_AUTH_TOKEN: apiKey,
283
- ANTHROPIC_CUSTOM_HEADERS,
284
- ...restEnvVars,
283
+ ANTHROPIC_BASE_URL: settings?.env?.ANTHROPIC_BASE_URL,
284
+ ANTHROPIC_CUSTOM_HEADERS: settings?.env?.ANTHROPIC_CUSTOM_HEADERS,
285
+ ANTHROPIC_DEFAULT_SONNET_MODEL: settings.env?.ANTHROPIC_DEFAULT_SONNET_MODEL,
286
+ ANTHROPIC_DEFAULT_HAIKU_MODEL: settings.env?.ANTHROPIC_DEFAULT_HAIKU_MODEL,
287
+ ANTHROPIC_DEFAULT_OPUS_MODEL: settings.env?.ANTHROPIC_DEFAULT_OPUS_MODEL,
288
+ API_TIMEOUT_MS: settings.env?.API_TIMEOUT_MS,
285
289
  PATH: process.env.PATH
286
290
  };
287
291
  if (isDebug) {
288
292
  const { ANTHROPIC_AUTH_TOKEN, ...restVars } = envVars;
289
- console.log(`${CONSOLE_ICONS.info} Claude configuration environment variables, excluding ANTHROPIC_AUTH_TOKEN:`);
290
- console.log(restVars);
293
+ console.log(`${CONSOLE_ICONS.info} Claude configuration environment variables:`, JSON.stringify(restVars));
291
294
  }
292
295
  return {
293
296
  resume: sessionId,
294
297
  env: envVars,
295
- permissionMode: codemodOptions?.isDry ? void 0 : "acceptEdits",
298
+ permissionMode: "acceptEdits",
296
299
  systemPrompt: {
297
300
  type: "preset",
298
301
  preset: "claude_code",
299
302
  append: SYSTEM_PROMPT
300
303
  },
301
- allowedTools: [
302
- "Glob",
303
- "Grep",
304
- "Read",
305
- ...!codemodOptions?.isDry ? ["Write", "Edit"] : []
306
- ],
307
304
  settingSources: [
308
305
  "local",
309
306
  "project",
@@ -312,26 +309,26 @@ function getQueryOptions(sessionId, codemodOptions, isDebug) {
312
309
  };
313
310
  }
314
311
  /** Initiate a new Claude session/conversation and return reusable options */
315
- async function initiateClaudeSessionOptions(codemodOptions, isDebug = false) {
316
- console.log(`${CONSOLE_ICONS.info} Starting Claude instance - your browser may open for Okta authentication if required.`);
317
- const options = getQueryOptions(void 0, codemodOptions, isDebug);
318
- if (isDebug) console.log(`${CONSOLE_ICONS.info} Allowed tools for this session: ${options.allowedTools?.join(", ")}`);
312
+ async function initiateClaudeSessionOptions(isDebug = false) {
313
+ const options = getQueryOptions(void 0, isDebug);
314
+ await checkVPN(options.env?.ANTHROPIC_BASE_URL);
315
+ const claudeSessionSpinner = (0, ora.default)("Starting Claude instance - your browser may open for Okta authentication if required.").start();
319
316
  const result = (0, __anthropic_ai_claude_agent_sdk.query)({
320
317
  options,
321
- prompt: `You'll be given file paths in additional individual queries to search in for files using deprecated Wise Design System (WDS) components. Migrate the code per the provided migration rules.`
318
+ prompt: `You'll be given directories in additional individual queries to search in for files using deprecated Wise Design System (WDS) components. Migrate the code per the provided migration rules.`
322
319
  });
323
320
  for await (const message of result) switch (message.type) {
324
321
  case "system":
325
322
  if (message.subtype === "init" && !options.resume) {
326
- console.log(`${CONSOLE_ICONS.success} Successfully initialised Claude instance`);
323
+ claudeSessionSpinner.succeed("Successfully initialised Claude instance");
327
324
  options.resume = message.session_id;
328
325
  }
329
326
  break;
330
- default: if (message.type === "result" && message.subtype !== "success") console.log(`${CONSOLE_ICONS.error} Claude encountered an error: ${message.errors.join("\n")}`);
327
+ default: if (message.type === "result" && message.subtype !== "success") claudeSessionSpinner.fail(`Claude encountered an error: ${message.errors.join("\n")}`);
331
328
  }
332
329
  return options;
333
330
  }
334
- async function queryClaude(path, options, codemodOptions, isDebug = false) {
331
+ async function queryClaude(path, options, isDebug = false) {
335
332
  const result = (0, __anthropic_ai_claude_agent_sdk.query)({
336
333
  options,
337
334
  prompt: path
@@ -341,22 +338,14 @@ async function queryClaude(path, options, codemodOptions, isDebug = false) {
341
338
  case "assistant":
342
339
  for (const msg of message.message.content) switch (msg.type) {
343
340
  case "tool_use":
344
- if (msg.name === "Glob") console.log(`${CONSOLE_ICONS.info} Finding all files within: ${formatPathOutput(path)}...`);
345
- else if (msg.name === "Grep") console.log(`${CONSOLE_ICONS.info} Identifying files with deprecated imports: ${formatPathOutput(path)}...`);
341
+ if (msg.name === "Glob" || msg.name === "Grep") console.log(`${CONSOLE_ICONS.focus} Processing directory: ${formatPathOutput(path)}...`);
346
342
  else if (msg.name === "Read") console.log(`${CONSOLE_ICONS.info} Reading ${formatPathOutput(path, msg.input.file_path)}`);
347
343
  else if ((msg.name === "Write" || msg.name === "Edit") && !modifiedFiles.includes(msg.input.file_path)) {
348
344
  modifiedFiles.push(msg.input.file_path);
349
- if (codemodOptions.isPrint) {
350
- console.log(`${CONSOLE_ICONS.info} Changes for ${formatPathOutput(path, msg.input.file_path)}:`);
351
- const diff$1 = generateDiff(msg.input.old_string, msg.input.new_string);
352
- console.log(diff$1);
353
- } else console.log(`${CONSOLE_ICONS.info} ${codemodOptions.isDry ? "Proposed changes for" : "Migrated"} ${formatPathOutput(path, msg.input.file_path)}`);
345
+ console.log(`${CONSOLE_ICONS.info} Migrated ${formatPathOutput(path, msg.input.file_path)}`);
354
346
  }
355
347
  break;
356
- case "text":
357
- if (isDebug) console.log(`${CONSOLE_ICONS.claude} ${formatClaudeResponseContent(msg.text)}`);
358
- break;
359
- default: if (isDebug) console.log(msg);
348
+ default:
360
349
  }
361
350
  break;
362
351
  case "result":
@@ -369,11 +358,11 @@ async function queryClaude(path, options, codemodOptions, isDebug = false) {
369
358
 
370
359
  //#endregion
371
360
  //#region src/transforms/list-item/transformer.ts
372
- const transformer = async (codemodOptions, codemodPath, isDebug = false) => {
361
+ const transformer = async (targetPaths, codemodPath, isDebug = false) => {
373
362
  const startTime = Date.now();
374
- const queryOptions = await initiateClaudeSessionOptions(codemodOptions, isDebug);
363
+ const queryOptions = await initiateClaudeSessionOptions(isDebug);
375
364
  console.log(`${CONSOLE_ICONS.info} Analysing targetted paths - this may take a while...`);
376
- for (const directory of codemodOptions.targetPaths) await queryClaude(directory, queryOptions, codemodOptions, isDebug);
365
+ for (const directory of targetPaths) if (require_helpers.assessPrerequisites(directory, codemodPath)) await queryClaude(directory, queryOptions, isDebug);
377
366
  console.log(`${CONSOLE_ICONS.success} Finished migrating - elapsed time: \x1b[1m${generateElapsedTime(startTime)}\x1b[0m`);
378
367
  };
379
368
  var transformer_default = transformer;
@@ -391,4 +380,4 @@ Object.defineProperty(exports, 'transformer_default', {
391
380
  return transformer_default;
392
381
  }
393
382
  });
394
- //# sourceMappingURL=transformer-BqBEjGcV.js.map
383
+ //# sourceMappingURL=transformer-C2vxxgYT.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transformer-C2vxxgYT.js","names":["https","modifiedFiles: string[]","assessPrerequisites"],"sources":["../src/constants.ts","../src/transforms/list-item/constants.ts","../src/transforms/list-item/helpers.ts","../src/transforms/list-item/claude.ts","../src/transforms/list-item/transformer.ts"],"sourcesContent":["export const CONSOLE_ICONS = {\n info: '\\x1b[34mℹ\\x1b[0m', // Blue info icon\n focus: '\\x1b[34m➙\\x1b[0m', // Blue arrow icon\n success: '\\x1b[32m✔\\x1b[0m', // Green checkmark\n warning: '\\x1b[33m⚠\\x1b[0m', // Yellow warning icon\n error: '\\x1b[31m✖\\x1b[0m', // Red cross icon\n};\n","const DEPRECATED_COMPONENT_NAMES = [\n 'ActionOption',\n 'NavigationOption',\n 'NavigationOptionsList',\n 'Summary',\n 'SwitchOption',\n 'CheckboxOption',\n 'RadioOption',\n];\n\nconst MIGRATION_RULES = `Migration rules:\n# Legacy Component → ListItem Migration Guide\n\n## Universal Rules\n\n1. \\`title\\` → \\`title\\` (direct)\n2. \\`content\\` or \\`description\\` → \\`subtitle\\`\n3. \\`disabled\\` stays on \\`ListItem\\` (not controls)\n4. Keep HTML attributes (\\`id\\`, \\`name\\`, \\`aria-label\\`), remove: \\`as\\`, \\`complex\\`, \\`showMediaAtAllSizes\\`, \\`showMediaCircle\\`, \\`isContainerAligned\\`\n5. In strings, don't convert \\`\\`to\\`'\\`or\\`\"\\`. Preserve what is there.\n\n---\n\n## ActionOption → ListItem.Button\n\n- \\`action\\` → Button children\n- \\`onClick\\` → Button \\`onClick\\`\n- Priority: default/\\`\"primary\"\\` → \\`\"primary\"\\`, \\`\"secondary\"\\` → \\`\"secondary-neutral\"\\`, \\`\"secondary-send\"\\` → \\`\"secondary\"\\`, \\`\"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- \\`onChange\\`: \\`(checked: boolean)\\` → \\`(event: ChangeEvent)\\` use \\`event.target.checked\\`\n- \\`name\\` move to Checkbox\n- Don't move \\`id\\` to Checkbox\n\n\\`\\`\\`tsx\n<CheckboxOption id=\"x\" name=\"y\" title=\"Title\" content=\"Text\" checked={v} onChange={(c) => set(c)} />\n→\n<ListItem 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- Don't move \\`id\\` 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 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- \\`onChange\\` → \\`onClick\\`, toggle manually\n- \\`aria-label\\` moves to Switch\n\n\\`\\`\\`tsx\n<SwitchOption title=\"Title\" content=\"Text\" checked={v} aria-label=\"Toggle\" onChange={set} />\n→\n<ListItem 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## Option → ListItem\n\n- Wrap \\`media\\` in \\`ListItem.AvatarView\\`\n\n\\`\\`\\`tsx\n<Option media={<Icon />} title=\"Title\" />\n→\n<ListItem title=\"Title\" media={<ListItem.AvatarView><Icon /></ListItem.AvatarView>} />\n\\`\\`\\`\n\n---\n\n## Summary → ListItem\n\n**Basic:**\n\n- \\`icon\\` → wrap in \\`ListItem.AvatarView\\` with \\`size={32}\\` as \\`media\\`\n- Remove \\`size\\` from child \\`<Icon />\\`\n\n**Status:**\n\n- \\`Status.DONE\\` → \\`badge={{ status: 'positive' }}\\`\n- \\`Status.PENDING\\` → \\`badge={{ status: 'pending' }}\\`\n- \\`Status.NOT_DONE\\` → no badge\n\n**Action:**\n\n- \\`action.text\\` → \\`action.label\\` in \\`ListItem.AdditionalInfo\\` as \\`additionalInfo\\`\n\n**Info (requires state):**\n\n- \\`MODAL\\` → \\`ListItem.IconButton partiallyInteractive\\` + \\`<Modal>\\` in \\`control\\`\n- \\`POPOVER\\` → \\`<Popover>\\` wrapping \\`ListItem.IconButton partiallyInteractive\\` in \\`control\\`\n- Use \\`QuestionMarkCircle\\` icon (import from \\`@transferwise/icons\\`)\n\n\\`\\`\\`tsx\n// Basic\n<Summary title=\"T\" description=\"D\" icon={<Icon />} />\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'}} />\n→\n<ListItem title=\"T\" subtitle=\"D\" media={<ListItem.AvatarView size={32}><Icon /></ListItem.AvatarView>} additionalInfo={<ListItem.AdditionalInfo action={{label:'Go', href:'/go'}} />} />\n\n// Modal (add: 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<ListItem title=\"T\" subtitle=\"D\" media={<ListItem.AvatarView size={32}><Icon /></ListItem.AvatarView>} control={<ListItem.IconButton partiallyInteractive aria-label=\"Info\" onClick={()=>setOpen(!open)}><QuestionMarkCircle /><Modal open={open} title=\"Help\" body=\"Text\" onClose={()=>setOpen(false)} /></ListItem.IconButton>} />\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={<Popover title=\"Help\" content=\"Text\" onClose={()=>setOpen(false)}><ListItem.IconButton partiallyInteractive aria-label=\"Info\"><QuestionMarkCircle /></ListItem.IconButton></Popover>} />\n\\`\\`\\`\n\n---\n\n## DefinitionList → Multiple ListItem\n\n- Array → individual \\`ListItem\\`s\n- \\`value\\` → \\`subtitle\\`\n- \\`key\\` → React \\`key\\` prop\n- Action type: \"Edit\"/\"Update\"/\"View\" → \\`ListItem.Button priority=\"secondary-neutral\"\\`, \"Change\"/\"Password\" → \\`ListItem.Navigation\\`, \"Copy\" → \\`ListItem.IconButton\\`\n\n\\`\\`\\`tsx\n<DefinitionList definitions={[\n {title:'T1', value:'V1', key:'k1'},\n {title:'T2', value:'V2', key:'k2', action:{label:'Edit', onClick:fn}}\n]} />\n→\n\n <ListItem key=\"k1\" title=\"T1\" subtitle=\"V1\" />\n <ListItem key=\"k2\" title=\"T2\" subtitle=\"V2\" control={<ListItem.Button priority=\"secondary-neutral\" onClick={fn}>Edit</ListItem.Button>} />\n\n\\`\\`\\`\n`;\n\nexport const SYSTEM_PROMPT = `Transform TypeScript/JSX code from legacy Wise Design System (WDS) components to the new ListItem component and ListItem subcomponents from '@transferwise/components'.\n\nRead and transform each file individually, instead of reading them all at the start. Only make a single edit or write per file, once you've fully processed it.\n\nRules:\n1. Ignore any files that do not contain deprecated WDS components, unless they are necessary for context.\n2. Migrate components per provided migration rules\n3. Maintain TypeScript type safety and update types to match new API\n4. Map props: handle renamed, deprecated, new required, and changed types\n5. Update imports to new WDS components and types\n6. Preserve code style, formatting, and calculated logic\n7. Handle conditional rendering, spread props, and complex expressions\n8. Note: New components may lack feature parity with legacy versions\n9. Only modify code requiring changes per migration rules, and any impacted surrounding code for context.\n10. Provide only the transformed code as output, without explanations or additional text\n11. Do not summarise the initial user request in a response.\n12. Use glob or grep tool usage to find the files with deprecated components.\n13. Final result response should just be whether the migration was successful overall, or if any errors were encountered\n\nMake the necessary updates to the files and do not respond with any explanations or reasoning. \n\nYou'll receive:\n- File paths/directories to search in individual queries\n- Deprecated component names at the end of this prompt\n- Migration context and rules for each deprecated component\n\nDeprecated components: ${DEPRECATED_COMPONENT_NAMES.join(', ')}.\n\n${MIGRATION_RULES}`;\n","/** Split the path to get the relative path after the directory, and wrap with ANSI color codes */\nexport function formatPathOutput(directory: string, path?: string): string {\n return `\\x1b[32m${path ? (path.split(directory)[1] ?? path) : directory}\\x1b[0m`;\n}\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 { type Options, query } from '@anthropic-ai/claude-agent-sdk';\nimport { execSync } from 'child_process';\nimport { readFileSync } from 'fs';\nimport https from 'node:https';\nimport ora from 'ora';\nimport { resolve } from 'path';\n\nimport { CONSOLE_ICONS } from '../../constants';\nimport { SYSTEM_PROMPT } from './constants';\nimport { formatPathOutput } from './helpers';\nimport type { ClaudeResponseToolUse, ClaudeSettings } from './types';\n\nconst CLAUDE_SETTINGS_FILE = '.claude/settings.json';\n\nasync function checkVPN(baseUrl?: string): Promise<boolean> {\n if (baseUrl) {\n const vpnSpinner = ora('Checking VPN connection...').start();\n const checkOnce = async (): Promise<boolean> =>\n await 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 vpnSpinner.succeed('Connected to VPN');\n break;\n }\n vpnSpinner.text = 'Please connect to VPN... retrying in 3s';\n await new Promise<void>((response) => {\n setTimeout(response, 3000);\n });\n }\n }\n return true;\n}\n\nexport function getQueryOptions(sessionId?: string, isDebug?: boolean): 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) {\n throw new Error(\n 'Failed to retrieve Anthropic API key. Please check your Claude Code x LLM Gateway configuration - https://transferwise.atlassian.net/wiki/x/_YUe3Q',\n );\n }\n\n const envVars = {\n ANTHROPIC_AUTH_TOKEN: apiKey,\n ANTHROPIC_BASE_URL: settings?.env?.ANTHROPIC_BASE_URL,\n ANTHROPIC_CUSTOM_HEADERS: settings?.env?.ANTHROPIC_CUSTOM_HEADERS,\n ANTHROPIC_DEFAULT_SONNET_MODEL: settings.env?.ANTHROPIC_DEFAULT_SONNET_MODEL,\n ANTHROPIC_DEFAULT_HAIKU_MODEL: settings.env?.ANTHROPIC_DEFAULT_HAIKU_MODEL,\n ANTHROPIC_DEFAULT_OPUS_MODEL: settings.env?.ANTHROPIC_DEFAULT_OPUS_MODEL,\n API_TIMEOUT_MS: settings.env?.API_TIMEOUT_MS,\n PATH: process.env.PATH, // Specifying PATH, as Claude Agent SDK seems to struggle consuming the actual environment PATH\n };\n\n if (isDebug) {\n // Not logging the auth token for security reasons\n const { ANTHROPIC_AUTH_TOKEN, ...restVars } = envVars;\n console.log(\n `${CONSOLE_ICONS.info} Claude configuration environment variables:`,\n JSON.stringify(restVars),\n );\n }\n\n return {\n resume: sessionId,\n env: envVars,\n permissionMode: 'acceptEdits',\n systemPrompt: {\n type: 'preset',\n preset: 'claude_code',\n append: SYSTEM_PROMPT,\n },\n settingSources: ['local', 'project', 'user'],\n };\n}\n\n/** Initiate a new Claude session/conversation and return reusable options */\nexport async function initiateClaudeSessionOptions(isDebug = false): Promise<Options> {\n const options = getQueryOptions(undefined, isDebug);\n await checkVPN(options.env?.ANTHROPIC_BASE_URL as string | undefined);\n\n const claudeSessionSpinner = ora(\n 'Starting Claude instance - your browser may open for Okta authentication if required.',\n ).start();\n\n const result = query({\n options,\n prompt: `You'll be given directories in additional individual queries to search in for files using deprecated Wise Design System (WDS) components. Migrate the code per the provided migration rules.`,\n });\n\n for await (const message of result) {\n switch (message.type) {\n case 'system':\n if (message.subtype === 'init' && !options.resume) {\n claudeSessionSpinner.succeed('Successfully initialised Claude instance');\n\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 claudeSessionSpinner.fail(`Claude encountered an error: ${message.errors.join('\\n')}`);\n }\n }\n }\n\n return options;\n}\n\nexport async function queryClaude(path: string, options: Options, isDebug = false) {\n const result = query({\n options,\n prompt: path,\n });\n const modifiedFiles: string[] = [];\n\n for await (const message of result) {\n switch (message.type) {\n case 'assistant':\n for (const msg of message.message.content) {\n switch (msg.type) {\n // Handles logging of tool uses to determine key stages of the migration ()\n case 'tool_use':\n if (msg.name === 'Glob' || msg.name === 'Grep') {\n console.log(\n `${CONSOLE_ICONS.focus} Processing directory: ${formatPathOutput(path)}...`,\n );\n // NOTE: Possibly want to only log reading files when in debug mode, to reduce noise\n } else if (msg.name === 'Read') {\n console.log(\n `${CONSOLE_ICONS.info} Reading ${formatPathOutput(path, (msg as ClaudeResponseToolUse).input.file_path)}`,\n );\n } else if (\n (msg.name === 'Write' || msg.name === 'Edit') &&\n !modifiedFiles.includes((msg as ClaudeResponseToolUse).input.file_path) // Safeguard against duplicate logs, where Claude may write multiple times to the same file\n ) {\n modifiedFiles.push((msg as ClaudeResponseToolUse).input.file_path);\n console.log(\n `${CONSOLE_ICONS.info} Migrated ${formatPathOutput(path, (msg as ClaudeResponseToolUse).input.file_path)}`,\n );\n }\n break;\n default:\n }\n }\n break;\n case 'result':\n if (message.subtype === 'success') {\n // TODO: Handle case where migration failed for some files?\n console.log(\n `${CONSOLE_ICONS.success} Migrated all applicable files in ${formatPathOutput(path)}`,\n );\n } else {\n console.log(\n `${CONSOLE_ICONS.error} Claude encountered an error: ${message.errors.join('\\n').trim()}`,\n );\n }\n break;\n default:\n }\n }\n}\n","import { CONSOLE_ICONS } from '../../constants';\nimport { assessPrerequisites } from '../../controller/helpers';\nimport { initiateClaudeSessionOptions, queryClaude } from './claude';\nimport { generateElapsedTime } from './helpers';\n\nconst transformer = async (targetPaths: string[], codemodPath: string, isDebug = false) => {\n const startTime = Date.now();\n\n // TODO: We need to check whether the user is connected to the VPN\n\n const queryOptions = await initiateClaudeSessionOptions(isDebug);\n\n console.log(`${CONSOLE_ICONS.info} Analysing targetted paths - this may take a while...`);\n\n for (const directory of targetPaths) {\n const isCompliant = assessPrerequisites(directory, codemodPath);\n\n if (isCompliant) {\n // TODO: Potential improvement could be getting all of the file paths first -\n // Make sure claude can still handle related files (imported components/wrapping parents)\n // Get all files within directory, and call queryClaude for each file\n await queryClaude(directory, queryOptions, isDebug);\n }\n\n // await queryClaude(directory, queryOptions, isDebug);\n }\n\n console.log(\n `${CONSOLE_ICONS.success} Finished migrating - elapsed time: \\x1b[1m${generateElapsedTime(startTime)}\\x1b[0m`,\n );\n};\n\nexport default transformer;\n"],"mappings":"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"}
@@ -1,3 +1,4 @@
1
- const require_transformer = require('../../transformer-BqBEjGcV.js');
1
+ require('../../helpers-Cca1yIbM.js');
2
+ const require_transformer = require('../../transformer-C2vxxgYT.js');
2
3
 
3
4
  module.exports = require_transformer.transformer_default;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wise/wds-codemods",
3
- "version": "1.0.0-experimental-0f5c549",
3
+ "version": "1.0.0-experimental-dfd0a93",
4
4
  "license": "UNLICENSED",
5
5
  "author": "Wise Payments Ltd.",
6
6
  "repository": {
@@ -36,8 +36,8 @@
36
36
  "dependencies": {
37
37
  "@anthropic-ai/claude-agent-sdk": "^0.1.37",
38
38
  "@inquirer/prompts": "^7.8.6",
39
- "diff": "^8.0.2",
40
- "jscodeshift": "^17.3"
39
+ "jscodeshift": "^17.3",
40
+ "ora": "^9.0.0"
41
41
  },
42
42
  "devDependencies": {
43
43
  "@anthropic-ai/sdk": "^0.68.0",
@@ -1 +0,0 @@
1
- {"version":3,"file":"transformer-BqBEjGcV.js","names":["modifiedFiles: string[]","diff"],"sources":["../src/constants.ts","../src/transforms/list-item/constants.ts","../src/transforms/list-item/helpers.ts","../src/transforms/list-item/claude.ts","../src/transforms/list-item/transformer.ts"],"sourcesContent":["export const CONSOLE_ICONS = {\n info: '\\x1b[34mℹ\\x1b[0m', // Blue info icon\n focus: '\\x1b[34m➙\\x1b[0m', // Blue arrow icon\n success: '\\x1b[32m✔\\x1b[0m', // Green checkmark\n warning: '\\x1b[33m⚠\\x1b[0m', // Yellow warning icon\n error: '\\x1b[31m✖\\x1b[0m', // Red cross icon\n claude: '\\x1b[35m💬\\x1b[0m', // Magenta speech bubble\n};\n","const DEPRECATED_COMPONENT_NAMES = [\n 'ActionOption',\n 'NavigationOption',\n 'NavigationOptionsList',\n 'Summary',\n 'SwitchOption',\n 'CheckboxOption',\n 'RadioOption',\n];\n\nconst MIGRATION_RULES = `Migration rules:\n# Legacy Component → ListItem Migration Guide\n\n## Universal Rules\n\n1. Wrap all \\`ListItem\\` in \\`<List>\\`\n2. \\`title\\` → \\`title\\` (direct)\n3. \\`content\\` or \\`description\\` → \\`subtitle\\`\n4. \\`disabled\\` stays on \\`ListItem\\` (not controls)\n5. Keep HTML attributes (\\`id\\`, \\`name\\`, \\`aria-label\\`), remove: \\`as\\`, \\`complex\\`, \\`showMediaAtAllSizes\\`, \\`showMediaCircle\\`, \\`isContainerAligned\\`\n\n---\n\n## ActionOption → ListItem.Button\n\n- \\`action\\` → Button children\n- \\`onClick\\` → Button \\`onClick\\`\n- Priority: default/\\`\"primary\"\\` → \\`\"primary\"\\`, \\`\"secondary\"\\` → \\`\"secondary-neutral\"\\`, \\`\"secondary-send\"\\` → \\`\"secondary\"\\`, \\`\"tertiary\"\\` → \\`\"tertiary\"\\`\n\n\\`\\`\\`tsx\n<ActionOption title=\"Title\" content=\"Text\" action=\"Click\" priority=\"secondary\" onClick={fn} />\n→\n<List><ListItem title=\"Title\" subtitle=\"Text\" control={<ListItem.Button priority=\"secondary-neutral\" onClick={fn}>Click</ListItem.Button>} /></List>\n\\`\\`\\`\n\n---\n\n## CheckboxOption → ListItem.Checkbox\n\n- \\`onChange\\`: \\`(checked: boolean)\\` → \\`(event: ChangeEvent)\\` use \\`event.target.checked\\`\n- \\`id\\`, \\`name\\` move to Checkbox\n\n\\`\\`\\`tsx\n<CheckboxOption id=\"x\" name=\"y\" title=\"Title\" content=\"Text\" checked={v} onChange={(c) => set(c)} />\n→\n<List><ListItem title=\"Title\" subtitle=\"Text\" control={<ListItem.Checkbox id=\"x\" name=\"y\" checked={v} onChange={(e) => set(e.target.checked)} />} /></List>\n\\`\\`\\`\n\n---\n\n## RadioOption → ListItem.Radio\n\n- \\`id\\`, \\`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<List><ListItem title=\"Title\" subtitle=\"Text\" control={<ListItem.Radio id=\"x\" name=\"y\" value=\"v\" checked={v==='v'} onChange={set} />} /></List>\n\\`\\`\\`\n\n---\n\n## SwitchOption → ListItem.Switch\n\n- \\`onChange\\` → \\`onClick\\`, toggle manually\n- \\`aria-label\\` moves to Switch\n\n\\`\\`\\`tsx\n<SwitchOption title=\"Title\" content=\"Text\" checked={v} aria-label=\"Toggle\" onChange={set} />\n→\n<List><ListItem title=\"Title\" subtitle=\"Text\" control={<ListItem.Switch checked={v} aria-label=\"Toggle\" onClick={() => set(!v)} />} /></List>\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<List><ListItem title=\"Title\" subtitle=\"Text\" control={<ListItem.Navigation onClick={fn} />} /></List>\n\\`\\`\\`\n\n---\n\n## Option → ListItem\n\n- Wrap \\`media\\` in \\`ListItem.AvatarView\\`\n\n\\`\\`\\`tsx\n<Option media={<Icon />} title=\"Title\" />\n→\n<List><ListItem title=\"Title\" media={<ListItem.AvatarView><Icon /></ListItem.AvatarView>} /></List>\n\\`\\`\\`\n\n---\n\n## Summary → ListItem\n\n**Basic:**\n\n- \\`icon\\` → wrap in \\`ListItem.AvatarView\\` with \\`size={32}\\` as \\`media\\`\n\n**Status:**\n\n- \\`Status.DONE\\` → \\`badge={{ status: 'positive' }}\\`\n- \\`Status.PENDING\\` → \\`badge={{ status: 'pending' }}\\`\n- \\`Status.NOT_DONE\\` → no badge\n\n**Action:**\n\n- \\`action.text\\` → \\`action.label\\` in \\`ListItem.AdditionalInfo\\` as \\`additionalInfo\\`\n\n**Info (requires state):**\n\n- \\`MODAL\\` → \\`ListItem.IconButton partiallyInteractive\\` + \\`<Modal>\\` in \\`control\\`\n- \\`POPOVER\\` → \\`<Popover>\\` wrapping \\`ListItem.IconButton partiallyInteractive\\` in \\`control\\`\n- Use \\`QuestionMarkCircle\\` icon\n\n\\`\\`\\`tsx\n// Basic\n<Summary title=\"T\" description=\"D\" icon={<Icon />} />\n→\n<List><ListItem title=\"T\" subtitle=\"D\" media={<ListItem.AvatarView size={32}><Icon /></ListItem.AvatarView>} /></List>\n\n// Status\n<Summary title=\"T\" description=\"D\" icon={<Icon />} status={Status.DONE} />\n→\n<List><ListItem title=\"T\" subtitle=\"D\" media={<ListItem.AvatarView size={32} badge={{status:'positive'}}><Icon /></ListItem.AvatarView>} /></List>\n\n// Action\n<Summary title=\"T\" description=\"D\" icon={<Icon />} action={{text:'Go', href:'/go'}} />\n→\n<List><ListItem title=\"T\" subtitle=\"D\" media={<ListItem.AvatarView size={32}><Icon /></ListItem.AvatarView>} additionalInfo={<ListItem.AdditionalInfo action={{label:'Go', href:'/go'}} />} /></List>\n\n// Modal (add: 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<List><ListItem title=\"T\" subtitle=\"D\" media={<ListItem.AvatarView size={32}><Icon /></ListItem.AvatarView>} control={<ListItem.IconButton partiallyInteractive aria-label=\"Info\" onClick={()=>setOpen(!open)}><QuestionMarkCircle /><Modal open={open} title=\"Help\" body=\"Text\" onClose={()=>setOpen(false)} /></ListItem.IconButton>} /></List>\n\n// Popover\n<Summary title=\"T\" description=\"D\" icon={<Icon />} info={{title:'Help', content:'Text', presentation:'POPOVER', 'aria-label':'Info'}} />\n→\n<List><ListItem title=\"T\" subtitle=\"D\" media={<ListItem.AvatarView size={32}><Icon /></ListItem.AvatarView>} control={<Popover title=\"Help\" content=\"Text\" onClose={()=>setOpen(false)}><ListItem.IconButton partiallyInteractive aria-label=\"Info\"><QuestionMarkCircle /></ListItem.IconButton></Popover>} /></List>\n\\`\\`\\`\n\n---\n\n## DefinitionList → Multiple ListItem\n\n- Array → individual \\`ListItem\\`s\n- \\`value\\` → \\`subtitle\\`\n- \\`key\\` → React \\`key\\` prop\n- Action type: \"Edit\"/\"Update\"/\"View\" → \\`ListItem.Button priority=\"secondary-neutral\"\\`, \"Change\"/\"Password\" → \\`ListItem.Navigation\\`, \"Copy\" → \\`ListItem.IconButton\\`\n\n\\`\\`\\`tsx\n<DefinitionList definitions={[\n {title:'T1', value:'V1', key:'k1'},\n {title:'T2', value:'V2', key:'k2', action:{label:'Edit', onClick:fn}}\n]} />\n→\n<List>\n <ListItem key=\"k1\" title=\"T1\" subtitle=\"V1\" />\n <ListItem key=\"k2\" title=\"T2\" subtitle=\"V2\" control={<ListItem.Button priority=\"secondary-neutral\" onClick={fn}>Edit</ListItem.Button>} />\n</List>\n\\`\\`\\`\n`;\n\nexport const SYSTEM_PROMPT = `Transform TypeScript/JSX code from legacy Wise Design System (WDS) components to the new ListItem component and ListItem subcomponents from '@transferwise/components'.\n\nRead and transform each file individually, instead of reading them all at the start. Only make a single edit or write per file, once you've fully processed it.\n\nRules:\n1. Ignore any files that do not contain deprecated WDS components, unless they are necessary for context.\n2. Migrate components per provided migration rules\n3. Maintain TypeScript type safety and update types to match new API\n4. Map props: handle renamed, deprecated, new required, and changed types\n5. Update imports to new WDS components and types\n6. Preserve code style, formatting, and calculated logic\n7. Handle conditional rendering, spread props, and complex expressions\n8. Note: New components may lack feature parity with legacy versions\n9. Only modify code requiring changes per migration rules, and any impacted surrounding code for context.\n10. Do not summarise the initial user request in a response.\n11. Use glob to find files in the directory and then grep to identify files with deprecated imports.\n12. Final result response should just be whether the migration was successful overall, or if any errors were encountered\n13. Explain your reasoning and justification before making changes, as you edit each file. Keep it concise and succinct as only bullet points\n14. Do not summarise the changes made after modifying a file.\n\nMake the necessary updates to the files. \n\nYou'll receive:\n- File paths/directories to search in individual queries\n- Deprecated component names at the end of this prompt\n- Migration context and rules for each deprecated component\n\nDeprecated components: ${DEPRECATED_COMPONENT_NAMES.join(', ')}.\n\n${MIGRATION_RULES}`;\n","import { createPatch } from 'diff';\n\n/** Split the path to get the relative path after the directory, and wrap with ANSI color codes */\nexport function formatPathOutput(directory: string, path?: string): string {\n return `\\x1b[32m${path ? (path.split(directory)[1] ?? path) : directory}\\x1b[0m`;\n}\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\n// Formats Claude response with ANSI codes (bold for **, green for `), indenting all lines except the first. Also wraps entire content in dim color\nexport function formatClaudeResponseContent(content: string): string {\n const formatted = content\n .replace(/\\*\\*(.+?)\\*\\*/gu, '\\x1b[1m$1\\x1b[0m\\x1b[2m')\n .replace(/`(.+?)`/gu, '\\x1b[32m$1\\x1b[0m\\x1b[2m')\n .split('\\n')\n .map((line, index) => (index === 0 ? line : ` ${line}`))\n .join('\\n');\n\n return `\\x1b[2m${formatted}\\x1b[0m`;\n}\n\n// Formats the content of a file for output, wrapping it in ANSI color codes for better visibility in the console\nexport function formatFileContentOutput(content: string): string {\n // Remove surrounding double quotes if present\n let cleanedContent = content.trim();\n if (cleanedContent.startsWith('\"') && cleanedContent.endsWith('\"')) {\n cleanedContent = cleanedContent.slice(1, -1);\n }\n\n // Remove <system-reminder> tags and their content\n cleanedContent = cleanedContent\n .replace(/<system-reminder>[\\s\\S]*?<\\/system-reminder>/gu, '')\n .trim();\n\n // Split into lines and format each line\n const lines = cleanedContent.split('\\\\n');\n const linePattern = /^(\\s*)(\\d+)(→)(.*)$/u;\n const formattedLines = lines.map((line) => {\n // Match line number and arrow pattern: \" 1→\"\n const match = linePattern.exec(line);\n if (match) {\n const [, spaces, lineNum, arrow, code] = match;\n // Gray for line numbers, cyan for arrow, white/default for code\n return `${spaces}\\x1b[90m${lineNum}\\x1b[0m\\x1b[36m${arrow}\\x1b[0m${code}`;\n }\n return line;\n });\n\n return formattedLines.join('\\n');\n}\n\n// Generates a unified diff between the original and modified strings, with ANSI color codes for additions and deletions and line numbers\nexport function generateDiff(original: string, modified: string): string {\n const diffResult = createPatch('', original, modified, '', '').trim();\n\n // Parse and colorize the diff output\n const lines = diffResult\n .split('\\n')\n .slice(4) // Skip the header lines\n .filter((line) => !line.startsWith('\\\')); // Remove \"No newline\" messages\n\n // Track current line numbers from hunk headers\n let oldLineNumber = 0;\n let newLineNumber = 0;\n\n const colouredDiff = lines\n .map((line: string) => {\n const trimmedLine = line.trimEnd();\n\n // Parse hunk header to get starting line numbers - format: @@ -oldStart,oldCount +newStart,newCount @@\n if (trimmedLine.startsWith('@@')) {\n const match = /@@ -(\\d+)(?:,\\d+)? \\+(\\d+)(?:,\\d+)? @@/u.exec(trimmedLine);\n if (match) {\n oldLineNumber = Number.parseInt(match[1], 10);\n newLineNumber = Number.parseInt(match[2], 10);\n }\n return `\\x1b[36m${trimmedLine}\\x1b[0m`; // Cyan for hunk headers\n }\n\n let linePrefix = '';\n // Green styling for additions\n if (trimmedLine.startsWith('+')) {\n linePrefix = `${newLineNumber.toString().padStart(4, ' ')} `;\n newLineNumber += 1;\n return `\\x1b[32m${linePrefix}${trimmedLine}\\x1b[0m`;\n }\n\n // Red styling for deletions\n if (trimmedLine.startsWith('-')) {\n linePrefix = `${oldLineNumber.toString().padStart(4, ' ')} `;\n oldLineNumber += 1;\n return `\\x1b[31m${linePrefix}${trimmedLine}\\x1b[0m`;\n }\n\n // Handle unchanged lines\n linePrefix = `${oldLineNumber.toString().padStart(4, ' ')} `;\n oldLineNumber += 1;\n newLineNumber += 1;\n return `${linePrefix}${trimmedLine}`;\n })\n .join('\\n');\n\n return colouredDiff;\n}\n","import { type Options, query } from '@anthropic-ai/claude-agent-sdk';\nimport { execSync } from 'child_process';\nimport { createPatch } from 'diff';\nimport { readFileSync } from 'fs';\nimport { resolve } from 'path';\n\nimport { CONSOLE_ICONS } from '../../constants';\nimport type { CodemodOptions } from '../../controller/types';\nimport { SYSTEM_PROMPT } from './constants';\nimport {\n formatClaudeResponseContent,\n formatFileContentOutput,\n formatPathOutput,\n generateDiff,\n} from './helpers';\nimport type { ClaudeResponseToolUse, ClaudeSettings } from './types';\n\nconst CLAUDE_SETTINGS_FILE = '.claude/settings.json';\n\nexport function getQueryOptions(\n sessionId?: string,\n codemodOptions?: CodemodOptions,\n isDebug?: boolean,\n): 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) {\n throw new Error(\n 'Failed to retrieve Anthropic API key. 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 if (isDebug) {\n // Not logging the auth token and path\n const { ANTHROPIC_AUTH_TOKEN, ...restVars } = envVars;\n console.log(\n `${CONSOLE_ICONS.info} Claude configuration environment variables, excluding ANTHROPIC_AUTH_TOKEN:`,\n );\n console.log(restVars);\n }\n\n return {\n resume: sessionId,\n env: envVars,\n permissionMode: codemodOptions?.isDry ? undefined : 'acceptEdits',\n systemPrompt: {\n type: 'preset',\n preset: 'claude_code',\n append: SYSTEM_PROMPT,\n },\n allowedTools: ['Glob', 'Grep', 'Read', ...(!codemodOptions?.isDry ? ['Write', 'Edit'] : [])],\n settingSources: ['local', 'project', 'user'],\n };\n}\n\n/** Initiate a new Claude session/conversation and return reusable options */\nexport async function initiateClaudeSessionOptions(\n codemodOptions: CodemodOptions,\n isDebug = false,\n): Promise<Options> {\n console.log(\n `${CONSOLE_ICONS.info} Starting Claude instance - your browser may open for Okta authentication if required.`,\n );\n\n const options = getQueryOptions(undefined, codemodOptions, isDebug);\n\n if (isDebug) {\n console.log(\n `${CONSOLE_ICONS.info} Allowed tools for this session: ${options.allowedTools?.join(', ')}`,\n );\n }\n\n const result = query({\n options,\n prompt: `You'll be given file paths in additional individual queries to search in for files using deprecated Wise Design System (WDS) components. Migrate the code per the provided migration rules.`,\n });\n\n for await (const message of result) {\n switch (message.type) {\n case 'system':\n if (message.subtype === 'init' && !options.resume) {\n console.log(`${CONSOLE_ICONS.success} Successfully initialised Claude instance`);\n\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 console.log(\n `${CONSOLE_ICONS.error} Claude encountered an error: ${message.errors.join('\\n')}`,\n );\n }\n }\n }\n\n return options;\n}\n\nexport async function queryClaude(\n path: string,\n options: Options,\n codemodOptions: CodemodOptions,\n isDebug = false,\n) {\n const result = query({\n options,\n prompt: path,\n });\n const modifiedFiles: string[] = [];\n\n for await (const message of result) {\n switch (message.type) {\n case 'assistant':\n for (const msg of message.message.content) {\n switch (msg.type) {\n // Handles logging of tool uses to determine key stages of the migration\n case 'tool_use':\n if (msg.name === 'Glob') {\n console.log(\n `${CONSOLE_ICONS.info} Finding all files within: ${formatPathOutput(path)}...`,\n );\n } else if (msg.name === 'Grep') {\n console.log(\n `${CONSOLE_ICONS.info} Identifying files with deprecated imports: ${formatPathOutput(path)}...`,\n );\n } else if (msg.name === 'Read') {\n console.log(\n `${CONSOLE_ICONS.info} Reading ${formatPathOutput(path, (msg as ClaudeResponseToolUse).input.file_path)}`,\n );\n } else if (\n (msg.name === 'Write' || msg.name === 'Edit') &&\n !modifiedFiles.includes((msg as ClaudeResponseToolUse).input.file_path) // Safeguard against duplicate logs, where Claude may write multiple times to the same file\n ) {\n // TODO: Consider how to handle multiple writes to the same file more gracefully (logged changes won't always match final file content)\n modifiedFiles.push((msg as ClaudeResponseToolUse).input.file_path);\n\n // Generate and print out a pretty diff of changes made by Claude\n if (codemodOptions.isPrint) {\n console.log(\n `${CONSOLE_ICONS.info} Changes for ${formatPathOutput(path, (msg as ClaudeResponseToolUse).input.file_path)}:`,\n );\n const diff = generateDiff(\n (msg as ClaudeResponseToolUse).input.old_string as string,\n (msg as ClaudeResponseToolUse).input.new_string as string,\n );\n\n console.log(diff);\n } else {\n console.log(\n `${CONSOLE_ICONS.info} ${codemodOptions.isDry ? 'Proposed changes for' : 'Migrated'} ${formatPathOutput(path, (msg as ClaudeResponseToolUse).input.file_path)}`,\n );\n }\n }\n break;\n case 'text':\n // Logs Claude's textual responses in debug mode, to help with understanding justifications or errors\n if (isDebug) {\n console.log(`${CONSOLE_ICONS.claude} ${formatClaudeResponseContent(msg.text)}`);\n }\n break;\n default:\n if (isDebug) {\n console.log(msg);\n }\n }\n }\n break;\n case 'result':\n if (message.subtype === 'success') {\n // TODO: Handle case where migration failed for some files?\n console.log(\n `${CONSOLE_ICONS.success} Migrated all applicable files in ${formatPathOutput(path)}`,\n );\n } else {\n console.log(\n `${CONSOLE_ICONS.error} Claude encountered an error: ${message.errors.join('\\n').trim()}`,\n );\n }\n break;\n default:\n // console.log(message);\n }\n }\n}\n","import { CONSOLE_ICONS } from '../../constants';\nimport { assessPrerequisites } from '../../controller/helpers';\nimport type { CodemodOptions } from '../../controller/types';\nimport { initiateClaudeSessionOptions, queryClaude } from './claude';\nimport { generateElapsedTime } from './helpers';\n\nconst transformer = async (\n codemodOptions: CodemodOptions,\n codemodPath: string,\n isDebug = false,\n) => {\n const startTime = Date.now();\n\n // TODO: We need to check whether the user is connected to the VPN first before proceeding\n\n const queryOptions = await initiateClaudeSessionOptions(codemodOptions, isDebug);\n\n console.log(`${CONSOLE_ICONS.info} Analysing targetted paths - this may take a while...`);\n\n for (const directory of codemodOptions.targetPaths) {\n // const isCompliant = assessPrerequisites(directory, codemodPath);\n\n // if (isCompliant) {\n // // TODO: Potential improvement could be getting all of the file paths first -\n // // Make sure claude can still handle related files (imported components/wrapping parents)\n // // Get all files within directory, and call queryClaude for each file\n // await queryClaude(directory, queryOptions, isDebug);\n // }\n\n await queryClaude(directory, queryOptions, codemodOptions, isDebug);\n }\n\n console.log(\n `${CONSOLE_ICONS.success} Finished migrating - elapsed time: \\x1b[1m${generateElapsedTime(startTime)}\\x1b[0m`,\n );\n};\n\nexport default transformer;\n"],"mappings":"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"}