@wise/wds-codemods 1.0.0-experimental-939a014 → 1.0.0-experimental-be47db3
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/{helpers-C9EIzTCd.js → helpers-IFtIGywc.js} +2 -2
- package/dist/{helpers-C9EIzTCd.js.map → helpers-IFtIGywc.js.map} +1 -1
- package/dist/index.js +2 -2
- package/dist/{transformer-G1wsjfTA.js → transformer-DoAMzZmy.js} +67 -105
- package/dist/transformer-DoAMzZmy.js.map +1 -0
- package/dist/transforms/button/transformer.js +1 -1
- package/dist/transforms/list-item/transformer.js +1 -1
- package/package.json +3 -3
- package/dist/transformer-G1wsjfTA.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
const require_constants = require('./constants-CcE2TmzN.js');
|
|
3
|
-
const require_transformer = require('./transformer-
|
|
4
|
-
const require_helpers = require('./helpers-
|
|
3
|
+
const require_transformer = require('./transformer-DoAMzZmy.js');
|
|
4
|
+
const require_helpers = require('./helpers-IFtIGywc.js');
|
|
5
5
|
let node_child_process = require("node:child_process");
|
|
6
6
|
let node_fs_promises = require("node:fs/promises");
|
|
7
7
|
node_fs_promises = require_constants.__toESM(node_fs_promises);
|
|
@@ -3,10 +3,10 @@ let node_child_process = require("node:child_process");
|
|
|
3
3
|
let node_path = require("node:path");
|
|
4
4
|
let spinnies = require("spinnies");
|
|
5
5
|
spinnies = require_constants.__toESM(spinnies);
|
|
6
|
+
let node_fs = require("node:fs");
|
|
6
7
|
let node_https = require("node:https");
|
|
7
8
|
node_https = require_constants.__toESM(node_https);
|
|
8
9
|
let __anthropic_ai_claude_agent_sdk = require("@anthropic-ai/claude-agent-sdk");
|
|
9
|
-
let node_fs = require("node:fs");
|
|
10
10
|
let diff = require("diff");
|
|
11
11
|
|
|
12
12
|
//#region src/helpers/spinnerLogs.ts
|
|
@@ -199,34 +199,26 @@ const MIGRATION_RULES = `Migration rules:
|
|
|
199
199
|
`;
|
|
200
200
|
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'.
|
|
201
201
|
|
|
202
|
-
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.
|
|
203
|
-
|
|
204
|
-
Use the following exact grep pattern when identifying files to migrate - only search for .tsx files:
|
|
205
|
-
\`import\\s*\\{[\\s\\S]*?(${DEPRECATED_COMPONENT_NAMES.join("|")})[\\s\\S]*?\\}\\s*from\\s*['"]@transferwise/components['"]\`
|
|
206
|
-
|
|
207
202
|
Rules:
|
|
208
|
-
1.
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
12. Explain your reasoning and justification before making changes, as you edit each file. Keep it concise and succinct as only bullet points
|
|
225
|
-
13. Do not summarise the changes made after modifying a file.
|
|
226
|
-
14. If you do not have permission to edit a file, still attempt to edit it and then move onto the next file.
|
|
203
|
+
1. Only ever modify files via the Edit tool - do not use the Write tool
|
|
204
|
+
2. When identifying what code to migrate within a file, explain how you identified it first.
|
|
205
|
+
2. Migrate components per provided migration rules
|
|
206
|
+
3. Maintain TypeScript type safety and update types to match new API
|
|
207
|
+
4. Map props: handle renamed, deprecated, new required, and changed types
|
|
208
|
+
5. Update imports to new WDS components and types
|
|
209
|
+
6. Preserve code style, formatting, and calculated logic
|
|
210
|
+
7. Handle conditional rendering, spread props, and complex expressions
|
|
211
|
+
8. Note: New components may lack feature parity with legacy versions
|
|
212
|
+
9. Only modify code requiring changes per migration rules, and any impacted surrounding code for context.
|
|
213
|
+
10. Final result response should just be whether the migration was successful overall, or if any errors were encountered
|
|
214
|
+
- Do not summarise or explain the changes made
|
|
215
|
+
11. Explain your reasoning and justification before making changes, as you edit each file.
|
|
216
|
+
- Keep it concise and succinct, as only bullet points
|
|
217
|
+
12. After modifying the file, do not summarise the changes made.
|
|
218
|
+
13. If you do not have permission to edit a file, still attempt to edit it and then move onto the next file.
|
|
227
219
|
|
|
228
220
|
You'll receive:
|
|
229
|
-
- File paths
|
|
221
|
+
- File paths to migrate in individual queries
|
|
230
222
|
- Deprecated component names at the end of this prompt
|
|
231
223
|
- Migration context and rules for each deprecated component
|
|
232
224
|
|
|
@@ -319,7 +311,7 @@ async function checkVPN(spinnies$1, baseUrl) {
|
|
|
319
311
|
}
|
|
320
312
|
return true;
|
|
321
313
|
}
|
|
322
|
-
function getQueryOptions(sessionId
|
|
314
|
+
function getQueryOptions(sessionId) {
|
|
323
315
|
const claudeSettingsPath = (0, node_path.resolve)(process.env.HOME || "", CLAUDE_SETTINGS_FILE);
|
|
324
316
|
const settings = JSON.parse((0, node_fs.readFileSync)(claudeSettingsPath, "utf-8"));
|
|
325
317
|
let apiKey;
|
|
@@ -336,34 +328,13 @@ function getQueryOptions(sessionId, codemodOptions) {
|
|
|
336
328
|
...restEnvVars,
|
|
337
329
|
PATH: process.env.PATH
|
|
338
330
|
},
|
|
339
|
-
permissionMode:
|
|
331
|
+
permissionMode: "acceptEdits",
|
|
340
332
|
systemPrompt: {
|
|
341
333
|
type: "preset",
|
|
342
334
|
preset: "claude_code",
|
|
343
335
|
append: SYSTEM_PROMPT
|
|
344
336
|
},
|
|
345
|
-
|
|
346
|
-
type: "json_schema",
|
|
347
|
-
schema: {
|
|
348
|
-
type: "object",
|
|
349
|
-
properties: {
|
|
350
|
-
migrationStatus: {
|
|
351
|
-
type: "string",
|
|
352
|
-
enum: [
|
|
353
|
-
"success",
|
|
354
|
-
"fail",
|
|
355
|
-
"no-files"
|
|
356
|
-
]
|
|
357
|
-
},
|
|
358
|
-
filesChanged: { type: "number" }
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
},
|
|
362
|
-
allowedTools: [
|
|
363
|
-
"Grep",
|
|
364
|
-
"Read",
|
|
365
|
-
...!codemodOptions?.isDry ? ["Edit"] : []
|
|
366
|
-
],
|
|
337
|
+
allowedTools: ["Grep", "Read"],
|
|
367
338
|
settingSources: [
|
|
368
339
|
"local",
|
|
369
340
|
"project",
|
|
@@ -372,8 +343,8 @@ function getQueryOptions(sessionId, codemodOptions) {
|
|
|
372
343
|
};
|
|
373
344
|
}
|
|
374
345
|
/** Initiate a new Claude session/conversation and return reusable options */
|
|
375
|
-
async function initiateClaudeSessionOptions(
|
|
376
|
-
const options = getQueryOptions(void 0
|
|
346
|
+
async function initiateClaudeSessionOptions(spinnies$1) {
|
|
347
|
+
const options = getQueryOptions(void 0);
|
|
377
348
|
await checkVPN(spinnies$1, options.env?.ANTHROPIC_BASE_URL);
|
|
378
349
|
spinnies$1.add("claudeSession", { text: "Starting and verifying Claude instance - your browser may open for Okta authentication if required." });
|
|
379
350
|
const result = (0, __anthropic_ai_claude_agent_sdk.query)({
|
|
@@ -395,45 +366,21 @@ async function initiateClaudeSessionOptions(codemodOptions, spinnies$1) {
|
|
|
395
366
|
}
|
|
396
367
|
return options;
|
|
397
368
|
}
|
|
398
|
-
async function queryClaude(
|
|
369
|
+
async function queryClaude(directory, filePath, options, codemodOptions, spinnies$1, isDebug = false) {
|
|
370
|
+
const startTime = Date.now();
|
|
399
371
|
const debugSpinnies = new spinnies.default();
|
|
400
372
|
const result = (0, __anthropic_ai_claude_agent_sdk.query)({
|
|
401
373
|
options,
|
|
402
|
-
prompt:
|
|
374
|
+
prompt: filePath
|
|
403
375
|
});
|
|
404
|
-
let currentMigrationPath = "";
|
|
405
376
|
for await (const message of result) switch (message.type) {
|
|
406
377
|
case "assistant":
|
|
407
378
|
for (const msg of message.message.content) switch (msg.type) {
|
|
408
379
|
case "tool_use":
|
|
409
|
-
if (msg.name === "
|
|
380
|
+
if (msg.name === "Read") {
|
|
410
381
|
spinnies$1.remove("placeholder");
|
|
411
|
-
spinnies$1.update(`
|
|
412
|
-
|
|
413
|
-
text: " ",
|
|
414
|
-
indent: 4
|
|
415
|
-
});
|
|
416
|
-
} else if (msg.name === "Read") {
|
|
417
|
-
spinnies$1.remove("placeholder");
|
|
418
|
-
spinnies$1.add(`file-${msg.input.file_path}`, {
|
|
419
|
-
indent: 4,
|
|
420
|
-
text: `${formatPathOutput(path, msg.input.file_path, true)} - Processing...`
|
|
421
|
-
});
|
|
422
|
-
currentMigrationPath = "";
|
|
423
|
-
} else if (msg.name === "Edit") {
|
|
424
|
-
const editedFilePath = msg.input.file_path;
|
|
425
|
-
if (codemodOptions.isPrint || codemodOptions.isDry) {
|
|
426
|
-
if (currentMigrationPath !== editedFilePath) {
|
|
427
|
-
currentMigrationPath = editedFilePath;
|
|
428
|
-
logStaticMessage(spinnies$1, `${require_constants.CONSOLE_ICONS.info} ${codemodOptions.isDry ? "Proposed changes" : "Changes"} for ${formatPathOutput(path, editedFilePath)}`);
|
|
429
|
-
}
|
|
430
|
-
logStaticMessage(spinnies$1, generateDiff(msg.input.old_string, msg.input.new_string));
|
|
431
|
-
} else spinnies$1.succeed(`file-${editedFilePath}`, { text: `${formatPathOutput(path, editedFilePath, true)} - Migrated` });
|
|
432
|
-
spinnies$1.add("placeholder", {
|
|
433
|
-
text: " ",
|
|
434
|
-
indent: 4
|
|
435
|
-
});
|
|
436
|
-
}
|
|
382
|
+
spinnies$1.update(`file-${filePath}`, { text: `${formatPathOutput(directory, filePath, true)} - Reading...` });
|
|
383
|
+
} else if (msg.name === "Edit") spinnies$1.update(`file-${filePath}`, { text: `${formatPathOutput(directory, filePath, true)} - Migrating...` });
|
|
437
384
|
break;
|
|
438
385
|
case "text":
|
|
439
386
|
if (isDebug) logStaticMessage(debugSpinnies, `${require_constants.CONSOLE_ICONS.claude} ${formatClaudeResponseContent(msg.text)}`);
|
|
@@ -442,32 +389,14 @@ async function queryClaude(path, options, codemodOptions, spinnies$1, isDebug =
|
|
|
442
389
|
}
|
|
443
390
|
break;
|
|
444
391
|
case "result":
|
|
445
|
-
if (message.subtype === "success") {
|
|
446
|
-
|
|
447
|
-
const output = message.structured_output;
|
|
448
|
-
spinnies$1.remove("placeholder");
|
|
449
|
-
if (output.migrationStatus === "no-files" || output.filesChanged === 0) spinnies$1.succeed(`directory-${path}`, { text: `${formatPathOutput(path)} - No files need migration` });
|
|
450
|
-
else if (output.migrationStatus === "success") spinnies$1.succeed(`directory-${path}`, { text: `${formatPathOutput(path)} - Migrated \x1b[32m${output.filesChanged}\x1b[0m file(s) successfully` });
|
|
451
|
-
else spinnies$1.fail(`directory-${path}`, {
|
|
452
|
-
text: `${formatPathOutput(path)} - Migration failed due to an error`,
|
|
453
|
-
status: "fail"
|
|
454
|
-
});
|
|
455
|
-
}
|
|
456
|
-
} else if (message.is_error) {
|
|
392
|
+
if (message.subtype === "success") spinnies$1.succeed(`file-${filePath}`, { text: `${formatPathOutput(directory, filePath, true)} - Migrated in: ${generateElapsedTime(startTime)}` });
|
|
393
|
+
else {
|
|
457
394
|
logStaticMessage(debugSpinnies, `${require_constants.CONSOLE_ICONS.error} Claude encountered an error:`);
|
|
458
395
|
console.log(message);
|
|
459
396
|
spinnies$1.stopAll("fail");
|
|
460
397
|
debugSpinnies.stopAll("fail");
|
|
461
398
|
}
|
|
462
399
|
break;
|
|
463
|
-
case "user":
|
|
464
|
-
for (const msg of message.message.content) if (typeof msg !== "string" && msg.type === "tool_result" && typeof msg.content === "string") {
|
|
465
|
-
if (msg.content.startsWith("Found ")) {
|
|
466
|
-
const foundFileCount = msg.content.match(/\.tsx/gu)?.length ?? 0;
|
|
467
|
-
spinnies$1.update(`directory-${path}`, { text: `${formatPathOutput(path)} - Found \x1b[32m${foundFileCount}\x1b[0m \x1b[2m*.tsx\x1b[0m file(s) needing migration` });
|
|
468
|
-
}
|
|
469
|
-
}
|
|
470
|
-
break;
|
|
471
400
|
default:
|
|
472
401
|
}
|
|
473
402
|
}
|
|
@@ -477,7 +406,7 @@ async function queryClaude(path, options, codemodOptions, spinnies$1, isDebug =
|
|
|
477
406
|
const transformer = async (targetPaths, codemodOptions, isDebug = false) => {
|
|
478
407
|
const startTime = Date.now();
|
|
479
408
|
const spinnies$1 = new spinnies.default();
|
|
480
|
-
const queryOptions = await initiateClaudeSessionOptions(
|
|
409
|
+
const queryOptions = await initiateClaudeSessionOptions(spinnies$1);
|
|
481
410
|
spinnies$1.remove("placeholder");
|
|
482
411
|
spinnies$1.add("analysing", { text: "Analysing targetted paths - this may take a while..." });
|
|
483
412
|
for (const directory of targetPaths) {
|
|
@@ -485,7 +414,40 @@ const transformer = async (targetPaths, codemodOptions, isDebug = false) => {
|
|
|
485
414
|
indent: 2,
|
|
486
415
|
text: `${formatPathOutput(directory)} - Processing directory`
|
|
487
416
|
});
|
|
488
|
-
|
|
417
|
+
const allTsxFiles = (0, node_child_process.execSync)(`find "${directory}" -name "*.tsx" -type f`, { encoding: "utf-8" }).trim().split("\n").filter(Boolean);
|
|
418
|
+
const grepPattern = /* @__PURE__ */ new RegExp(`import\\s*\\{[\\s\\S]*?(${DEPRECATED_COMPONENT_NAMES.join("|")})[\\s\\S]*?\\}\\s*from\\s*['"]@transferwise/components['"]`);
|
|
419
|
+
const matchingFilePaths = allTsxFiles.filter((filePath) => {
|
|
420
|
+
try {
|
|
421
|
+
const content = (0, node_fs.readFileSync)(filePath, "utf-8");
|
|
422
|
+
return grepPattern.test(content);
|
|
423
|
+
} catch {
|
|
424
|
+
return false;
|
|
425
|
+
}
|
|
426
|
+
});
|
|
427
|
+
spinnies$1.update(`directory-${directory}`, { text: `${formatPathOutput(directory)} - Found \x1b[32m${matchingFilePaths.length}\x1b[0m \x1b[2m*.tsx\x1b[0m file(s) needing migration` });
|
|
428
|
+
for (const filePath of matchingFilePaths) {
|
|
429
|
+
spinnies$1.add(`file-${filePath}`, {
|
|
430
|
+
indent: 4,
|
|
431
|
+
text: `${formatPathOutput(directory, filePath, true)} - Parsing...`
|
|
432
|
+
});
|
|
433
|
+
const originalFileContent = (0, node_fs.readFileSync)(filePath, "utf-8");
|
|
434
|
+
spinnies$1.update(`file-${filePath}`, {
|
|
435
|
+
indent: 4,
|
|
436
|
+
text: `${formatPathOutput(directory, filePath, true)} - Processing...`
|
|
437
|
+
});
|
|
438
|
+
await queryClaude(directory, filePath, queryOptions, codemodOptions, spinnies$1, isDebug);
|
|
439
|
+
const newFileContent = (0, node_fs.readFileSync)(filePath, "utf-8");
|
|
440
|
+
if (codemodOptions.isPrint) {
|
|
441
|
+
logStaticMessage(spinnies$1, `Diff for ${formatPathOutput(directory, filePath, true)}:`);
|
|
442
|
+
logStaticMessage(spinnies$1, generateDiff(originalFileContent, newFileContent));
|
|
443
|
+
}
|
|
444
|
+
spinnies$1.remove("placeholder");
|
|
445
|
+
}
|
|
446
|
+
if (matchingFilePaths.length === 0) spinnies$1.succeed(`directory-${directory}`, { text: `${formatPathOutput(directory)} - No files need migration` });
|
|
447
|
+
else spinnies$1.succeed(`directory-${directory}`, {
|
|
448
|
+
indent: 2,
|
|
449
|
+
text: `${formatPathOutput(directory)} - Migrated \x1b[32m${matchingFilePaths.length}\x1b[0m file(s) successfully`
|
|
450
|
+
});
|
|
489
451
|
}
|
|
490
452
|
spinnies$1.succeed("analysing", { text: "Successfully analysed all targetted paths" });
|
|
491
453
|
spinnies$1.stopAll("succeed");
|
|
@@ -503,4 +465,4 @@ Object.defineProperty(exports, 'transformer_default', {
|
|
|
503
465
|
return transformer_default;
|
|
504
466
|
}
|
|
505
467
|
});
|
|
506
|
-
//# sourceMappingURL=transformer-
|
|
468
|
+
//# sourceMappingURL=transformer-DoAMzZmy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transformer-DoAMzZmy.js","names":["https","spinnies","Spinnies","CONSOLE_ICONS","spinnies","Spinnies"],"sources":["../src/helpers/spinnerLogs.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":["import type Spinnies from 'spinnies';\n\ninterface LogConsoleSpinnerSettings {\n spinnies: Spinnies;\n spinnerId: string;\n options: Partial<Spinnies.SpinnerOptions>;\n}\n\nexport function logToConsole({ spinnies, spinnerId, options }: LogConsoleSpinnerSettings): void {\n spinnies.add(spinnerId, options);\n}\n\nexport function logStaticMessage(spinnies: Spinnies, message: string): void {\n // Create a unique spinner ID for the static message using timestamp, first part of the message and a random number\n const uuid = `static-message-${Date.now()}-${message.slice(0, 10).replace(/\\s+/gu, '-')}-${Math.floor(Math.random() * 10000)}`;\n logToConsole({ spinnies, spinnerId: uuid, options: { text: message, status: 'non-spinnable' } });\n}\n","export 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\nRules:\n1. Only ever modify files via the Edit tool - do not use the Write tool\n2. When identifying what code to migrate within a file, explain how you identified it first.\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. Final result response should just be whether the migration was successful overall, or if any errors were encountered\n - Do not summarise or explain the changes made\n11. Explain your reasoning and justification before making changes, as you edit each file.\n - Keep it concise and succinct, as only bullet points\n12. After modifying the file, do not summarise the changes made.\n13. 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 to migrate 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 { resolve } from 'path';\nimport Spinnies from 'spinnies';\n\nimport { CONSOLE_ICONS } from '../../constants';\nimport type { CodemodOptions } from '../../controller/types';\nimport { logStaticMessage } from '../../helpers/spinnerLogs';\nimport { SYSTEM_PROMPT } from './constants';\nimport { formatClaudeResponseContent, formatPathOutput, generateElapsedTime } from './helpers';\nimport type { ClaudeSettings } from './types';\n\nconst CLAUDE_SETTINGS_FILE = '.claude/settings.json';\n\nasync function checkVPN(spinnies: Spinnies, baseUrl?: string): Promise<boolean> {\n if (baseUrl) {\n spinnies.add('vpnCheck', { text: 'Checking VPN connection...' });\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 spinnies.succeed('vpnCheck', { text: 'Connected to VPN' });\n break;\n }\n\n // TODO: Could update this to count down from 3s and fail after a certain number of attempts\n spinnies.update('vpnCheck', { 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): 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 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 allowedTools: ['Grep', 'Read'],\n settingSources: ['local', 'project', 'user'],\n };\n}\n\n/** Initiate a new Claude session/conversation and return reusable options */\nexport async function initiateClaudeSessionOptions(spinnies: Spinnies): Promise<Options> {\n const options = getQueryOptions(undefined);\n await checkVPN(spinnies, options.env?.ANTHROPIC_BASE_URL);\n\n spinnies.add('claudeSession', {\n text: 'Starting and verifying Claude instance - your browser may open for Okta authentication if required.',\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 spinnies.succeed('claudeSession', { text: 'Successfully initialised Claude instance' });\n spinnies.add('placeholder', { text: ' ' });\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 spinnies.fail('claudeSession', {\n text: `Claude encountered an error when initialising: ${message.errors.join('\\n')}`,\n });\n spinnies.stopAll('fail');\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 directory: string,\n filePath: string,\n options: Options,\n codemodOptions: CodemodOptions,\n spinnies: Spinnies,\n isDebug = false,\n) {\n const startTime = Date.now();\n const debugSpinnies = new Spinnies();\n const result = query({\n options,\n prompt: filePath,\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 === 'Read') {\n spinnies.remove('placeholder');\n spinnies.update(`file-${filePath}`, {\n text: `${formatPathOutput(directory, filePath, true)} - Reading...`,\n });\n } else if (msg.name === 'Edit') {\n spinnies.update(`file-${filePath}`, {\n text: `${formatPathOutput(directory, filePath, true)} - Migrating...`,\n });\n }\n\n break;\n case 'text':\n if (isDebug) {\n logStaticMessage(\n debugSpinnies,\n `${CONSOLE_ICONS.claude} ${formatClaudeResponseContent(msg.text)}`,\n );\n }\n break;\n default:\n }\n }\n break;\n case 'result':\n if (message.subtype === 'success') {\n spinnies.succeed(`file-${filePath}`, {\n text: `${formatPathOutput(directory, filePath, true)} - Migrated in: ${generateElapsedTime(startTime)}`,\n });\n } else {\n // Silence non-error errors (false positives)\n logStaticMessage(debugSpinnies, `${CONSOLE_ICONS.error} Claude encountered an error:`);\n console.log(message);\n spinnies.stopAll('fail');\n debugSpinnies.stopAll('fail');\n }\n break;\n default:\n }\n }\n}\n","import { execSync } from 'child_process';\nimport { readFileSync } from 'fs';\nimport Spinnies from 'spinnies';\n\nimport type { CodemodOptions } from '../../controller/types';\nimport { logStaticMessage } from '../../helpers/spinnerLogs';\nimport { initiateClaudeSessionOptions, queryClaude } from './claude';\nimport { DEPRECATED_COMPONENT_NAMES } from './constants';\nimport { formatPathOutput, generateDiff, generateElapsedTime } from './helpers';\n\nconst transformer = async (\n targetPaths: string[],\n codemodOptions: CodemodOptions,\n isDebug = false,\n) => {\n const startTime = Date.now();\n const spinnies = new Spinnies();\n const queryOptions = await initiateClaudeSessionOptions(spinnies);\n\n spinnies.remove('placeholder');\n spinnies.add('analysing', {\n text: 'Analysing targetted paths - this may take a while...',\n });\n\n for (const directory of targetPaths) {\n spinnies.add(`directory-${directory}`, {\n indent: 2,\n text: `${formatPathOutput(directory)} - Processing directory`,\n });\n\n // First, find all .tsx files in the directory\n const allTsxFiles = execSync(`find \"${directory}\" -name \"*.tsx\" -type f`, {\n encoding: 'utf-8',\n })\n .trim()\n .split('\\n')\n .filter(Boolean);\n\n const grepPattern = new RegExp(\n `import\\\\s*\\\\{[\\\\s\\\\S]*?(${DEPRECATED_COMPONENT_NAMES.join('|')})[\\\\s\\\\S]*?\\\\}\\\\s*from\\\\s*['\"]@transferwise/components['\"]`,\n );\n\n // Filter files that match the pattern by reading and testing each file\n const matchingFilePaths = allTsxFiles.filter((filePath) => {\n try {\n const content = readFileSync(filePath, 'utf-8');\n return grepPattern.test(content);\n } catch {\n return false;\n }\n });\n\n spinnies.update(`directory-${directory}`, {\n text: `${formatPathOutput(directory)} - Found \\x1b[32m${matchingFilePaths.length}\\x1b[0m \\x1b[2m*.tsx\\x1b[0m file(s) needing migration`,\n });\n\n // Get file path and file content for each matching file, and query Claude for migration\n for (const filePath of matchingFilePaths) {\n spinnies.add(`file-${filePath}`, {\n indent: 4,\n text: `${formatPathOutput(directory, filePath, true)} - Parsing...`,\n });\n\n const originalFileContent = readFileSync(filePath, 'utf-8');\n\n spinnies.update(`file-${filePath}`, {\n indent: 4,\n text: `${formatPathOutput(directory, filePath, true)} - Processing...`,\n });\n\n await queryClaude(directory, filePath, queryOptions, codemodOptions, spinnies, isDebug);\n\n const newFileContent = readFileSync(filePath, 'utf-8');\n\n // Create diff from originalFileContent to newFileContent\n if (codemodOptions.isPrint) {\n logStaticMessage(spinnies, `Diff for ${formatPathOutput(directory, filePath, true)}:`);\n logStaticMessage(spinnies, generateDiff(originalFileContent, newFileContent));\n }\n\n spinnies.remove('placeholder');\n }\n\n if (matchingFilePaths.length === 0) {\n spinnies.succeed(`directory-${directory}`, {\n text: `${formatPathOutput(directory)} - No files need migration`,\n });\n } else {\n spinnies.succeed(`directory-${directory}`, {\n indent: 2,\n text: `${formatPathOutput(directory)} - Migrated \\x1b[32m${matchingFilePaths.length}\\x1b[0m file(s) successfully`,\n });\n }\n }\n\n spinnies.succeed('analysing', {\n text: 'Successfully analysed all targetted paths',\n });\n spinnies.stopAll('succeed');\n spinnies.add('done', {\n text: `Finished migrating - elapsed time: \\x1b[1m${generateElapsedTime(startTime)}\\x1b[0m`,\n status: 'succeed',\n });\n};\n\nexport default transformer;\n"],"mappings":"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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Object.defineProperty(exports, '__esModule', { value: true });
|
|
2
2
|
require('../../constants-CcE2TmzN.js');
|
|
3
|
-
const require_helpers = require('../../helpers-
|
|
3
|
+
const require_helpers = require('../../helpers-IFtIGywc.js');
|
|
4
4
|
|
|
5
5
|
//#region src/helpers/addImport.ts
|
|
6
6
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wise/wds-codemods",
|
|
3
|
-
"version": "1.0.0-experimental-
|
|
3
|
+
"version": "1.0.0-experimental-be47db3",
|
|
4
4
|
"license": "UNLICENSED",
|
|
5
5
|
"author": "Wise Payments Ltd.",
|
|
6
6
|
"repository": {
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"test:watch": "jest --watch"
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"@anthropic-ai/claude-agent-sdk": "^0.1.
|
|
37
|
+
"@anthropic-ai/claude-agent-sdk": "^0.1.56",
|
|
38
38
|
"@inquirer/prompts": "^7.8.6",
|
|
39
39
|
"@types/spinnies": "^0.5.3",
|
|
40
40
|
"diff": "^8.0.2",
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
"spinnies": "^0.5.1"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
|
-
"@anthropic-ai/sdk": "^0.
|
|
45
|
+
"@anthropic-ai/sdk": "^0.71.0",
|
|
46
46
|
"@babel/core": "^7.28.4",
|
|
47
47
|
"@babel/plugin-syntax-import-meta": "^7.10.4",
|
|
48
48
|
"@babel/preset-env": "^7.28.3",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"transformer-G1wsjfTA.js","names":["https","spinnies","Spinnies","CONSOLE_ICONS","spinnies","Spinnies"],"sources":["../src/helpers/spinnerLogs.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":["import type Spinnies from 'spinnies';\n\ninterface LogConsoleSpinnerSettings {\n spinnies: Spinnies;\n spinnerId: string;\n options: Partial<Spinnies.SpinnerOptions>;\n}\n\nexport function logToConsole({ spinnies, spinnerId, options }: LogConsoleSpinnerSettings): void {\n spinnies.add(spinnerId, options);\n}\n\nexport function logStaticMessage(spinnies: Spinnies, message: string): void {\n // Create a unique spinner ID for the static message using timestamp, first part of the message and a random number\n const uuid = `static-message-${Date.now()}-${message.slice(0, 10).replace(/\\s+/gu, '-')}-${Math.floor(Math.random() * 10000)}`;\n logToConsole({ spinnies, spinnerId: uuid, options: { text: message, status: 'non-spinnable' } });\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 = `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 exact grep pattern when identifying files to migrate - only search for .tsx files:\n\\`import\\\\s*\\\\{[\\\\s\\\\S]*?(${DEPRECATED_COMPONENT_NAMES.join('|')})[\\\\s\\\\S]*?\\\\}\\\\s*from\\\\s*['\"]@transferwise/components['\"]\\`\n\nRules:\n1. Identify appropriate files in the directory using only the grep tool - use only the provided grep pattern only to find appropriate .tsx files.\n - Do not modify the grep pattern, always use it as provided\n - If no files are found matching the original grep pattern, do not ever attempt other patterns\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 { resolve } from 'path';\nimport Spinnies from 'spinnies';\n\nimport { CONSOLE_ICONS } from '../../constants';\nimport type { CodemodOptions } from '../../controller/types';\nimport { logStaticMessage } from '../../helpers/spinnerLogs';\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(spinnies: Spinnies, baseUrl?: string): Promise<boolean> {\n if (baseUrl) {\n spinnies.add('vpnCheck', { text: 'Checking VPN connection...' });\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 spinnies.succeed('vpnCheck', { text: 'Connected to VPN' });\n break;\n }\n\n // TODO: Could update this to count down from 3s and fail after a certain number of attempts\n spinnies.update('vpnCheck', { 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, codemodOptions?: CodemodOptions): 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 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 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 spinnies: Spinnies,\n): Promise<Options> {\n const options = getQueryOptions(undefined, codemodOptions);\n await checkVPN(spinnies, options.env?.ANTHROPIC_BASE_URL);\n\n spinnies.add('claudeSession', {\n text: 'Starting and verifying Claude instance - your browser may open for Okta authentication if required.',\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 spinnies.succeed('claudeSession', { text: 'Successfully initialised Claude instance' });\n spinnies.add('placeholder', { text: ' ' });\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 spinnies.fail('claudeSession', {\n text: `Claude encountered an error when initialising: ${message.errors.join('\\n')}`,\n });\n spinnies.stopAll('fail');\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 spinnies: Spinnies,\n isDebug = false,\n) {\n const debugSpinnies = new Spinnies();\n const result = query({\n options,\n prompt: path,\n });\n let currentMigrationPath = '';\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 spinnies.remove('placeholder');\n spinnies.update(`directory-${path}`, {\n text: `${formatPathOutput(path)} - Searching for files with deprecated imports...`,\n });\n spinnies.add('placeholder', { text: ' ', indent: 4 });\n } else if (msg.name === 'Read') {\n spinnies.remove('placeholder');\n spinnies.add(`file-${(msg as ClaudeResponseToolUse).input.file_path}`, {\n indent: 4,\n text: `${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 (msg.name === 'Edit') {\n const editedFilePath = (msg as ClaudeResponseToolUse).input.file_path;\n // Generate and print out a pretty diff of changes made by Claude\n if (codemodOptions.isPrint || codemodOptions.isDry) {\n // Print new file header if migrating a new file\n if (currentMigrationPath !== editedFilePath) {\n currentMigrationPath = editedFilePath;\n\n logStaticMessage(\n spinnies,\n `${CONSOLE_ICONS.info} ${codemodOptions.isDry ? 'Proposed changes' : 'Changes'} for ${formatPathOutput(path, editedFilePath)}`,\n );\n }\n\n logStaticMessage(\n spinnies,\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 spinnies.succeed(`file-${editedFilePath}`, {\n text: `${formatPathOutput(path, editedFilePath, true)} - Migrated`,\n });\n }\n spinnies.add('placeholder', { text: ' ', indent: 4 });\n }\n\n break;\n case 'text':\n if (isDebug) {\n logStaticMessage(\n debugSpinnies,\n `${CONSOLE_ICONS.claude} ${formatClaudeResponseContent(msg.text)}`,\n );\n }\n break;\n default:\n }\n }\n break;\n case 'result':\n if (message.subtype === 'success') {\n if (message.structured_output) {\n const output = message.structured_output as {\n migrationStatus: 'success' | 'fail' | 'no-files';\n filesChanged: number;\n };\n\n spinnies.remove('placeholder');\n\n if (output.migrationStatus === 'no-files' || output.filesChanged === 0) {\n spinnies.succeed(`directory-${path}`, {\n text: `${formatPathOutput(path)} - No files need migration`,\n });\n } else if (output.migrationStatus === 'success') {\n spinnies.succeed(`directory-${path}`, {\n text: `${formatPathOutput(path)} - Migrated \\x1b[32m${output.filesChanged}\\x1b[0m file(s) successfully`,\n });\n } else {\n spinnies.fail(`directory-${path}`, {\n text: `${formatPathOutput(path)} - Migration failed due to an error`,\n status: 'fail',\n });\n }\n }\n } else if (message.is_error) {\n // Silence non-error errors (false positives)\n logStaticMessage(debugSpinnies, `${CONSOLE_ICONS.error} Claude encountered an error:`);\n console.log(message);\n spinnies.stopAll('fail');\n debugSpinnies.stopAll('fail');\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 spinnies.update(`directory-${path}`, {\n text: `${formatPathOutput(path)} - Found \\x1b[32m${foundFileCount}\\x1b[0m \\x1b[2m*.tsx\\x1b[0m file(s) needing migration`,\n });\n }\n }\n }\n break;\n default:\n }\n }\n}\n","import Spinnies from 'spinnies';\n\nimport type { CodemodOptions } from '../../controller/types';\nimport { initiateClaudeSessionOptions, queryClaude } from './claude';\nimport { formatPathOutput, generateElapsedTime } from './helpers';\n\nconst transformer = async (\n targetPaths: string[],\n codemodOptions: CodemodOptions,\n isDebug = false,\n) => {\n const startTime = Date.now();\n const spinnies = new Spinnies();\n const queryOptions = await initiateClaudeSessionOptions(codemodOptions, spinnies);\n\n spinnies.remove('placeholder');\n spinnies.add('analysing', {\n text: 'Analysing targetted paths - this may take a while...',\n });\n\n for (const directory of targetPaths) {\n spinnies.add(`directory-${directory}`, {\n indent: 2,\n text: `${formatPathOutput(directory)} - Processing directory`,\n });\n\n await queryClaude(directory, queryOptions, codemodOptions, spinnies, isDebug);\n }\n\n spinnies.succeed('analysing', {\n text: 'Successfully analysed all targetted paths',\n });\n spinnies.stopAll('succeed');\n spinnies.add('done', {\n text: `Finished migrating - elapsed time: \\x1b[1m${generateElapsedTime(startTime)}\\x1b[0m`,\n status: 'succeed',\n });\n};\n\nexport default transformer;\n"],"mappings":"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"}
|