@intuitionrobotics/thunderstorm-codemod
v2.0.2
Published
One-shot migration tool for v0.x to v1.0 of the @intuitionrobotics/* framework. Rewrites legacy module.exports = ... API/aggregator/re-export files to ESM-style export default, and generates static RouteResolver manifests from api/ directories.
Readme
@intuitionrobotics/thunderstorm-codemod
One-shot migration tools for upgrading downstream consumers of the
@intuitionrobotics/* framework. Ships three CLI binaries, all designed
to be run once per repository (and then deleted from your dev deps).
pnpm dlx @intuitionrobotics/thunderstorm-codemod migrate ./src/main/api
pnpm dlx @intuitionrobotics/thunderstorm-codemod bootstrap-routes ./src/main/api --out ./src/main/api/routes.ts
pnpm dlx @intuitionrobotics/thunderstorm-codemod check-routes ./src/main/api --routes ./src/main/api/routes.tsThe two
bootstrap-routes/check-routesflags above are the short form of the binsthunderstorm-bootstrap-routesandthunderstorm-check-routes— both are also published with their ownbinentry, so you can wirecheck-routesinto CI directly without going through the umbrella command.
What each tool does
thunderstorm-codemod migrate <api-dir>
Two transformations in one pass.
Pass 1 — module.exports rewrite (v0.x → v1.0). Idempotent; safe to
run on any tree.
| Before | After |
|---|---|
| module.exports = new ServerApi_X(); | export default new ServerApi_X(); |
| module.exports = [a, b, c]; | export default [a, b, c]; |
| module.exports = expr; (anything else) | export default expr; |
| module.exports = require("X"); | import _reexport from "X";export default _reexport; |
Preserves whitespace, comments, and existing imports. Regex-based; multi-line
or conditional module.exports are left alone — review with --dry-run
first if you have an unusual layout.
Pass 2 — strip super("path") (v1.x → v2.0). Removes the URL fragment
from every ServerApi_* subclass's constructor. The captured leaf path is
recorded in <api-dir>/.thunderstorm-routes-sidecar.json so the next step,
bootstrap-routes, knows where to mount each endpoint.
| Parent class | Captured arg |
|---|---|
| extends ServerApi_Get<...> | super("leaf") → arg 0 |
| extends ServerApi_Post<...> | super("leaf") → arg 0 |
| extends ServerApi_Redirect | super("leaf", code, url) → arg 0 |
| extends ServerApi<...> | super(METHOD, "leaf"[, tag]) → arg 1 |
Idempotent. Files with super() already (or no string literal at the path
slot) are left untouched.
thunderstorm-bootstrap-routes <api-dir> --out <file.ts>
Reads the sidecar from migrate plus the filesystem layout of <api-dir>
and writes a single Express-Router-tree routes file:
// Auto-generated by thunderstorm-bootstrap-routes — initial seed.
// Hand-edit this from here. The tool runs ONCE; do not regenerate.
import {Router} from "express";
import {mount} from "@intuitionrobotics/thunderstorm/backend";
import register from "./v1/register.js";
import endpointExample from "./v1/types-testing/post-without-response-endpoint.js";
const typesTesting = Router();
mount(typesTesting, "/post-without-response-endpoint", endpointExample);
const v1 = Router();
mount(v1, "/register", register);
v1.use("/types-testing", typesTesting);
export const apiRoutes = Router();
apiRoutes.use("/v1", v1);Conventions inherited from the v0/v1 walker:
_<name>.tsfiles are skipped (private helpers — there's no v2 equivalent).&<name>.tsfiles were RouteResolver aggregators in v1. They are omitted from the generated routes file. Delete them from your repo after the seed is written; they're vestigial.- Directory names become Express Router subtrees mounted at
/{dirname}.
After bootstrap-routes writes the file: edit it freely. The tool
runs once. From here on, adding an endpoint means writing the handler
file and adding one mount(...) line in this routes file.
thunderstorm-check-routes <api-dir> --routes <file.ts>
CI guard. Verifies every leaf endpoint in <api-dir> is mounted somewhere
in the routes file.
- Resolves every
import X from "./..."in the routes file to a leaf path. - Finds every
mount(router, path, identifier)call. - If the same identifier is destructured (
const [a, b] = importedThing;), the underlying file is considered mounted — destructure-then-mount-each is a supported pattern for files whose default export is an array of endpoints. - Exits 0 if all leaves are mounted; exits 1 with
file:linelines for the missing ones; exits 2 on bad CLI args.
End-to-end migration flow
# 1. Inside your consumer repo, on a clean branch:
pnpm dlx @intuitionrobotics/thunderstorm-codemod migrate ./src/main/api
# 2. Generate the initial Express routes file:
pnpm dlx @intuitionrobotics/thunderstorm-codemod bootstrap-routes \
./src/main/api --out ./src/main/api/routes.ts
# 3. Delete legacy aggregators the tool flagged but left in place:
find ./src/main/api -name '&*.ts' -delete
# 4. Update your server entrypoint:
# - .setInitialRouteResolver(new RouteResolver())
# - .registerApis(...)
# + .setRoutes(apiRoutes) // import from ./api/routes.js
# + (everything else is unchanged)
# 5. Add the CI guard. Once it passes, commit and remove the codemod
# from your devDependencies — its job is done.
pnpm dlx @intuitionrobotics/thunderstorm-codemod check-routes \
./src/main/api --routes ./src/main/api/routes.tsKnown limitations
- Array-of-endpoints files. A file like
export default [new ApiA(), new ApiB()];is captured in the sidecar with one entry per class, but the bootstrap emits a single import. Hand-edit the generated routes file to destructure:const [a, b] = arrayFile; mount(r, "/a", a); mount(r, "/b", b);.check-routesthen accepts this pattern. - Direct
new ServerApi_Redirect("path", code, url)constructions. Not yet rewritten by the codemod (only subclasses are). The bootstrap falls back to the filename for the mount path. Hand-edit if you need the original URL fragment preserved. - Non-string path literals. If
super(METHOD, somePath)uses a variable or expression instead of a string literal, the codemod leaves the file alone. Fix manually. - Bare
.tsfiles only..tsx,.js,.cjs,.d.ts, and.test.tsare skipped. - Skips
node_modules,dist,build,.git. Hidden directories (.<name>) too.
Exit codes
| Code | Meaning | |---|---| | 0 | success | | 1 | one or more files failed to read/transform/write, or an endpoint was unmounted | | 2 | invalid CLI arguments |
Bins
The package exposes four binaries:
| Bin | Purpose |
|---|---|
| thunderstorm-codemod | umbrella, currently supports migrate |
| thunderstorm-bootstrap-routes | one-shot Express routes-file seed |
| thunderstorm-check-routes | CI guard: every leaf is mounted |
| thunderstorm-gen-routes | legacy v1.0 RouteResolver-tree generator (deprecated; kept for repos that haven't migrated yet) |
