@wise/wds-codemods 1.0.0-experimental-a21270b → 1.0.0-experimental-cdf94ec

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,7 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  const require_constants = require('./constants-CcE2TmzN.js');
3
- const require_transformer = require('./transformer-Dn1DlLCn.js');
4
- const require_helpers = require('./helpers-DJ-DdOjE.js');
3
+ const require_helpers = require('./helpers-Z1jZGLmt.js');
5
4
  let node_child_process = require("node:child_process");
6
5
  let node_fs_promises = require("node:fs/promises");
7
6
  node_fs_promises = require_constants.__toESM(node_fs_promises);
@@ -12,6 +11,36 @@ let _inquirer_prompts = require("@inquirer/prompts");
12
11
  let spinnies = require("spinnies");
13
12
  spinnies = require_constants.__toESM(spinnies);
14
13
 
14
+ //#region src/controller/helpers/claudePrereqs.ts
15
+ function handleClaudePrereqs({ options, codemodPath, spinners }) {
16
+ spinners.add("prerequisite-check", { text: "Checking prerequisites..." });
17
+ const { allPassed, packageRootToTargets, failedPackageRoots } = require_helpers.assessPrerequisitesBatch(options.targetPaths, codemodPath, spinners);
18
+ if (!allPassed) {
19
+ spinners.add("prerequisite-partial-failure", {
20
+ text: "One or more packages failed prerequisite checks - these targets will be skipped:",
21
+ status: "fail"
22
+ });
23
+ for (const failedRoot of failedPackageRoots) {
24
+ const targets = packageRootToTargets.get(failedRoot) ?? [];
25
+ options.targetPaths = options.targetPaths.filter((targetPath) => !targets.includes(targetPath));
26
+ spinners.add(`prerequisite-fail-${failedRoot}`, {
27
+ text: `- \x1b[2m\x1b[31m${failedRoot}\x1b[0m${targets.length > 0 && targets[0] !== failedRoot ? ` (targets: ${targets.join(", ")})` : ""}\x1b[0m`,
28
+ indent: 2,
29
+ status: "non-spinnable"
30
+ });
31
+ }
32
+ }
33
+ if (!options.targetPaths.length) {
34
+ spinners.add("no-valid-targets", {
35
+ text: `${require_constants.CONSOLE_ICONS.error} No valid target paths remaining after prerequisite checks - exiting.`,
36
+ status: "fail"
37
+ });
38
+ spinners.stopAll("fail");
39
+ spinners.checkIfActiveSpinners();
40
+ }
41
+ }
42
+
43
+ //#endregion
15
44
  //#region src/controller/index.ts
16
45
  let isDebug = false;
17
46
  const currentFilePath = (0, node_url.fileURLToPath)(require("url").pathToFileURL(__filename).href);
@@ -67,11 +96,11 @@ async function runCodemod(transformsDir) {
67
96
  const codemodConfig = require_helpers.getCodemodConfig(codemodPath);
68
97
  if (isDebug) console.debug(`${require_constants.CONSOLE_ICONS.info} Resolved codemod path: ${codemodPath}`);
69
98
  if (codemodConfig?.type === "claude") require_helpers.validateClaudeConfig();
70
- const spinnies$1 = new spinnies.default();
71
- spinnies$1.add("loading-codemod", { text: `Loading codemod (${transformFile})...` });
99
+ const spinners = new spinnies.default();
100
+ spinners.add("loading-codemod", { text: `Loading codemod (${transformFile})...` });
72
101
  const packages = await packagesPromise;
73
102
  const reportPath = node_path.default.resolve(root, "codemod-report.txt");
74
- spinnies$1.succeed("loading-codemod", { text: `Successfully loaded codemod: \x1b[2m${transformFile}\x1b[0m` });
103
+ spinners.succeed("loading-codemod", { text: `Successfully loaded codemod: \x1b[2m${transformFile}\x1b[0m` });
75
104
  if (codemodConfig?.type === "jscodeshift") await resetReportFile(reportPath);
76
105
  const promptAnswers = await require_helpers.runTransformPrompts(codemodPath);
77
106
  const options = await require_helpers.getOptions_default({
@@ -82,36 +111,16 @@ async function runCodemod(transformsDir) {
82
111
  transformerType: codemodConfig?.type
83
112
  });
84
113
  if (codemodConfig?.type === "claude") {
85
- spinnies$1.add("prerequisite-check", { text: "Checking prerequisites..." });
86
- const { allPassed, packageRootToTargets, failedPackageRoots } = require_helpers.assessPrerequisitesBatch(options.targetPaths, codemodPath, spinnies$1);
87
- if (!allPassed) {
88
- spinnies$1.add("prerequisite-partial-failure", {
89
- text: "One or more packages failed prerequisite checks - these targets will be skipped:",
90
- status: "fail"
91
- });
92
- for (const failedRoot of failedPackageRoots) {
93
- const targets = packageRootToTargets.get(failedRoot) ?? [];
94
- options.targetPaths = options.targetPaths.filter((targetPath) => !targets.includes(targetPath));
95
- spinnies$1.add(`prerequisite-fail-${failedRoot}`, {
96
- text: `- \x1b[2m\x1b[31m${failedRoot}\x1b[0m${targets.length > 0 && targets[0] !== failedRoot ? ` (targets: ${targets.join(", ")})` : ""}\x1b[0m`,
97
- indent: 2,
98
- status: "non-spinnable"
99
- });
100
- }
101
- }
102
- if (!options.targetPaths.length) {
103
- spinnies$1.add("no-valid-targets", {
104
- text: `${require_constants.CONSOLE_ICONS.error} No valid target paths remaining after prerequisite checks - exiting.`,
105
- status: "fail"
106
- });
107
- spinnies$1.stopAll("fail");
108
- spinnies$1.checkIfActiveSpinners();
109
- return;
110
- }
111
- await require_transformer.transformer_default(options.targetPaths, isDebug);
114
+ handleClaudePrereqs({
115
+ options,
116
+ codemodPath,
117
+ spinners
118
+ });
119
+ const transformer = (await import(codemodPath)).default;
120
+ await transformer(options.targetPaths, isDebug);
112
121
  } else {
113
- spinnies$1.stopAll("succeed");
114
- spinnies$1.checkIfActiveSpinners();
122
+ spinners.stopAll("succeed");
123
+ spinners.checkIfActiveSpinners();
115
124
  await Promise.all(options.targetPaths.map(async (targetPath) => {
116
125
  console.info(`${require_constants.CONSOLE_ICONS.focus} \x1b[1mProcessing:\x1b[0m \x1b[32m${targetPath}\x1b[0m`);
117
126
  if (!require_helpers.assessPrerequisites(targetPath, codemodPath)) return;
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["path","fs","CONSOLE_ICONS","logToInquirer","findProjectRoot","findPackages","loadTransformModules","transformFile: string","getCodemodConfig","spinnies","Spinnies","runTransformPrompts","getOptions","assessPrerequisitesBatch","transformer","assessPrerequisites","error: unknown"],"sources":["../src/controller/index.ts","../src/index.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport { execSync } from 'node:child_process';\nimport fs from 'node:fs/promises';\nimport path from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nimport { select as list } from '@inquirer/prompts';\nimport Spinnies from 'spinnies';\n\nimport { CONSOLE_ICONS } from '../constants';\nimport transformer from '../transforms/list-item/transformer';\nimport {\n assessPrerequisites,\n assessPrerequisitesBatch,\n findPackages,\n findProjectRoot,\n getCodemodConfig,\n getOptions,\n loadTransformModules,\n logToInquirer,\n runTransformPrompts,\n validateClaudeConfig,\n} from './helpers';\n\nlet isDebug = false;\nconst currentFilePath = fileURLToPath(import.meta.url);\nconst currentDirPath = path.dirname(currentFilePath);\n\nconst resetReportFile = async (reportPath: string) => {\n try {\n await fs.access(reportPath);\n await fs.rm(reportPath);\n console.debug(\n `${CONSOLE_ICONS.info} Removed existing report file${isDebug ? `: ${reportPath}` : '.'}`,\n );\n } catch {\n console.debug(\n `${CONSOLE_ICONS.info} No existing report file to remove${isDebug ? `: ${reportPath}` : '.'}`,\n );\n }\n};\n\nconst summariseReportFile = async (reportPath: string) => {\n try {\n const reportContent = await fs.readFile(reportPath, 'utf8');\n const lines = reportContent.split('\\n').filter(Boolean);\n if (lines.length) {\n console.debug(\n `\\n${CONSOLE_ICONS.warning} ${lines.length} manual review${lines.length > 1 ? 's are' : ' is'} required. See ${reportPath} for details.`,\n );\n } else {\n console.debug(\n `${CONSOLE_ICONS.info} Report file exists but is empty${isDebug ? `: ${reportPath}` : '.'}`,\n );\n }\n } catch {\n console.debug(`${CONSOLE_ICONS.info} No report file generated - no manual reviews needed`);\n }\n};\n\nconst log = (label: string, value?: string): void => {\n if (typeof logToInquirer === 'function') {\n logToInquirer(label, value || '');\n } else {\n console.info(label, value || '');\n }\n};\n\nasync function runCodemod(transformsDir?: string) {\n const args = process.argv.slice(2);\n const candidate = args[0];\n isDebug = args.includes('--debug');\n\n try {\n const root = findProjectRoot();\n const packagesPromise = findPackages();\n const resolvedTransformsDir =\n transformsDir ?? path.resolve(currentDirPath, '../dist/transforms');\n\n if (isDebug) {\n console.debug(\n `${CONSOLE_ICONS.info} Resolved transforms directory: ${resolvedTransformsDir}`,\n );\n }\n\n const { transformFiles: resolvedTransformNames } =\n await loadTransformModules(resolvedTransformsDir);\n if (resolvedTransformNames.length === 0) {\n throw new Error(\n `${CONSOLE_ICONS.error} No transform scripts found${isDebug ? ` in: ${resolvedTransformsDir}` : '.'}`,\n );\n }\n\n let transformFile: string;\n\n if (candidate && resolvedTransformNames.includes(candidate)) {\n log('Select codemod to run:', candidate);\n transformFile = candidate;\n } else {\n transformFile = await list({\n message: 'Select codemod to run:',\n choices: resolvedTransformNames.map((name: string) => ({ name, value: name })),\n });\n log('Selected codemod:', transformFile);\n }\n\n const codemodPath = path.resolve(resolvedTransformsDir, transformFile, 'transformer.js');\n const codemodConfig = getCodemodConfig(codemodPath);\n if (isDebug) {\n console.debug(`${CONSOLE_ICONS.info} Resolved codemod path: ${codemodPath}`);\n }\n\n if (codemodConfig?.type === 'claude') {\n validateClaudeConfig();\n }\n\n const spinnies = new Spinnies();\n spinnies.add('loading-codemod', { text: `Loading codemod (${transformFile})...` });\n const packages = await packagesPromise;\n const reportPath = path.resolve(root, 'codemod-report.txt');\n spinnies.succeed('loading-codemod', {\n text: `Successfully loaded codemod: \\x1b[2m${transformFile}\\x1b[0m`,\n });\n\n if (codemodConfig?.type === 'jscodeshift') {\n await resetReportFile(reportPath);\n }\n\n const promptAnswers = await runTransformPrompts(codemodPath);\n const options = await getOptions({\n packages,\n root,\n transformFiles: resolvedTransformNames,\n preselectedTransformFile: transformFile,\n transformerType: codemodConfig?.type,\n });\n\n // Handle ListItem transform differently as it uses Claude, not jscodeshift\n if (codemodConfig?.type === 'claude') {\n spinnies.add('prerequisite-check', { text: 'Checking prerequisites...' });\n // Fail-fast: check prerequisites for all target paths up-front\n const { allPassed, packageRootToTargets, failedPackageRoots } = assessPrerequisitesBatch(\n options.targetPaths,\n codemodPath,\n spinnies,\n );\n if (!allPassed) {\n spinnies.add('prerequisite-partial-failure', {\n text: 'One or more packages failed prerequisite checks - these targets will be skipped:',\n status: 'fail',\n });\n\n for (const failedRoot of failedPackageRoots) {\n const targets = packageRootToTargets.get(failedRoot) ?? [];\n // Filter out targets that belong to failed package roots\n options.targetPaths = options.targetPaths.filter(\n (targetPath) => !targets.includes(targetPath),\n );\n spinnies.add(`prerequisite-fail-${failedRoot}`, {\n text: `- \\x1b[2m\\x1b[31m${failedRoot}\\x1b[0m${targets.length > 0 && targets[0] !== failedRoot ? ` (targets: ${targets.join(', ')})` : ''}\\x1b[0m`,\n indent: 2,\n status: 'non-spinnable',\n });\n }\n }\n\n if (!options.targetPaths.length) {\n spinnies.add('no-valid-targets', {\n text: `${CONSOLE_ICONS.error} No valid target paths remaining after prerequisite checks - exiting.`,\n status: 'fail',\n });\n spinnies.stopAll('fail');\n spinnies.checkIfActiveSpinners();\n return;\n }\n\n await transformer(options.targetPaths, isDebug);\n } else {\n // Button codemod doesn't use spinnies\n spinnies.stopAll('succeed');\n spinnies.checkIfActiveSpinners();\n\n await Promise.all(\n options.targetPaths.map(async (targetPath) => {\n console.info(\n `${CONSOLE_ICONS.focus} \\x1b[1mProcessing:\\x1b[0m \\x1b[32m${targetPath}\\x1b[0m`,\n );\n\n // Check prerequisites for this target before running\n const ok = assessPrerequisites(targetPath, codemodPath);\n if (!ok) {\n return;\n }\n\n const answerArgs = Object.entries(promptAnswers).map(\n ([promptName, answerValue]) => `--${promptName}=${String(answerValue)}`,\n );\n\n const argsList = [\n '-t',\n codemodPath,\n targetPath,\n options.isDry ? '--dry' : '',\n options.isPrint ? '--print' : '',\n options.ignorePatterns\n ? options.ignorePatterns\n .split(',')\n .map((pattern) => `--ignore-pattern=${pattern.trim()}`)\n .join(' ')\n : '',\n options.useGitIgnore ? '--gitignore' : '',\n ...answerArgs,\n ].filter(Boolean);\n const command = `npx jscodeshift ${argsList.join(' ')}`;\n\n if (isDebug) {\n console.debug(`${CONSOLE_ICONS.info} Running: ${command}`);\n }\n\n return execSync(command, { stdio: 'inherit' });\n }),\n );\n }\n\n if (codemodConfig?.type === 'jscodeshift') {\n await summariseReportFile(reportPath);\n }\n } catch (error: unknown) {\n if (error instanceof Error) {\n console.error(`${CONSOLE_ICONS.error} Error running ${candidate} codemod:`, error.message);\n } else {\n console.error(`${CONSOLE_ICONS.error} Error running ${candidate} codemod:`, String(error));\n }\n if (process.env.NODE_ENV !== 'test') {\n process.exit(1);\n }\n }\n}\n\nexport { runCodemod };\n","#!/usr/bin/env node\nimport { runCodemod } from './controller';\n\nvoid runCodemod();\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":["assessPrerequisitesBatch","CONSOLE_ICONS","path","fs","CONSOLE_ICONS","logToInquirer","findProjectRoot","findPackages","loadTransformModules","transformFile: string","getCodemodConfig","Spinnies","runTransformPrompts","getOptions","assessPrerequisites","error: unknown"],"sources":["../src/controller/helpers/claudePrereqs.ts","../src/controller/index.ts","../src/index.ts"],"sourcesContent":["import type Spinnies from 'spinnies';\n\nimport { CONSOLE_ICONS } from '../../constants';\nimport type { CodemodOptions } from '../types';\nimport { assessPrerequisitesBatch } from './dependencyChecks';\n\ninterface ClaudePrereqHandlerOptions {\n options: CodemodOptions;\n spinners: Spinnies;\n codemodPath: string;\n}\n\nexport function handleClaudePrereqs({\n options,\n codemodPath,\n spinners,\n}: ClaudePrereqHandlerOptions) {\n spinners.add('prerequisite-check', { text: 'Checking prerequisites...' });\n // Fail-fast: check prerequisites for all target paths up-front\n const { allPassed, packageRootToTargets, failedPackageRoots } = assessPrerequisitesBatch(\n options.targetPaths,\n codemodPath,\n spinners,\n );\n if (!allPassed) {\n spinners.add('prerequisite-partial-failure', {\n text: 'One or more packages failed prerequisite checks - these targets will be skipped:',\n status: 'fail',\n });\n\n for (const failedRoot of failedPackageRoots) {\n const targets = packageRootToTargets.get(failedRoot) ?? [];\n // Filter out targets that belong to failed package roots\n // eslint-disable-next-line no-param-reassign\n options.targetPaths = options.targetPaths.filter(\n (targetPath) => !targets.includes(targetPath),\n );\n spinners.add(`prerequisite-fail-${failedRoot}`, {\n text: `- \\x1b[2m\\x1b[31m${failedRoot}\\x1b[0m${targets.length > 0 && targets[0] !== failedRoot ? ` (targets: ${targets.join(', ')})` : ''}\\x1b[0m`,\n indent: 2,\n status: 'non-spinnable',\n });\n }\n }\n\n if (!options.targetPaths.length) {\n spinners.add('no-valid-targets', {\n text: `${CONSOLE_ICONS.error} No valid target paths remaining after prerequisite checks - exiting.`,\n status: 'fail',\n });\n spinners.stopAll('fail');\n spinners.checkIfActiveSpinners();\n }\n}\n","#!/usr/bin/env node\n\nimport { execSync } from 'node:child_process';\nimport fs from 'node:fs/promises';\nimport path from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nimport { select as list } from '@inquirer/prompts';\nimport Spinnies from 'spinnies';\n\nimport { CONSOLE_ICONS } from '../constants';\nimport {\n assessPrerequisites,\n findPackages,\n findProjectRoot,\n getCodemodConfig,\n getOptions,\n loadTransformModules,\n logToInquirer,\n runTransformPrompts,\n validateClaudeConfig,\n} from './helpers';\nimport { handleClaudePrereqs } from './helpers/claudePrereqs';\n\nlet isDebug = false;\nconst currentFilePath = fileURLToPath(import.meta.url);\nconst currentDirPath = path.dirname(currentFilePath);\n\nconst resetReportFile = async (reportPath: string) => {\n try {\n await fs.access(reportPath);\n await fs.rm(reportPath);\n console.debug(\n `${CONSOLE_ICONS.info} Removed existing report file${isDebug ? `: ${reportPath}` : '.'}`,\n );\n } catch {\n console.debug(\n `${CONSOLE_ICONS.info} No existing report file to remove${isDebug ? `: ${reportPath}` : '.'}`,\n );\n }\n};\n\nconst summariseReportFile = async (reportPath: string) => {\n try {\n const reportContent = await fs.readFile(reportPath, 'utf8');\n const lines = reportContent.split('\\n').filter(Boolean);\n if (lines.length) {\n console.debug(\n `\\n${CONSOLE_ICONS.warning} ${lines.length} manual review${lines.length > 1 ? 's are' : ' is'} required. See ${reportPath} for details.`,\n );\n } else {\n console.debug(\n `${CONSOLE_ICONS.info} Report file exists but is empty${isDebug ? `: ${reportPath}` : '.'}`,\n );\n }\n } catch {\n console.debug(`${CONSOLE_ICONS.info} No report file generated - no manual reviews needed`);\n }\n};\n\nconst log = (label: string, value?: string): void => {\n if (typeof logToInquirer === 'function') {\n logToInquirer(label, value || '');\n } else {\n console.info(label, value || '');\n }\n};\n\nasync function runCodemod(transformsDir?: string) {\n const args = process.argv.slice(2);\n const candidate = args[0];\n isDebug = args.includes('--debug');\n\n try {\n const root = findProjectRoot();\n const packagesPromise = findPackages();\n const resolvedTransformsDir =\n transformsDir ?? path.resolve(currentDirPath, '../dist/transforms');\n\n if (isDebug) {\n console.debug(\n `${CONSOLE_ICONS.info} Resolved transforms directory: ${resolvedTransformsDir}`,\n );\n }\n\n const { transformFiles: resolvedTransformNames } =\n await loadTransformModules(resolvedTransformsDir);\n if (resolvedTransformNames.length === 0) {\n throw new Error(\n `${CONSOLE_ICONS.error} No transform scripts found${isDebug ? ` in: ${resolvedTransformsDir}` : '.'}`,\n );\n }\n\n let transformFile: string;\n\n if (candidate && resolvedTransformNames.includes(candidate)) {\n log('Select codemod to run:', candidate);\n transformFile = candidate;\n } else {\n transformFile = await list({\n message: 'Select codemod to run:',\n choices: resolvedTransformNames.map((name: string) => ({ name, value: name })),\n });\n log('Selected codemod:', transformFile);\n }\n\n const codemodPath = path.resolve(resolvedTransformsDir, transformFile, 'transformer.js');\n const codemodConfig = getCodemodConfig(codemodPath);\n if (isDebug) {\n console.debug(`${CONSOLE_ICONS.info} Resolved codemod path: ${codemodPath}`);\n }\n\n if (codemodConfig?.type === 'claude') {\n validateClaudeConfig();\n }\n\n const spinners = new Spinnies();\n spinners.add('loading-codemod', { text: `Loading codemod (${transformFile})...` });\n const packages = await packagesPromise;\n const reportPath = path.resolve(root, 'codemod-report.txt');\n spinners.succeed('loading-codemod', {\n text: `Successfully loaded codemod: \\x1b[2m${transformFile}\\x1b[0m`,\n });\n\n if (codemodConfig?.type === 'jscodeshift') {\n await resetReportFile(reportPath);\n }\n\n const promptAnswers = await runTransformPrompts(codemodPath);\n const options = await getOptions({\n packages,\n root,\n transformFiles: resolvedTransformNames,\n preselectedTransformFile: transformFile,\n transformerType: codemodConfig?.type,\n });\n\n // Handle Claude transforms differently, as they work on multiple targets at once\n if (codemodConfig?.type === 'claude') {\n handleClaudePrereqs({ options, codemodPath, spinners });\n\n // Dynamically import the transformer module\n const transformerModule = (await import(codemodPath)) as {\n default: (targetPaths: string[], isDebug?: boolean) => Promise<void>;\n };\n const transformer = transformerModule.default;\n await transformer(options.targetPaths, isDebug);\n } else {\n // Button codemod doesn't use spinnies\n spinners.stopAll('succeed');\n spinners.checkIfActiveSpinners();\n\n await Promise.all(\n options.targetPaths.map(async (targetPath) => {\n console.info(\n `${CONSOLE_ICONS.focus} \\x1b[1mProcessing:\\x1b[0m \\x1b[32m${targetPath}\\x1b[0m`,\n );\n\n // Check prerequisites for this target before running\n const ok = assessPrerequisites(targetPath, codemodPath);\n if (!ok) {\n return;\n }\n\n const answerArgs = Object.entries(promptAnswers).map(\n ([promptName, answerValue]) => `--${promptName}=${String(answerValue)}`,\n );\n\n const argsList = [\n '-t',\n codemodPath,\n targetPath,\n options.isDry ? '--dry' : '',\n options.isPrint ? '--print' : '',\n options.ignorePatterns\n ? options.ignorePatterns\n .split(',')\n .map((pattern) => `--ignore-pattern=${pattern.trim()}`)\n .join(' ')\n : '',\n options.useGitIgnore ? '--gitignore' : '',\n ...answerArgs,\n ].filter(Boolean);\n const command = `npx jscodeshift ${argsList.join(' ')}`;\n\n if (isDebug) {\n console.debug(`${CONSOLE_ICONS.info} Running: ${command}`);\n }\n\n return execSync(command, { stdio: 'inherit' });\n }),\n );\n }\n\n if (codemodConfig?.type === 'jscodeshift') {\n await summariseReportFile(reportPath);\n }\n } catch (error: unknown) {\n if (error instanceof Error) {\n console.error(`${CONSOLE_ICONS.error} Error running ${candidate} codemod:`, error.message);\n } else {\n console.error(`${CONSOLE_ICONS.error} Error running ${candidate} codemod:`, String(error));\n }\n if (process.env.NODE_ENV !== 'test') {\n process.exit(1);\n }\n }\n}\n\nexport { runCodemod };\n","#!/usr/bin/env node\nimport { runCodemod } from './controller';\n\nvoid runCodemod();\n"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  Object.defineProperty(exports, '__esModule', { value: true });
2
- const require_helpers = require('../../helpers-DJ-DdOjE.js');
2
+ const require_helpers = require('../../helpers-Z1jZGLmt.js');
3
3
 
4
4
  //#region src/helpers/addImport.ts
5
5
  /**
@@ -1,3 +1,426 @@
1
- const require_transformer = require('../../transformer-Dn1DlLCn.js');
1
+ const require_constants = require('../../constants-CcE2TmzN.js');
2
+ let node_child_process = require("node:child_process");
3
+ let node_path = require("node:path");
4
+ let node_fs = require("node:fs");
5
+ let _listr2_manager = require("@listr2/manager");
6
+ let listr2 = require("listr2");
7
+ let node_https = require("node:https");
8
+ node_https = require_constants.__toESM(node_https);
9
+ let _anthropic_ai_claude_agent_sdk = require("@anthropic-ai/claude-agent-sdk");
2
10
 
3
- module.exports = require_transformer.transformer_default;
11
+ //#region src/transforms/list-item/constants.ts
12
+ const CLAUDE_SETTINGS_FILE = ".claude/settings.json";
13
+ const VPN_COUNTDOWN_TIMEOUT = 5;
14
+ const DIRECTORY_CONCURRENCY_LIMIT = 3;
15
+ const FILE_CONCURRENCY_LIMIT = 10;
16
+ const DEPRECATED_COMPONENT_NAMES = [
17
+ "ActionOption",
18
+ "NavigationOption",
19
+ "NavigationOptionList",
20
+ "Summary",
21
+ "SwitchOption",
22
+ "CheckboxOption",
23
+ "RadioOption"
24
+ ];
25
+ const GREP_PATTERN = new RegExp(`import\\s*\\{[\\s\\S]*?(${DEPRECATED_COMPONENT_NAMES.join("|")})[\\s\\S]*?\\}\\s*from\\s*['"]@transferwise/components['"]`, "u");
26
+ const MIGRATION_RULES = `Migration rules:
27
+ # Legacy Component → ListItem Migration Guide
28
+
29
+ ## Import Management
30
+ 1. Remove imports: \`{ ActionOption, NavigationOption, NavigationOptionList, Summary, SwitchOption, CheckboxOption, RadioOption }\` from \`'@transferwise/components'\`
31
+ 2. Add import: \`{ ListItem }\` from \`'@transferwise/components'\`
32
+ 3. For Summary with info: Add \`{ Modal }\` and/or \`{ Popover }\` from \`'@transferwise/components'\`
33
+ 4. For Summary with info: Add \`{ QuestionMarkCircle }\` from \`'@transferwise/icons'\`
34
+ 5. For Summary with info modal: Add \`{ useState }\` from \`'react'\`
35
+
36
+ ## Universal Rules
37
+ 1. \`title\` → \`title\` (direct)
38
+ 2. \`content\` or \`description\` → \`subtitle\`
39
+ 3. \`disabled\` stays on \`ListItem\` (not controls)
40
+ 4. \`id\` stays on \`ListItem\` (not controls)
41
+ 5. Remove deprecated props: \`as\`, \`complex\`, \`showMediaAtAllSizes\`, \`showMediaCircle\`, \`isContainerAligned\`
42
+ 6. Preserve string quotes exactly as they are (don't convert \`\` to \`'\` or \`"\`)
43
+ 7. When wrapping icons in \`ListItem.AvatarView\`: move \`size\` from icon to AvatarView (except Summary always uses 32)
44
+
45
+ ---
46
+
47
+ ## ActionOption → ListItem.Button
48
+
49
+ - \`action\` → Button children
50
+ - \`onClick\` → Button \`onClick\`
51
+ - Priority mapping:
52
+ - default or \`"primary"\` → \`"primary"\`
53
+ - \`"secondary"\` → \`"secondary-neutral"\`
54
+ - \`"secondary-send"\` → \`"secondary"\`
55
+ - \`"tertiary"\` → \`"tertiary"\`
56
+
57
+ \`\`\`tsx
58
+ <ActionOption title="Title" content="Text" action="Click" priority="secondary" onClick={fn} />
59
+
60
+ <ListItem title="Title" subtitle="Text" control={<ListItem.Button priority="secondary-neutral" onClick={fn}>Click</ListItem.Button>} />
61
+ \`\`\`
62
+
63
+ ---
64
+
65
+ ## CheckboxOption → ListItem.Checkbox
66
+
67
+ - \`name\` is moved to \`ListItem.Checkbox\`
68
+ - \`onChange\`: \`(checked: boolean) => void\` → \`(event: ChangeEvent<HTMLInputElement>) => void\`
69
+ - Update handler: \`(checked) => setX(checked)\` → \`(event) => setX(event.target.checked)\`
70
+
71
+ \`\`\`tsx
72
+ <CheckboxOption id="x" name="y" title="Title" content="Text" checked={v} onChange={(c) => set(c)} />
73
+
74
+ <ListItem id="x" title="Title" subtitle="Text" control={<ListItem.Checkbox name="y" checked={v} onChange={(e) => set(e.target.checked)} />} />
75
+ \`\`\`
76
+
77
+ ---
78
+
79
+ ## RadioOption → ListItem.Radio
80
+
81
+ - \`name\`, \`value\`, \`checked\`, \`onChange\` move to Radio
82
+
83
+ \`\`\`tsx
84
+ <RadioOption id="x" name="y" value="v" title="Title" content="Text" checked={v==='v'} onChange={set} />
85
+
86
+ <ListItem id="x" title="Title" subtitle="Text" control={<ListItem.Radio name="y" value="v" checked={v==='v'} onChange={set} />} />
87
+ \`\`\`
88
+
89
+ ---
90
+
91
+ ## SwitchOption → ListItem.Switch
92
+
93
+ - \`aria-label\` is moved to \`ListItem.Switch\`
94
+ - \`onChange\` → \`onClick\` with manual toggle: \`onChange={set}\` → \`onClick={() => set(!v)}\`
95
+
96
+ \`\`\`tsx
97
+ <SwitchOption id="x" title="Title" content="Text" checked={v} aria-label="Toggle" onChange={set} />
98
+
99
+ <ListItem id="x" title="Title" subtitle="Text" control={<ListItem.Switch checked={v} aria-label="Toggle" onClick={() => set(!v)} />} />
100
+ \`\`\`
101
+
102
+ ---
103
+
104
+ ## NavigationOption → ListItem.Navigation
105
+
106
+ - \`onClick\` or \`href\` move to Navigation
107
+
108
+ \`\`\`tsx
109
+ <NavigationOption title="Title" content="Text" onClick={fn} />
110
+
111
+ <ListItem title="Title" subtitle="Text" control={<ListItem.Navigation onClick={fn} />} />
112
+ \`\`\`
113
+
114
+ ---
115
+
116
+ ## NavigationOptionList → (remove wrapper)
117
+
118
+ - Convert \`NavigationOptionList\` wrapper to \`<List></List>\`
119
+ - Convert each child \`NavigationOption\` to \`ListItem\` with \`ListItem.Navigation\`
120
+
121
+ \`\`\`tsx
122
+ <NavigationOptionList>
123
+ <NavigationOption title="T1" content="C1" onClick={fn1} />
124
+ <NavigationOption title="T2" content="C2" href="/link" />
125
+ </NavigationOptionList>
126
+
127
+ <>
128
+ <ListItem title="T1" subtitle="C1" control={<ListItem.Navigation onClick={fn1} />} />
129
+ <ListItem title="T2" subtitle="C2" control={<ListItem.Navigation href="/link" />} />
130
+ </>
131
+ \`\`\`
132
+
133
+ ---
134
+
135
+ ## Option → ListItem
136
+
137
+ - Wrap \`media\` in \`ListItem.AvatarView\`
138
+ - Move \`size\` prop from icon to AvatarView (keeping same value)
139
+
140
+ \`\`\`tsx
141
+ <Option media={<Icon size={48} />} title="Title" />
142
+
143
+ <ListItem title="Title" media={<ListItem.AvatarView size={48}><Icon /></ListItem.AvatarView>} />
144
+ \`\`\`
145
+
146
+ ---
147
+
148
+ ## Summary → ListItem
149
+
150
+ ### Basic
151
+ - \`icon\` prop → wrap in \`ListItem.AvatarView\` with \`size={32}\` as \`media\`
152
+ - ALWAYS use \`size={32}\` on AvatarView (regardless of original icon size)
153
+ - ALWAYS remove \`size\` prop from child icon
154
+ - Summary-specific: always uses 32, while other components move size value from icon to AvatarView
155
+
156
+ ### Status mapping
157
+ - \`Status.DONE\` → \`badge={{ status: 'positive' }}\` on AvatarView
158
+ - \`Status.PENDING\` → \`badge={{ status: 'pending' }}\` on AvatarView
159
+ - \`Status.NOT_DONE\` → no badge
160
+
161
+ ### Action
162
+ - \`action.text\` → \`action.label\` in \`ListItem.AdditionalInfo\`
163
+ - Pass entire action object including \`href\`, \`onClick\`, \`target\`
164
+ - Remove \`aria-label\` from action (not supported)
165
+
166
+ ### Info with Modal (add state management)
167
+ - Add: \`const [open, setOpen] = useState(false)\`
168
+ - Create \`ListItem.IconButton\` with \`partiallyInteractive\` and \`onClick\`
169
+ - Render \`Modal\` separately with state
170
+ - Transfer \`aria-label\` to IconButton
171
+
172
+ ### Info with Popover
173
+ - \`Popover\` wraps \`ListItem.IconButton\` with \`partiallyInteractive\`
174
+ - Transfer \`aria-label\` to IconButton
175
+
176
+ \`\`\`tsx
177
+ // Basic (icon with or without size prop always becomes size={32} on AvatarView)
178
+ <Summary title="T" description="D" icon={<Icon size={24} />} />
179
+
180
+ <ListItem title="T" subtitle="D" media={<ListItem.AvatarView size={32}><Icon /></ListItem.AvatarView>} />
181
+
182
+ // Status
183
+ <Summary title="T" description="D" icon={<Icon />} status={Status.DONE} />
184
+
185
+ <ListItem title="T" subtitle="D" media={<ListItem.AvatarView size={32} badge={{status:'positive'}}><Icon /></ListItem.AvatarView>} />
186
+
187
+ // Action
188
+ <Summary title="T" description="D" icon={<Icon />} action={{text:'Go', href:'/go', target:'_blank'}} />
189
+
190
+ <ListItem title="T" subtitle="D" media={<ListItem.AvatarView size={32}><Icon /></ListItem.AvatarView>} additionalInfo={<ListItem.AdditionalInfo action={{label:'Go', href:'/go', target:'_blank'}} />} />
191
+
192
+ // Modal (requires state: const [open, setOpen] = useState(false))
193
+ <Summary title="T" description="D" icon={<Icon />} info={{title:'Help', content:'Text', presentation:'MODAL', 'aria-label':'Info'}} />
194
+
195
+ <>
196
+ <ListItem title="T" subtitle="D" media={<ListItem.AvatarView size={32}><Icon /></ListItem.AvatarView>} control={
197
+ <ListItem.IconButton partiallyInteractive aria-label="Info" onClick={()=>setOpen(!open)}>
198
+ <QuestionMarkCircle />
199
+ </ListItem.IconButton>
200
+ } />
201
+ <Modal open={open} title="Help" body="Text" onClose={()=>setOpen(false)} />
202
+ </>
203
+
204
+ // Popover
205
+ <Summary title="T" description="D" icon={<Icon />} info={{title:'Help', content:'Text', presentation:'POPOVER', 'aria-label':'Info'}} />
206
+
207
+ <ListItem title="T" subtitle="D" media={<ListItem.AvatarView size={32}><Icon /></ListItem.AvatarView>} control={
208
+ <Popover title="Help" content="Text" onClose={()=>setOpen(false)}>
209
+ <ListItem.IconButton partiallyInteractive aria-label="Info">
210
+ <QuestionMarkCircle />
211
+ </ListItem.IconButton>
212
+ </Popover>
213
+ } />
214
+ \`\`\`
215
+
216
+ `;
217
+ 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'.
218
+
219
+ Rules:
220
+ 1. Only ever modify files via the Edit tool - do not use the Write tool
221
+ 2. When identifying what code to migrate within a file, explain how you identified it first.
222
+ 3. Migrate components per provided migration rules
223
+ 4. Maintain TypeScript type safety and update types to match new API
224
+ 5. Map props: handle renamed, deprecated, new required, and changed types
225
+ 6. Update imports to new WDS components and types
226
+ 7. Preserve code style, formatting, and calculated logic
227
+ 8. Handle conditional rendering, spread props, and complex expressions
228
+ 9. Note: New components may lack feature parity with legacy versions
229
+ 10. Only modify code requiring changes per migration rules, and any impacted surrounding code for context.
230
+ 11. Final result response should just be whether the migration was successful overall, or if any errors were encountered
231
+ - Do not summarise or explain the changes made
232
+ 12. Explain your reasoning and justification before making changes, as you edit each file.
233
+ - Keep it concise and succinct, as only bullet points
234
+ 13. After modifying the file, do not summarise the changes made.
235
+ 14. If you do not have permission to edit a file, still attempt to edit it and then move onto the next file.
236
+
237
+ You'll receive:
238
+ - File paths to migrate in individual queries
239
+ - Deprecated component names at the end of this prompt
240
+ - Migration context and rules for each deprecated component
241
+
242
+ Deprecated components: ${DEPRECATED_COMPONENT_NAMES.join(", ")}.
243
+
244
+ ${MIGRATION_RULES}`;
245
+
246
+ //#endregion
247
+ //#region src/transforms/list-item/helpers.ts
248
+ /** Split the path to get the relative path after the directory, and wrap with ANSI color codes */
249
+ function formatPathOutput(directory, path, asDim) {
250
+ const relativePath = path ? path.split(directory.replace(".", ""))[1] ?? path : directory;
251
+ return asDim ? `\x1b[2m${relativePath}\x1b[0m` : `\x1b[32m${relativePath}\x1b[0m`;
252
+ }
253
+ /** Generates a formatted string representing the total elapsed time since the given start time */
254
+ function generateElapsedTime(startTime) {
255
+ const endTime = Date.now();
256
+ const elapsedTime = Math.floor((endTime - startTime) / 1e3);
257
+ const hours = Math.floor(elapsedTime / 3600);
258
+ const minutes = Math.floor(elapsedTime % 3600 / 60);
259
+ const seconds = elapsedTime % 60;
260
+ return `${hours ? `${hours}h ` : ""}${minutes ? `${minutes}m ` : ""}${seconds ? `${seconds}s` : ""}`;
261
+ }
262
+
263
+ //#endregion
264
+ //#region src/transforms/list-item/claude.ts
265
+ async function checkVPN(baseUrl, task) {
266
+ if (!baseUrl) return;
267
+ const checkOnce = async () => new Promise((resolveCheck) => {
268
+ const url = new URL("/health", baseUrl);
269
+ const req = node_https.default.get(url, {
270
+ timeout: 2e3,
271
+ rejectUnauthorized: false
272
+ }, (res) => {
273
+ const ok = !!(res.statusCode && res.statusCode >= 200 && res.statusCode < 400);
274
+ res.resume();
275
+ resolveCheck(ok);
276
+ });
277
+ req.on("timeout", () => {
278
+ req.destroy(/* @__PURE__ */ new Error("timeout"));
279
+ });
280
+ req.on("error", () => resolveCheck(false));
281
+ });
282
+ while (true) {
283
+ if (await checkOnce()) {
284
+ task.title = "Connected to VPN";
285
+ break;
286
+ }
287
+ for (let countdown = VPN_COUNTDOWN_TIMEOUT; countdown > 0; countdown -= 1) {
288
+ task.output = `Please connect to VPN... retrying in ${countdown}s`;
289
+ await new Promise((response) => {
290
+ setTimeout(response, 1e3);
291
+ });
292
+ }
293
+ }
294
+ }
295
+ function getQueryOptions(sessionId) {
296
+ const claudeSettingsPath = (0, node_path.resolve)(process.env.HOME || "", CLAUDE_SETTINGS_FILE);
297
+ const settings = JSON.parse((0, node_fs.readFileSync)(claudeSettingsPath, "utf-8"));
298
+ let apiKey;
299
+ try {
300
+ apiKey = (0, node_child_process.execSync)(`bash ${settings.apiKeyHelper}`, { encoding: "utf-8" }).trim();
301
+ } catch {}
302
+ if (!apiKey || !settings.env?.ANTHROPIC_BASE_URL) throw new Error("Failed to retrieve Anthropic API key or Base URL. Please check your Claude Code x LLM Gateway configuration - https://transferwise.atlassian.net/wiki/x/_YUe3Q");
303
+ const { ANTHROPIC_CUSTOM_HEADERS, ...restEnvVars } = settings?.env ?? {};
304
+ return {
305
+ resume: sessionId,
306
+ env: {
307
+ ANTHROPIC_AUTH_TOKEN: apiKey,
308
+ ANTHROPIC_CUSTOM_HEADERS,
309
+ ...restEnvVars,
310
+ PATH: process.env.PATH
311
+ },
312
+ permissionMode: "acceptEdits",
313
+ systemPrompt: {
314
+ type: "preset",
315
+ preset: "claude_code",
316
+ append: SYSTEM_PROMPT
317
+ },
318
+ allowedTools: ["Grep", "Read"],
319
+ settingSources: [
320
+ "local",
321
+ "project",
322
+ "user"
323
+ ]
324
+ };
325
+ }
326
+ /** Initiate a new Claude session/conversation and return reusable options */
327
+ async function initiateClaudeSessionOptions(manager) {
328
+ const options = getQueryOptions(void 0);
329
+ manager.add([{
330
+ title: "Checking VPN connection",
331
+ task: async (_, task) => checkVPN(options.env?.ANTHROPIC_BASE_URL, task)
332
+ }]);
333
+ await manager.runAll();
334
+ manager.add([{
335
+ title: "Starting Claude instance",
336
+ task: async (_, task) => {
337
+ task.output = "Your browser may open for Okta authentication if required";
338
+ const result = (0, _anthropic_ai_claude_agent_sdk.query)({
339
+ options,
340
+ 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.`
341
+ });
342
+ for await (const message of result) switch (message.type) {
343
+ case "system":
344
+ if (message.subtype === "init" && !options.resume) {
345
+ task.title = "Successfully initialised Claude instance\n";
346
+ options.resume = message.session_id;
347
+ }
348
+ break;
349
+ default: if (message.type === "result" && message.subtype !== "success") throw new Error(`Claude encountered an error when initialising: ${message.errors.join("\n")}`);
350
+ }
351
+ task.task.complete();
352
+ }
353
+ }]);
354
+ await manager.runAll().finally(() => {
355
+ manager.options = { concurrent: DIRECTORY_CONCURRENCY_LIMIT };
356
+ });
357
+ return options;
358
+ }
359
+ async function queryClaude(directory, filePath, options, task, isDebug = false) {
360
+ const startTime = Date.now();
361
+ const result = (0, _anthropic_ai_claude_agent_sdk.query)({
362
+ options,
363
+ prompt: filePath
364
+ });
365
+ for await (const message of result) switch (message.type) {
366
+ case "result":
367
+ if (message.subtype === "success" && isDebug) {
368
+ task.title = `\x1b[2m${formatPathOutput(directory, filePath)}\x1b[0m]`;
369
+ task.output = `\x1b[2mMigrated in ${generateElapsedTime(startTime)}\x1b[0m`;
370
+ } else if (message.is_error) {
371
+ task.title = `\x1b[2m${formatPathOutput(directory, filePath)}\x1b[0m]`;
372
+ task.output = `${require_constants.CONSOLE_ICONS.error} Claude encountered an error: ${JSON.stringify(message)}`;
373
+ }
374
+ break;
375
+ default:
376
+ }
377
+ }
378
+
379
+ //#endregion
380
+ //#region src/transforms/list-item/transformer.ts
381
+ const transformer = async (targetPaths, isDebug = false) => {
382
+ process.setMaxListeners(20);
383
+ const startTime = Date.now();
384
+ const manager = new _listr2_manager.Manager({ concurrent: true });
385
+ const queryOptions = await initiateClaudeSessionOptions(manager);
386
+ for (const directory of targetPaths) {
387
+ const matchingFilePaths = (0, node_child_process.execSync)(`find "${directory}" -name "*.tsx" -type f`, { encoding: "utf-8" }).trim().split("\n").filter(Boolean).filter((filePath) => {
388
+ const content = (0, node_fs.readFileSync)(filePath, "utf-8");
389
+ return GREP_PATTERN.test(content);
390
+ });
391
+ if (matchingFilePaths.length === 0) manager.add([{
392
+ title: `\x1b[2m${formatPathOutput(directory)} - No files need migration\x1b[0m`,
393
+ task: (ctx) => {
394
+ ctx.skip = true;
395
+ }
396
+ }]);
397
+ else manager.add([{
398
+ title: `${formatPathOutput(directory)} - Found \x1b[32m${matchingFilePaths.length}\x1b[0m file(s) needing migration`,
399
+ task: async (ctx, parentTask) => {
400
+ parentTask.title = `${formatPathOutput(directory)} - Migrating \x1b[32m${matchingFilePaths.length}\x1b[0m file(s)...`;
401
+ const completedFilesInDirectory = { count: 0 };
402
+ return parentTask.newListr(matchingFilePaths.map((filePath) => ({
403
+ title: "",
404
+ task: async (fileCtx, fileTask) => {
405
+ await queryClaude(directory, filePath, queryOptions, fileTask, isDebug).finally(() => {
406
+ completedFilesInDirectory.count += 1;
407
+ const isDim = completedFilesInDirectory.count === matchingFilePaths.length;
408
+ parentTask.title = `${isDim ? "\x1B[2m" : ""}${formatPathOutput(directory)} - Migrated \x1b[32m${completedFilesInDirectory.count}\x1b[0m/\x1b[32m${matchingFilePaths.length}\x1b[0m files${isDim ? "\x1B[0m" : ""}`;
409
+ });
410
+ }
411
+ })), { concurrent: FILE_CONCURRENCY_LIMIT }).run();
412
+ }
413
+ }]);
414
+ }
415
+ await manager.runAll().finally(async () => {
416
+ await new listr2.Listr([{
417
+ title: `Finished migrating - elapsed time: \x1b[32m${generateElapsedTime(startTime)}\x1b[0m `,
418
+ task: async () => {}
419
+ }]).run();
420
+ });
421
+ };
422
+ var transformer_default = transformer;
423
+
424
+ //#endregion
425
+ module.exports = transformer_default;
426
+ //# sourceMappingURL=transformer.js.map