chore: move the scaffold onto the current ecosystem and offer Fastify - #146
Merged
Conversation
Bump the pinned auth API to v0.6.0, the admin dashboard to v0.4.0, and the templates ref to v0.6.0, which carries @seamless-auth/react ^0.7.0 in both React starters and @seamless-auth/express ^0.11.0 in the Express starter. The API drops the admin bootstrap invite flow in favor of the OWNER_EMAIL grant the CLI already writes, adds admin:read and admin:write to AVAILABLE_ROLES, and gains DB_SSL configuration. Those reach generated projects without code changes: the CLI fetches .env.example at the pinned tag and mutates it. Move the conformance adapter to @seamless-auth/express ^0.11.0. Its breaking change splits error into errorCode and errorBody on the handler result types, which only affects code importing handlers from @seamless-auth/core directly, so the adapter needed no source change.
Bump the templates ref to v0.7.0, which adds a Fastify API starter beside Express. The backend prompt renders it as "Fastify (beta)" from the registry status, so no CLI code changes are needed. The starter serves the same surface as Express on the same env contract, including the admin console at /console behind SERVE_ADMIN_CONSOLE, which the Fastify adapter gained in @seamless-auth/fastify 0.2.0. Both API starters now ship .env.example secrets long enough to clear the adapter's 32 character minimum.
The auth API adds an unauthenticated GET /system-config/public returning the configured login methods. The adapters proxy it, and the React starters read it to offer a skip on the passkey registration screen when the instance has another login method enabled. The API, adapters, and templates have to move together for that path to work, so bump them as a set. Templates v0.8.0 carries @seamless-auth/react ^0.8.0, @seamless-auth/express ^0.12.0, and @seamless-auth/fastify ^0.3.0, plus two Fastify boot fixes: an empty PORT= now falls back to 3000 instead of binding a random free port, and pino-pretty moves to a runtime dependency. Move the conformance adapter to @seamless-auth/express ^0.12.0, the release that proxies the new route.
Registering against a scaffolded Fastify API returned a 500 with TypeError: option maxAge is invalid: 300. The auth server sent the registration response's ttl as the string "300", and the Fastify adapter handed it to a cookie library that requires an integer. Express never showed this because its adapter multiplies the value into milliseconds, coercing the string on the way past. It is fixed from both ends. @seamless-auth/core 0.12.1 parses the lifetime before it reaches an adapter and rejects anything that is not a positive whole number of seconds, and auth API v0.7.1 sends a number. Templates v0.8.1 carries the fix through the Fastify starter's committed lockfile, which is what pinned the broken version. The conformance adapter needs no pin change: it has no lockfile, so ^0.12.0 already resolves to the fixed 0.12.1.
Every command now answers -h / --help with usage, flags, subcommands, and examples scoped to that command, and seamless help <command> prints the same thing. The text lives in one registry (src/commands/helpTopics.ts) that both the full seamless --help output and the per-command output render from, so a flag is documented once. The dispatcher's known-command list comes from there too. The help check runs before a command parses its own arguments, so seamless init -h prints help instead of scaffolding ./-h. A -- separator ends the check, leaving room for a literal -h operand (seamless config set key -- -h).
Add `seamless templates list [--json]`, which prints every starter init can scaffold with its id, kind, framework, selecting flags, and status. It reads the same registry init does and needs no login, so the available templates no longer have to be looked up in the source. Every template now answers to `--<id>` as well as its shorter `--<alias>`, so `init --react-vite` works alongside `--basic`, and the api starters get a flag for the first time. The unknown-option error lists both spellings and points at `templates list`. Template flags are resolved in runCLI before a directory is created and before the non-empty-directory confirmation, so an unrecognized or conflicting flag fails immediately instead of surfacing only after the overwrite prompt. The registry is opened at most once per run, and not at all on the integrate path. Closes #149, closes #150, closes #151
`--yes` (-y) answers every question with the option the prompt marks as recommended, so a scaffold runs from CI, a Dockerfile, or a script with no terminal attached. Each question also gets its own flag, honored with or without --yes: --web and --api choose the starters (by id or alias), --email sets the owner who becomes the admin, --auth picks how the auth server runs, and --admin picks where the console is hosted. Values are validated against the registry and the known modes in runCLI, before a directory is created. --yes stops rather than guessing in three places. Managed or local needs --app or --local, because that decides where the project's auth lives for good. Scaffolding into a non-empty directory needs --force, because starter files overwrite anything with the same name. Rotating an application's existing service token needs --force, because it breaks whatever is deployed on the old one. init's argument parsing moves into parseInitArgs so the new switches are not mistaken for template flags or for the project name. Closes #152
Run on a pipe, init used to render a @Clack prompt nobody could answer and wait forever, so a CI step failed only when its job timed out. Every prompt in the init flow is now fronted by requireInteractive, which stops on the first unanswered question and names the flag that answers it. A run whose answers all come from flags is unaffected and behaves the same on a pipe as on a terminal. A terminal too narrow to render a prompt (a pty allocated without a size reports one column, which printed one character per line) now warns rather than just looking broken. Scoped to init. The other commands that prompt are still unguarded, tracked in the issue. Closes #153
seamless init stopped rendering prompts to a pipe in #158; every other command still hung there until its job timed out. Every prompt is now fronted by requireInteractive, so a run without a TTY on stdin fails naming the flag that answers the question: login, profile add, users delete, users prepare-device-replacement, sessions revoke, org members remove, config apply, and config oauth-providers remove. A guard with nothing to point at would only convert a hang into a dead end, so the destructive confirmations now take --force, matching what init means by it. They share confirmDestructive, which answers itself when forced and otherwise asks. hasForceFlag also accepts --yes and -y, because config oauth-providers remove --yes shipped before the convention existed. --force does not override --dry-run, and cancelling still reads as declining rather than as an error. The one-time code at login has no flag form (it does not exist until the code is sent), so that step reports that it genuinely needs a terminal. Tests get a setup file that marks stdin a TTY, since the guard would otherwise fail every test that exercises a prompt; the no-terminal tests opt back out. Closes #159
# Conflicts: # README.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Bumps the pinned ecosystem so
seamless initscaffolds from the current releases, and adds Fastify as a backend option.SEAMLESS_AUTH_API_VERSIONSEAMLESS_AUTH_ADMIN_DASHBOARD_VERSIONSEAMLESS_TEMPLATES_REF@seamless-auth/express(conformance adapter)Templates v0.8.1 carries
@seamless-auth/react^0.8.0in both React starters,@seamless-auth/express^0.12.0in the Express starter, and@seamless-auth/fastify^0.3.1in the Fastify starter.Registration without a passkey
Registration used to end on a screen with one control. A user who did not want a passkey, or whose device could not make one, had no way forward. The starters now offer a skip when the instance has another login method enabled, and say so plainly when it does not.
That path only works if the whole chain moves together, which is why this bumps them as a set:
GET /system-config/publicreturning the configuredloginMethodsand nothing else (v0.7.0).@seamless-auth/express0.12.0,@seamless-auth/fastify0.3.0).@seamless-auth/react0.8.0).A web template upgraded on its own would read nothing and show no skip at all. This also fixes a real mismatch: the React SDK previously fell back to a hardcoded
['passkey', 'magic_link', 'phone_otp'], so a client could advertise a method the instance had turned off.Fastify registration fix
Registering against a scaffolded Fastify API returned a 500 with
TypeError: option maxAge is invalid: 300. The auth server sent the registration response'sttlas the string"300", and the Fastify adapter handed it to a cookie library whoseNumber.isIntegercheck rejects a string. The Express starter never showed this, because its adapter multiplies the value into milliseconds and so coerced it on the way past.Fixed from both ends:
@seamless-auth/core0.12.1 parses the lifetime once, before it reaches an adapter, and throws on anything that is not a positive whole number of seconds rather than issuing a cookie with a lifetime nobody can vouch for; auth API v0.7.1 sends a number. Templates v0.8.1 carries it through the Fastify starter's committed lockfile, which is what pinned the broken version.The conformance adapter needed no pin change. It has no lockfile, so
^0.12.0already resolves to the fixed 0.12.1.Fastify
seamless initoffers a second backend, listed as "Fastify (beta)" beside Express. It serves the same surface as the Express starter on an identical env contract, including the admin console at/consolebehindSERVE_ADMIN_CONSOLE.This needed no CLI code. The backend prompt builds its options from the registry and already renders a
betastatus as a(beta)suffix without disabling the entry, andrequires.cliMin(0.10.0) is satisfied by 0.10.2. Verified through the real code path at the new ref:Two boot-time fixes land with v0.8.0: an empty
PORT=in.envnow falls back to 3000 rather than binding a random free port, andpino-prettymoves to a runtime dependency so an install without dev dependencies boots.What else users get
Both React starters gain a protected
/sessionroute showing the issued claims, roles, organization context, step-up freshness, and registered passkeys, so the first authenticated screen reads as an app rather than aJSON.stringifydump. Missing configuration now stops a scaffolded project with a message naming the variable instead of surfacing later as a 500.The auth API drops the admin bootstrap invite flow in favor of the
OWNER_EMAILgrant the CLI already writes, so generated.envfiles no longer carrySEAMLESS_BOOTSTRAP_ENABLED,SEAMLESS_BOOTSTRAP_SECRET, orSEAMLESS_AUTH_DEBUG_SECRETS.AVAILABLE_ROLESnow offersadmin:readandadmin:write, and Postgres TLS is configurable throughDB_SSL,DB_SSL_CA, andDB_SSL_REJECT_UNAUTHORIZED.Both API starters ship
.env.examplesecret placeholders long enough to clear the adapter's 32 character minimum, so the documentedcp .env.example .env && npm run devpath boots. A project fromseamless initwas already unaffected, because the CLI fillsCOOKIE_SIGNING_KEYitself.Why the rest needed no CLI code
{ error }.loginFlow.tsandsystemConfig.tsalready readerrorfirst..env.exampleat the pinned tag and mutates the parsed result, soAVAILABLE_ROLESand theDB_SSL*keys flow through. v0.7.0 changed no env at all.OWNER_EMAILgeneration was already in place and was already supported by v0.5.0 (insrc/lib/ownerAdmin.ts), just undocumented. Nothing was broken before this bump.GET /system-config/publicis consumed by the React SDK, not by the CLI. The CLI's ownconfigcommand talks to the admin routes, which are unchanged.Checks
npm run buildclean, 691 unit tests pass,verify/harnesstsc --noEmitclean.Conformance passed on all three layers against auth API 0.7.1,
@seamless-auth/express^0.12.0, and@seamless-auth/react^0.8.0, using clean checkouts at the pinned tags:Two caveats on coverage, both worth knowing rather than blocking:
seamless-auth-serverasserts the two adapters return identical status, body, andSet-Cookieheaders, including the console proxy, which is the current basis for confidence.SEAMLESS_API_DIRandSEAMLESS_TEMPLATES_DIR.