@wocha/upgrade 0.1.0

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/README.md ADDED
@@ -0,0 +1,120 @@
1
+ # @wocha/upgrade
2
+
3
+ Automated codemods for upgrading `@wocha/*` SDK packages. Detects installed versions, applies AST-level transforms for breaking changes, and updates `package.json` dependency ranges.
4
+
5
+ ## Usage
6
+
7
+ ```bash
8
+ npx @wocha/upgrade
9
+ ```
10
+
11
+ From the monorepo during development:
12
+
13
+ ```bash
14
+ pnpm --filter @wocha/upgrade build
15
+ node tools/upgrade/dist/index.js
16
+ ```
17
+
18
+ ### Example output
19
+
20
+ ```
21
+ @wocha/upgrade v0.1.0
22
+
23
+ Detected packages:
24
+ @wocha/react 0.1.0 → 0.2.0
25
+ @wocha/nextjs 0.1.0 → 0.2.0
26
+
27
+ Codemods to apply:
28
+ ✓ rename-provider (affects 3 files)
29
+ ✓ update-hook-return (affects 5 files)
30
+ ✓ middleware-config (affects 1 file)
31
+
32
+ ? Apply changes? (Y/n)
33
+
34
+ Applied 3 codemods across 9 files.
35
+ Updated package.json versions.
36
+
37
+ Run `pnpm install` to complete the upgrade.
38
+ ```
39
+
40
+ ## Flags
41
+
42
+ | Flag | Description |
43
+ |------|-------------|
44
+ | `--dry-run` | Preview codemod changes without writing files or updating `package.json` |
45
+ | `--package <name>` | Upgrade a single package, e.g. `@wocha/react` |
46
+ | `--from <version>` | Override the detected current version |
47
+ | `--to <version>` | Target version (default: latest from npm registry) |
48
+ | `--cwd <path>` | Project directory to upgrade (default: current directory) |
49
+ | `--yes`, `-y` | Apply changes without prompting |
50
+ | `--help`, `-h` | Show help |
51
+
52
+ ## How it works
53
+
54
+ 1. **Detect** — Reads `package.json` and finds all `@wocha/*` dependencies.
55
+ 2. **Compare** — Queries the npm registry for the latest version of each package.
56
+ 3. **Plan** — Selects codemods whose `version` falls between your current and target versions.
57
+ 4. **Preview** — Runs each transform in dry mode to count affected files.
58
+ 5. **Apply** — Runs jscodeshift transforms on matching TypeScript/JSX files and bumps dependency versions.
59
+
60
+ ## Writing custom codemods
61
+
62
+ Codemods live in `src/codemods/<version>/` and export a default jscodeshift transform:
63
+
64
+ ```typescript
65
+ import type { API, FileInfo, Options } from "jscodeshift";
66
+
67
+ export default function myTransform(file: FileInfo, api: API, _options: Options) {
68
+ const j = api.jscodeshift.withParser("tsx");
69
+ const root = j(file.source);
70
+ let changed = false;
71
+
72
+ // ... AST mutations ...
73
+
74
+ return changed ? root.toSource() : null;
75
+ }
76
+ ```
77
+
78
+ Register the codemod in `src/registry.ts`:
79
+
80
+ ```typescript
81
+ {
82
+ id: "my-codemod",
83
+ version: "0.3.0",
84
+ package: "@wocha/react",
85
+ description: "Short description of the breaking change",
86
+ transform: "codemods/v0.3/my-codemod.js",
87
+ }
88
+ ```
89
+
90
+ The `version` field is the SDK release that **introduces** the breaking change. Codemods run when upgrading from a version **below** `version` to a version **at or above** it.
91
+
92
+ Add the file to `tsup.config.ts` `entry` array so it is compiled to `dist/`.
93
+
94
+ ## Contributing new codemods
95
+
96
+ 1. Create a transform under `src/codemods/v<release>/`.
97
+ 2. Add an entry to `src/registry.ts` with the target package and release version.
98
+ 3. Add the file path to `tsup.config.ts` entries.
99
+ 4. Test against a sample project:
100
+
101
+ ```bash
102
+ pnpm build
103
+ node dist/index.js --dry-run --from 0.1.0 --to 0.2.0 --cwd ../../examples/react-quickstart
104
+ ```
105
+
106
+ 5. Document the breaking change in the SDK CHANGELOG.
107
+
108
+ ### Included example codemods (v0.1 → v0.2)
109
+
110
+ | ID | Package | Change |
111
+ |----|---------|--------|
112
+ | `rename-provider` | `@wocha/react`, `@wocha/nextjs` | `WochaProvider` → `WochaAuthProvider` |
113
+ | `update-hook-return` | `@wocha/react`, `@wocha/nextjs` | `useSession().session` → `useSession().data` |
114
+ | `middleware-config` | `@wocha/nextjs` | `withWochaAuth({ publicPaths })` → `{ publicRoutes }` |
115
+
116
+ These represent hypothetical breaking changes for demonstration purposes.
117
+
118
+ ## License
119
+
120
+ Apache-2.0
@@ -0,0 +1,30 @@
1
+ // src/codemods/v0.2/middleware-config.ts
2
+ function renamePublicPathsKey(node) {
3
+ let changed = false;
4
+ for (const prop of node.properties) {
5
+ const isProperty = prop.type === "Property" || prop.type === "ObjectProperty";
6
+ if (isProperty && prop.key.type === "Identifier" && prop.key.name === "publicPaths") {
7
+ prop.key.name = "publicRoutes";
8
+ changed = true;
9
+ }
10
+ }
11
+ return changed;
12
+ }
13
+ function middlewareConfig(file, api, _options) {
14
+ const j = api.jscodeshift.withParser("tsx");
15
+ const root = j(file.source);
16
+ let changed = false;
17
+ root.find(j.CallExpression, {
18
+ callee: { type: "Identifier", name: "withWochaAuth" }
19
+ }).forEach((path) => {
20
+ for (const arg of path.node.arguments) {
21
+ if (arg?.type === "ObjectExpression" && renamePublicPathsKey(arg)) {
22
+ changed = true;
23
+ }
24
+ }
25
+ });
26
+ return changed ? root.toSource() : null;
27
+ }
28
+ export {
29
+ middlewareConfig as default
30
+ };
@@ -0,0 +1,46 @@
1
+ // src/codemods/v0.2/rename-provider.ts
2
+ var WOCHA_PACKAGES = ["@wocha/react", "@wocha/nextjs"];
3
+ function renameProvider(file, api, _options) {
4
+ const j = api.jscodeshift.withParser("tsx");
5
+ const root = j(file.source);
6
+ let changed = false;
7
+ root.find(j.ImportDeclaration).forEach((path) => {
8
+ const source = path.node.source.value;
9
+ if (typeof source !== "string" || !WOCHA_PACKAGES.includes(source)) {
10
+ return;
11
+ }
12
+ path.node.specifiers?.forEach((spec) => {
13
+ if (spec.type === "ImportSpecifier" && spec.imported.type === "Identifier" && spec.imported.name === "WochaProvider") {
14
+ spec.imported.name = "WochaAuthProvider";
15
+ if (spec.local?.type === "Identifier" && spec.local.name === "WochaProvider") {
16
+ spec.local.name = "WochaAuthProvider";
17
+ }
18
+ changed = true;
19
+ }
20
+ });
21
+ });
22
+ root.find(j.JSXIdentifier, { name: "WochaProvider" }).forEach((path) => {
23
+ path.node.name = "WochaAuthProvider";
24
+ changed = true;
25
+ });
26
+ root.find(j.Identifier, { name: "WochaProvider" }).forEach((path) => {
27
+ if (j(path).closest(j.ImportDeclaration).size() > 0) {
28
+ return;
29
+ }
30
+ if (j(path).closest(j.JSXOpeningElement).size() > 0) {
31
+ return;
32
+ }
33
+ if (j(path).closest(j.JSXClosingElement).size() > 0) {
34
+ return;
35
+ }
36
+ if (j(path).closest(j.JSXMemberExpression).size() > 0) {
37
+ return;
38
+ }
39
+ path.node.name = "WochaAuthProvider";
40
+ changed = true;
41
+ });
42
+ return changed ? root.toSource() : null;
43
+ }
44
+ export {
45
+ renameProvider as default
46
+ };
@@ -0,0 +1,23 @@
1
+ // src/codemods/v0.2/update-hook-return.ts
2
+ function updateHookReturn(file, api, _options) {
3
+ const j = api.jscodeshift.withParser("tsx");
4
+ const root = j(file.source);
5
+ let changed = false;
6
+ root.find(j.CallExpression, {
7
+ callee: { type: "Identifier", name: "useSession" }
8
+ }).forEach((callPath) => {
9
+ const parent = callPath.parent;
10
+ if (parent.node.type !== "MemberExpression") {
11
+ return;
12
+ }
13
+ const member = parent.node;
14
+ if (member.property.type === "Identifier" && member.property.name === "session" && member.object === callPath.node) {
15
+ member.property.name = "data";
16
+ changed = true;
17
+ }
18
+ });
19
+ return changed ? root.toSource() : null;
20
+ }
21
+ export {
22
+ updateHookReturn as default
23
+ };
@@ -0,0 +1,2 @@
1
+
2
+ export { }
package/dist/index.js ADDED
@@ -0,0 +1,442 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/cli.ts
4
+ import chalk from "chalk";
5
+ import { existsSync } from "fs";
6
+ import { join as join3 } from "path";
7
+ import prompts from "prompts";
8
+
9
+ // src/detect.ts
10
+ import { readFileSync, writeFileSync } from "fs";
11
+ import { join } from "path";
12
+ import { execSync } from "child_process";
13
+ import semver2 from "semver";
14
+
15
+ // src/registry.ts
16
+ import semver from "semver";
17
+ var codemods = [
18
+ {
19
+ id: "rename-provider",
20
+ version: "0.2.0",
21
+ package: "@wocha/react",
22
+ description: "Renames WochaProvider to WochaAuthProvider",
23
+ transform: "codemods/v0.2/rename-provider.js"
24
+ },
25
+ {
26
+ id: "rename-provider-nextjs",
27
+ version: "0.2.0",
28
+ package: "@wocha/nextjs",
29
+ description: "Renames WochaProvider to WochaAuthProvider",
30
+ transform: "codemods/v0.2/rename-provider.js"
31
+ },
32
+ {
33
+ id: "update-hook-return",
34
+ version: "0.2.0",
35
+ package: "@wocha/react",
36
+ description: "Changes useSession().session to useSession().data",
37
+ transform: "codemods/v0.2/update-hook-return.js"
38
+ },
39
+ {
40
+ id: "update-hook-return-nextjs",
41
+ version: "0.2.0",
42
+ package: "@wocha/nextjs",
43
+ description: "Changes useSession().session to useSession().data",
44
+ transform: "codemods/v0.2/update-hook-return.js"
45
+ },
46
+ {
47
+ id: "middleware-config",
48
+ version: "0.2.0",
49
+ package: "@wocha/nextjs",
50
+ description: "Renames publicPaths to publicRoutes in withWochaAuth config",
51
+ transform: "codemods/v0.2/middleware-config.js"
52
+ }
53
+ ];
54
+ function codemodsForUpgrade(pkg, fromVersion, toVersion) {
55
+ return codemods.filter((mod) => {
56
+ if (mod.package !== pkg) {
57
+ return false;
58
+ }
59
+ return semver.gt(mod.version, fromVersion) && semver.lte(mod.version, toVersion);
60
+ });
61
+ }
62
+ function uniqueCodemods(mods) {
63
+ const seen = /* @__PURE__ */ new Set();
64
+ const result = [];
65
+ for (const mod of mods) {
66
+ if (seen.has(mod.transform)) {
67
+ continue;
68
+ }
69
+ seen.add(mod.transform);
70
+ result.push(mod);
71
+ }
72
+ return result;
73
+ }
74
+
75
+ // src/detect.ts
76
+ var WOCHA_SCOPE = "@wocha/";
77
+ function readPackageJson(cwd) {
78
+ const path = join(cwd, "package.json");
79
+ const raw = readFileSync(path, "utf8");
80
+ return JSON.parse(raw);
81
+ }
82
+ function collectGreetPackages(pkg) {
83
+ const sections = [pkg.dependencies, pkg.devDependencies, pkg.peerDependencies];
84
+ const found = /* @__PURE__ */ new Map();
85
+ for (const section of sections) {
86
+ if (!section) {
87
+ continue;
88
+ }
89
+ for (const [name, range] of Object.entries(section)) {
90
+ if (name.startsWith(WOCHA_SCOPE)) {
91
+ found.set(name, range);
92
+ }
93
+ }
94
+ }
95
+ return found;
96
+ }
97
+ function normalizeVersion(range) {
98
+ const cleaned = range.replace(/^[\^~>=<]+/, "");
99
+ const coerced = semver2.coerce(cleaned);
100
+ return coerced?.version ?? cleaned;
101
+ }
102
+ async function fetchLatestVersion(packageName) {
103
+ try {
104
+ const output = execSync(`npm view ${packageName} version --json`, {
105
+ encoding: "utf8",
106
+ stdio: ["pipe", "pipe", "pipe"]
107
+ }).trim();
108
+ const parsed = JSON.parse(output);
109
+ const version = Array.isArray(parsed) ? parsed[parsed.length - 1] : parsed;
110
+ if (semver2.valid(version)) {
111
+ return version;
112
+ }
113
+ } catch {
114
+ }
115
+ try {
116
+ const res = await fetch(`https://registry.npmjs.org/${packageName}/latest`);
117
+ if (res.ok) {
118
+ const data = await res.json();
119
+ if (data.version && semver2.valid(data.version)) {
120
+ return data.version;
121
+ }
122
+ }
123
+ } catch {
124
+ }
125
+ throw new Error(`Could not resolve latest version for ${packageName}`);
126
+ }
127
+ async function detectUpgrades(options) {
128
+ const pkgJson = readPackageJson(options.cwd);
129
+ const greetPackages = collectGreetPackages(pkgJson);
130
+ const results = [];
131
+ for (const [name, range] of greetPackages) {
132
+ if (options.packageFilter && name !== options.packageFilter) {
133
+ continue;
134
+ }
135
+ const currentVersion = options.fromVersion ?? normalizeVersion(range);
136
+ let latestVersion;
137
+ if (options.toVersion) {
138
+ latestVersion = options.toVersion;
139
+ } else {
140
+ try {
141
+ latestVersion = await fetchLatestVersion(name);
142
+ } catch {
143
+ continue;
144
+ }
145
+ }
146
+ if (!semver2.valid(currentVersion) || !semver2.valid(latestVersion)) {
147
+ continue;
148
+ }
149
+ if (semver2.gte(currentVersion, latestVersion)) {
150
+ continue;
151
+ }
152
+ const applicable = codemodsForUpgrade(name, currentVersion, latestVersion);
153
+ results.push({
154
+ package: name,
155
+ currentVersion,
156
+ latestVersion,
157
+ codemods: applicable
158
+ });
159
+ }
160
+ return results;
161
+ }
162
+ function updatePackageJsonVersions(cwd, upgrades, dryRun) {
163
+ const path = join(cwd, "package.json");
164
+ const pkgJson = readPackageJson(cwd);
165
+ let changed = false;
166
+ for (const upgrade of upgrades) {
167
+ const sections = ["dependencies", "devDependencies", "peerDependencies"];
168
+ for (const section of sections) {
169
+ const deps = pkgJson[section];
170
+ if (deps?.[upgrade.package]) {
171
+ const next = `^${upgrade.latestVersion}`;
172
+ if (deps[upgrade.package] !== next) {
173
+ deps[upgrade.package] = next;
174
+ changed = true;
175
+ }
176
+ }
177
+ }
178
+ }
179
+ if (changed && !dryRun) {
180
+ writeFileSync(path, `${JSON.stringify(pkgJson, null, 2)}
181
+ `, "utf8");
182
+ }
183
+ return changed;
184
+ }
185
+
186
+ // src/runner.ts
187
+ import { readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
188
+ import { dirname, join as join2 } from "path";
189
+ import { fileURLToPath, pathToFileURL } from "url";
190
+ import { globSync } from "glob";
191
+ import jscodeshift from "jscodeshift";
192
+ var __dirname = dirname(fileURLToPath(import.meta.url));
193
+ var FILE_GLOBS = [
194
+ "**/*.{ts,tsx,js,jsx,mjs,cjs}",
195
+ "!**/node_modules/**",
196
+ "!**/dist/**",
197
+ "!**/.next/**",
198
+ "!**/build/**",
199
+ "!**/coverage/**"
200
+ ];
201
+ function findSourceFiles(cwd) {
202
+ return globSync(FILE_GLOBS, { cwd, absolute: true, nodir: true });
203
+ }
204
+ async function loadTransform(transformPath) {
205
+ const absolute = join2(__dirname, transformPath);
206
+ const mod = await import(pathToFileURL(absolute).href);
207
+ return mod.default;
208
+ }
209
+ async function applyTransform(filePath, source, transform) {
210
+ const api = {
211
+ jscodeshift,
212
+ j: jscodeshift,
213
+ stats: () => {
214
+ },
215
+ report: () => {
216
+ }
217
+ };
218
+ const result = transform({ path: filePath, source }, api, {});
219
+ const resolved = result instanceof Promise ? await result : result;
220
+ if (typeof resolved !== "string" || resolved === source) {
221
+ return null;
222
+ }
223
+ return resolved;
224
+ }
225
+ async function previewCodemod(codemod, cwd) {
226
+ const transform = await loadTransform(codemod.transform);
227
+ const files = findSourceFiles(cwd);
228
+ const affected = [];
229
+ for (const file of files) {
230
+ const source = readFileSync2(file, "utf8");
231
+ const output = await applyTransform(file, source, transform);
232
+ if (output !== null) {
233
+ affected.push(file);
234
+ }
235
+ }
236
+ return affected;
237
+ }
238
+ async function planCodemods(codemods2, cwd) {
239
+ const plans = [];
240
+ for (const codemod of codemods2) {
241
+ const affectedFiles = await previewCodemod(codemod, cwd);
242
+ plans.push({ codemod, affectedFiles });
243
+ }
244
+ return plans;
245
+ }
246
+ async function runCodemods(plans, _cwd, dryRun) {
247
+ const results = [];
248
+ for (const plan of plans) {
249
+ if (plan.affectedFiles.length === 0) {
250
+ continue;
251
+ }
252
+ const transform = await loadTransform(plan.codemod.transform);
253
+ const filesModified = [];
254
+ for (const file of plan.affectedFiles) {
255
+ const source = readFileSync2(file, "utf8");
256
+ const output = await applyTransform(file, source, transform);
257
+ if (output !== null) {
258
+ filesModified.push(file);
259
+ if (!dryRun) {
260
+ writeFileSync2(file, output, "utf8");
261
+ }
262
+ }
263
+ }
264
+ results.push({
265
+ codemodId: plan.codemod.id,
266
+ filesModified,
267
+ dryRun
268
+ });
269
+ }
270
+ return results;
271
+ }
272
+
273
+ // src/cli.ts
274
+ var VERSION = "0.1.0";
275
+ function parseArgs(argv) {
276
+ const options = {
277
+ cwd: process.cwd(),
278
+ dryRun: false,
279
+ yes: false
280
+ };
281
+ for (let i = 0; i < argv.length; i++) {
282
+ const arg = argv[i];
283
+ switch (arg) {
284
+ case "--dry-run":
285
+ options.dryRun = true;
286
+ break;
287
+ case "--yes":
288
+ case "-y":
289
+ options.yes = true;
290
+ break;
291
+ case "--package":
292
+ options.packageFilter = argv[++i];
293
+ break;
294
+ case "--from":
295
+ options.fromVersion = argv[++i];
296
+ break;
297
+ case "--to":
298
+ options.toVersion = argv[++i];
299
+ break;
300
+ case "--cwd":
301
+ options.cwd = argv[++i] ?? process.cwd();
302
+ break;
303
+ case "--help":
304
+ case "-h":
305
+ printHelp();
306
+ process.exit(0);
307
+ break;
308
+ default:
309
+ break;
310
+ }
311
+ }
312
+ return options;
313
+ }
314
+ function printHelp() {
315
+ console.log(`
316
+ Usage: wocha-upgrade [options]
317
+
318
+ Automated codemods for upgrading @wocha SDK packages.
319
+
320
+ Options:
321
+ --dry-run Preview changes without writing files
322
+ --package <name> Upgrade a specific @wocha package
323
+ --from <version> Override detected current version
324
+ --to <version> Target version (default: latest from npm)
325
+ --cwd <path> Project directory (default: current directory)
326
+ --yes, -y Apply changes without prompting
327
+ -h, --help Show this help message
328
+ `);
329
+ }
330
+ function collectCodemods(upgrades) {
331
+ const all = [];
332
+ for (const upgrade of upgrades) {
333
+ all.push(...upgrade.codemods);
334
+ }
335
+ return uniqueCodemods(all);
336
+ }
337
+ async function runCli(argv = process.argv.slice(2)) {
338
+ const options = parseArgs(argv);
339
+ if (!existsSync(join3(options.cwd, "package.json"))) {
340
+ console.error(chalk.red("Error: No package.json found in the current directory."));
341
+ return 1;
342
+ }
343
+ console.log();
344
+ console.log(chalk.bold(` @wocha/upgrade v${VERSION}`));
345
+ console.log();
346
+ let upgrades;
347
+ try {
348
+ upgrades = await detectUpgrades({
349
+ cwd: options.cwd,
350
+ packageFilter: options.packageFilter,
351
+ fromVersion: options.fromVersion,
352
+ toVersion: options.toVersion
353
+ });
354
+ } catch (err) {
355
+ console.error(chalk.red(`Error: ${err instanceof Error ? err.message : String(err)}`));
356
+ return 1;
357
+ }
358
+ if (upgrades.length === 0) {
359
+ console.log(chalk.dim(" No @wocha packages need upgrading."));
360
+ console.log();
361
+ return 0;
362
+ }
363
+ console.log(chalk.bold(" Detected packages:"));
364
+ for (const upgrade of upgrades) {
365
+ console.log(
366
+ ` ${upgrade.package.padEnd(22)} ${chalk.yellow(upgrade.currentVersion)} \u2192 ${chalk.green(upgrade.latestVersion)}`
367
+ );
368
+ }
369
+ console.log();
370
+ const codemodList = collectCodemods(upgrades);
371
+ if (codemodList.length === 0) {
372
+ console.log(chalk.dim(" No codemods required for this upgrade."));
373
+ console.log();
374
+ return 0;
375
+ }
376
+ const plans = await planCodemods(codemodList, options.cwd);
377
+ console.log(chalk.bold(" Codemods to apply:"));
378
+ for (const plan of plans) {
379
+ const count = plan.affectedFiles.length;
380
+ const marker = count > 0 ? chalk.green("\u2713") : chalk.dim("\u25CB");
381
+ const suffix = count === 1 ? "1 file" : count > 0 ? `${count} files` : "no matching files";
382
+ console.log(` ${marker} ${plan.codemod.id} ${chalk.dim(`(affects ${suffix})`)}`);
383
+ }
384
+ console.log();
385
+ if (options.dryRun) {
386
+ console.log(chalk.cyan(" Dry run \u2014 no files will be modified."));
387
+ console.log();
388
+ }
389
+ const hasChanges = plans.some((plan) => plan.affectedFiles.length > 0);
390
+ if (!hasChanges) {
391
+ console.log(chalk.dim(" No source files matched the codemods."));
392
+ console.log();
393
+ return 0;
394
+ }
395
+ let shouldApply = options.yes;
396
+ if (!shouldApply && !options.dryRun) {
397
+ const response = await prompts({
398
+ type: "confirm",
399
+ name: "apply",
400
+ message: "Apply changes?",
401
+ initial: true
402
+ });
403
+ shouldApply = response.apply === true;
404
+ } else if (options.dryRun) {
405
+ shouldApply = true;
406
+ }
407
+ if (!shouldApply) {
408
+ console.log(chalk.dim(" Upgrade cancelled."));
409
+ console.log();
410
+ return 0;
411
+ }
412
+ const results = await runCodemods(plans, options.cwd, options.dryRun);
413
+ const totalFiles = new Set(results.flatMap((r) => r.filesModified)).size;
414
+ const appliedCodemods = results.filter((r) => r.filesModified.length > 0).length;
415
+ console.log();
416
+ if (options.dryRun) {
417
+ console.log(
418
+ chalk.cyan(
419
+ ` Would apply ${appliedCodemods} codemod${appliedCodemods === 1 ? "" : "s"} across ${totalFiles} file${totalFiles === 1 ? "" : "s"}.`
420
+ )
421
+ );
422
+ } else {
423
+ console.log(
424
+ chalk.green(
425
+ ` Applied ${appliedCodemods} codemod${appliedCodemods === 1 ? "" : "s"} across ${totalFiles} file${totalFiles === 1 ? "" : "s"}.`
426
+ )
427
+ );
428
+ const pkgUpdated = updatePackageJsonVersions(options.cwd, upgrades, false);
429
+ if (pkgUpdated) {
430
+ console.log(chalk.green(" Updated package.json versions."));
431
+ }
432
+ console.log();
433
+ console.log(chalk.dim(" Run `pnpm install` to complete the upgrade."));
434
+ }
435
+ console.log();
436
+ return 0;
437
+ }
438
+
439
+ // src/index.ts
440
+ runCli().then((code) => {
441
+ process.exit(code);
442
+ });
package/package.json ADDED
@@ -0,0 +1,59 @@
1
+ {
2
+ "name": "@wocha/upgrade",
3
+ "version": "0.1.0",
4
+ "description": "Automated codemods for upgrading @wocha SDK packages",
5
+ "author": "Wocha",
6
+ "license": "Apache-2.0",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/wocha-dev/wocha.git",
10
+ "directory": "tools/upgrade"
11
+ },
12
+ "homepage": "https://github.com/wocha-dev/wocha/tree/main/tools/upgrade#readme",
13
+ "bugs": {
14
+ "url": "https://github.com/wocha-dev/wocha/issues"
15
+ },
16
+ "keywords": [
17
+ "wocha",
18
+ "authentication",
19
+ "codemod",
20
+ "upgrade",
21
+ "migration"
22
+ ],
23
+ "type": "module",
24
+ "main": "dist/index.js",
25
+ "types": "dist/index.d.ts",
26
+ "files": [
27
+ "dist"
28
+ ],
29
+ "publishConfig": {
30
+ "access": "public"
31
+ },
32
+ "bin": {
33
+ "wocha-upgrade": "./dist/index.js"
34
+ },
35
+ "scripts": {
36
+ "build": "tsup",
37
+ "dev": "tsup --watch",
38
+ "typecheck": "tsc --noEmit",
39
+ "prepublishOnly": "pnpm build"
40
+ },
41
+ "dependencies": {
42
+ "chalk": "^5.3.0",
43
+ "glob": "^11.0.0",
44
+ "jscodeshift": "^17.0.0",
45
+ "prompts": "^2.4.2",
46
+ "semver": "^7.6.0"
47
+ },
48
+ "devDependencies": {
49
+ "@types/jscodeshift": "^17.0.0",
50
+ "@types/node": "^22.0.0",
51
+ "@types/prompts": "^2.4.9",
52
+ "@types/semver": "^7.5.8",
53
+ "tsup": "^8.0.0",
54
+ "typescript": "^5.8.0"
55
+ },
56
+ "engines": {
57
+ "node": ">=18"
58
+ }
59
+ }