@wise/wds-codemods 1.0.0-experimental-581e474 → 1.0.0-experimental-f0b8d38

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,475 @@
1
+ const require_constants = require('./constants-BhVbK_XZ.js');
2
+ let node_child_process = require("node:child_process");
3
+ let node_path = require("node:path");
4
+ let ora = require("ora");
5
+ ora = require_constants.__toESM(ora);
6
+ let node_https = require("node:https");
7
+ node_https = require_constants.__toESM(node_https);
8
+ let __anthropic_ai_claude_agent_sdk = require("@anthropic-ai/claude-agent-sdk");
9
+ let node_fs = require("node:fs");
10
+ let diff = require("diff");
11
+
12
+ //#region src/transforms/list-item/constants.ts
13
+ const DEPRECATED_COMPONENT_NAMES = [
14
+ "ActionOption",
15
+ "NavigationOption",
16
+ "NavigationOptionsList",
17
+ "Summary",
18
+ "SwitchOption",
19
+ "CheckboxOption",
20
+ "RadioOption"
21
+ ];
22
+ const MIGRATION_RULES = `Migration rules:
23
+ # Legacy Component → ListItem Migration Guide
24
+
25
+ ## Universal Rules
26
+
27
+ 1. \`title\` → \`title\` (direct)
28
+ 2. \`content\` or \`description\` → \`subtitle\`
29
+ 3. \`disabled\` stays on \`ListItem\` (not controls)
30
+ 4. Keep HTML attributes (\`id\`, \`name\`, \`aria-label\`), remove: \`as\`, \`complex\`, \`showMediaAtAllSizes\`, \`showMediaCircle\`, \`isContainerAligned\`
31
+ 5. In strings, don't convert \`\`to\`'\`or\`"\`. Preserve what is there.
32
+
33
+ ---
34
+
35
+ ## ActionOption → ListItem.Button
36
+
37
+ - \`action\` → Button children
38
+ - \`onClick\` → Button \`onClick\`
39
+ - Priority: default/\`"primary"\` → \`"primary"\`, \`"secondary"\` → \`"secondary-neutral"\`, \`"secondary-send"\` → \`"secondary"\`, \`"tertiary"\` → \`"tertiary"\`
40
+
41
+ \`\`\`tsx
42
+ <ActionOption title="Title" content="Text" action="Click" priority="secondary" onClick={fn} />
43
+
44
+ <ListItem title="Title" subtitle="Text" control={<ListItem.Button priority="secondary-neutral" onClick={fn}>Click</ListItem.Button>} />
45
+ \`\`\`
46
+
47
+ ---
48
+
49
+ ## CheckboxOption → ListItem.Checkbox
50
+
51
+ - \`onChange\`: \`(checked: boolean)\` → \`(event: ChangeEvent)\` use \`event.target.checked\`
52
+ - \`name\` move to Checkbox
53
+ - Don't move \`id\` to Checkbox
54
+
55
+ \`\`\`tsx
56
+ <CheckboxOption id="x" name="y" title="Title" content="Text" checked={v} onChange={(c) => set(c)} />
57
+
58
+ <ListItem title="Title" subtitle="Text" control={<ListItem.Checkbox name="y" checked={v} onChange={(e) => set(e.target.checked)} />} />
59
+ \`\`\`
60
+
61
+ ---
62
+
63
+ ## RadioOption → ListItem.Radio
64
+
65
+ - \`name\`, \`value\`, \`checked\`, \`onChange\` move to Radio
66
+ - Don't move \`id\` to Radio
67
+
68
+ \`\`\`tsx
69
+ <RadioOption id="x" name="y" value="v" title="Title" content="Text" checked={v==='v'} onChange={set} />
70
+
71
+ <ListItem title="Title" subtitle="Text" control={<ListItem.Radio name="y" value="v" checked={v==='v'} onChange={set} />} />
72
+ \`\`\`
73
+
74
+ ---
75
+
76
+ ## SwitchOption → ListItem.Switch
77
+
78
+ - \`onChange\` → \`onClick\`, toggle manually
79
+ - \`aria-label\` moves to Switch
80
+
81
+ \`\`\`tsx
82
+ <SwitchOption title="Title" content="Text" checked={v} aria-label="Toggle" onChange={set} />
83
+
84
+ <ListItem title="Title" subtitle="Text" control={<ListItem.Switch checked={v} aria-label="Toggle" onClick={() => set(!v)} />} />
85
+ \`\`\`
86
+
87
+ ---
88
+
89
+ ## NavigationOption → ListItem.Navigation
90
+
91
+ - \`onClick\` or \`href\` move to Navigation
92
+
93
+ \`\`\`tsx
94
+ <NavigationOption title="Title" content="Text" onClick={fn} />
95
+
96
+ <ListItem title="Title" subtitle="Text" control={<ListItem.Navigation onClick={fn} />} />
97
+ \`\`\`
98
+
99
+ ---
100
+
101
+ ## Option → ListItem
102
+
103
+ - Wrap \`media\` in \`ListItem.AvatarView\`
104
+
105
+ \`\`\`tsx
106
+ <Option media={<Icon />} title="Title" />
107
+
108
+ <ListItem title="Title" media={<ListItem.AvatarView><Icon /></ListItem.AvatarView>} />
109
+ \`\`\`
110
+
111
+ ---
112
+
113
+ ## Summary → ListItem
114
+
115
+ **Basic:**
116
+
117
+ - \`icon\` → wrap in \`ListItem.AvatarView\` with \`size={32}\` as \`media\`
118
+ - Remove \`size\` from child \`<Icon />\`
119
+
120
+ **Status:**
121
+
122
+ - \`Status.DONE\` → \`badge={{ status: 'positive' }}\`
123
+ - \`Status.PENDING\` → \`badge={{ status: 'pending' }}\`
124
+ - \`Status.NOT_DONE\` → no badge
125
+
126
+ **Action:**
127
+
128
+ - \`action.text\` → \`action.label\` in \`ListItem.AdditionalInfo\` as \`additionalInfo\`
129
+
130
+ **Info (requires state):**
131
+
132
+ - \`MODAL\` → \`ListItem.IconButton partiallyInteractive\` + \`<Modal>\` in \`control\`
133
+ - \`POPOVER\` → \`<Popover>\` wrapping \`ListItem.IconButton partiallyInteractive\` in \`control\`
134
+ - Use \`QuestionMarkCircle\` icon (import from \`@transferwise/icons\`)
135
+
136
+ \`\`\`tsx
137
+ // Basic
138
+ <Summary title="T" description="D" icon={<Icon />} />
139
+
140
+ <ListItem title="T" subtitle="D" media={<ListItem.AvatarView size={32}><Icon /></ListItem.AvatarView>} />
141
+
142
+ // Status
143
+ <Summary title="T" description="D" icon={<Icon />} status={Status.DONE} />
144
+
145
+ <ListItem title="T" subtitle="D" media={<ListItem.AvatarView size={32} badge={{status:'positive'}}><Icon /></ListItem.AvatarView>} />
146
+
147
+ // Action
148
+ <Summary title="T" description="D" icon={<Icon />} action={{text:'Go', href:'/go'}} />
149
+
150
+ <ListItem title="T" subtitle="D" media={<ListItem.AvatarView size={32}><Icon /></ListItem.AvatarView>} additionalInfo={<ListItem.AdditionalInfo action={{label:'Go', href:'/go'}} />} />
151
+
152
+ // Modal (add: const [open, setOpen] = useState(false))
153
+ <Summary title="T" description="D" icon={<Icon />} info={{title:'Help', content:'Text', presentation:'MODAL', 'aria-label':'Info'}} />
154
+
155
+ <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>} />
156
+
157
+ // Popover
158
+ <Summary title="T" description="D" icon={<Icon />} info={{title:'Help', content:'Text', presentation:'POPOVER', 'aria-label':'Info'}} />
159
+
160
+ <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>} />
161
+ \`\`\`
162
+
163
+ ---
164
+
165
+ ## DefinitionList → Multiple ListItem
166
+
167
+ - Array → individual \`ListItem\`s
168
+ - \`value\` → \`subtitle\`
169
+ - \`key\` → React \`key\` prop
170
+ - Action type: "Edit"/"Update"/"View" → \`ListItem.Button priority="secondary-neutral"\`, "Change"/"Password" → \`ListItem.Navigation\`, "Copy" → \`ListItem.IconButton\`
171
+
172
+ \`\`\`tsx
173
+ <DefinitionList definitions={[
174
+ {title:'T1', value:'V1', key:'k1'},
175
+ {title:'T2', value:'V2', key:'k2', action:{label:'Edit', onClick:fn}}
176
+ ]} />
177
+
178
+
179
+ <ListItem key="k1" title="T1" subtitle="V1" />
180
+ <ListItem key="k2" title="T2" subtitle="V2" control={<ListItem.Button priority="secondary-neutral" onClick={fn}>Edit</ListItem.Button>} />
181
+
182
+ \`\`\`
183
+ `;
184
+ const SYSTEM_PROMPT = `You are a code migration assistant that helps migrate TypeScript/JSX code from deprecated Wise Design System (WDS) components to the new ListItem component and ListItem subcomponents from '@transferwise/components'.
185
+
186
+ Process and migrate each file individually, before reading the next file - e.g. Read file A, migrate file A, then Read file B, migrate file B, etc.
187
+
188
+ Use the following grep pattern when identifying files to migrate - only search for .tsx files:
189
+ "import\\s*\\{[\\s\\S]*?(ActionOption|NavigationOption|NavigationOptionsList|Summary|SwitchOption|CheckboxOption|RadioOption)[\\s\\S]*?\\}\\s*from\\s*['"]@transferwise/components['"]"
190
+
191
+ Rules:
192
+ 1. Identify appropriate files in the directory using only the grep tool - use the provided grep pattern only to find appropriate .tsx files.
193
+ - Use the recursive flag to search all subdirectories as a single grep command
194
+ - Use the multiline flag to capture imports spanning multiple lines
195
+ - Use the glob property of the grep tool to only search *.tsx files - do not search by file type
196
+ 2. Only ever modify files via the Edit tool - do not use the Write tool
197
+ 3. Migrate components per provided migration rules
198
+ 4. Maintain TypeScript type safety and update types to match new API
199
+ 5. Map props: handle renamed, deprecated, new required, and changed types
200
+ 6. Update imports to new WDS components and types
201
+ 7. Preserve code style, formatting, and calculated logic
202
+ 8. Handle conditional rendering, spread props, and complex expressions
203
+ 9. Note: New components may lack feature parity with legacy versions
204
+ 10. Only modify code requiring changes per migration rules, and any impacted surrounding code for context.
205
+ 11. Final result response should just be whether the migration was successful overall, or if any errors were encountered
206
+ 12. Explain your reasoning and justification before making changes, as you edit each file. Keep it concise and succinct as only bullet points
207
+ 13. Do not summarise the changes made after modifying a file.
208
+ 14. If you do not have permission to edit a file, still attempt to edit it and then move onto the next file.
209
+
210
+ You'll receive:
211
+ - File paths/directories to search in individual queries
212
+ - Deprecated component names at the end of this prompt
213
+ - Migration context and rules for each deprecated component
214
+
215
+ Deprecated components: ${DEPRECATED_COMPONENT_NAMES.join(", ")}.
216
+
217
+ ${MIGRATION_RULES}`;
218
+
219
+ //#endregion
220
+ //#region src/transforms/list-item/helpers.ts
221
+ /** Split the path to get the relative path after the directory, and wrap with ANSI color codes */
222
+ function formatPathOutput(directory, path, asDim) {
223
+ const relativePath = path ? path.split(directory.replace(".", ""))[1] ?? path : directory;
224
+ return asDim ? `\x1b[2m${relativePath}\x1b[0m` : `\x1b[32m${relativePath}\x1b[0m`;
225
+ }
226
+ /** Generates a formatted string representing the total elapsed time since the given start time */
227
+ function generateElapsedTime(startTime) {
228
+ const endTime = Date.now();
229
+ const elapsedTime = Math.floor((endTime - startTime) / 1e3);
230
+ const hours = Math.floor(elapsedTime / 3600);
231
+ const minutes = Math.floor(elapsedTime % 3600 / 60);
232
+ const seconds = elapsedTime % 60;
233
+ return `${hours ? `${hours}h ` : ""}${minutes ? `${minutes}m ` : ""}${seconds ? `${seconds}s` : ""}`;
234
+ }
235
+ function formatClaudeResponseContent(content) {
236
+ 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`;
237
+ }
238
+ function generateDiff(original, modified) {
239
+ const lines = (0, diff.createPatch)("", original, modified, "", "").trim().split("\n").slice(4).filter((line) => !line.startsWith("\"));
240
+ let oldLineNumber = 0;
241
+ let newLineNumber = 0;
242
+ return lines.map((line) => {
243
+ const trimmedLine = line.trimEnd();
244
+ if (trimmedLine.startsWith("@@")) {
245
+ const match = /@@ -(\d+)(?:,\d+)? \+(\d+)(?:,\d+)? @@/u.exec(trimmedLine);
246
+ if (match) {
247
+ oldLineNumber = Number.parseInt(match[1], 10);
248
+ newLineNumber = Number.parseInt(match[2], 10);
249
+ }
250
+ return `\x1b[36m${trimmedLine}\x1b[0m`;
251
+ }
252
+ let linePrefix = "";
253
+ if (trimmedLine.startsWith("+")) {
254
+ linePrefix = `${newLineNumber.toString().padStart(4, " ")} `;
255
+ newLineNumber += 1;
256
+ return `\x1b[32m${linePrefix}${trimmedLine}\x1b[0m`;
257
+ }
258
+ if (trimmedLine.startsWith("-")) {
259
+ linePrefix = `${oldLineNumber.toString().padStart(4, " ")} `;
260
+ oldLineNumber += 1;
261
+ return `\x1b[31m${linePrefix}${trimmedLine}\x1b[0m`;
262
+ }
263
+ linePrefix = `${oldLineNumber.toString().padStart(4, " ")} `;
264
+ oldLineNumber += 1;
265
+ newLineNumber += 1;
266
+ return `${linePrefix}${trimmedLine}`;
267
+ }).join("\n");
268
+ }
269
+
270
+ //#endregion
271
+ //#region src/transforms/list-item/claude.ts
272
+ const CLAUDE_SETTINGS_FILE = ".claude/settings.json";
273
+ async function checkVPN(baseUrl) {
274
+ if (baseUrl) {
275
+ const vpnSpinner = (0, ora.default)("Checking VPN connection...").start();
276
+ const checkOnce = async () => new Promise((resolveCheck) => {
277
+ const url = new URL("/health", baseUrl);
278
+ const req = node_https.default.get(url, {
279
+ timeout: 2e3,
280
+ rejectUnauthorized: false
281
+ }, (res) => {
282
+ const ok = !!(res.statusCode && res.statusCode >= 200 && res.statusCode < 400);
283
+ res.resume();
284
+ resolveCheck(ok);
285
+ });
286
+ req.on("timeout", () => {
287
+ req.destroy(/* @__PURE__ */ new Error("timeout"));
288
+ });
289
+ req.on("error", () => resolveCheck(false));
290
+ });
291
+ while (true) {
292
+ if (await checkOnce()) {
293
+ vpnSpinner.succeed("Connected to VPN");
294
+ break;
295
+ }
296
+ vpnSpinner.text = "Please connect to VPN... retrying in 3s";
297
+ await new Promise((response) => {
298
+ setTimeout(response, 3e3);
299
+ });
300
+ }
301
+ }
302
+ return true;
303
+ }
304
+ function getQueryOptions(sessionId, codemodOptions, isDebug) {
305
+ const claudeSettingsPath = (0, node_path.resolve)(process.env.HOME || "", CLAUDE_SETTINGS_FILE);
306
+ const settings = JSON.parse((0, node_fs.readFileSync)(claudeSettingsPath, "utf-8"));
307
+ let apiKey;
308
+ try {
309
+ apiKey = (0, node_child_process.execSync)(`bash ${settings.apiKeyHelper}`, { encoding: "utf-8" }).trim();
310
+ } catch {}
311
+ 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");
312
+ const { ANTHROPIC_CUSTOM_HEADERS, ...restEnvVars } = settings?.env ?? {};
313
+ const envVars = {
314
+ ANTHROPIC_AUTH_TOKEN: apiKey,
315
+ ANTHROPIC_CUSTOM_HEADERS,
316
+ ...restEnvVars,
317
+ PATH: process.env.PATH
318
+ };
319
+ const promptPrefix = !codemodOptions?.useGitIgnore ? "Do not use .gitignore to exclude files from the migration. Ensure all relevant files are included." : "Respect .gitignore when determining which files to include in the migration.";
320
+ return {
321
+ resume: sessionId,
322
+ env: envVars,
323
+ permissionMode: codemodOptions?.isDry ? void 0 : "acceptEdits",
324
+ systemPrompt: {
325
+ type: "preset",
326
+ preset: "claude_code",
327
+ append: `${promptPrefix}\n${SYSTEM_PROMPT}`
328
+ },
329
+ outputFormat: {
330
+ type: "json_schema",
331
+ schema: {
332
+ type: "object",
333
+ properties: {
334
+ migrationStatus: {
335
+ type: "string",
336
+ enum: [
337
+ "success",
338
+ "fail",
339
+ "no-files"
340
+ ]
341
+ },
342
+ filesChanged: { type: "number" }
343
+ }
344
+ }
345
+ },
346
+ allowedTools: [
347
+ "Grep",
348
+ "Read",
349
+ ...!codemodOptions?.isDry ? ["Edit"] : []
350
+ ],
351
+ settingSources: [
352
+ "local",
353
+ "project",
354
+ "user"
355
+ ]
356
+ };
357
+ }
358
+ /** Initiate a new Claude session/conversation and return reusable options */
359
+ async function initiateClaudeSessionOptions(codemodOptions, isDebug = false) {
360
+ const claudeSessionSpinner = (0, ora.default)("Starting and verifying Claude instance - your browser may open for Okta authentication if required.").start();
361
+ const options = getQueryOptions(void 0, codemodOptions, isDebug);
362
+ await checkVPN(options.env?.ANTHROPIC_BASE_URL);
363
+ const result = (0, __anthropic_ai_claude_agent_sdk.query)({
364
+ options,
365
+ 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.`
366
+ });
367
+ for await (const message of result) switch (message.type) {
368
+ case "system":
369
+ if (message.subtype === "init" && !options.resume) {
370
+ claudeSessionSpinner.succeed("Successfully initialised Claude instance");
371
+ options.resume = message.session_id;
372
+ }
373
+ break;
374
+ default: if (message.type === "result" && message.subtype !== "success") claudeSessionSpinner.fail(`Claude encountered an error: ${message.errors.join("\n")}`);
375
+ }
376
+ return options;
377
+ }
378
+ async function queryClaude(path, options, codemodOptions, directorySpinner, isDebug = false) {
379
+ const result = (0, __anthropic_ai_claude_agent_sdk.query)({
380
+ options,
381
+ prompt: path
382
+ });
383
+ const fileMigrationSpinner = (0, ora.default)({ prefixText: " " });
384
+ let currentMigrationPath = "";
385
+ let lastClaudeTextResponse = "";
386
+ function logClaudeResponseContent() {
387
+ if (isDebug && lastClaudeTextResponse.length) {
388
+ console.log(`${require_constants.CONSOLE_ICONS.claude} ${formatClaudeResponseContent(lastClaudeTextResponse)}`);
389
+ lastClaudeTextResponse = "";
390
+ }
391
+ }
392
+ for await (const message of result) switch (message.type) {
393
+ case "assistant":
394
+ for (const msg of message.message.content) switch (msg.type) {
395
+ case "tool_use":
396
+ if (msg.name === "Grep") {
397
+ logClaudeResponseContent();
398
+ directorySpinner.start(`${formatPathOutput(path)} - Searching for files with deprecated imports...`);
399
+ } else if (msg.name === "Read") {
400
+ logClaudeResponseContent();
401
+ fileMigrationSpinner.start(`${formatPathOutput(path, msg.input.file_path, true)} - Processing...`);
402
+ currentMigrationPath = "";
403
+ } else if (msg.name === "Edit") {
404
+ const editedFilePath = msg.input.file_path;
405
+ if (codemodOptions.isPrint || codemodOptions.isDry) {
406
+ if (codemodOptions.isDry) logClaudeResponseContent();
407
+ if (currentMigrationPath !== editedFilePath) {
408
+ currentMigrationPath = editedFilePath;
409
+ logClaudeResponseContent();
410
+ console.log(`${require_constants.CONSOLE_ICONS.info} ${codemodOptions.isDry ? "Proposed changes" : "Changes"} for ${formatPathOutput(path, editedFilePath)}:`);
411
+ }
412
+ console.log(generateDiff(msg.input.old_string, msg.input.new_string));
413
+ } else {
414
+ fileMigrationSpinner.succeed(`${formatPathOutput(path, editedFilePath, true)} - Migrated`);
415
+ fileMigrationSpinner.prefixText = " ";
416
+ fileMigrationSpinner.start(" ");
417
+ }
418
+ logClaudeResponseContent();
419
+ }
420
+ break;
421
+ case "text":
422
+ if (isDebug) {
423
+ logClaudeResponseContent();
424
+ lastClaudeTextResponse = msg.text;
425
+ }
426
+ break;
427
+ default:
428
+ }
429
+ break;
430
+ case "result":
431
+ if (message.subtype === "success") {
432
+ if (message.structured_output) {
433
+ fileMigrationSpinner.stop();
434
+ const output = message.structured_output;
435
+ if (output.migrationStatus === "no-files" || output.filesChanged === 0) directorySpinner.warn(`${formatPathOutput(path)} - No files need migration`);
436
+ else if (output.migrationStatus === "success") directorySpinner.succeed(`${formatPathOutput(path)} - Migrated \x1b[32m${output.filesChanged}\x1b[0m file(s) successfully`);
437
+ else directorySpinner.fail(`${formatPathOutput(path)} - Migration failed due to an error`);
438
+ }
439
+ } else {
440
+ console.log(`${require_constants.CONSOLE_ICONS.error} Claude encountered an error:`);
441
+ console.log(message);
442
+ }
443
+ break;
444
+ case "user":
445
+ for (const msg of message.message.content) if (typeof msg !== "string" && msg.type === "tool_result" && typeof msg.content === "string") {
446
+ if (msg.content.startsWith("Found ")) {
447
+ const foundFileCount = msg.content.match(/\.tsx/gu)?.length ?? 0;
448
+ directorySpinner.info(`${formatPathOutput(path)} - Found \x1b[32m${foundFileCount}\x1b[0m \x1b[2m*.tsx\x1b[0m file(s) needing migration`);
449
+ fileMigrationSpinner.start();
450
+ }
451
+ }
452
+ break;
453
+ default:
454
+ }
455
+ }
456
+
457
+ //#endregion
458
+ //#region src/transforms/list-item/transformer.ts
459
+ const transformer = async (targetPaths, codemodOptions, isDebug = false) => {
460
+ const startTime = Date.now();
461
+ const queryOptions = await initiateClaudeSessionOptions(codemodOptions, isDebug);
462
+ console.log(`${require_constants.CONSOLE_ICONS.info} Analysing targetted paths - this may take a while...`);
463
+ for (const directory of targetPaths) await queryClaude(directory, queryOptions, codemodOptions, (0, ora.default)({ prefixText: " " }).start(), isDebug);
464
+ console.log(`${require_constants.CONSOLE_ICONS.success} Finished migrating - elapsed time: \x1b[1m${generateElapsedTime(startTime)}\x1b[0m`);
465
+ };
466
+ var transformer_default = transformer;
467
+
468
+ //#endregion
469
+ Object.defineProperty(exports, 'transformer_default', {
470
+ enumerable: true,
471
+ get: function () {
472
+ return transformer_default;
473
+ }
474
+ });
475
+ //# sourceMappingURL=transformer-CZXBO1GJ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transformer-CZXBO1GJ.js","names":["https","CONSOLE_ICONS","CONSOLE_ICONS"],"sources":["../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":["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 = `You are a code migration assistant that helps migrate TypeScript/JSX code from deprecated Wise Design System (WDS) components to the new ListItem component and ListItem subcomponents from '@transferwise/components'.\n\nProcess and migrate each file individually, before reading the next file - e.g. Read file A, migrate file A, then Read file B, migrate file B, etc.\n\nUse the following grep pattern when identifying files to migrate - only search for .tsx files:\n\"import\\\\s*\\\\{[\\\\s\\\\S]*?(ActionOption|NavigationOption|NavigationOptionsList|Summary|SwitchOption|CheckboxOption|RadioOption)[\\\\s\\\\S]*?\\\\}\\\\s*from\\\\s*['\"]@transferwise/components['\"]\"\n\nRules:\n1. Identify appropriate files in the directory using only the grep tool - use the provided grep pattern only to find appropriate .tsx files.\n - Use the recursive flag to search all subdirectories as a single grep command\n - Use the multiline flag to capture imports spanning multiple lines\n - Use the glob property of the grep tool to only search *.tsx files - do not search by file type\n2. Only ever modify files via the Edit tool - do not use the Write tool\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\n12. Explain your reasoning and justification before making changes, as you edit each file. Keep it concise and succinct as only bullet points\n13. Do not summarise the changes made after modifying a file.\n14. If you do not have permission to edit a file, still attempt to edit it and then move onto the next file.\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, 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\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// 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 https from 'node:https';\n\nimport { type Options, query } from '@anthropic-ai/claude-agent-sdk';\nimport { execSync } from 'child_process';\nimport { readFileSync } from 'fs';\nimport ora, { type Ora } from 'ora';\nimport { resolve } from 'path';\n\nimport { CONSOLE_ICONS } from '../../constants';\nimport type { CodemodOptions } from '../../controller/types';\nimport { SYSTEM_PROMPT } from './constants';\nimport { formatClaudeResponseContent, formatPathOutput, generateDiff } 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 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(\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 const promptPrefix = !codemodOptions?.useGitIgnore\n ? 'Do not use .gitignore to exclude files from the migration. Ensure all relevant files are included.'\n : 'Respect .gitignore when determining which files to include in the migration.';\n\n // NOTE: ignore patterns not working...\n // codemodOptions?.ignorePatterns?.length ? `Ignore any files with names matching the following .gitignore glob patterns, separated by commas and do not migrate them: ${codemodOptions.ignorePatterns.replaceAll(',', ', ')}` : '';\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: `${promptPrefix}\\n${SYSTEM_PROMPT}`,\n },\n outputFormat: {\n type: 'json_schema',\n schema: {\n type: 'object',\n properties: {\n migrationStatus: { type: 'string', enum: ['success', 'fail', 'no-files'] },\n filesChanged: { type: 'number' },\n },\n },\n },\n allowedTools: ['Grep', 'Read', ...(!codemodOptions?.isDry ? ['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 const claudeSessionSpinner = ora(\n 'Starting and verifying Claude instance - your browser may open for Okta authentication if required.',\n ).start();\n\n const options = getQueryOptions(undefined, codemodOptions, isDebug);\n await checkVPN(options.env?.ANTHROPIC_BASE_URL);\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 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\n// Queries Claude with the given path and handles logging of tool uses and results\nexport async function queryClaude(\n path: string,\n options: Options,\n codemodOptions: CodemodOptions,\n directorySpinner: Ora,\n isDebug = false,\n) {\n const result = query({\n options,\n prompt: path,\n });\n const fileMigrationSpinner = ora({ prefixText: ' ' });\n const modifiedFiles: string[] = [];\n let currentMigrationPath = '';\n let lastClaudeTextResponse = '';\n\n function logClaudeResponseContent() {\n if (isDebug && lastClaudeTextResponse.length) {\n // Logs Claude's textual responses in debug mode, to help with understanding justifications/errors\n console.log(`${CONSOLE_ICONS.claude} ${formatClaudeResponseContent(lastClaudeTextResponse)}`);\n lastClaudeTextResponse = '';\n }\n }\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 === 'Grep') {\n logClaudeResponseContent();\n directorySpinner.start(\n `${formatPathOutput(path)} - Searching for files with deprecated imports...`,\n );\n } else if (msg.name === 'Read') {\n logClaudeResponseContent();\n fileMigrationSpinner.start(\n `${formatPathOutput(path, (msg as ClaudeResponseToolUse).input.file_path, true)} - Processing...`,\n );\n // Track the current file being migrated, to help with logging\n currentMigrationPath = '';\n } else if (\n msg.name === 'Edit'\n // && !modifiedFiles.includes((msg as ClaudeResponseToolUse).input.file_path)\n ) {\n // Safeguard against duplicate logs, where Claude may write multiple times to the same file) {\n const editedFilePath = (msg as ClaudeResponseToolUse).input.file_path;\n // modifiedFiles.push(editedFilePath);\n // Generate and print out a pretty diff of changes made by Claude\n if (codemodOptions.isPrint || codemodOptions.isDry) {\n if (codemodOptions.isDry) logClaudeResponseContent();\n\n // Print new file header if migrating a new file\n if (currentMigrationPath !== editedFilePath) {\n currentMigrationPath = editedFilePath;\n logClaudeResponseContent();\n console.log(\n `${CONSOLE_ICONS.info} ${codemodOptions.isDry ? 'Proposed changes' : 'Changes'} for ${formatPathOutput(path, editedFilePath)}:`,\n );\n }\n\n console.log(\n generateDiff(\n (msg as ClaudeResponseToolUse).input.old_string as string,\n (msg as ClaudeResponseToolUse).input.new_string as string,\n ),\n );\n } else {\n fileMigrationSpinner.succeed(\n `${formatPathOutput(path, editedFilePath, true)} - Migrated`,\n );\n fileMigrationSpinner.prefixText = ' ';\n fileMigrationSpinner.start(' ');\n }\n\n // Logs any pending Claude textual responses in debug mode, to help with understanding justifications/errors\n logClaudeResponseContent();\n }\n\n break;\n case 'text':\n if (isDebug) {\n // If we already have something in the \"queue\", print that first\n logClaudeResponseContent();\n lastClaudeTextResponse = msg.text;\n }\n break;\n default:\n }\n }\n break;\n case 'result':\n if (message.subtype === 'success') {\n if (message.structured_output) {\n fileMigrationSpinner.stop();\n const output = message.structured_output as {\n migrationStatus: 'success' | 'fail' | 'no-files';\n filesChanged: number;\n };\n\n if (output.migrationStatus === 'no-files' || output.filesChanged === 0) {\n directorySpinner.warn(`${formatPathOutput(path)} - No files need migration`);\n } else if (output.migrationStatus === 'success') {\n directorySpinner.succeed(\n `${formatPathOutput(path)} - Migrated \\x1b[32m${output.filesChanged}\\x1b[0m file(s) successfully`,\n );\n } else {\n directorySpinner.fail(`${formatPathOutput(path)} - Migration failed due to an error`);\n }\n }\n } else {\n console.log(`${CONSOLE_ICONS.error} Claude encountered an error:`);\n console.log(message);\n }\n break;\n case 'user':\n for (const msg of message.message.content) {\n if (\n typeof msg !== 'string' &&\n msg.type === 'tool_result' &&\n typeof msg.content === 'string'\n ) {\n if (msg.content.startsWith('Found ')) {\n const foundFileCount = msg.content.match(/\\.tsx/gu)?.length ?? 0;\n\n // Wrap `.tsx` files in dim color code\n directorySpinner.info(\n `${formatPathOutput(path)} - Found \\x1b[32m${foundFileCount}\\x1b[0m \\x1b[2m*.tsx\\x1b[0m file(s) needing migration`,\n );\n fileMigrationSpinner.start();\n }\n }\n }\n break;\n default:\n }\n }\n}\n","import ora from 'ora';\n\nimport { CONSOLE_ICONS } from '../../constants';\nimport type { CodemodOptions } from '../../controller/types';\nimport { initiateClaudeSessionOptions, queryClaude } from './claude';\nimport { generateElapsedTime } from './helpers';\n\nconst transformer = async (\n targetPaths: string[],\n codemodOptions: CodemodOptions,\n isDebug = false,\n) => {\n const startTime = Date.now();\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 targetPaths) {\n const directorySpinner = ora({ prefixText: ' ' }).start();\n await queryClaude(directory, queryOptions, codemodOptions, directorySpinner, 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,5 +1,6 @@
1
1
  Object.defineProperty(exports, '__esModule', { value: true });
2
- const require_helpers = require('../../helpers-BNLAPdMR.js');
2
+ require('../../constants-BhVbK_XZ.js');
3
+ const require_helpers = require('../../helpers-BvNeqqYU.js');
3
4
 
4
5
  //#region src/helpers/addImport.ts
5
6
  /**