Skip to content

feat(cli): ✨ vitnode generate for plugin API modules, routes and services - #917

Open
aXenDeveloper wants to merge 45 commits into
canaryfrom
claude/charming-mayer-rh0yub
Open

aXenDeveloper wants to merge 45 commits into
canaryfrom
claude/charming-mayer-rh0yub

Conversation

@aXenDeveloper

Copy link
Copy Markdown
Owner

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

…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
@github-actions github-actions Bot added the 💡 Feature A new feature label Nov 9, 2026
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Nov 9, 2026 •

Copy link
Copy Markdown
Codex Review Summary: ✅ Completed

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-11-09T20:39:32.720600Z 7215ff3 New commits
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@github-actions

github-actions Bot commented Nov 9, 2026 •

Copy link
Copy Markdown

React Doctor found 12 new issues in 11 files · 12 warnings · score 78 / 100 (Needs work) · 2 fixed · vs canary

12 warnings

scripts/cli/commands/doctor.ts

  • ⚠️ L213 await inside a loop async-await-in-loop

scripts/cli/commands/generate.ts

  • ⚠️ L604 Independent awaits run sequentially async-parallel
  • ⚠️ L605 Sequential independent awaits server-sequential-independent-await

scripts/cli/commands/inspect.ts

  • ⚠️ L146 await inside a loop async-await-in-loop

scripts/cli/commands/plugin-validate.ts

  • ⚠️ L134 await inside a loop async-await-in-loop

scripts/cli/generate/content-resource.ts

  • ⚠️ L238 Array lookup inside a loop js-set-map-lookups

scripts/cli/generate/endpoints.ts

  • ⚠️ L287 Array lookup inside a loop js-set-map-lookups

scripts/cli/generate/format.ts

  • ⚠️ L101 await inside a loop async-await-in-loop

src/api/modules/admin/ai/routes/update-role-access.route.ts

  • ⚠️ L74 await inside a loop async-await-in-loop

src/api/modules/admin/ai/routes/update-translation-sources.route.ts

  • ⚠️ L73 await inside a loop async-await-in-loop

src/api/modules/forum/routes/list-forum-stats.route.ts

  • ⚠️ L117 Sequential independent awaits server-sequential-independent-await

src/api/modules/forum/routes/list-topic-stats.route.ts

  • ⚠️ L72 Sequential independent awaits server-sequential-independent-await

Reviewed by React Doctor for commit 7215ff3. See inline comments for fixes.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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)}] } }`;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

claude added 18 commits November 9, 2026 19:28
…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
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
…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
claude added 16 commits November 9, 2026 19:36
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
…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

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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}`;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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
status: "error",
});
} else {
const validation = await validatePlugin(target.root, {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Docs

throw new UserError(`--id-type must be string or number, not "${idType}".`);
}

const ctx = await start(context, "Generate a route", options);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Docs

}

const ctx = await start(context, "Generate a route", options);
const method = await pickMethod(context, name, options.method);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Docs

let failed = 0;
for (const [index, target] of targets.entries()) {
if (index > 0) ui.line();
const load: PluginApiLoad = await loadPluginApi(

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Docs

"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"),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Docs

if (!/\.(?:ts|tsx|mts|cts|js|mjs)$/.test(change.path)) continue;

if (change.before !== null) {
const original = await format(change.path, change.before, options);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Docs

}
for (const grant of grants) {
if (grant.granted === null) continue;
await tx

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Docs

targetFingerprint: entry.targetFingerprint,
updatedById,
};
await c

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Docs

const latestOf = new Map(latest.map(row => [row.forumId, row]));
const readable = await readableForumIds(c);

const headers = await forumStatsHeaders(c);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Docs

const ids = c.req.valid("query").ids.split(",").map(Number);
const items = await loadTopicStats(c, await readableTopicIds(c, ids));

const headers = await forumStatsHeaders(c);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Docs

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment on lines +391 to +395
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));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

💡 Feature A new feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants