Repository navigation
feat(cli): ✨ vitnode generate for plugin API modules, routes and services - #917
aXenDeveloper wants to merge 45 commits into
Conversation
…ices Adds `vitnode generate` (alias `g`) with `module`, `route` and `service`: - A shared planner holds every create/update in memory, formats it with the project's own oxfmt, prints the real diff under --dry-run, re-checks files before writing and rolls back a failed batch. - Sources are read with oxc-parser and edited with span-based insertions, so comments, options and the VitNodeApiPlugin contract are preserved. Module and route registrations stay explicit; unsupported structures stop before writing with exact manual steps. - Project resolution: the current plugin, --project <name|path>, or the only/selected API plugin in the workspace. - Routes separate file name from HTTP path, refuse method/path collisions, return labelled sample data for GET and 501 for unimplemented writes. - Admin routes require an explicit staff permission, validated against permissionStaff; --declare-permission adds it and its AdminCP labels. - Services are plain factories over explicit dependencies. Docs: CLI reference plus how-tos for modules, endpoints, admin endpoints and services. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CnAiFZ3bExobTQrGyYnt3P
Composes the module, route and permission generators into one plan that writes a whole API resource or nothing: - `<name>.schema.ts` with item/create/update Zod schemas and inferred types (fields from --fields, or an id-only scaffold that says so); - `<name>.service.ts` with typed operation stubs that reject with 501 - no fabricated records or success values; - list/show/create/update/delete routes calling the service, with 400/404/501 in their contracts; - admin resources guard each operation with its own permission (can_view/can_create/can_edit/can_delete) from --permission-module. `--kind content` is refused with an explanation: Content Engine resources are not generated yet. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CnAiFZ3bExobTQrGyYnt3P
- `vitnode doctor` runs the existing plugin validation, then checks API registration from source (unregistered modules/routes), the fetcher registry prerequisites, app registration of both plugin halves, declared staff permissions, duplicate endpoints and background work on nested modules. Checks that cannot run are reported as skipped, never as passed. - `vitnode inspect` lists routes with their declared staff permissions, cron jobs and queue tasks, and event listeners and WebSockets, read from build output without starting schedulers or touching a database. - buildRoute records its admin/moderator staff permissions as non-enumerable metadata (route-metadata.ts), so tooling can read them. - buildApiPlugin throws in development when a nested module declares cron jobs, events, queue tasks or WebSockets (still ignored in production, as before). Core and the bundled plugins have none. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CnAiFZ3bExobTQrGyYnt3P
…ssions
`defineStaffPermissions({ admin, moderator })` (browser-safe, from
`@vitnode/core/lib/staff-permissions`) returns `config` for
`buildApiPlugin({ permissionStaff })` and typed `{ module, permission }`
references such as `staffPermissions.admin.notes.canCreate`, which routes and
AdminCP navigation already accept. String declarations keep working.
`vitnode generate` follows `permissionStaff: X.config` to the
defineStaffPermissions call: it validates and declares permissions there and
writes routes with the typed reference.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CnAiFZ3bExobTQrGyYnt3P
Scaffolds an administrative Content Engine content type through the shared planner: the definition (src/content), the model and table export (src/database), the buildContentAdminModule mount in the admin module, the builders binding, the AdminCP sidebar entry and screen, and the labels. The Content Engine still generates the CRUD routes and permissions. No public API, member writes, publication or ownership is enabled, and no migration is written or applied; the command prints the build -> db generate -> db migrate order. Existing nav/admin/content structures are extended when supported; a custom adminContent stops the command with manual steps. Merging into an existing named import now keeps the names sorted. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CnAiFZ3bExobTQrGyYnt3P
From an independent review, each reproduced before fixing:
- applyEdits keeps the given order for inserts at one position, so adding a
property after one without a trailing comma no longer writes `,,`.
- Commas are found across comments; appending no longer creates array holes;
comments in an empty array or object are kept; a property sharing the
anchor's line no longer loses its separator.
- Every planned TypeScript file is re-parsed before it is shown or written.
- A typed permission reference is generated only when the permission is
declared in an exported defineStaffPermissions module outside
config.api.ts; otherwise the { module, permission } literal is used (no
circular import, no reference to an undeclared module).
- StaffPermissionKey and the runtime key follow one rule
(staffPermissionKey), shared with the generator; permission names are
limited to words joined by single _ or -.
- Resources refuse identifiers the module already declares.
- Admin routes are detected by any `admin` segment, as core does.
- Reserved words and `c` are refused as path parameter names; field types
are checked with Object.hasOwn.
- Multi-dot content ids use the Content Engine's entity key.
- Imports go after "use" directives and above comment lines that belong to
the next import.
- Created files are linked into place, so a file that appeared after the
stale check is not replaced.
- doctor inside a plugin reads whether an app configures it instead of
assuming so.
- Service templates type db as Omit<db, "$client">, so a transaction fits.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CnAiFZ3bExobTQrGyYnt3P
- Generator guides form their own track from a fresh plugin; prerequisites for admin routes, services and typed references now hold. - Admin route check uses the call the generator prints; services check and example match the Database lesson's columns; DELETE has no body. - Correct links, doctor wording for missing build output, option defaults, and the review step skipped by the root db:migrate script. - Content Engine events and queue pages: nested registrations throw in development and are reported by vitnode doctor. - Move the content-type generator page after the beginner sequence. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CnAiFZ3bExobTQrGyYnt3P
- admin-routes and the generate reference describe the warning (not error) for an undeclared permission module in plugins with Content Engine admin modules. - The content-type guide migrates in apps/api, the app with drizzle.config.ts in the monorepo layout. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CnAiFZ3bExobTQrGyYnt3P
Codex Review Summary: ✅ CompletedThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
|
React Doctor found 12 new issues in 11 files · 12 warnings · score 78 / 100 (Needs work) · 2 fixed · vs 12 warnings
Reviewed by React Doctor for commit |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 78e1327f5f
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| ): void => { | ||
| const declarations = readAdminPermissions(ctx); | ||
| const wanted = `${ref.module}.${ref.permission}`; | ||
| const snippet = `permissionStaff: { admin: { ${ref.module}: [${JSON.stringify(ref.permission)}] } }`; |
There was a problem hiding this comment.
Quote permission module names in the initial declaration
When a plugin has no permissionStaff yet, generating an admin route with --permission site-notes.can_view --declare-permission inserts permissionStaff: { admin: { site-notes: ["can_view"] } }. Hyphenated module names are explicitly accepted by parsePermission, but this unquoted property is invalid TypeScript, so generation aborts without writing any files. API resources with a hyphenated --permission-module hit the same issue. Use the quoted-key handling already present below when constructing this initial declaration too.
Useful? React with 👍 / 👎.
…rom the AI module - posts.module.ts: postSlugRoute -> routes/show-slug.route.ts - categories.module.ts: publicCategoriesRoute and its helpers -> routes/list.route.ts - admin/ai/ai.admin.module.ts -> admin/ai/ai.module.ts (test follows) Methods, paths, permissions and registration order are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CnAiFZ3bExobTQrGyYnt3P
…outes/ - forumStatsRoute -> routes/list-forum-stats.route.ts - topicStatsRoute -> routes/list-topic-stats.route.ts - topicSlugRoute -> routes/show-topic-slug.route.ts - forumTopicsRoute -> routes/list-topics.route.ts - shared helpers -> lib/stats.ts, lib/public-condition.ts, lib/json-response.ts Methods, paths and registration order are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CnAiFZ3bExobTQrGyYnt3P
- articles.module.ts: articleSlugRoute -> routes/show-slug.route.ts - hello/hello.route.ts -> hello/routes/hello.route.ts Methods, paths and registration order are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CnAiFZ3bExobTQrGyYnt3P
…atus.ts - catalogRoute -> routes/show-catalog.route.ts - myPurchasesRoute -> routes/list-purchases.route.ts - checkoutRoute -> routes/checkout.route.ts - purchaseRoute -> routes/show-purchase.route.ts - zodPurchase, PurchaseResponse -> purchases.schema.ts - describePurchase -> lib/describe-purchase.ts Methods, paths and registration order are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CnAiFZ3bExobTQrGyYnt3P
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CnAiFZ3bExobTQrGyYnt3P
Generic or wrong route names get their operation (cron/queue list, activity log show, notification type policy update, send notification), schemas.ts and staff/lib/schema.ts become <feature>.schema.ts next to their module, and the roles list mapper leaves routes/ for lib/. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CnAiFZ3bExobTQrGyYnt3P
… modules updates.schema.ts leaves routes/ and keeps only schemas; its response serializers move to plugins/lib/serialize-update.ts. The role field schemas shared by the create and update routes move to roles.schema.ts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CnAiFZ3bExobTQrGyYnt3P
…oint Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CnAiFZ3bExobTQrGyYnt3P
…tenance routes One endpoint per routes/<operation>.route.ts; the danger-zone audit, permission and response helpers move to notifications/lib/danger-zone.ts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CnAiFZ3bExobTQrGyYnt3P
…tion source routes One endpoint per routes/<operation>.route.ts; helpers shared by the split routes move to ai/lib (decimal-or-null, run-row, assert-can-edit-content-item). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CnAiFZ3bExobTQrGyYnt3P
… endpoint Shared bodies and params move to report-reasons.schema.ts, word and row helpers to report-reasons/lib. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CnAiFZ3bExobTQrGyYnt3P
…to one file per endpoint Shared bodies, params and responses move to warning-reasons.schema.ts and warning-thresholds.schema.ts, helpers to warnings/lib. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CnAiFZ3bExobTQrGyYnt3P
…outes Follows the admin API migration: ai.schema, plugins/updates.schema, routes/send-notification.route, and the parity test's staff schema path. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CnAiFZ3bExobTQrGyYnt3P
Move every hand-written route example into its module's routes/ folder, fix the relative imports (../notes.schema, ./routes/list.route), move the site-notes sample data to lib/sample-notes.ts, and follow the plugin renames (forum show-topic-slug route, newsroom articles show-slug route, payments-demo checkout route and describe-purchase helper, ai.module.ts). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CnAiFZ3bExobTQrGyYnt3P
`findDefinition` swallowed what an exported factory threw and returned null, so `vitnode doctor`, `vitnode inspect` and `vitnode plugin validate` said a plugin "exports no buildApiPlugin() factory" when `buildApiPlugin` had actually refused it - for example the development check for cron jobs on nested modules. `resolveDefinition` now returns an explicit outcome - a definition, no candidate, or the exports that threw with their original errors - and each export is still called at most once. Validation and the API metadata loader report the factory's own error; doctor reports a failed load once and skips the checks that needed it. Permission and nested-kind lookups use Sets. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CnAiFZ3bExobTQrGyYnt3P
Generator corrections found in review:
- Effective properties: a registration property is read and edited only
when it is the one that wins at runtime. Duplicate properties resolve to
the last; a later spread, unknown computed key or getter makes it unsafe;
an earlier spread is fine. An absent property next to a spread is unsafe
too: added before the spread it may be overridden, added after it, it
drops what the spread supplied. Applies to buildApiPlugin and buildModule
`modules`/`routes`/`name`, permissionStaff/admin/module entries (inline or
in defineStaffPermissions), the admin module's `modules`,
buildContentAdminModule `contentTypes` and adminNav `contentTypes`, and to
the readers that decide what is declared. Unsafe structures stop with an
unsupported-structure error and manual steps before anything is planned.
- Endpoint collisions are checked against every route the reachable module
tree declares, with full paths joined exactly as Hono's mergePath joins
`route("/name", sub)` and parameters normalized, and against the other
endpoints of the same command (an API resource's five). Parts of the tree
that cannot be read become a warning for endpoints under them.
- `g resource --kind content` checks the effective permission module: the
Content Engine only adds can_view/can_create/can_edit/can_delete when the
plugin does not declare the module, so a partial declaration stops with
the missing lines, and unreadable declarations stop as unsupported.
- `--declare-permission` quotes non-identifier module keys in a new
permissionStaff declaration (`"site-notes": [...]`).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CnAiFZ3bExobTQrGyYnt3P
`vitnode g route notes/list` now writes src/api/modules/notes/routes/list.route.ts and the module imports it from ./routes/list.route; an API resource's five routes go there too, importing the schema and service next to the module (../notes.schema). Manual steps name the new location. create-vitnode-app's plugin scaffold follows the same layout: src/api/modules/hello/routes/hello.route.ts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CnAiFZ3bExobTQrGyYnt3P
The three commands were parsed but missing from `vitnode --help`. They are in PUBLIC_COMMANDS now, with a generate and a doctor example, and the parser takes their descriptions from the same list so the two cannot drift. Handlers stay lazily imported: the root help loads no generator code. The root help inline snapshot is updated for the new lines. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CnAiFZ3bExobTQrGyYnt3P
When `vitnode g route --permission` cannot read or edit the plugin's staff permission declarations, the error now says how to declare the permission and wire the route by hand instead of the generic advice. Messages name a permission module as permissionStaff.admin["site-notes"], which stays valid for keys that are not identifiers. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CnAiFZ3bExobTQrGyYnt3P
…haviour Generator pages show the routes/ layout captured from the current CLI, the tree-wide endpoint collision check, the effective-property rules with their error, the content permission-module check, the factory-threw diagnostic in doctor and inspect, the root help listing generate, doctor and inspect, and the defineStaffPermissions reference-key collision. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CnAiFZ3bExobTQrGyYnt3P
`vitnode doctor` validated the plugin (calling its buildApiPlugin factory) and then loaded the API metadata (calling it again), so a factory that threw - or a missing factory - was reported twice: under "API definition" and again under "Staff permissions". validatePlugin now returns the API definition its check resolved, and doctor builds the staff permission, endpoint and background registration checks from it. A failure is reported once, under "API definition", and the dependent checks are skipped with "API definition failed". The factory is called once per doctor run; the metadata is only loaded separately when validation stopped before its API check. plugin validate and inspect are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CnAiFZ3bExobTQrGyYnt3P
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CnAiFZ3bExobTQrGyYnt3P
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CnAiFZ3bExobTQrGyYnt3P
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CnAiFZ3bExobTQrGyYnt3P
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CnAiFZ3bExobTQrGyYnt3P
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CnAiFZ3bExobTQrGyYnt3P
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CnAiFZ3bExobTQrGyYnt3P
…o lib Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CnAiFZ3bExobTQrGyYnt3P
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CnAiFZ3bExobTQrGyYnt3P
…hema names Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CnAiFZ3bExobTQrGyYnt3P
…key and queue paths Follows the core API migration: modules/middleware/middleware.schema, users/passkeys/passkeys.schema, queue/lib/process-queue-tasks and notifications/tasks/notifications.task. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CnAiFZ3bExobTQrGyYnt3P
… key error Doctor now reports a throwing or missing factory once and skips the checks that need it; quote the defineStaffPermissions collision message. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CnAiFZ3bExobTQrGyYnt3P
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CnAiFZ3bExobTQrGyYnt3P
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CnAiFZ3bExobTQrGyYnt3P
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c2daeaa0c1
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const model = `${camel}Content`; | ||
| const definitionFile = `src/content/${name}.ts`; | ||
| const modelFile = `src/database/${name}.ts`; | ||
| const adminPath = `${plugin.replace(/[^a-z0-9-]/g, "")}/${name}`; |
There was a problem hiding this comment.
Reject reserved content admin paths before writing files
For a content resource named create or edit, the generator accepts the name and writes an explicit admin.path ending in that segment. However, resolveAdminPath in packages/vitnode/src/content/define-admin.ts explicitly rejects both endings. The command therefore reports success and registers a definition that throws when imported, preventing the plugin API from loading until the generated code is manually repaired. Validate the derived admin path against the Content Engine's restrictions before applying the plan.
Useful? React with 👍 / 👎.
collectEndpoints skipped a module the second time it saw it, so a module registered under two parents only contributed its first mount's paths and a colliding route under the second parent was written. Mounts are now keyed by module and mount path; cycles are detected on the active ancestry and reported as unverified regions, with a cap on total mounts. doctor's registration walk gets a guard so a self-mounting module cannot recurse. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CnAiFZ3bExobTQrGyYnt3P
Exact package export keys map each former deep import to its new file, or, where a file was split or lost exports, to a deprecated re-export module under src/compat. No implementation is duplicated and no compat entry pulls in route code its former file did not have. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CnAiFZ3bExobTQrGyYnt3P
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CnAiFZ3bExobTQrGyYnt3P
| status: "error", | ||
| }); | ||
| } else { | ||
| const validation = await validatePlugin(target.root, { |
There was a problem hiding this comment.
React Doctor · react-doctor/async-await-in-loop (warning)
This for…of loop waits before starting the next iteration. If iterations perform independent asynchronous work, consider bounded concurrency; await syntax alone does not establish a speedup.
Fix → Consider concurrent calls only for independent asynchronous work. Shared queues or synchronous work may not benefit. Preserve resource limits, transaction ordering, and failure/cancellation semantics; observe all callback promises.
| throw new UserError(`--id-type must be string or number, not "${idType}".`); | ||
| } | ||
|
|
||
| const ctx = await start(context, "Generate a route", options); |
There was a problem hiding this comment.
React Doctor · react-doctor/async-parallel (warning)
These 3 sequential await statements run one after another even though they look independent, so the page waits longer than it needs to. Run them together with Promise.all() instead
Fix → Use const [a, b] = await Promise.all([fetchA(), fetchB()]) so independent calls run at the same time
| } | ||
|
|
||
| const ctx = await start(context, "Generate a route", options); | ||
| const method = await pickMethod(context, name, options.method); |
There was a problem hiding this comment.
React Doctor · react-doctor/server-sequential-independent-await (warning)
This awaited initializer does not read the previous result. If the operations are independent asynchronous work, they may overlap; await syntax alone does not establish a speedup.
Fix → Consider Promise.all([...]) only for independent asynchronous work. Shared queues or synchronous work may not benefit. Preserve resource limits, transaction ordering, and failure/cancellation semantics.
| let failed = 0; | ||
| for (const [index, target] of targets.entries()) { | ||
| if (index > 0) ui.line(); | ||
| const load: PluginApiLoad = await loadPluginApi( |
There was a problem hiding this comment.
React Doctor · react-doctor/async-await-in-loop (warning)
This for…of loop waits before starting the next iteration. If iterations perform independent asynchronous work, consider bounded concurrency; await syntax alone does not establish a speedup.
Fix → Consider concurrent calls only for independent asynchronous work. Shared queues or synchronous work may not benefit. Preserve resource limits, transaction ordering, and failure/cancellation semantics; observe all callback promises.
| "The generated staff routes would require permissions nobody can be granted. Nothing was written.", | ||
| `Add the missing ones to permissionStaff.admin[${JSON.stringify(permissionModule)}] in ${file}:`, | ||
| ...lines.filter((_, index) => | ||
| missing.includes(CONTENT_ADMIN_PERMISSIONS[index] ?? "can_view"), |
There was a problem hiding this comment.
React Doctor · react-doctor/js-set-map-lookups (warning)
This scales poorly because array.includes() inside a loop scans the whole list every time. Use a Set for constant-time lookups.
Fix → Use a Set or Map when you check for the same items over and over. Array.includes/find scans the whole list each time
| if (!/\.(?:ts|tsx|mts|cts|js|mjs)$/.test(change.path)) continue; | ||
|
|
||
| if (change.before !== null) { | ||
| const original = await format(change.path, change.before, options); |
There was a problem hiding this comment.
React Doctor · react-doctor/async-await-in-loop (warning)
This for…of loop waits before starting the next iteration. If iterations perform independent asynchronous work, consider bounded concurrency; await syntax alone does not establish a speedup.
Fix → Consider concurrent calls only for independent asynchronous work. Shared queues or synchronous work may not benefit. Preserve resource limits, transaction ordering, and failure/cancellation semantics; observe all callback promises.
| } | ||
| for (const grant of grants) { | ||
| if (grant.granted === null) continue; | ||
| await tx |
There was a problem hiding this comment.
React Doctor · react-doctor/async-await-in-loop (warning)
This for…of loop waits before starting the next iteration. If iterations perform independent asynchronous work, consider bounded concurrency; await syntax alone does not establish a speedup.
Fix → Consider concurrent calls only for independent asynchronous work. Shared queues or synchronous work may not benefit. Preserve resource limits, transaction ordering, and failure/cancellation semantics; observe all callback promises.
| targetFingerprint: entry.targetFingerprint, | ||
| updatedById, | ||
| }; | ||
| await c |
There was a problem hiding this comment.
React Doctor · react-doctor/async-await-in-loop (warning)
This for…of loop waits before starting the next iteration. If iterations perform independent asynchronous work, consider bounded concurrency; await syntax alone does not establish a speedup.
Fix → Consider concurrent calls only for independent asynchronous work. Shared queues or synchronous work may not benefit. Preserve resource limits, transaction ordering, and failure/cancellation semantics; observe all callback promises.
| const latestOf = new Map(latest.map(row => [row.forumId, row])); | ||
| const readable = await readableForumIds(c); | ||
|
|
||
| const headers = await forumStatsHeaders(c); |
There was a problem hiding this comment.
React Doctor · react-doctor/server-sequential-independent-await (warning)
This awaited initializer does not read the previous result. If the operations are independent asynchronous work, they may overlap; await syntax alone does not establish a speedup.
Fix → Consider Promise.all([...]) only for independent asynchronous work. Shared queues or synchronous work may not benefit. Preserve resource limits, transaction ordering, and failure/cancellation semantics.
| const ids = c.req.valid("query").ids.split(",").map(Number); | ||
| const items = await loadTopicStats(c, await readableTopicIds(c, ids)); | ||
|
|
||
| const headers = await forumStatsHeaders(c); |
There was a problem hiding this comment.
React Doctor · react-doctor/server-sequential-independent-await (warning)
This awaited initializer does not read the previous result. If the operations are independent asynchronous work, they may overlap; await syntax alone does not establish a speedup.
Fix → Consider Promise.all([...]) only for independent asynchronous work. Shared queues or synchronous work may not benefit. Preserve resource limits, transaction ordering, and failure/cancellation semantics.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 7215ff3849
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| for (const endpoint of proposed) { | ||
| const path = joinEndpointPath(endpoint.modulePath, endpoint.routePath); | ||
| const label = `${endpoint.method.toUpperCase()} ${path}`; | ||
| const destination = `Destination: ${endpoint.identifier} in ${endpoint.file} (module "${endpoint.modulePath.join("/")}", path ${endpoint.routePath}).`; | ||
| const existing = known.get(endpointKey(endpoint.method, path)); |
There was a problem hiding this comment.
Check proposed routes at every mount of a shared module
When a module is mounted under multiple parents, this checks the proposed route only at the logical path supplied to the command, even though planRoute updates the shared module declaration and therefore adds the route at every mount. For example, if a and b both mount notesModule, and b already declares GET /notes/items, generating a/notes/items --method get --path /items succeeds but introduces a collision at /b/notes/items; the existing parent handler shadows the generated handler there. Expand proposed endpoints to every mount of the destination module, or check the complete inventory after planning the registration, before writing files.
Useful? React with 👍 / 👎.
Adds
vitnode generate(aliasg) withmodule,routeandservice:project's own oxfmt, prints the real diff under --dry-run, re-checks files
before writing and rolls back a failed batch.
comments, options and the VitNodeApiPlugin contract are preserved.
Module and route registrations stay explicit; unsupported structures stop
before writing with exact manual steps.
only/selected API plugin in the workspace.
return labelled sample data for GET and 501 for unimplemented writes.
permissionStaff; --declare-permission adds it and its AdminCP labels.
Docs: CLI reference plus how-tos for modules, endpoints, admin endpoints
and services.
Co-Authored-By: Claude Opus 5.5 noreply@anthropic.com
Claude-Session: https://claude.ai/code/session_01CnAiFZ3bExobTQrGyYnt3P