@wise/wds-codemods 1.2.0 → 1.2.1-experimental-1f1926f
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/{claude-BHYfEMfb.js → claude-cHSlHbX1.js} +26 -42
- package/dist/claude-cHSlHbX1.js.map +1 -0
- package/dist/common-nTdKnfMB.js +13 -0
- package/dist/{common-DdEQmI2h.js.map → common-nTdKnfMB.js.map} +1 -1
- package/dist/{helpers-Cj5geKJl.js → helpers-BrTOp8HX.js} +235 -382
- package/dist/helpers-BrTOp8HX.js.map +1 -0
- package/dist/index.js +143 -49
- package/dist/index.js.map +1 -1
- package/dist/transforms/button/transformer.js +14 -26
- package/dist/transforms/button/transformer.js.map +1 -1
- package/dist/transforms/info-prompt/transformer.js +9 -12
- package/dist/transforms/info-prompt/transformer.js.map +1 -1
- package/dist/transforms/list-item/MIGRATION_RULES.md +20 -4
- package/dist/transforms/list-item/transformer.js +9 -13
- package/dist/transforms/list-item/transformer.js.map +1 -1
- package/package.json +41 -46
- package/dist/claude-BHYfEMfb.js.map +0 -1
- package/dist/common-DdEQmI2h.js +0 -59
- package/dist/helpers-Cj5geKJl.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,20 +1,89 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
2
|
+
import { a as getOptions, c as findPackages, i as loadTransformModules, l as findProjectRoot, n as runTransformPrompts, o as logToInquirer, s as assessPrerequisitesBatch, t as validateClaudeConfig, u as getCodemodConfig } from "./helpers-BrTOp8HX.js";
|
|
3
|
+
import { t as CONSOLE_ICONS } from "./common-nTdKnfMB.js";
|
|
4
|
+
import { execSync } from "node:child_process";
|
|
5
|
+
import fs from "node:fs/promises";
|
|
6
|
+
import path from "node:path";
|
|
7
|
+
import { fileURLToPath } from "node:url";
|
|
8
|
+
import { select } from "@inquirer/prompts";
|
|
9
|
+
import Spinnies from "spinnies";
|
|
10
|
+
import { createHash } from "node:crypto";
|
|
11
|
+
import { hostname, userInfo } from "node:os";
|
|
12
|
+
import Mixpanel from "mixpanel";
|
|
13
|
+
//#region src/helpers/tracking/index.ts
|
|
14
|
+
const MIXPANEL_TOKEN = "8ba4a7a5182f05e0a79ded57d5d2f051";
|
|
15
|
+
let client = null;
|
|
16
|
+
const getClient = () => {
|
|
17
|
+
client ??= Mixpanel.init(MIXPANEL_TOKEN, { geolocate: false });
|
|
18
|
+
return client;
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Anonymous but stable user identifier derived from OS username + hostname.
|
|
22
|
+
* No PII is sent — only a SHA-256 hash.
|
|
23
|
+
*/
|
|
24
|
+
const getDistinctId = () => {
|
|
25
|
+
const raw = `${userInfo().username}@${hostname()}`;
|
|
26
|
+
return createHash("sha256").update(raw).digest("hex").slice(0, 16);
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Maps internal camelCase properties to Wise Mixpanel naming conventions:
|
|
30
|
+
* - Spaces between words, each word capitalized
|
|
31
|
+
* - Booleans prefixed with "Is"
|
|
32
|
+
* - Counts use "Number Of"
|
|
33
|
+
* - Duration in seconds (not ms)
|
|
34
|
+
*/
|
|
35
|
+
const toMixpanelProperties = (properties) => ({
|
|
36
|
+
Transform: properties.transform,
|
|
37
|
+
Engine: properties.engine,
|
|
38
|
+
"Is Monorepo": properties.isMonorepo,
|
|
39
|
+
"Is Git Ignore Enabled": properties.useGitIgnore,
|
|
40
|
+
"Is Ignore Patterns Used": properties.hasIgnorePatterns,
|
|
41
|
+
"Selection Method": properties.selectionMethod,
|
|
42
|
+
"Number Of Target Paths": properties.targetPathCount,
|
|
43
|
+
"Is Debug": properties.isDebug,
|
|
44
|
+
Repository: properties.repository,
|
|
45
|
+
Timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
46
|
+
Error: properties.error,
|
|
47
|
+
Duration: properties.durationMs != null ? properties.durationMs / 1e3 : void 0
|
|
48
|
+
});
|
|
49
|
+
/**
|
|
50
|
+
* Event names follow Wise Mixpanel naming conventions:
|
|
51
|
+
* `[Subject] - [Action]` with past-tense verbs, capitalized words, delimited by " - ".
|
|
52
|
+
*/
|
|
53
|
+
const TRACKING_EVENTS = {
|
|
54
|
+
STARTED: "WDS - Codemod - Started",
|
|
55
|
+
FINISHED: "WDS - Codemod - Finished",
|
|
56
|
+
FAILED: "WDS - Codemod - Failed"
|
|
57
|
+
};
|
|
58
|
+
/**
|
|
59
|
+
* Fire-and-forget event tracking. Never throws — failures are silently ignored
|
|
60
|
+
* so tracking never interferes with the CLI experience.
|
|
61
|
+
*/
|
|
62
|
+
const track = (event, properties) => {
|
|
63
|
+
try {
|
|
64
|
+
const mapped = toMixpanelProperties(properties);
|
|
65
|
+
if (properties.isDebug) console.debug(`${CONSOLE_ICONS.info} [Mixpanel] ${event}`, JSON.stringify(mapped, null, 2));
|
|
66
|
+
if (properties.noTracking) return;
|
|
67
|
+
const mp = getClient();
|
|
68
|
+
if (!mp) return;
|
|
69
|
+
mp.track(event, {
|
|
70
|
+
distinct_id: getDistinctId(),
|
|
71
|
+
...mapped
|
|
72
|
+
});
|
|
73
|
+
} catch {}
|
|
74
|
+
};
|
|
75
|
+
/**
|
|
76
|
+
* Returns a flush promise you can await before process.exit.
|
|
77
|
+
* Mixpanel's Node client batches requests, so we give it a small window.
|
|
78
|
+
*/
|
|
79
|
+
const flushTracking = async () => new Promise((resolve) => {
|
|
80
|
+
setTimeout(resolve, 300);
|
|
81
|
+
});
|
|
82
|
+
//#endregion
|
|
14
83
|
//#region src/controller/helpers/claudePrereqs.ts
|
|
15
84
|
function handleClaudePrereqs({ options, codemodPath, spinners }) {
|
|
16
85
|
spinners.add("prerequisite-check", { text: "Checking prerequisites..." });
|
|
17
|
-
const { allPassed, packageRootToTargets, failedPackageRoots } =
|
|
86
|
+
const { allPassed, packageRootToTargets, failedPackageRoots } = assessPrerequisitesBatch(options.targetPaths, codemodPath, spinners);
|
|
18
87
|
if (!allPassed) {
|
|
19
88
|
spinners.add("prerequisite-partial-failure", {
|
|
20
89
|
text: "One or more packages failed prerequisite checks - these targets will be skipped:",
|
|
@@ -32,58 +101,59 @@ function handleClaudePrereqs({ options, codemodPath, spinners }) {
|
|
|
32
101
|
}
|
|
33
102
|
if (!options.targetPaths.length) {
|
|
34
103
|
spinners.add("no-valid-targets", {
|
|
35
|
-
text: `${
|
|
104
|
+
text: `${CONSOLE_ICONS.error} No valid target paths remaining after prerequisite checks - exiting.`,
|
|
36
105
|
status: "fail"
|
|
37
106
|
});
|
|
38
107
|
spinners.stopAll("fail");
|
|
39
108
|
spinners.checkIfActiveSpinners();
|
|
40
109
|
}
|
|
41
110
|
}
|
|
42
|
-
|
|
43
111
|
//#endregion
|
|
44
112
|
//#region src/controller/index.ts
|
|
45
113
|
let isDebug = false;
|
|
46
|
-
const currentFilePath =
|
|
47
|
-
const currentDirPath =
|
|
114
|
+
const currentFilePath = fileURLToPath(import.meta.url);
|
|
115
|
+
const currentDirPath = path.dirname(currentFilePath);
|
|
48
116
|
const resetReportFile = async (reportPath) => {
|
|
49
117
|
try {
|
|
50
|
-
await
|
|
51
|
-
await
|
|
52
|
-
console.debug(`${
|
|
118
|
+
await fs.access(reportPath);
|
|
119
|
+
await fs.rm(reportPath);
|
|
120
|
+
console.debug(`${CONSOLE_ICONS.info} Removed existing report file${isDebug ? `: ${reportPath}` : "."}`);
|
|
53
121
|
} catch {
|
|
54
|
-
console.debug(`${
|
|
122
|
+
console.debug(`${CONSOLE_ICONS.info} No existing report file to remove${isDebug ? `: ${reportPath}` : "."}`);
|
|
55
123
|
}
|
|
56
124
|
};
|
|
57
125
|
const summariseReportFile = async (reportPath) => {
|
|
58
126
|
try {
|
|
59
|
-
const lines = (await
|
|
60
|
-
if (lines.length) console.debug(`\n${
|
|
61
|
-
else console.debug(`${
|
|
127
|
+
const lines = (await fs.readFile(reportPath, "utf8")).split("\n").filter(Boolean);
|
|
128
|
+
if (lines.length) console.debug(`\n${CONSOLE_ICONS.warning} ${lines.length} manual review${lines.length > 1 ? "s are" : " is"} required. See ${reportPath} for details.`);
|
|
129
|
+
else console.debug(`${CONSOLE_ICONS.info} Report file exists but is empty${isDebug ? `: ${reportPath}` : "."}`);
|
|
62
130
|
} catch {
|
|
63
|
-
console.debug(`${
|
|
131
|
+
console.debug(`${CONSOLE_ICONS.info} No report file generated - no manual reviews needed`);
|
|
64
132
|
}
|
|
65
133
|
};
|
|
66
134
|
const log = (label, value) => {
|
|
67
|
-
if (typeof
|
|
135
|
+
if (typeof logToInquirer === "function") logToInquirer(label, value || "");
|
|
68
136
|
else console.info(label, value || "");
|
|
69
137
|
};
|
|
70
138
|
async function runCodemod(transformsDir) {
|
|
71
139
|
const args = process.argv.slice(2);
|
|
72
140
|
const candidate = args[0];
|
|
73
141
|
isDebug = args.includes("--debug");
|
|
142
|
+
const noTracking = args.includes("--no-tracking");
|
|
143
|
+
const startTime = Date.now();
|
|
74
144
|
try {
|
|
75
|
-
const root =
|
|
76
|
-
const packagesPromise =
|
|
77
|
-
const resolvedTransformsDir = transformsDir ??
|
|
78
|
-
if (isDebug) console.debug(`${
|
|
79
|
-
const { transformFiles: resolvedTransformNames } = await
|
|
80
|
-
if (resolvedTransformNames.length === 0) throw new Error(`${
|
|
145
|
+
const root = findProjectRoot();
|
|
146
|
+
const packagesPromise = findPackages();
|
|
147
|
+
const resolvedTransformsDir = transformsDir ?? path.resolve(currentDirPath, "../dist/transforms");
|
|
148
|
+
if (isDebug) console.debug(`${CONSOLE_ICONS.info} Resolved transforms directory: ${resolvedTransformsDir}`);
|
|
149
|
+
const { transformFiles: resolvedTransformNames } = await loadTransformModules(resolvedTransformsDir);
|
|
150
|
+
if (resolvedTransformNames.length === 0) throw new Error(`${CONSOLE_ICONS.error} No transform scripts found${isDebug ? ` in: ${resolvedTransformsDir}` : "."}`);
|
|
81
151
|
let transformFile;
|
|
82
152
|
if (candidate && resolvedTransformNames.includes(candidate)) {
|
|
83
153
|
log("Select codemod to run:", candidate);
|
|
84
154
|
transformFile = candidate;
|
|
85
155
|
} else {
|
|
86
|
-
transformFile = await
|
|
156
|
+
transformFile = await select({
|
|
87
157
|
message: "Select codemod to run:",
|
|
88
158
|
choices: resolvedTransformNames.map((name) => ({
|
|
89
159
|
name,
|
|
@@ -92,24 +162,38 @@ async function runCodemod(transformsDir) {
|
|
|
92
162
|
});
|
|
93
163
|
log("Selected codemod:", transformFile);
|
|
94
164
|
}
|
|
95
|
-
const codemodPath =
|
|
96
|
-
const codemodConfig =
|
|
97
|
-
if (isDebug) console.debug(`${
|
|
98
|
-
if (codemodConfig?.type === "claude")
|
|
99
|
-
const spinners = new
|
|
165
|
+
const codemodPath = path.resolve(resolvedTransformsDir, transformFile, "transformer.js");
|
|
166
|
+
const codemodConfig = getCodemodConfig(codemodPath);
|
|
167
|
+
if (isDebug) console.debug(`${CONSOLE_ICONS.info} Resolved codemod path: ${codemodPath}`);
|
|
168
|
+
if (codemodConfig?.type === "claude") validateClaudeConfig();
|
|
169
|
+
const spinners = new Spinnies();
|
|
100
170
|
spinners.add("loading-codemod", { text: `Loading codemod (${transformFile})...` });
|
|
101
171
|
const packages = await packagesPromise;
|
|
102
|
-
const reportPath =
|
|
172
|
+
const reportPath = path.resolve(root, "codemod-report.txt");
|
|
103
173
|
spinners.succeed("loading-codemod", { text: `Successfully loaded codemod: \x1b[2m${transformFile}\x1b[0m` });
|
|
104
174
|
if (codemodConfig?.type === "jscodeshift") await resetReportFile(reportPath);
|
|
105
|
-
const promptAnswers = await
|
|
106
|
-
const options = await
|
|
175
|
+
const promptAnswers = await runTransformPrompts(codemodPath);
|
|
176
|
+
const options = await getOptions({
|
|
107
177
|
packages,
|
|
108
178
|
root,
|
|
109
179
|
transformFiles: resolvedTransformNames,
|
|
110
180
|
preselectedTransformFile: transformFile,
|
|
111
181
|
transformerType: codemodConfig?.type
|
|
112
182
|
});
|
|
183
|
+
const selectionMethod = candidate && resolvedTransformNames.includes(candidate) ? "cli" : "interactive";
|
|
184
|
+
const trackingProps = {
|
|
185
|
+
transform: transformFile,
|
|
186
|
+
engine: codemodConfig?.type,
|
|
187
|
+
isMonorepo: args.includes("--monorepo"),
|
|
188
|
+
useGitIgnore: options.useGitIgnore,
|
|
189
|
+
hasIgnorePatterns: Boolean(options.ignorePatterns),
|
|
190
|
+
selectionMethod,
|
|
191
|
+
targetPathCount: options.targetPaths.length,
|
|
192
|
+
isDebug,
|
|
193
|
+
noTracking,
|
|
194
|
+
repository: path.basename(root)
|
|
195
|
+
};
|
|
196
|
+
track(TRACKING_EVENTS.STARTED, trackingProps);
|
|
113
197
|
if (codemodConfig?.type === "claude") {
|
|
114
198
|
handleClaudePrereqs({
|
|
115
199
|
options,
|
|
@@ -122,8 +206,7 @@ async function runCodemod(transformsDir) {
|
|
|
122
206
|
spinners.stopAll("succeed");
|
|
123
207
|
spinners.checkIfActiveSpinners();
|
|
124
208
|
await Promise.all(options.targetPaths.map(async (targetPath) => {
|
|
125
|
-
console.info(`${
|
|
126
|
-
if (!require_helpers.assessPrerequisites(targetPath, codemodPath)) return;
|
|
209
|
+
console.info(`${CONSOLE_ICONS.focus} \x1b[1mProcessing:\x1b[0m \x1b[32m${targetPath}\x1b[0m`);
|
|
127
210
|
const answerArgs = Object.entries(promptAnswers).map(([promptName, answerValue]) => `--${promptName}=${String(answerValue)}`);
|
|
128
211
|
const command = `npx jscodeshift ${[
|
|
129
212
|
"-t",
|
|
@@ -135,21 +218,32 @@ async function runCodemod(transformsDir) {
|
|
|
135
218
|
options.useGitIgnore ? "--gitignore" : "",
|
|
136
219
|
...answerArgs
|
|
137
220
|
].filter(Boolean).join(" ")}`;
|
|
138
|
-
if (isDebug) console.debug(`${
|
|
139
|
-
return
|
|
221
|
+
if (isDebug) console.debug(`${CONSOLE_ICONS.info} Running: ${command}`);
|
|
222
|
+
return execSync(command, { stdio: "inherit" });
|
|
140
223
|
}));
|
|
141
224
|
}
|
|
142
225
|
if (codemodConfig?.type === "jscodeshift") await summariseReportFile(reportPath);
|
|
226
|
+
track(TRACKING_EVENTS.FINISHED, {
|
|
227
|
+
...trackingProps,
|
|
228
|
+
durationMs: Date.now() - startTime
|
|
229
|
+
});
|
|
143
230
|
} catch (error) {
|
|
144
|
-
if (error instanceof Error) console.error(`${
|
|
145
|
-
else console.error(`${
|
|
231
|
+
if (error instanceof Error) console.error(`${CONSOLE_ICONS.error} Error running ${candidate} codemod:`, error.message);
|
|
232
|
+
else console.error(`${CONSOLE_ICONS.error} Error running ${candidate} codemod:`, String(error));
|
|
233
|
+
track(TRACKING_EVENTS.FAILED, {
|
|
234
|
+
transform: candidate ?? "unknown",
|
|
235
|
+
error: error instanceof Error ? error.message : String(error),
|
|
236
|
+
durationMs: Date.now() - startTime
|
|
237
|
+
});
|
|
238
|
+
await flushTracking();
|
|
146
239
|
if (process.env.NODE_ENV !== "test") process.exit(1);
|
|
147
240
|
}
|
|
241
|
+
await flushTracking();
|
|
148
242
|
}
|
|
149
|
-
|
|
150
243
|
//#endregion
|
|
151
244
|
//#region src/index.ts
|
|
152
245
|
runCodemod();
|
|
153
|
-
|
|
154
246
|
//#endregion
|
|
247
|
+
export {};
|
|
248
|
+
|
|
155
249
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
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/common';\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/common';\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
|
+
{"version":3,"file":"index.js","names":["list"],"sources":["../src/helpers/tracking/index.ts","../src/controller/helpers/claudePrereqs.ts","../src/controller/index.ts","../src/index.ts"],"sourcesContent":["import { createHash } from 'node:crypto';\nimport { hostname, userInfo } from 'node:os';\n\nimport Mixpanel from 'mixpanel';\n\nimport { CONSOLE_ICONS } from '../../constants/common';\n\nconst MIXPANEL_TOKEN = '8ba4a7a5182f05e0a79ded57d5d2f051';\n\nlet client: Mixpanel.Mixpanel | null = null;\n\nconst getClient = (): Mixpanel.Mixpanel | null => {\n if (!MIXPANEL_TOKEN) return null;\n client ??= Mixpanel.init(MIXPANEL_TOKEN, {\n geolocate: false,\n });\n return client;\n};\n\n/**\n * Anonymous but stable user identifier derived from OS username + hostname.\n * No PII is sent — only a SHA-256 hash.\n */\nconst getDistinctId = (): string => {\n const raw = `${userInfo().username}@${hostname()}`;\n return createHash('sha256').update(raw).digest('hex').slice(0, 16);\n};\n\n/**\n * Internal interface for passing tracking data from call sites.\n * Property names are mapped to Wise Mixpanel naming conventions before sending.\n *\n * @see https://transferwise.atlassian.net/wiki — Handbook for Mixpanel tracking\n */\nexport interface TrackingProperties {\n transform: string;\n engine?: 'jscodeshift' | 'claude';\n isMonorepo?: boolean;\n useGitIgnore?: boolean;\n hasIgnorePatterns?: boolean;\n selectionMethod?: 'cli' | 'interactive';\n targetPathCount?: number;\n isDebug?: boolean;\n noTracking?: boolean;\n repository?: string;\n error?: string;\n durationMs?: number;\n}\n\n/**\n * Maps internal camelCase properties to Wise Mixpanel naming conventions:\n * - Spaces between words, each word capitalized\n * - Booleans prefixed with \"Is\"\n * - Counts use \"Number Of\"\n * - Duration in seconds (not ms)\n */\nconst toMixpanelProperties = (\n properties: TrackingProperties,\n): Record<string, string | number | boolean | undefined> => ({\n Transform: properties.transform,\n Engine: properties.engine,\n 'Is Monorepo': properties.isMonorepo,\n 'Is Git Ignore Enabled': properties.useGitIgnore,\n 'Is Ignore Patterns Used': properties.hasIgnorePatterns,\n 'Selection Method': properties.selectionMethod,\n 'Number Of Target Paths': properties.targetPathCount,\n 'Is Debug': properties.isDebug,\n Repository: properties.repository,\n Timestamp: new Date().toISOString(),\n Error: properties.error,\n Duration: properties.durationMs != null ? properties.durationMs / 1000 : undefined,\n});\n\n/**\n * Event names follow Wise Mixpanel naming conventions:\n * `[Subject] - [Action]` with past-tense verbs, capitalized words, delimited by \" - \".\n */\nexport const TRACKING_EVENTS = {\n STARTED: 'WDS - Codemod - Started',\n FINISHED: 'WDS - Codemod - Finished',\n FAILED: 'WDS - Codemod - Failed',\n} as const;\n\n/**\n * Fire-and-forget event tracking. Never throws — failures are silently ignored\n * so tracking never interferes with the CLI experience.\n */\nexport const track = (event: string, properties: TrackingProperties): void => {\n try {\n const mapped = toMixpanelProperties(properties);\n\n if (properties.isDebug) {\n console.debug(`${CONSOLE_ICONS.info} [Mixpanel] ${event}`, JSON.stringify(mapped, null, 2));\n }\n\n if (properties.noTracking) return;\n\n const mp = getClient();\n if (!mp) return;\n\n mp.track(event, {\n distinct_id: getDistinctId(),\n ...mapped,\n });\n } catch {\n // Intentionally swallowed — tracking must never break the CLI\n }\n};\n\n/**\n * Returns a flush promise you can await before process.exit.\n * Mixpanel's Node client batches requests, so we give it a small window.\n */\nexport const flushTracking = async (): Promise<void> =>\n new Promise((resolve) => {\n // Mixpanel Node SDK sends HTTP requests async. Give it a brief grace period.\n setTimeout(resolve, 300);\n });\n","import type Spinnies from 'spinnies';\n\nimport { CONSOLE_ICONS } from '../../constants/common';\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/common';\nimport {\n flushTracking,\n track,\n TRACKING_EVENTS,\n type TrackingProperties,\n} from '../helpers/tracking';\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 const noTracking = args.includes('--no-tracking');\n const startTime = Date.now();\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 const selectionMethod: TrackingProperties['selectionMethod'] =\n candidate && resolvedTransformNames.includes(candidate) ? 'cli' : 'interactive';\n\n const trackingProps: TrackingProperties = {\n transform: transformFile,\n engine: codemodConfig?.type,\n isMonorepo: args.includes('--monorepo'),\n useGitIgnore: options.useGitIgnore,\n hasIgnorePatterns: Boolean(options.ignorePatterns),\n selectionMethod,\n targetPathCount: options.targetPaths.length,\n isDebug,\n noTracking,\n repository: path.basename(root),\n };\n\n track(TRACKING_EVENTS.STARTED, trackingProps);\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 // TODO: re-enable after testing tracking\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\n track(TRACKING_EVENTS.FINISHED, {\n ...trackingProps,\n durationMs: Date.now() - startTime,\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 track(TRACKING_EVENTS.FAILED, {\n transform: candidate ?? 'unknown',\n error: error instanceof Error ? error.message : String(error),\n durationMs: Date.now() - startTime,\n });\n\n await flushTracking();\n\n if (process.env.NODE_ENV !== 'test') {\n process.exit(1);\n }\n }\n\n await flushTracking();\n}\n\nexport { runCodemod };\n","#!/usr/bin/env node\nimport { runCodemod } from './controller';\n\nvoid runCodemod();\n"],"mappings":"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"}
|
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
const require_helpers = require('../../helpers-Cj5geKJl.js');
|
|
3
|
-
|
|
1
|
+
import { r as reportManualReview } from "../../helpers-BrTOp8HX.js";
|
|
4
2
|
//#region src/helpers/jscodeshift/addImport.ts
|
|
5
3
|
/**
|
|
6
4
|
* Adds a named import if it doesn't already exist.
|
|
@@ -22,8 +20,6 @@ function addImport(root, sourceValue, importName, j) {
|
|
|
22
20
|
}
|
|
23
21
|
}
|
|
24
22
|
}
|
|
25
|
-
var addImport_default = addImport;
|
|
26
|
-
|
|
27
23
|
//#endregion
|
|
28
24
|
//#region src/helpers/jscodeshift/hasImport.ts
|
|
29
25
|
/**
|
|
@@ -83,8 +79,6 @@ function hasImport(root, sourceValue, importName, j) {
|
|
|
83
79
|
conflictingImports
|
|
84
80
|
};
|
|
85
81
|
}
|
|
86
|
-
var hasImport_default = hasImport;
|
|
87
|
-
|
|
88
82
|
//#endregion
|
|
89
83
|
//#region src/helpers/jscodeshift/iconUtils.ts
|
|
90
84
|
/**
|
|
@@ -104,7 +98,7 @@ const processIconChildren = (j, children, iconImports, openingElement) => {
|
|
|
104
98
|
});
|
|
105
99
|
if (iconChildIndex === -1) return;
|
|
106
100
|
const iconChild = unwrapJsxElement(children[iconChildIndex]);
|
|
107
|
-
if (
|
|
101
|
+
if (iconChild?.openingElement.name.type !== "JSXIdentifier") return;
|
|
108
102
|
iconChild.openingElement.name.name;
|
|
109
103
|
const iconPropName = iconChildIndex <= totalChildren - 1 - iconChildIndex ? "addonStart" : "addonEnd";
|
|
110
104
|
const iconObject = j.objectExpression([j.property("init", j.identifier("type"), j.literal("icon")), j.property("init", j.identifier("value"), iconChild)]);
|
|
@@ -117,15 +111,13 @@ const processIconChildren = (j, children, iconImports, openingElement) => {
|
|
|
117
111
|
if (iconChildIndex - 1 >= 0 && isWhitespaceJsxText(children[iconChildIndex - 1])) children.splice(iconChildIndex - 1, 1);
|
|
118
112
|
else if (isWhitespaceJsxText(children[iconChildIndex])) children.splice(iconChildIndex, 1);
|
|
119
113
|
};
|
|
120
|
-
var iconUtils_default = processIconChildren;
|
|
121
|
-
|
|
122
114
|
//#endregion
|
|
123
115
|
//#region src/helpers/jscodeshift/jsxElementUtils.ts
|
|
124
116
|
/**
|
|
125
117
|
* Rename a JSX element name if it is a JSXIdentifier.
|
|
126
118
|
*/
|
|
127
119
|
const setNameIfJSXIdentifier = (elementName, newName) => {
|
|
128
|
-
if (elementName
|
|
120
|
+
if (elementName?.type === "JSXIdentifier") return {
|
|
129
121
|
...elementName,
|
|
130
122
|
name: newName
|
|
131
123
|
};
|
|
@@ -173,7 +165,6 @@ const removeAttributeByName = (j, openingElement, attributeName) => {
|
|
|
173
165
|
return !(attr.type === "JSXAttribute" && attr.name.type === "JSXIdentifier" && attr.name.name === attributeName);
|
|
174
166
|
});
|
|
175
167
|
};
|
|
176
|
-
|
|
177
168
|
//#endregion
|
|
178
169
|
//#region src/helpers/jscodeshift/jsxReportingUtils.ts
|
|
179
170
|
/**
|
|
@@ -346,7 +337,6 @@ const createReporter = (j, issues) => {
|
|
|
346
337
|
issues
|
|
347
338
|
});
|
|
348
339
|
};
|
|
349
|
-
|
|
350
340
|
//#endregion
|
|
351
341
|
//#region src/transforms/button/transformer.ts
|
|
352
342
|
const parser = "tsx";
|
|
@@ -483,9 +473,9 @@ const transformer = (file, api, options) => {
|
|
|
483
473
|
return priority;
|
|
484
474
|
};
|
|
485
475
|
const reporter = createReporter(j, manualReviewIssues);
|
|
486
|
-
const { exists: hasButtonImport, aliases: buttonAliases, resolvedName: buttonName, conflictingImports: conflictingButtonImport } =
|
|
476
|
+
const { exists: hasButtonImport, aliases: buttonAliases, resolvedName: buttonName, conflictingImports: conflictingButtonImport } = hasImport(root, "@transferwise/components", "Button", j);
|
|
487
477
|
if (conflictingButtonImport.length) conflictingButtonImport.forEach((node) => reporter.reportConflictingImports(node));
|
|
488
|
-
const { exists: hasActionButtonImport, remove: removeActionButtonImport, aliases: actionButtonAliases } =
|
|
478
|
+
const { exists: hasActionButtonImport, remove: removeActionButtonImport, aliases: actionButtonAliases } = hasImport(root, "@transferwise/components", "ActionButton", j);
|
|
489
479
|
if (!hasButtonImport && !hasActionButtonImport) return file.source;
|
|
490
480
|
const iconImports = /* @__PURE__ */ new Set();
|
|
491
481
|
root.find(j.ImportDeclaration, { source: { value: "@transferwise/icons" } }).forEach((path) => {
|
|
@@ -497,12 +487,12 @@ const transformer = (file, api, options) => {
|
|
|
497
487
|
});
|
|
498
488
|
});
|
|
499
489
|
if (hasActionButtonImport) {
|
|
500
|
-
if (!hasButtonImport)
|
|
490
|
+
if (!hasButtonImport) addImport(root, "@transferwise/components", "Button", j);
|
|
501
491
|
findJSXElementsByName(root, j)("ActionButton", actionButtonAliases).forEach((path) => {
|
|
502
492
|
const { openingElement, closingElement } = path.node;
|
|
503
493
|
openingElement.name = setNameIfJSXIdentifier(openingElement.name, buttonName);
|
|
504
494
|
if (closingElement) closingElement.name = setNameIfJSXIdentifier(closingElement.name, buttonName);
|
|
505
|
-
|
|
495
|
+
processIconChildren(j, path.node.children, iconImports, openingElement);
|
|
506
496
|
if ((openingElement.attributes ?? []).some((attr) => attr.type === "JSXSpreadAttribute")) reporter.reportSpreadProps(path);
|
|
507
497
|
const legacyPropNames = [
|
|
508
498
|
"priority",
|
|
@@ -511,7 +501,7 @@ const transformer = (file, api, options) => {
|
|
|
511
501
|
];
|
|
512
502
|
const legacyProps = {};
|
|
513
503
|
openingElement.attributes?.forEach((attr) => {
|
|
514
|
-
if (attr.type === "JSXAttribute" && attr.name
|
|
504
|
+
if (attr.type === "JSXAttribute" && attr.name?.type === "JSXIdentifier") {
|
|
515
505
|
const { name } = attr.name;
|
|
516
506
|
if (legacyPropNames.includes(name)) {
|
|
517
507
|
if (attr.value) {
|
|
@@ -551,7 +541,7 @@ const transformer = (file, api, options) => {
|
|
|
551
541
|
attribute: j.jsxAttribute(j.jsxIdentifier("v2")),
|
|
552
542
|
name: "v2"
|
|
553
543
|
}]);
|
|
554
|
-
|
|
544
|
+
processIconChildren(j, path.node.children, iconImports, openingElement);
|
|
555
545
|
const legacyProps = {};
|
|
556
546
|
const legacyPropNames = [
|
|
557
547
|
"priority",
|
|
@@ -561,7 +551,7 @@ const transformer = (file, api, options) => {
|
|
|
561
551
|
"sentiment"
|
|
562
552
|
];
|
|
563
553
|
openingElement.attributes?.forEach((attr) => {
|
|
564
|
-
if (attr.type === "JSXAttribute" && attr.name
|
|
554
|
+
if (attr.type === "JSXAttribute" && attr.name?.type === "JSXIdentifier") {
|
|
565
555
|
const { name } = attr.name;
|
|
566
556
|
if (legacyPropNames.includes(name)) if (attr.value) {
|
|
567
557
|
if (attr.value.type === "StringLiteral") legacyProps[name] = attr.value.value;
|
|
@@ -695,7 +685,7 @@ const transformer = (file, api, options) => {
|
|
|
695
685
|
} else if (rawType !== void 0 || rawHtmlType !== void 0) reporter.reportAmbiguousExpression(path, typeof rawType === "string" ? "type" : "htmlType");
|
|
696
686
|
}
|
|
697
687
|
if ("sentiment" in legacyProps) {
|
|
698
|
-
if (!openingElement.attributes?.some((attr) => attr.type === "JSXAttribute" && attr.name
|
|
688
|
+
if (!openingElement.attributes?.some((attr) => attr.type === "JSXAttribute" && attr.name?.name === "sentiment")) {
|
|
699
689
|
const rawValue = legacyProps.sentiment;
|
|
700
690
|
if (rawValue === "negative") {
|
|
701
691
|
removeAttributeByName(j, openingElement, "sentiment");
|
|
@@ -729,13 +719,11 @@ const transformer = (file, api, options) => {
|
|
|
729
719
|
if ((openingElement.attributes ?? []).some((attr) => attr.type === "JSXSpreadAttribute")) reporter.reportSpreadProps(path);
|
|
730
720
|
});
|
|
731
721
|
if (manualReviewIssues.length > 0) manualReviewIssues.forEach(async (issue) => {
|
|
732
|
-
await
|
|
722
|
+
await reportManualReview(file.path, issue);
|
|
733
723
|
});
|
|
734
724
|
return root.toSource();
|
|
735
725
|
};
|
|
736
|
-
var transformer_default = transformer;
|
|
737
|
-
|
|
738
726
|
//#endregion
|
|
739
|
-
|
|
740
|
-
|
|
727
|
+
export { transformer as default, parser };
|
|
728
|
+
|
|
741
729
|
//# sourceMappingURL=transformer.js.map
|