Conversation
* Deleted `tsconfig-paths` from `package.json`, `pnpm-lock.yaml`, and `pnpm-workspace.yaml` as it is no longer needed. * Updated `turbo.json` to adjust build dependencies for better clarity and organization.
|
WalkthroughAdds a new Moonbeam ESM loader package and integrates it into the API’s OpenAPI build script. Updates ESLint config in API and Moonbeam. Adjusts Turbo build dependencies (root adds an OpenAPI pre-step; API removes local dependsOn). Documents Moonbeam’s usage and behavior. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor Dev as Developer
participant TSX as tsx (with --import @ucdjs/moonbeam/register)
participant Reg as @ucdjs/moonbeam/register
participant Ldr as Moonbeam ESM Loader
participant FS as Filesystem
participant Node as Node Resolver (nextResolve)
Dev->>TSX: run build:openapi
TSX->>Reg: import register.mjs
Reg->>Ldr: register("./esm-loader.mjs")
Note over Ldr: On import resolution
Ldr->>FS: Detect workspace root (pnpm-workspace.yaml / workspaces)
Ldr->>FS: Map packages/* by name
alt specifier matches workspace pkg or subpath
Ldr->>FS: Check src/<entry>.ts
alt src exists
Ldr-->>TSX: file://.../src/<entry>.ts (shortCircuit)
else dist exists
Ldr-->>TSX: file://.../dist/<entry>.js (shortCircuit)
else no match
Ldr->>Node: nextResolve(specifier, context)
Node-->>TSX: resolved URL
end
else non-workspace import
Ldr->>Node: nextResolve(specifier, context)
Node-->>TSX: resolved URL
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Pre-merge checks (2 passed, 1 warning)❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
Poem
✨ Finishing Touches
🧪 Generate unit tests
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Preview Deployment for WebThe Web worker has been deployed successfully. Preview URL: https://preview.ucdjs.dev This preview was built from commit 7502488 🤖 This comment will be updated automatically when you push new commits to this PR. |
Preview Deployment for ApiThe Api worker has been deployed successfully. Preview URL: https://preview.api.ucdjs.dev This preview was built from commit 7502488 🤖 This comment will be updated automatically when you push new commits to this PR. |
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
* Introduced `eslint.config.js` for linting configuration. * Updated `esm-loader.mjs` and `register.mjs` to use consistent import syntax and added TypeScript checking. * Removed commented-out code in `register.mjs` for clarity.
There was a problem hiding this comment.
Pull Request Overview
This PR introduces "moonbeam", a custom ESM loader tooling package that resolves workspace package imports to source files instead of built dist files, eliminating the need to build shared packages before generating OpenAPI schemas.
- Creates a new moonbeam tooling package with workspace-aware module resolution
- Updates the API worker to use moonbeam for OpenAPI schema generation
- Removes build dependencies from the build:openapi task in turbo configuration
Reviewed Changes
Copilot reviewed 9 out of 10 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| tooling/moonbeam/ | New package implementing ESM loader for workspace resolution |
| apps/api/package.json | Updates build:openapi script to use moonbeam loader |
| apps/api/turbo.json | Removes build dependencies from build:openapi task |
| turbo.json | Adds build:openapi as dependency for main build task |
| apps/api/eslint.config.js | Minor ESLint configuration updates |
Files not reviewed (1)
- pnpm-lock.yaml: Language not supported
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (11)
tooling/moonbeam/eslint.config.js (1)
1-6: ESLint config looks good; optionally declare this as tooling for clarity.If supported by @luxass/eslint-config, set type to "tooling" for intent and possible rule tuning.
export default luxass({ pnpm: true, + type: "tooling", });tooling/moonbeam/README.md (3)
24-27: Clarify Node.js version requirements for register API.You mention Node.js 22.18+. The node:module register API requires Node 22.x; specify the minimum exact version you rely on (e.g., >=22.6) and note that using tsx still requires a Node runtime that supports register since this module calls it at import time.
-### With Node.js 22.18+ +### With Node.js (requires >=22.6 for node:module register)node --import @ucdjs/moonbeam/register ./your-script.ts
+Note: Using `tsx --import` also runs on your system Node; ensure it’s Node >=22.6.
31-37: Mention src/dist file extension handling.Call out which extensions are tried (e.g., src: .ts/.tsx/.mts; dist: .js/.mjs), so consumers know when fallback to dist or default resolution may occur.
21-22: Add a troubleshooting tip for enabling debug logs.Since the loader logs discoveries, suggest a MOONBEAM_DEBUG flag to toggle verbosity and include a short snippet.
-```bash -tsx --import @ucdjs/moonbeam/register ./your-script.ts -``` +```bash +# Optional: enable verbose logs +MOONBEAM_DEBUG=1 tsx --import @ucdjs/moonbeam/register ./your-script.ts +```tooling/moonbeam/src/esm-loader.mjs (5)
10-34: Don’t hard-fail when no workspace root is found; degrade gracefully.Throwing here will crash any script that accidentally preloads Moonbeam outside the monorepo. Prefer returning null/undefined and no-op resolution.
-function findWorkspaceRoot(startDir = __dirname) { +function findWorkspaceRoot(startDir = __dirname) { let currentDir = startDir; @@ - throw new Error("Could not find workspace root. Make sure you have pnpm-workspace.yaml or package.json with workspaces field."); + return null; // No workspace detected; loader can no-op. }
36-60: Broaden workspace discovery and prepare simple helpers.Only scanning workspaceRoot/packages misses apps/* and tooling/* packages commonly named @ucdjs/*. Also, add a tiny file-existence helper.
-function discoverWorkspacePackages(workspaceRoot) { +function discoverWorkspacePackages(workspaceRoot) { const packages = new Map(); - const packagesDir = path.join(workspaceRoot, "packages"); + const roots = ["packages", "apps", "tooling"].map((d) => path.join(workspaceRoot, d)); - if (existsSync(packagesDir)) { - const entries = readdirSync(packagesDir, { withFileTypes: true }); - for (const entry of entries) { - if (entry.isDirectory()) { - const packageJsonPath = path.join(packagesDir, entry.name, "package.json"); + for (const root of roots) { + if (!existsSync(root)) continue; + const entries = readdirSync(root, { withFileTypes: true }); + for (const entry of entries) { + if (entry.isDirectory()) { + const packageJsonPath = path.join(root, entry.name, "package.json"); if (existsSync(packageJsonPath)) { try { const pkg = JSON.parse(readFileSync(packageJsonPath, "utf8")); if (pkg.name) { - packages.set(pkg.name, `packages/${entry.name}`); + // Store relative path from workspace root + const rel = path.relative(workspaceRoot, path.join(root, entry.name)); + packages.set(pkg.name, rel); } } catch { } } } } - } + } return packages; } + +function firstExisting(paths) { + for (const p of paths) if (existsSync(p)) return p; + return null; +}
62-71: Gate logs behind an env flag and handle “no workspace” gracefully.Avoid noisy logs and accidental crashes outside a workspace. Also, don’t shadow the imported
pathmodule in the for-of loop.-const workspaceRoot = findWorkspaceRoot(); -console.info(`🔍 Found workspace root: ${workspaceRoot}`); - -const workspacePackages = discoverWorkspacePackages(workspaceRoot); -console.info(`🌙 Moonbeam loaded - found ${workspacePackages.size} workspace packages`); - -for (const [name, path] of workspacePackages) { - console.info(` 📦 ${name} -> ${path}`); -} +const DEBUG = process.env.MOONBEAM_DEBUG === "1"; +const workspaceRoot = findWorkspaceRoot(); +const workspacePackages = workspaceRoot ? discoverWorkspacePackages(workspaceRoot) : new Map(); + +if (DEBUG && workspaceRoot) { + console.info(`🔍 Found workspace root: ${workspaceRoot}`); + console.info(`🌙 Moonbeam loaded - found ${workspacePackages.size} workspace packages`); + for (const [name, pkgPath] of workspacePackages) { + console.info(` 📦 ${name} -> ${pkgPath}`); + } +} else if (DEBUG && !workspaceRoot) { + console.info("🌙 Moonbeam disabled: no workspace root detected"); +}
72-93: Support multiple index entry points and extensions for exact package specifiers.Limit to src/index.ts and dist/index.js is brittle. Try common variants before falling back.
if (workspacePackages.has(specifier)) { const packagePath = workspacePackages.get(specifier); const srcPath = path.join(workspaceRoot, packagePath, "src"); - const indexPath = path.join(srcPath, "index.ts"); - - if (existsSync(indexPath)) { - return { - shortCircuit: true, - url: pathToFileURL(indexPath).href, - }; - } - - // fallback to dist - const distPath = path.join(workspaceRoot, packagePath, "dist", "index.js"); - if (existsSync(distPath)) { + const srcCandidates = [ + path.join(srcPath, "index.ts"), + path.join(srcPath, "index.tsx"), + path.join(srcPath, "mod.ts"), + ]; + const distCandidates = [ + path.join(workspaceRoot, packagePath, "dist", "index.js"), + path.join(workspaceRoot, packagePath, "dist", "index.mjs"), + ]; + const hit = firstExisting(srcCandidates) || firstExisting(distCandidates); + if (hit) { return { shortCircuit: true, - url: pathToFileURL(distPath).href, + url: pathToFileURL(hit).href, }; } }
95-116: Handle directory subpaths and multiple extensions.Support both files (utils.ts/tsx/mts) and directory indexes (utils/index.ts etc.), with dist fallbacks.
for (const [packageName, packagePath] of workspacePackages) { if (specifier.startsWith(`${packageName}/`)) { const subpath = specifier.slice(packageName.length + 1); - const srcPath = path.join(workspaceRoot, packagePath, "src", `${subpath}.ts`); - - if (existsSync(srcPath)) { - return { - shortCircuit: true, - url: pathToFileURL(srcPath).href, - }; - } - - const distPath = path.join(workspaceRoot, packagePath, "dist", `${subpath}.js`); - if (existsSync(distPath)) { - return { - shortCircuit: true, - url: pathToFileURL(distPath).href, - }; - } + const srcBase = path.join(workspaceRoot, packagePath, "src", subpath); + const distBase = path.join(workspaceRoot, packagePath, "dist", subpath); + const srcCandidates = [ + `${srcBase}.ts`, + `${srcBase}.tsx`, + `${srcBase}.mts`, + path.join(srcBase, "index.ts"), + path.join(srcBase, "index.tsx"), + path.join(srcBase, "index.mts"), + ]; + const distCandidates = [ + `${distBase}.js`, + `${distBase}.mjs`, + path.join(distBase, "index.js"), + path.join(distBase, "index.mjs"), + ]; + const hit = firstExisting(srcCandidates) || firstExisting(distCandidates); + if (hit) { + return { shortCircuit: true, url: pathToFileURL(hit).href }; + } } }tooling/moonbeam/package.json (2)
30-33: Optional: add a tiny sanity script for quick local check.A no-op script like "check" that prints process.versions.node can help debug engine mismatches when invoked via pnpm -w.
"scripts": { "clean": "git clean -xdf node_modules", - "lint": "eslint ." + "lint": "eslint .", + "check": "node -e \"console.log(process.versions.node)\"" },
27-29: Node engine requirement is consistent (>=22.18) across all package.json. Optional: if Node.js >=22.17 suffices for node:module.register, lower the engine floor to>=22.17and update everypackage.jsonaccordingly.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (9)
apps/api/eslint.config.js(1 hunks)apps/api/package.json(2 hunks)apps/api/turbo.json(0 hunks)tooling/moonbeam/README.md(1 hunks)tooling/moonbeam/eslint.config.js(1 hunks)tooling/moonbeam/package.json(1 hunks)tooling/moonbeam/src/esm-loader.mjs(1 hunks)tooling/moonbeam/src/register.mjs(1 hunks)turbo.json(1 hunks)
💤 Files with no reviewable changes (1)
- apps/api/turbo.json
🧰 Additional context used
📓 Path-based instructions (4)
**/src/**
📄 CodeRabbit inference engine (AGENTS.md)
Include all source files under any src directory in coverage
Files:
tooling/moonbeam/src/register.mjstooling/moonbeam/src/esm-loader.mjs
tooling/**
📄 CodeRabbit inference engine (AGENTS.md)
Exclude the tooling/* directory from coverage
Files:
tooling/moonbeam/src/register.mjstooling/moonbeam/eslint.config.jstooling/moonbeam/package.jsontooling/moonbeam/README.mdtooling/moonbeam/src/esm-loader.mjs
turbo.json
📄 CodeRabbit inference engine (AGENTS.md)
Manage build orchestration and caching via the root turbo.json
Files:
turbo.json
**/package.json
📄 CodeRabbit inference engine (AGENTS.md)
Use ESM across the codebase (package.json "type": "module")
Files:
tooling/moonbeam/package.jsonapps/api/package.json
🧠 Learnings (4)
📚 Learning: 2025-09-10T14:28:08.429Z
Learnt from: CR
PR: ucdjs/ucd#0
File: AGENTS.md:0-0
Timestamp: 2025-09-10T14:28:08.429Z
Learning: Applies to **/package.json : Use ESM across the codebase (package.json "type": "module")
Applied to files:
tooling/moonbeam/src/register.mjstooling/moonbeam/src/esm-loader.mjs
📚 Learning: 2025-09-10T14:28:08.429Z
Learnt from: CR
PR: ucdjs/ucd#0
File: AGENTS.md:0-0
Timestamp: 2025-09-10T14:28:08.429Z
Learning: Applies to turbo.json : Manage build orchestration and caching via the root turbo.json
Applied to files:
turbo.json
📚 Learning: 2025-06-28T08:01:22.596Z
Learnt from: luxass
PR: ucdjs/ucd#85
File: packages/fetch/package.json:37-38
Timestamp: 2025-06-28T08:01:22.596Z
Learning: In the ucdjs/ucd project, relative paths in npm scripts within packages (like `../../apps/api/.generated/openapi.json` in packages/fetch/package.json) resolve correctly even when run via Turborepo from the repo root, contrary to initial concerns about working directory changes.
Applied to files:
turbo.json
📚 Learning: 2025-07-20T05:37:40.565Z
Learnt from: luxass
PR: ucdjs/ucd#131
File: tooling/eslint-plugin/package.json:0-0
Timestamp: 2025-07-20T05:37:40.565Z
Learning: In the ucdjs/ucd project, internal tooling packages (private packages in the tooling/ directory) export TypeScript files directly without requiring a build step, unlike published packages which use tsdown to build and export from ./dist/. Examples include ucdjs/tsdown-config and ucdjs/eslint-plugin.
Applied to files:
turbo.jsontooling/moonbeam/package.json
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: test (windows-latest)
- GitHub Check: test (macos-latest)
🔇 Additional comments (8)
turbo.json (1)
15-18: Dependency wiring and outputs verified – no cycles introducedapps/api/eslint.config.js (1)
6-9: LGTM: app + pnpm settings.The added type: "app" and pnpm: true align with the monorepo setup and our learnings around pnpm-aware configs.
tooling/moonbeam/README.md (1)
13-16: Document directory subpath resolution.Moonbeam resolves subpaths like @ucdjs/package/utils. Explicitly state whether utils/index.ts is supported in addition to utils.ts, and whether .tsx/.mts are recognized.
If not yet supported, align the README with current behavior or extend the loader (see suggestions in src/esm-loader.mjs comments).
tooling/moonbeam/src/esm-loader.mjs (1)
118-120: Fallback behavior is correct.Deferring to nextResolve preserves default Node/tsx semantics when Moonbeam doesn’t match.
tooling/moonbeam/src/register.mjs (1)
3-5: Loader registration looks correct and minimal.Using node:module.register with import.meta.url and a relative specifier is the right pattern here.
apps/api/package.json (2)
39-39: Dev dependency addition is appropriate.Adding @ucdjs/moonbeam as a workspace devDependency matches usage via --import.
10-10: No change required: TSX already uses apps/api/tsconfig.json by default.
TSX will automatically locate and applyapps/api/tsconfig.jsonwhen running the script, so adding an explicit--tsconfigflag is redundant.tooling/moonbeam/package.json (1)
22-26: Exports map is well-scoped.Explicit subpath exports for ./register and ./esm are clear and avoid accidental top-level imports.
🔗 Linked issue
📚 Description
This PR introduces a new tooling package called "moonbeam" (funny name i know). The job of this tooling package is to make the gap between the tsx and the build:openapi better.
Currently with the raw tsx setup, we need to build the shared package before we can build the openapi schema, this is because the tsx resolver resolves the import to the "@ucdjs/shared" dist instead of the raw source files.
So by migrating to moonbeam, we can get rid of this headache since it resolves it to the source files, and therefore doesn't require any packages to be built before the openapi schema.
This PR is generated using Claude Code, but based on a idea of mine.
Summary by CodeRabbit
New Features
Documentation
Chores