feat(cli): project scaffolding — connectum init and generate service#229
feat(cli): project scaffolding — connectum init and generate service#229intech wants to merge 13 commits into
connectum init and generate service#229Conversation
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
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (9)
🚧 Files skipped from review as they are similar to previous changes (9)
📝 WalkthroughWalkthroughThe CLI adds ChangesCLI scaffolding
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
Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
@connectum/auth
@connectum/cli
@connectum/core
@connectum/events
@connectum/events-amqp
@connectum/events-kafka
@connectum/events-nats
@connectum/events-redis
@connectum/healthcheck
@connectum/interceptors
@connectum/otel
@connectum/protoc-gen-catalog
@connectum/reflection
@connectum/test-fixtures
@connectum/testing
commit: |
…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
There was a problem hiding this comment.
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
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (28)
.changeset/swift-pandas-scaffold.md.github/workflows/cli-scaffold-matrix.ymlpackages/cli/README.mdpackages/cli/package.jsonpackages/cli/src/commands/generate-service.tspackages/cli/src/commands/init.tspackages/cli/src/index.tspackages/cli/src/scaffold/authFragment.tspackages/cli/src/scaffold/bufConfig.tspackages/cli/src/scaffold/config.tspackages/cli/src/scaffold/eventsFragment.tspackages/cli/src/scaffold/fetchBase.tspackages/cli/src/scaffold/generateService.tspackages/cli/src/scaffold/prompts.tspackages/cli/src/scaffold/serverGen.tspackages/cli/src/scaffold/transform.tspackages/cli/src/scaffold/types.tspackages/cli/src/tiged.d.tspackages/cli/src/utils/emit.tspackages/cli/tests/unit/emit.test.tspackages/cli/tests/unit/scaffold-auth.test.tspackages/cli/tests/unit/scaffold-catalog.test.tspackages/cli/tests/unit/scaffold-commands.test.tspackages/cli/tests/unit/scaffold-events.test.tspackages/cli/tests/unit/scaffold-prompts.test.tspackages/cli/tests/unit/scaffold-serverGen.test.tspackages/cli/tests/unit/scaffold-transform.test.tspackages/cli/tsup.config.ts
| const eventsRaw = (input.events ?? "").trim(); | ||
| const events = eventsRaw === "" ? undefined : { adapter: oneOf(eventsRaw, EVENT_ADAPTERS, "events", "nats") }; |
There was a problem hiding this comment.
🎯 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.
| 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") }; |
| 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.
| 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); | ||
| } |
There was a problem hiding this comment.
🎯 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.
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
Summary
Adds project scaffolding to
@connectum/cli:connectum init(create a new project) andconnectum generate service(add a service to an existing one).initfetches the dogfoodedgetting-startedexample 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):--otel), EventBus with an adapter (--events nats|kafka|redpanda|redis|amqp), auth (--auth, JWT + proto-driven authorization), service catalog (--catalog, typedctx.call/ctx.stream), opt-in resilience interceptors (--resilience timeout,retry,…), health/reflection togglesnode/bun, package managerpnpm/npm, Node execution modelraw(>= 25.2) /tsx(>= 22.13)buf generateis chained into the generatedstart/test/typecheckscripts — deliberately not a pnpmpre*hook, which silently no-ops whenenable-pre-post-scriptsis off — so a fresh clone never fails on an unresolved#gen/...import. Standalone pnpm projects also get thebufbuild approval pnpm 11 requires.connectum generate service <name>— scaffolds a starter proto plus adefineServiceskeleton whose rpc handlers throwCode.Unimplemented.--with-eventsalso scaffolds an event-handler service and an ack-by-defaultEventRoute. It never edits yoursrc/server.ts(that file is yours) — it prints the exact registration line to add.Generated tests are runtime-agnostic: the e2e test uses the public in-process
createLocalClientfrom@connectum/testing(no socket — identical on Node and Bun); event-enabled projects also get a broker-freeMemoryAdaptersmoke test.Validation
Every module was validated end-to-end against real published
@connectum/*packages, not just unit tests:init→ install →buf lint/buf generate→tsc --noEmit→ e2e, on Node + pnpm, npm, and vianpx.That live validation caught two real bugs unit tests alone would have missed:
pnpm-workspace.yaml— thepackage.jsonpnpm.*field is no longer read — so without itbufcannot install andbuf generatefails in a scaffolded project.@connectum/coreinternals (register(router, ctx)) plus a cast-heavy bridge; the generated test now uses the publiccreateLocalClientinstead.A new CI workflow
cli-scaffold-matrixscaffolds each named module combination (base pnpm/npm, otel, events-nats, auth, catalog, kitchen-sink, plus a Bun cell) and runsbuf 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.@connectum/climinor)Companion PRs
connectum init/generate servicedocs#64 — scaffolding guide, sidebar entry, cli-commands and runtime-compatibility notesbuf generateinto typecheck/test/start examples#40 — the same lifecycle fix retrofitted intogetting-started(the degit / scaffold base)🤖 Generated with Claude Code
https://claude.ai/code/session_01SdapMMJeamn5TDrY8yzHRQ
Summary by CodeRabbit
connectum initfor interactive or non-interactive standalone project scaffolding with configurable runtime, package manager, observability, authentication, events (with multiple adapters), resilience, catalog, healthcheck, and reflection.connectum generate service <name>to scaffold starter RPC services and optional event handlers, including clear next-step registration output.