Skip to content

feat(cli): project scaffolding — connectum init and generate service#229

Open
intech wants to merge 13 commits into
mainfrom
feature/cli-scaffolding
Open

feat(cli): project scaffolding — connectum init and generate service#229
intech wants to merge 13 commits into
mainfrom
feature/cli-scaffolding

Conversation

@intech

@intech intech commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds project scaffolding to @connectum/cli: connectum init (create a new project) and connectum generate service (add a service to an existing one).

init fetches the dogfooded getting-started example as its base via a degit-style clone — so the starter layout stays in sync with a tested example instead of a drift-prone template copy — and composes the selected modules on top.

What's included

connectum init — interactive wizard (@clack/prompts) or fully flag-driven (--yes / CI / non-TTY):

  • Modules: OpenTelemetry (--otel), EventBus with an adapter (--events nats|kafka|redpanda|redis|amqp), auth (--auth, JWT + proto-driven authorization), service catalog (--catalog, typed ctx.call/ctx.stream), opt-in resilience interceptors (--resilience timeout,retry,…), health/reflection toggles
  • Targets: runtime node/bun, package manager pnpm/npm, Node execution model raw (>= 25.2) / tsx (>= 22.13)
  • Deterministic interceptor order across any module subset: OpenTelemetry → error handler → auth → validation → resilience → custom, with exactly one error handler
  • Lifecycle fix baked in: buf generate is chained into the generated start / test / typecheck scripts — deliberately not a pnpm pre* hook, which silently no-ops when enable-pre-post-scripts is off — so a fresh clone never fails on an unresolved #gen/... import. Standalone pnpm projects also get the buf build approval pnpm 11 requires.

connectum generate service <name> — scaffolds a starter proto plus a defineService skeleton whose rpc handlers throw Code.Unimplemented. --with-events also scaffolds an event-handler service and an ack-by-default EventRoute. It never edits your src/server.ts (that file is yours) — it prints the exact registration line to add.

The throwing rpc stub is a deliberate, documented trade-off: it costs the "did I replace every stub" guarantee, but the handler-map key must still exist, so adding a method to the proto later remains a compile error.

Generated tests are runtime-agnostic: the e2e test uses the public in-process createLocalClient from @connectum/testing (no socket — identical on Node and Bun); event-enabled projects also get a broker-free MemoryAdapter smoke test.

Validation

Every module was validated end-to-end against real published @connectum/* packages, not just unit tests: init → install → buf lint / buf generatetsc --noEmit → e2e, on Node + pnpm, npm, and via npx.

That live validation caught two real bugs unit tests alone would have missed:

  1. pnpm 11 moved the build-approval setting into pnpm-workspace.yaml — the package.json pnpm.* field is no longer read — so without it buf cannot install and buf generate fails in a scaffolded project.
  2. The originally planned in-memory test transport requires @connectum/core internals (register(router, ctx)) plus a cast-heavy bridge; the generated test now uses the public createLocalClient instead.

A new CI workflow cli-scaffold-matrix scaffolds each named module combination (base pnpm/npm, otel, events-nats, auth, catalog, kitchen-sink, plus a Bun cell) and runs buf generate → typecheck → test, so a broken fragment fails CI. Combinations intentionally not covered are named in the workflow header. This PR is that workflow's first real run, including the Bun cell.

  • 117 unit tests; build / typecheck / lint / frozen-lockfile clean
  • Changeset included (@connectum/cli minor)

Companion PRs

🤖 Generated with Claude Code

https://claude.ai/code/session_01SdapMMJeamn5TDrY8yzHRQ

Summary by CodeRabbit

  • New Features
    • Added connectum init for interactive or non-interactive standalone project scaffolding with configurable runtime, package manager, observability, authentication, events (with multiple adapters), resilience, catalog, healthcheck, and reflection.
    • Added connectum generate service <name> to scaffold starter RPC services and optional event handlers, including clear next-step registration output.
  • Bug Fixes
    • Improved lifecycle code generation so generated scripts reflect fresh types consistently.
  • Documentation
    • Expanded CLI README with command usage, flags, and examples for scaffolding and service generation.
  • Tests
    • Added unit and matrix coverage for scaffolding combinations, prompts, and safe file emission.

intech and others added 11 commits July 19, 2026 22:33
Add `connectum init` (plus the `generate service` seam) to @connectum/cli,
implementing Phase 0 + the Phase 1 base of the cli-scaffolding change:

- file-emission layer (`utils/emit`): refuse-to-clobber, path-safe, deterministic
- tiged-backed base fetcher (`scaffold/fetchBase`): fetches the source-of-truth
  `getting-started` example (no vendored duplicate), injectable for tests
- pure transform (`scaffold/transform`): de-monorepo, lifecycle-fix (`buf generate`
  chained into typecheck/test/start, not pnpm pre* hooks), runtime/PM/node-exec
  variance, standalone pnpm-workspace build-approval, in-process `createLocalClient`
  e2e test (public API, Bun-safe, replaces the internal-only createRouterTransport)
- non-interactive flags: --runtime, --package-manager, --node-exec, --sample, --force

End-to-end validated on Node with pnpm and npm (incl. via `npx`): real tiged fetch
-> transform -> install -> `buf generate` -> the generated e2e passes against real
@connectum/core + @connectum/testing. 73 unit tests; build/typecheck/lint clean;
frozen-lockfile clean.

The interactive TUI + module fragments (Phase 2), `generate service` (Phase 3),
the CI anti-drift gate and docs remain. `generate service` is a wired stub for now.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SdapMMJeamn5TDrY8yzHRQ
…nit`

Add the Phase 2 composition engine and the first module fragment (OpenTelemetry):

- `scaffold/serverGen`: generate `src/server.ts` + `src/index.ts` as a composition
  root from the module set. Encodes the ratified interceptor-ordering policy (D-3):
  otel is outermost — `[createOtelInterceptor({trustRemote:true}), ...createDefaultInterceptors()]`.
  For the base (no modules) the output is functionally identical to getting-started.
- otel fragment: `--otel` adds `@connectum/otel` (same @connectum slice), wires the
  interceptor, and adds `initProvider({serviceName})` / `await shutdownProvider()` to
  the entry.
- `ModuleSelection` type + `--otel` flag threaded through resolveConfig.

End-to-end validated: real `connectum init otelsvc --otel` -> npm install ->
`buf generate && tsc --noEmit` passes (generated server/index typecheck against the
real @connectum/otel API) -> e2e passes (2/2). 79 unit tests; build/typecheck/lint clean.

Remaining Phase 2 fragments (events, auth, interceptor selection, catalog, testing),
the clack TUI, and the module-aware sample slices follow the same shape.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SdapMMJeamn5TDrY8yzHRQ
Add the `--events <adapter>` module (task 2.2): `@connectum/events` + a transport
adapter (nats | kafka | redpanda | redis | amqp), a demo event-handler service
(vendored option proto + `GreeterEventHandlers` proto), the EventBus + `EventRoute`
wiring, and `eventBus:` in the composition root. buf.yaml gains the event-handler
lint excepts (SERVICE_SUFFIX / RPC_*) so `buf lint` stays green.

Adapter constructors are lifted from the dogfooded examples and verified against the
real package exports (NatsAdapter/KafkaAdapter/RedisAdapter/AmqpAdapter).

End-to-end validated with nats: real `connectum init evsvc --events nats` -> npm
install -> `buf lint` clean -> `buf generate` (options_pb + events_pb + greeter_pb)
-> `tsc --noEmit` clean (wiring typechecks against real @connectum/events +
events-nats) -> e2e passes (2/2). 92 unit tests; build/typecheck/lint clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SdapMMJeamn5TDrY8yzHRQ
Replace the Phase-0 stub with the real command: scaffold a service into an existing
project.

- starter proto `proto/<name>/v1/<name>.proto` with a demo rpc; `--with-events` adds
  an event-handler service + vendored option proto
- `src/services/<name>Service.ts`: `defineService` with rpc stubs that throw
  Unimplemented (D-6, a documented trade-off); `--with-events` also emits an
  `EventRoute` with an ack-by-default handler
- refuse-to-clobber; prints the exact registration edit rather than touching the
  user-owned `src/server.ts` (D-11)

End-to-end validated: `generate service billing --with-events` inside a scaffolded
project -> skips the existing option proto -> `buf generate` -> `tsc --noEmit` clean
(the defineService skeleton + EventRoute typecheck against real @connectum/core +
events). 94 unit tests; build/typecheck/lint clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SdapMMJeamn5TDrY8yzHRQ
Add the `--auth` module (task 2.4): `@connectum/auth` (JWT authentication +
proto-driven authorization).

- `src/auth.ts`: `buildAuthInterceptors()` = `createJwtAuthInterceptor` (JWKS, env
  config, skipMethods incl. health/reflection + `getPublicMethods`) + a deny-by-default
  `createProtoAuthzInterceptor`
- composition root places the auth chain per the ratified D-3 order:
  otel -> errorHandler -> auth -> validation, emitting an explicit errorHandler +
  `createDefaultInterceptors({errorHandler:false})` so it is never duplicated
- `buf.yaml` gains the auth option proto as a **second module**
  (`node_modules/@connectum/auth/proto`) + the RPC_* lint excepts. Install-before-generate
  is guaranteed by the lifecycle-fix (buf generate runs on test/start, after install)
- `scaffold/bufConfig.ts` extracted to compose buf.yaml modules + lint excepts across
  events and auth

APIs verified against @connectum/auth (createJwtAuthInterceptor, createProtoAuthzInterceptor,
getPublicMethods). End-to-end validated: `connectum init acct --auth` -> npm install ->
`buf lint` clean -> `buf generate` -> `tsc --noEmit` clean (auth.ts + the auth interceptor
chain typecheck against real @connectum/auth) -> e2e passes. 101 unit tests.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SdapMMJeamn5TDrY8yzHRQ
Add a `@clack/prompts` wizard that prompts for anything not passed as a flag, then
collapses to the same RawInput the non-interactive path uses (D-5):

- `scaffold/prompts.ts`: `collectConfig` (non-interactive short-circuit on `--yes` /
  CI / non-TTY) + `promptForMissing` (name, runtime, node-exec [node only], package
  manager, otel, auth, events + adapter, sample)
- prompt calls go through a small `Prompter` seam so the wizard logic is unit-tested
  with a stub (clack is lazily imported and never loaded in tests — also sidesteps
  clack's bun:test mock limitation)
- `init` command: `name` is now optional (prompted when omitted), `-y/--yes` added,
  and the promptable flags (otel/auth/sample) drop their citty defaults so the wizard
  can ask for them; `resolveConfig` still applies the final defaults

Wizard logic unit-tested (stub prompter); non-interactive `--yes` path verified via the
real CLI. 107 unit tests; build/typecheck/lint clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SdapMMJeamn5TDrY8yzHRQ
…2.5, 2.6)

- --resilience <list> enables opt-in resilience interceptors (timeout, bulkhead,
  circuitBreaker, retry, fallback) by injecting them into createDefaultInterceptors({...}),
  merged correctly with the auth errorHandler:false branch
- --healthcheck / --reflection (default true) toggle the protocols array and, for
  healthcheck, the healthcheckManager lifecycle in index.ts

Live-validated: `init --resilience retry,timeout --no-healthcheck` -> buf generate ->
tsc --noEmit clean. 112 unit tests; build/lint clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SdapMMJeamn5TDrY8yzHRQ
--catalog adds the service catalog: regenerates buf.gen.yaml with the
protoc-gen-connectum-catalog plugin (strategy: all), adds the @connectum/protoc-gen-catalog
dev dep, imports the generated serviceCatalog and passes it to createServer (typed
ctx.call / ctx.stream). buf.gen.yaml is now generated (es-only by default).

Live-validated: `init --catalog` -> npm install -> buf generate runs both plugins
(catalog.gen.ts + *_pb.ts) -> tsc --noEmit clean (serviceCatalog typechecks in server.ts).
116 unit tests; build/lint clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SdapMMJeamn5TDrY8yzHRQ
Every scaffolded project already ships an in-process createLocalClient e2e test (D-7,
Node+Bun). For event-enabled projects, also emit tests/e2e/events.test.ts — a
broker-free smoke test that registers the EventRoute on an in-memory bus
(createEventBus + MemoryAdapter) and exercises start/stop.

Live-validated: `init --events nats` -> buf generate -> `node --test` runs the greeter
e2e AND the events smoke (3 tests pass). 117 unit tests.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SdapMMJeamn5TDrY8yzHRQ
Add .github/workflows/cli-scaffold-matrix.yml: for each named combo (base node×pnpm,
base node×npm, otel, events-nats, auth, catalog, kitchen-sink, plus a bun cell) scaffold
a project, then install -> `buf generate` -> typecheck -> test. A broken module fragment
now fails CI. Scaffolded projects install published @connectum/* (consumer perspective)
and fetch the base via tiged.

Each combo's init->install->buf generate->typecheck->test pattern was validated locally;
the workflow codifies it. Uncovered combos are named in the workflow header (no silent
caps): tsx model, pnpm beyond base, non-nats adapters, live-broker event integration.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SdapMMJeamn5TDrY8yzHRQ
…hangeset

- README: new commands as the headline feature — `connectum init` (options table,
  interceptor-order note, lifecycle-fix) and `connectum generate service`
- changeset: minor bump for @connectum/cli describing the scaffolding commands,
  the module set, the deterministic interceptor order, the baked-in lifecycle fix,
  and the runtime-agnostic generated tests

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SdapMMJeamn5TDrY8yzHRQ
@github-actions github-actions Bot added type:feature New feature or enhancement request pkg:cli @connectum/cli (Layer 3) labels Jul 19, 2026
@coderabbitai

coderabbitai Bot commented Jul 19, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 40d93e71-a5ca-4d9b-9df2-7e8619f6348b

📥 Commits

Reviewing files that changed from the base of the PR and between f8c5caa and ed43844.

📒 Files selected for processing (9)
  • .github/workflows/cli-scaffold-matrix.yml
  • packages/cli/src/commands/generate-service.ts
  • packages/cli/src/commands/init.ts
  • packages/cli/src/scaffold/config.ts
  • packages/cli/src/scaffold/prompts.ts
  • packages/cli/src/scaffold/transform.ts
  • packages/cli/tests/unit/scaffold-commands.test.ts
  • packages/cli/tests/unit/scaffold-events.test.ts
  • packages/cli/tests/unit/scaffold-prompts.test.ts
🚧 Files skipped from review as they are similar to previous changes (9)
  • packages/cli/src/commands/generate-service.ts
  • .github/workflows/cli-scaffold-matrix.yml
  • packages/cli/tests/unit/scaffold-prompts.test.ts
  • packages/cli/src/scaffold/config.ts
  • packages/cli/tests/unit/scaffold-commands.test.ts
  • packages/cli/src/scaffold/prompts.ts
  • packages/cli/tests/unit/scaffold-events.test.ts
  • packages/cli/src/commands/init.ts
  • packages/cli/src/scaffold/transform.ts

📝 Walkthrough

Walkthrough

The CLI adds connectum init project scaffolding and connectum generate service, including interactive configuration, module-aware generated projects, safe file emission, event/auth/catalog integrations, runtime-specific tests, documentation, and CI matrix validation.

Changes

CLI scaffolding

Layer / File(s) Summary
Init pipeline and safe emission
packages/cli/src/commands/init.ts, packages/cli/src/scaffold/*, packages/cli/src/utils/emit.ts, packages/cli/src/index.ts, packages/cli/tests/unit/*
Validates scaffold options, supports interactive and non-interactive input, fetches and transforms a base project, safely emits files, and wires the new command into the CLI.
Generated module composition and runtime wiring
packages/cli/src/scaffold/{authFragment,bufConfig,eventsFragment,serverGen,transform}.ts, packages/cli/tests/unit/scaffold-*.test.ts
Generates auth, events, catalog, resilience, protocol, runtime, dependency, script, README, and local-client test artifacts from scaffold configuration.
Service generation command
packages/cli/src/commands/generate-service.ts, packages/cli/src/scaffold/generateService.ts, packages/cli/src/index.ts, packages/cli/tests/unit/scaffold-commands.test.ts
Adds proto and defineService skeleton generation, optional event routes, overwrite handling, and printed server registration instructions.
Release documentation and scaffold matrix
.changeset/*, .github/workflows/cli-scaffold-matrix.yml, packages/cli/README.md, packages/cli/tsup.config.ts
Documents the commands and generated behavior, exposes the new build entries, and validates Node, Bun, package-manager, and module combinations in CI.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Developer
  participant CLI
  participant ScaffoldBase
  participant GeneratedProject
  Developer->>CLI: run connectum init
  CLI->>ScaffoldBase: fetch and read getting-started base
  CLI->>GeneratedProject: write transformed files
  GeneratedProject->>GeneratedProject: run buf generate, typecheck, and tests
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description covers the feature well, but it omits the template's Type of change, Test plan, Parity coverage, and Related issues sections. Add the missing template sections with checkboxed type/test plan items, a parity coverage choice, and any related issues or links.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main change: adding project scaffolding commands to the CLI.
Docstring Coverage ✅ Passed Docstring coverage is 92.31% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/cli-scaffolding

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Comment thread .github/workflows/cli-scaffold-matrix.yml Fixed
Comment thread .github/workflows/cli-scaffold-matrix.yml Fixed
@pkg-pr-new

pkg-pr-new Bot commented Jul 19, 2026

Copy link
Copy Markdown

Open in StackBlitz

@connectum/auth

npm i https://pkg.pr.new/@connectum/auth@229

@connectum/cli

npm i https://pkg.pr.new/@connectum/cli@229

@connectum/core

npm i https://pkg.pr.new/@connectum/core@229

@connectum/events

npm i https://pkg.pr.new/@connectum/events@229

@connectum/events-amqp

npm i https://pkg.pr.new/@connectum/events-amqp@229

@connectum/events-kafka

npm i https://pkg.pr.new/@connectum/events-kafka@229

@connectum/events-nats

npm i https://pkg.pr.new/@connectum/events-nats@229

@connectum/events-redis

npm i https://pkg.pr.new/@connectum/events-redis@229

@connectum/healthcheck

npm i https://pkg.pr.new/@connectum/healthcheck@229

@connectum/interceptors

npm i https://pkg.pr.new/@connectum/interceptors@229

@connectum/otel

npm i https://pkg.pr.new/@connectum/otel@229

@connectum/protoc-gen-catalog

npm i https://pkg.pr.new/@connectum/protoc-gen-catalog@229

@connectum/reflection

npm i https://pkg.pr.new/@connectum/reflection@229

@connectum/test-fixtures

npm i https://pkg.pr.new/@connectum/test-fixtures@229

@connectum/testing

npm i https://pkg.pr.new/@connectum/testing@229

commit: ed43844

…n build buf

The generated standalone `pnpm-workspace.yaml` used `onlyBuiltDependencies` (a list),
which pnpm 11 does NOT honour — so `pnpm install` in a scaffolded project failed with
`ERR_PNPM_IGNORED_BUILDS: @bufbuild/buf`, leaving no `buf` binary and breaking
`buf generate`. The working key, used by the monorepo root and every dogfooded example,
is `allowBuilds` (a map):

    allowBuilds:
      '@bufbuild/buf': true
      esbuild: true

Caught by the new cli-scaffold-matrix gate on its first run (the `init base-node-pnpm`
cell). The same failure had appeared locally and was wrongly written off as a
machine-specific pnpm config — it was this key all along; the npm cells passed because
npm runs postinstall by default.

Verified: scaffolded pnpm project installs with no ignored-builds error, `buf` 1.72.0
present, `pnpm run typecheck` and `pnpm run test` both green. 117 unit tests.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SdapMMJeamn5TDrY8yzHRQ

@coderabbitai coderabbitai 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.

Actionable comments posted: 6

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/cli-scaffold-matrix.yml:
- Line 38: Disable credential persistence for both actions/checkout steps in
.github/workflows/cli-scaffold-matrix.yml at lines 38-38 and 64-64 by adding
with.persist-credentials: false to the checkout steps in the scaffold and
scaffold-bun jobs.
- Around line 22-23: Add an explicit permissions block to the scaffold workflow
under the scaffold job or workflow configuration, granting only the minimum
required access, such as contents: read. Do not retain broad default
permissions.

In `@packages/cli/src/commands/init.ts`:
- Around line 90-92: Update the targetDir validation in the init command to use
lstatSync before readdirSync, distinguishing existing directories from file
collisions. When targetDir exists but is not a directory, throw the same
user-facing non-empty/existing-target error instead of allowing readdirSync to
raise ENOTDIR; preserve the --force behavior and existing empty-directory
handling.

In `@packages/cli/src/scaffold/config.ts`:
- Around line 61-62: Handle an explicitly provided empty --events value as
opt-in: in packages/cli/src/scaffold/config.ts lines 61-62, resolve an empty
string to the default "nats" adapter instead of undefined; in
packages/cli/src/scaffold/prompts.ts lines 103-107, detect the empty-string flag
and immediately prompt for the adapter without showing the confirmation step.
Preserve existing behavior for omitted and explicitly valued adapters.

In `@packages/cli/src/scaffold/generateService.ts`:
- Around line 14-31: Validate the normalized result of packageName, pascalCase,
and camelCase in the service-name handling flow, rejecting names such as "!!!"
that contain no alphanumeric characters before buildServiceFiles runs. Raise a
clear validation error rather than allowing empty package or generated
identifier values to produce malformed proto and import paths.

In `@packages/cli/src/scaffold/transform.ts`:
- Around line 134-136: Update the dependency merge logic in the scaffold
transformation to apply non-empty extraDeps even when pkg.dependencies is
absent, initializing the base dependency map as needed before merging and
sorting. Preserve the existing behavior for packages with dependencies and leave
dependencies unchanged when extraDeps is empty.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 45988972-326b-48d1-9a62-a896a13c2c57

📥 Commits

Reviewing files that changed from the base of the PR and between 67c7b81 and f8c5caa.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (28)
  • .changeset/swift-pandas-scaffold.md
  • .github/workflows/cli-scaffold-matrix.yml
  • packages/cli/README.md
  • packages/cli/package.json
  • packages/cli/src/commands/generate-service.ts
  • packages/cli/src/commands/init.ts
  • packages/cli/src/index.ts
  • packages/cli/src/scaffold/authFragment.ts
  • packages/cli/src/scaffold/bufConfig.ts
  • packages/cli/src/scaffold/config.ts
  • packages/cli/src/scaffold/eventsFragment.ts
  • packages/cli/src/scaffold/fetchBase.ts
  • packages/cli/src/scaffold/generateService.ts
  • packages/cli/src/scaffold/prompts.ts
  • packages/cli/src/scaffold/serverGen.ts
  • packages/cli/src/scaffold/transform.ts
  • packages/cli/src/scaffold/types.ts
  • packages/cli/src/tiged.d.ts
  • packages/cli/src/utils/emit.ts
  • packages/cli/tests/unit/emit.test.ts
  • packages/cli/tests/unit/scaffold-auth.test.ts
  • packages/cli/tests/unit/scaffold-catalog.test.ts
  • packages/cli/tests/unit/scaffold-commands.test.ts
  • packages/cli/tests/unit/scaffold-events.test.ts
  • packages/cli/tests/unit/scaffold-prompts.test.ts
  • packages/cli/tests/unit/scaffold-serverGen.test.ts
  • packages/cli/tests/unit/scaffold-transform.test.ts
  • packages/cli/tsup.config.ts

Comment thread .github/workflows/cli-scaffold-matrix.yml
Comment thread .github/workflows/cli-scaffold-matrix.yml
Comment thread packages/cli/src/commands/init.ts Outdated
Comment on lines +61 to +62
const eventsRaw = (input.events ?? "").trim();
const events = eventsRaw === "" ? undefined : { adapter: oneOf(eventsRaw, EVENT_ADAPTERS, "events", "nats") };

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Treat empty --events flag as an opt-in, not an opt-out.

When a user runs connectum init --events without providing an adapter value, the CLI parser assigns it an empty string "". Because prompts.ts skips the prompt when the value is not undefined and config.ts silently maps "" back to undefined, the user's explicit opt-in is completely ignored and events are disabled without a warning.

Update both files to treat an empty string as a valid opt-in that defaults to "nats" (or explicitly prompts for it):

  • packages/cli/src/scaffold/config.ts#L61-L62: update the resolution to default to "nats" when the flag is present but empty.
  • packages/cli/src/scaffold/prompts.ts#L103-L107: update the wizard to immediately prompt for the adapter (skipping the confirmation) if the user explicitly provided the empty flag.
🛠️ Proposed fixes

For packages/cli/src/scaffold/config.ts:

-    const eventsRaw = (input.events ?? "").trim();
-    const events = eventsRaw === "" ? undefined : { adapter: oneOf(eventsRaw, EVENT_ADAPTERS, "events", "nats") };
+    const events = input.events === undefined
+        ? undefined
+        : { adapter: oneOf(input.events.trim() === "" ? "nats" : input.events.trim(), EVENT_ADAPTERS, "events", "nats") };

For packages/cli/src/scaffold/prompts.ts:

-    let events = flags.events;
-    if (events === undefined) {
-        const wantEvents = await prompter.confirm({ message: "Add an EventBus?", initialValue: false });
-        events = wantEvents ? await prompter.select({ message: "Event adapter", options: ADAPTER_OPTIONS, initialValue: "nats" }) : undefined;
-    }
+    let events = flags.events;
+    if (events === undefined || events === "") {
+        const wantEvents = events === "" ? true : await prompter.confirm({ message: "Add an EventBus?", initialValue: false });
+        events = wantEvents ? await prompter.select({ message: "Event adapter", options: ADAPTER_OPTIONS, initialValue: "nats" }) : undefined;
+    }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const eventsRaw = (input.events ?? "").trim();
const events = eventsRaw === "" ? undefined : { adapter: oneOf(eventsRaw, EVENT_ADAPTERS, "events", "nats") };
const events = input.events === undefined
? undefined
: { adapter: oneOf(input.events.trim() === "" ? "nats" : input.events.trim(), EVENT_ADAPTERS, "events", "nats") };
Suggested change
const eventsRaw = (input.events ?? "").trim();
const events = eventsRaw === "" ? undefined : { adapter: oneOf(eventsRaw, EVENT_ADAPTERS, "events", "nats") };
let events = flags.events;
if (events === undefined || events === "") {
const wantEvents = events === "" ? true : await prompter.confirm({ message: "Add an EventBus?", initialValue: false });
events = wantEvents ? await prompter.select({ message: "Event adapter", options: ADAPTER_OPTIONS, initialValue: "nats" }) : undefined;
}
📍 Affects 2 files
  • packages/cli/src/scaffold/config.ts#L61-L62 (this comment)
  • packages/cli/src/scaffold/prompts.ts#L103-L107
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/cli/src/scaffold/config.ts` around lines 61 - 62, Handle an
explicitly provided empty --events value as opt-in: in
packages/cli/src/scaffold/config.ts lines 61-62, resolve an empty string to the
default "nats" adapter instead of undefined; in
packages/cli/src/scaffold/prompts.ts lines 103-107, detect the empty-string flag
and immediately prompt for the adapter without showing the confirmation step.
Preserve existing behavior for omitted and explicitly valued adapters.

Comment on lines +14 to +31
export function packageName(name: string): string {
return name.toLowerCase().replace(/[^a-z0-9]/g, "");
}

/** PascalCase from an arbitrary name (`my-service` -> `MyService`). */
export function pascalCase(name: string): string {
return name
.split(/[^a-zA-Z0-9]+/)
.filter((p) => p.length > 0)
.map((p) => p.charAt(0).toUpperCase() + p.slice(1))
.join("");
}

/** camelCase from an arbitrary name (`my-service` -> `myService`). */
export function camelCase(name: string): string {
const p = pascalCase(name);
return p.charAt(0).toLowerCase() + p.slice(1);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Symbol-only service names silently produce malformed output.

packageName/pascalCase/camelCase can all reduce to "" for a name that has no alphanumeric characters (e.g. "!!!"). The caller only rejects an empty-after-trim name, so such input passes through and buildServiceFiles emits a broken package .v1; proto and an unresolvable #gen//v1/_pb.ts import instead of a clear error.

🔧 Proposed fix
 export function buildServiceFiles(name: string, withEvents: boolean): Map<string, string> {
     const pkg = packageName(name);
+    if (pkg === "") {
+        throw new Error(`connectum generate service: "${name}" must contain at least one letter or digit`);
+    }
     const files = new Map<string, string>([

Also applies to: 111-122

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/cli/src/scaffold/generateService.ts` around lines 14 - 31, Validate
the normalized result of packageName, pascalCase, and camelCase in the
service-name handling flow, rejecting names such as "!!!" that contain no
alphanumeric characters before buildServiceFiles runs. Raise a clear validation
error rather than allowing empty package or generated identifier values to
produce malformed proto and import paths.

Comment thread packages/cli/src/scaffold/transform.ts Outdated
CodeRabbit / CodeQL feedback on #229 — all six findings:

- workflow: add `permissions: contents: read` (matches the other 8 workflows) and
  `persist-credentials: false` on both checkout steps (precedent: snapshot.yml)
- `init`: guard with `statSync().isDirectory()` before `readdirSync`, so a target path
  that exists as a *file* fails with a clear message instead of a raw ENOTDIR stack
- **`--events` with no value no longer silently disables the module** (major): it is an
  explicit opt-in that named no adapter, so non-interactive runs now fail loudly and the
  wizard skips the yes/no question and goes straight to the adapter picker
- `generate service`: reject names with no alphanumeric characters (e.g. `!!!`), which
  previously reduced to an empty proto package and emitted a malformed `package .v1;`
- `transform`: drop the `&& pkg.dependencies` guard so module deps are added even when
  the base package.json has no `dependencies` block

+5 regression tests (122 total); build / typecheck / lint clean. Verified live:
`init --events` (no value) errors clearly; a normal init still scaffolds.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SdapMMJeamn5TDrY8yzHRQ
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

pkg:cli @connectum/cli (Layer 3) type:feature New feature or enhancement request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants