feat(projects): add forest projects:create:in-app - #806
Conversation
Register a Forest project for an app the user hosts themselves (Rails, Express, NestJS, …) — no agent scaffold, no database introspection. It creates the project with architecture 'in-app' and no agent (like the web UI), then prints the env/auth secrets and how to mount Forest in the existing app. The running agent pushes its own schema on first boot. - makes `architecture` overridable on the abstract create command - in-app: requiresDatabase=false, agent=null, dump is a no-op - Rails guidance lists the 5 required gems: `forest_admin_rails` alone installs but fails to boot (it doesn't declare its 4 companion gems as runtime deps), so the single-gem instruction is wrong. Validated end-to-end on both stacks against a real Forest project: a Node Express agent and a Rails 8 app each mounted with the CLI-issued secret and pushed their schema (verified server-side: dev env went active and collections appeared, 0 before boot). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
a7f3e2a to
8b2552f
Compare
- create:in-app now defaults applicationPort to 3000 (the user's own app)
instead of 3310 (scaffolded microservice agents), with an adapted prompt
- new --format json flag: stdout carries ONLY a parsable
{"projectId", "envSecret", "authSecret"} document (logger stdout is muted
in this mode, spinners already go to stderr) — the contract npx forest-start
parses; human output is unchanged
- drop the unused OclifConfig import
- tests: assert exact fixture secret values, add negative assertions via a new
`not` std helper (no 'Testing connection'/'Analyzing': the command must not
touch a DB), assert the whole JSON stdout, and document the nock exact-body
match as the guard for agent:null + architecture:in-app
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
`--format json` promised that stdout carries nothing but a parsable JSON
document, but did not keep it:
- host/port were always prompted (their defaults are only applied by
inquirer), so a scripted run got an inquirer question and ANSI escapes on
stdout, exit 0, and no JSON at all;
- the login check runs before the mute was installed, so an unauthenticated
run wrote 'Login required.' and a password prompt to stdout;
- the mute dropped stdout lines outright, and the real cause of a failure is
logged with logger.log() — so in JSON mode errors were silently swallowed.
Fixes, in order:
- Logger.silentStdout becomes Logger.reserveStdout and *diverts* stdout lines
to stderr instead of dropping them. Nothing is lost: diagnostics stay
visible to the operator, they just stop polluting the parsable stream.
- option-parser gains getDefaultOptions(), an unattended counterpart to
getInteractiveOptions() using each option's declared default. Selected via
getCommandLineOptions(instance, { interactive }) — default true, so every
other command is untouched.
- AbstractProjectCreateCommand exposes an `interactive` getter as the
extension point; in-app returns false in JSON mode.
- in-app parses --format in run(), before the login check, and refuses with
exit 10 rather than hanging on a password prompt.
- in-app refuses to emit a partial document: JSON.stringify drops undefined
keys, so a missing secret would otherwise look like success with exit 0.
Verified on the built binary: the previously failing invocation now writes
nothing to stdout and surfaces the real cause on stderr, and the nominal run
emits a JSON.parse-able document with the declared port default applied.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Pushed Why
What changed
Two things worth your attentionShared files. One out-of-scope addition. While exercising the fix I found that a missing env secret surfaced as success: VerifiedCI 6/6. Full suite locally: 114 failures / 1131 against 114 / 1119 on On the built binary, against a local stub API:
Rails gemsLeft exactly as you wrote them. I re-verified your finding and it still holds on 1.43.0 (published 4 days ago, 7 minors after the 1.36.0 you tested): |
What
New command
forest projects:create:in-app— register a Forest project for an app the user hosts themselves (Rails, Express, NestJS, …). Unlikecreate:sql/demo/nosql, it does not scaffold an agent and does not introspect a database: the project is created witharchitecture: 'in-app'and no agent (exactly like the web UI's in-app onboarding), and the command prints the env/auth secrets + how to mount Forest in the existing app. The user's own running agent pushes its schema on first boot.architectureoverridable onAbstractProjectCreateCommand(default stays'microservice').requiresDatabase = false,agent = null(so the server keepsarchitecture: 'in-app'),dumpis a no-op.gem 'forest_admin_rails'alone installs but fails to boot (cannot load such file -- forest_admin_agent) because 1.36.0 doesn't declare its 4 companion gems as runtime deps — so the single-gem instruction would be wrong. (Worth a separate report to the Ruby SDK team.)Tested
in-app.test.js(mirrorsdemo.test.js) — asserts the in-app create postsarchitecture: 'in-app'with no agent, skips all DB steps, scaffolds nothing, and prints the secrets + the 5-gem Rails guidance.createProjectfixture gained anarchitectureparam (defaults to'microservice', backward-compatible).0a8d5cd: 6 cases inin-app.test.jsnow, covering the--format jsoncontract (no prompting, pure-JSON stdout, refusal when logged out, error visibility, no partial document), plus new unit tests forLogger.reserveStdoutandoption-parser.getDefaultOptions. See the comment below for what that commit changes and why.@forestadmin/agent@1.90.3mounted with the CLI-issued secret → dev envis_activefalse→true, collections 0 → 8.rails new(Rails 8.0.5 / Ruby 3.3.9) + the 5 forest gems +rails g forest_admin_rails:install <secret>→is_activefalse→true, collections 0 → 2 (Company,Customer).Note
Add
projects:create:in-appcommand with JSON mode and non-interactive defaultsprojects:create:in-appcommand to register user-hosted applications within-apparchitecture, skipping database and agent scaffolding.AbstractProjectCreateCommandwith overridableinteractiveandarchitecturefields, retaining existingmicroserviceand interactive defaults for other commands.getCommandLineOptionsto accept an optionalinteractiveparameter, resolving missing values from declared defaults without prompting when disabled.reserveStdouttoLoggerwhich routes normal log lines to stderr, keeping stdout free for JSON payloads in the new command.getCommandLineOptionsin option-parser.ts changes signature to addinteractive;Loggerin logger.js addsreserveStdoutinitialization. Existing callers retain default behavior.Macroscope summarized 0a8d5cd.