Skip to content

Zitadel NextGen v0.1.0

Choose a tag to compare

@github-actions github-actions released this 21 Oct 23:50
c621117

0.1.0 is the first proper release of Zitadel NextGen, and the first on npm's latest tag. Registration and login live in your app, under your brand, while Zitadel guards the credentials, sessions and tokens underneath. An empty folder becomes an app with a working login in three commands, with no account and no Docker:

npx @zitadel/cli@latest start
npx @zitadel/cli@latest setup --framework next --server local
npm run dev

This release sums up everything the alpha previews built; those versions are deprecated now. NextGen is still a preview: until 1.0.0, a minor release can include breaking changes, and each one is called out as breaking. The quickstart walks you through it.

What you get

  • Sign-in and sign-up in your app. <zitadel-login> runs the whole flow in one element: email and password, passkeys, and social sign-in with Google or any OIDC provider, where the first sign-in creates the account. <zitadel-session> and <zitadel-logout> show who is signed in and sign them out. For a UI of your own, the flow and auth-attempt APIs drive sign-in headless. Components
  • 12 frameworks. zitadel setup creates a new app or patches an existing one with login, registration and profile pages, for Next.js, Nuxt, SvelteKit, SolidStart, Qwik Router, TanStack Start, React, Vue, Angular, Solid, Svelte and Qwik. The server SDKs keep auth on your own origin: they check sessions, protect routes and keep the project secret on the server. SDKs
  • Users shaped by your schema. A user schema in JSON Schema says which attributes your users have, which one they sign in with, which must be unique and which sign-in methods they may use. Every write is checked against it. Flows and schemas
  • Auth as code. Schemas, login flows, branding and sign-in connections are files in your repository. zitadel plan checks them against the server's rules before anything changes, and zitadel apply publishes them as immutable revisions. Plan and apply
  • Your brand, your words. Theme the login with --zl-* tokens and light and dark branding, or take over its markup with zitadel branding eject. Override any text per language; English, German and Italian are built in. Design, copy
  • A console. At /ui/console/, manage users through forms built from your schema, as well as teams and project admins, and look through your schemas, flows, sign-in methods and branding, with a live preview of the login.
  • Create first, claim later. zitadel setup --server https://preview.zitadel.com creates a hosted project without an account, and zitadel claim attaches it to your team within 14 days. Projects and teams
  • Runs anywhere. zitadel start runs the server from npm on your machine, with no Docker, Go or checkout, and signs you in to the console with a one-time link. To host it, take the binary for macOS, Linux or Windows or the image ghcr.io/zitadel/nextgen, with SQLite by default and PostgreSQL or Spanner in production. Local runtime, databases
  • Audit events. Sign-ins, sessions, users and configuration changes are recorded as typed events you can query through the API, and a self-hosted server can export them to a webhook. Events
  • One API for all of it. Every operation is in the API reference, with stable error codes. zitadel api calls any of them from the terminal, and @zitadel/api is a typed client with an error type per operation. API client
  • Built for agents. Every CLI command answers --json with one envelope. npx skills add zitadel/nextgen --full-depth installs Agent Skills for the CLI, the components and each SDK, and the docs come as llms.txt and over MCP. Agents
  • Tests against a real server. @zitadel/testing starts a local Zitadel for Playwright, with seeded users, signed-in pages and virtual passkeys.

Not there yet

  • Password recovery, email or SMS codes, TOTP, email verification and enforced MFA. The default flow covers sign-in and sign-up with passwords, passkeys and social sign-in.
  • Social sign-in is OIDC only, and a provider can't be linked to an existing account yet.
  • Zitadel doesn't issue OIDC or OAuth tokens to your apps in this preview: apps use its sessions through the SDKs, and the SPA frameworks need a proxy of your own in production.
  • Deploying a release to your own domains goes through the API; deploying to preview URLs, rollback and a CLI command for it are still to come.

Upgrading from 1.0.0-alpha.N

  • Install with @latest, for the CLI and every SDK; @alpha now points at 0.1.0 and stays there. npm sorts 0.1.0 below 1.0.0-alpha.28, so Renovate and Dependabot won't offer this update: set the @zitadel/* versions in your package.json to 0.1.0 yourself. The GitHub releases v1.0.0-alpha.20 to .28 are now v0.1.0-alpha.20 to .28.
  • Security: an auth attempt could issue a session after verifying only the user's identifier (#2211). Upgrade the server, then revoke active sessions whose only factor is identifier. Step-up sign-in now needs the session's session_token as well as its session_id (#2166).
  • Breaking: a deploy that names a primary origin answers 400 with dep.invalid; deploy with targets: ["primary"] (#1736). Also check the new error codes for schema failures (req.invalid, #1894) and over-long passwords (user.password_policy_violation, #2107), and how POST /flow picks the release by Origin (#2043).

The changes listed below are the ones since 1.0.0-alpha.28, the last alpha.

Generated Release Facts

This section is maintained by release automation. Edit product notes outside this block.

Release commit: c621117bceb2

Container Images

Image Reference
container ghcr.io/zitadel/nextgen:0.1.0
container ghcr.io/zitadel/nextgen:latest

npm Packages

Package Version
@zitadel/cli 0.1.0
@zitadel/server 0.1.0
@zitadel/server-linux-x64 0.1.0
@zitadel/server-linux-arm64 0.1.0
@zitadel/server-darwin-x64 0.1.0
@zitadel/server-darwin-arm64 0.1.0
@zitadel/server-win32-x64 0.1.0
@zitadel/api 0.1.0
@zitadel/config 0.1.0
@zitadel/components 0.1.0
@zitadel/sdk-core 0.1.0
@zitadel/sdk-next 0.1.0
@zitadel/sdk-nuxt 0.1.0
@zitadel/sdk-react 0.1.0
@zitadel/sdk-vue 0.1.0
@zitadel/sdk-angular 0.1.0
@zitadel/sdk-solid 0.1.0
@zitadel/sdk-svelte 0.1.0
@zitadel/sdk-qwik 0.1.0
@zitadel/sdk-sveltekit 0.1.0
@zitadel/sdk-solid-start 0.1.0
@zitadel/sdk-qwik-router 0.1.0
@zitadel/sdk-tanstack-start 0.1.0
@zitadel/testing 0.1.0

Package Changes

@zitadel/cli

Minor Changes

  • #2187 114c32c Thanks @fforootd! - Add zitadel api, which calls any operation of the Zitadel API with your project's server and secret, including the ones no command covers yet. Name an operation by its operationId (zitadel api queryUsers) or its path (zitadel api /users/query), set query parameters and body fields with -F name=value, typed by the operation's schema, pass a whole body with --input, follow every page with --paginate, and preview the request as cURL with --dry-run. zitadel api ls lists the operations, and --json returns the response body as data. @zitadel/api exports the operations as data from @zitadel/api/generated/operations: each one's method, path, parameters, authentication and paging.

  • #2215 ab19e1e Thanks @fforootd! - Releases now publish to npm's latest tag as 0.x preview versions, starting at 0.1.0, instead of 1.0.0-alpha.N under alpha: run the CLI as npx @zitadel/cli@latest and install the SDKs with @latest. The 1.0.0-alpha.N versions are deprecated on npm. Until 1.0.0, a breaking change ships in a minor version, and its release note marks it Breaking:. zitadel setup pins the @zitadel/* packages it adds, and the commands it suggests, to the CLI's exact version on every release, and zitadel start --runtime docker runs the server image of that version.

Patch Changes

  • #2190 43b20ea Thanks @mridang! - zitadel setup now patches an existing SvelteKit, SolidStart, Qwik City or TanStack Start app instead of failing on it. The four patchers wrote the app shell and landing page (SvelteKit's +layout.svelte and +page.svelte, the other three's routes/index.tsx) on every run, so setup stopped with E_CONFLICT on any app that already had them and --force overwrote the user's own root layout and homepage. Like the Next and Nuxt patchers, they now write those files only when setup created the app skeleton itself and report them as conditionally scaffolded, so a pre-existing app keeps its shell and landing page while still getting the auth boundary and the login, register and profile routes.

  • #2177 9194819 Thanks @mridang! - Add @zitadel/sdk-next/components, a "use client" entry that re-exports the React ZitadelLogin, ZitadelLogout and ZitadelSession wrappers, so project and locales reach the element as properties after server-rendered hydration on React 19 and on React 18, where the raw elements lose them or write [object Object] attributes. The Next.js pages zitadel setup scaffolds now render the wrappers.

@zitadel/server

Minor Changes

  • #2142 f4115f8 Thanks @IAM-marco! - The console's user detail screen now edits a boolean attribute with a switch and saves it as true or false. Optional fields are marked "Optional" beside their label. A cleared required field now says "This field is required." next to it, and a partly cleared date keeps Save off instead of deleting the stored date. An unsaved edit now survives a switch to the Authentication tab and back. The fields are locked while a save runs, so nothing typed meanwhile is lost.

  • #2135 7066bf8 Thanks @IAM-marco! - The console's user detail screen now shows which schema revision a user is on, and links to that schema.

  • #1884 8ef9c49 Thanks @vitorbari! - GET /deployments/{deployment_id} reads one deployment and GET /deployments lists them newest first, each with its kind and status. origin narrows the list to one origin, live=true lists what is served now, and expand=release embeds the release each deployment made live.

  • #1736 77a4f86 Thanks @vitorbari! - Every deployment carries a kind, primary or preview, and a status, live or inactive. Primary origins are deployed as a whole with targets: ["primary"], so they all serve the same deployment, and a rollback re-deploys to every primary origin the project has.

    Breaking: a deploy that names a primary origin answers 400 with dep.invalid; deploy to primary instead.

  • #1883 dedd93b Thanks @vitorbari! - Releases can now be deployed to a project's primary origins with POST /deployments and targets: ["primary"], which covers every primary origin. Deploying the release that is already live answers 200 and writes nothing, and expected_deployment_id guards against racing another deploy with 409 dep.conflict.

  • #1886 1653960 Thanks @peintnermax! - GET /users/{user_id}/password reports when the user's current password was set (set_at), and leaves it out when the user has none. It never returns the password or its hash. It takes user.read, or a Console session with access to the user's project, and the generated client gains getUserPasswordState.

  • #2107 f80ea64 Thanks @livio-a! - A password longer than 64 characters is now rejected with the error code user.password_policy_violation instead of user.password_too_long when it is set through the API (PUT /users/{user_id}/password); the response's details.details holds {"violations": {"max_length": 64}}. During sign-up, with a password or through a social provider, the same rejection now appears as an error on the sign-up step instead of the request failing; in the default sign-up flow that is the step with the password field, so the user can choose another password. Clients that match on user.password_too_long for over-long passwords should also handle user.password_policy_violation; user.password_too_long remains for passwords the project's hashing method cannot take.

  • #2043 0e1ee74 Thanks @adlerhurst! - Runtime operations document how a request finds its release from the browser Origin header. POST /flow takes Origin as the input that selects the deployment; an origin that matches no project pattern answers 403 with proj.origin_not_allowed, a preview URL with no live preview answers 403 with proj.preview_not_live, nothing deployed answers 409 with rel.not_deployed, and a revoked release answers 409 with rel.revoked. Later flow steps and app-plane auth-attempt operations re-use the sealed deployment and only refuse with rel.revoked (plus proj.mismatch / rel.not_deployed on create and handoff exchange). The server still serves today's behavior until the resolution path lands.

  • 5dea188 Thanks @adlerhurst! - POST /flow resolves the release from the browser Origin header: the gate admits the origin against the project's patterns and live previews, the newest deployment to that exact origin is the target, and that deployment's release is sealed into _zflow as deployment_id and release_id. An origin that matches no pattern answers 403 with proj.origin_not_allowed, a preview URL with no live preview answers 403 with proj.preview_not_live, nothing deployed to a pattern-matched origin answers 409 with rel.not_deployed, and a revoked release answers 409 with rel.revoked. A request with no Origin, or an open admission (empty allowlist / loopback) with nothing deployed yet, keeps the previous newest-revision path until the project default target lands.

  • #2160 4d661a6 Thanks @fforootd! - The "You already have an account" step after a sign-in with an identity provider now carries the account's email when the provider verified it (as step.identifier, so a password manager offers the matching password), no longer offers the provider that just led there, and offers "Use a passkey" only to an account that has one.

  • #1894 aae6e07 Thanks @wim07101993! - User attribute errors now name the fields to fix. When POST /users, PATCH /users/{user_id} or PATCH /users/me refuses attributes that fail the user schema, the answer is 400 req.invalid, the same as for a malformed body, and details.details.fields lists every failing field by its path from the request body, such as attributes.country or attributes.address.zip. A unique value another user holds still answers 409 user.already_exists, and details.details.fields now names the attributes whose value is taken in the same form, without naming the user who holds it. Clients that matched user.invalid for schema failures should match req.invalid.

  • #2202 831cb81 Thanks @peintnermax! - Add GET /users/me/passkeys, GET /users/me/identity_links and GET /users/me/password: the signed-in user's own passkeys, linked identity provider accounts and password state, readable with the session alone. Like the other /users/me reads they take an optional project_id, and refuse a session of another project with 403 sess.project_mismatch. The @zitadel/api client gains listMyPasskeys(), listMyIdentityLinks() and getMyPasswordState().

Patch Changes

  • #2211 d66f2ee Thanks @fforootd! - Fix an authentication bypass: a sign-in through POST /auth_attempts could
    issue a session after verifying only the user's identifier, with no password,
    passkey, or SSO. An attempt created without required checks counted as complete
    as soon as the identifier resolved, and the project's publishable key, which is
    public by design, can call these endpoints, so anyone holding it could sign in
    as any user whose identifier they knew.

    An attempt now completes only once it carries a verified password, passkey, or
    SSO factor, and POST /auth_attempts/{id}/handoff refuses one without it with
    att.not_completed. A client that drives the auth-attempt API directly must
    verify one of those factors before it asks for a handoff. Sessions issued
    through this gap before you upgrade stay valid until they expire: list your
    active sessions and revoke any whose only factor is identifier.

  • #1885 b69348e Thanks @peintnermax! - The console's user detail screen follows the updated design. Overview heads the
    profile with the user's schema and saves edited attributes with a Save button;
    only the changed attributes are sent, and a cleared optional field is removed.
    The Authentication tab lists the passkey state as a label and its value, with
    when the passkey was verified, and the identity providers in the same style.

  • #2192 54000c1 Thanks @peintnermax! - The console's users list keeps its attribute columns in the same order on every load; before, a list mixing user schemas could reshuffle them depending on which schema loaded first. Each row's ID gains a copy button, shown on hover or focus, which copies the whole ID even where the column truncates it.

  • #2188 17523dc Thanks @fforootd! - Declare the error codes that the branding, event, team, project-claim and health operations return. Their error response described only the generic { code, message } shape, so the OpenAPI document and the API reference did not list which codes these 17 operations can send. Each now names its codes, like every other operation, and the @zitadel/api client gains a code-discriminated <Operation>Default type for each (for example GetTeamDefault) to branch on.

  • #2166 fe8b825 Thanks @fforootd! - Step-up sign-in now requires proof that the caller holds the session. POST /auth_attempts and POST /flow take a new session_token field. A request that names an existing session_id must also send that session's session_token, as POST /sessions or POST /sessions/exchange returned it. A request that sends only session_token runs against the token's session. Before this, a session_id alone was accepted. The new attempt then inherited that session's verified factors, so a caller who knew a session id but did not hold the session could complete a step-up on it and receive a fresh session token. A session_id without a matching session_token of the same project is now refused with 400 (att.invalid_request on attempts, req.invalid on flows).

@zitadel/api

Minor Changes

  • #2187 114c32c Thanks @fforootd! - Add zitadel api, which calls any operation of the Zitadel API with your project's server and secret, including the ones no command covers yet. Name an operation by its operationId (zitadel api queryUsers) or its path (zitadel api /users/query), set query parameters and body fields with -F name=value, typed by the operation's schema, pass a whole body with --input, follow every page with --paginate, and preview the request as cURL with --dry-run. zitadel api ls lists the operations, and --json returns the response body as data. @zitadel/api exports the operations as data from @zitadel/api/generated/operations: each one's method, path, parameters, authentication and paging.

  • #1884 8ef9c49 Thanks @vitorbari! - GET /deployments/{deployment_id} reads one deployment and GET /deployments lists them newest first, each with its kind and status. origin narrows the list to one origin, live=true lists what is served now, and expand=release embeds the release each deployment made live.

  • #1883 dedd93b Thanks @vitorbari! - Releases can now be deployed to a project's primary origins with POST /deployments and targets: ["primary"], which covers every primary origin. Deploying the release that is already live answers 200 and writes nothing, and expected_deployment_id guards against racing another deploy with 409 dep.conflict.

  • #1886 1653960 Thanks @peintnermax! - GET /users/{user_id}/password reports when the user's current password was set (set_at), and leaves it out when the user has none. It never returns the password or its hash. It takes user.read, or a Console session with access to the user's project, and the generated client gains getUserPasswordState.

  • #2202 831cb81 Thanks @peintnermax! - Add GET /users/me/passkeys, GET /users/me/identity_links and GET /users/me/password: the signed-in user's own passkeys, linked identity provider accounts and password state, readable with the session alone. Like the other /users/me reads they take an optional project_id, and refuse a session of another project with 403 sess.project_mismatch. The @zitadel/api client gains listMyPasskeys(), listMyIdentityLinks() and getMyPasswordState().

Patch Changes

  • #2188 17523dc Thanks @fforootd! - Declare the error codes that the branding, event, team, project-claim and health operations return. Their error response described only the generic { code, message } shape, so the OpenAPI document and the API reference did not list which codes these 17 operations can send. Each now names its codes, like every other operation, and the @zitadel/api client gains a code-discriminated <Operation>Default type for each (for example GetTeamDefault) to branch on.

@zitadel/config

Patch Changes

  • #2155 b44d6f9 Thanks @fforootd! - The password step of <zitadel-login> now shows the account it signs in to, with a "Use another account" link that goes back to the step where the email was typed. Templates ejected with zitadel branding eject before this release keep their markup; eject again to get the account row.

  • #2155 b44d6f9 Thanks @fforootd! - <zitadel-login> shows a spinner only on the button that was used: following the "Sign up" or "Sign in" link no longer spins the "Continue" button, and a secondary button such as "Use a passkey" now shows its own progress.

@zitadel/components

Minor Changes

  • #2155 b44d6f9 Thanks @fforootd! - The password step of <zitadel-login> now shows the account it signs in to, with a "Use another account" link that goes back to the step where the email was typed. Templates ejected with zitadel branding eject before this release keep their markup; eject again to get the account row.

Patch Changes

  • #2199 5680eb7 Thanks @mridang! - Generate and publish a custom-elements.json manifest describing every
    zitadel-* and zl-* element's attributes and events, exposed through the
    package's customElements field so editors and framework tooling can discover
    the elements' surface.

  • #2090 e3cd8f8 Thanks @mridang! - <zitadel-login> no longer dispatches zitadel-flow-input for a password or one-time-code field, so wiring onFlowInput to logging or analytics cannot capture what a visitor types there. Other fields dispatch as before. The atoms' internal zl-input, zl-change and zl-passkey-result events are no longer composed, so they stay inside the widget.

  • #2175 be96308 Thanks @fforootd! - <zitadel-login> now falls back to your en entries in locales for any text
    the visitor's language has none for. A step you labelled only in English reads
    in English for every visitor instead of showing raw keys, and visitors in a
    language without built-in texts, such as French, see the English texts you
    changed. Built-in German and Italian translations still win over your English;
    add de or it entries to change those.

  • #2155 b44d6f9 Thanks @fforootd! - <zitadel-login> shows a spinner only on the button that was used: following the "Sign up" or "Sign in" link no longer spins the "Continue" button, and a secondary button such as "Use a passkey" now shows its own progress.

  • #2178 3b206b2 Thanks @mridang! - The widgets now wait for their listeners before they navigate. The detail of zitadel-flow-redirect, zitadel-flow-complete and zitadel-signout carries a waitUntil(promise) function; the widget navigates once every promise a listener passed has settled, or after two seconds, and at once when none was passed. The React, Solid, Svelte and Qwik wrappers forward each callback's returned promise to waitUntil, so an async or lazily loaded handler runs before the page unloads; the Vue and Angular wrappers expose waitUntil on the event detail for a handler to call itself.

@zitadel/sdk-core

Patch Changes

  • #2178 3b206b2 Thanks @mridang! - The widgets now wait for their listeners before they navigate. The detail of zitadel-flow-redirect, zitadel-flow-complete and zitadel-signout carries a waitUntil(promise) function; the widget navigates once every promise a listener passed has settled, or after two seconds, and at once when none was passed. The React, Solid, Svelte and Qwik wrappers forward each callback's returned promise to waitUntil, so an async or lazily loaded handler runs before the page unloads; the Vue and Angular wrappers expose waitUntil on the event detail for a handler to call itself.

@zitadel/sdk-next

Patch Changes

  • #2177 9194819 Thanks @mridang! - Add @zitadel/sdk-next/components, a "use client" entry that re-exports the React ZitadelLogin, ZitadelLogout and ZitadelSession wrappers, so project and locales reach the element as properties after server-rendered hydration on React 19 and on React 18, where the raw elements lose them or write [object Object] attributes. The Next.js pages zitadel setup scaffolds now render the wrappers.

@zitadel/sdk-react

Patch Changes

  • #2178 3b206b2 Thanks @mridang! - The widgets now wait for their listeners before they navigate. The detail of zitadel-flow-redirect, zitadel-flow-complete and zitadel-signout carries a waitUntil(promise) function; the widget navigates once every promise a listener passed has settled, or after two seconds, and at once when none was passed. The React, Solid, Svelte and Qwik wrappers forward each callback's returned promise to waitUntil, so an async or lazily loaded handler runs before the page unloads; the Vue and Angular wrappers expose waitUntil on the event detail for a handler to call itself.

@zitadel/sdk-vue

Patch Changes

  • #2178 3b206b2 Thanks @mridang! - The widgets now wait for their listeners before they navigate. The detail of zitadel-flow-redirect, zitadel-flow-complete and zitadel-signout carries a waitUntil(promise) function; the widget navigates once every promise a listener passed has settled, or after two seconds, and at once when none was passed. The React, Solid, Svelte and Qwik wrappers forward each callback's returned promise to waitUntil, so an async or lazily loaded handler runs before the page unloads; the Vue and Angular wrappers expose waitUntil on the event detail for a handler to call itself.

@zitadel/sdk-angular

Patch Changes

  • #2178 3b206b2 Thanks @mridang! - The widgets now wait for their listeners before they navigate. The detail of zitadel-flow-redirect, zitadel-flow-complete and zitadel-signout carries a waitUntil(promise) function; the widget navigates once every promise a listener passed has settled, or after two seconds, and at once when none was passed. The React, Solid, Svelte and Qwik wrappers forward each callback's returned promise to waitUntil, so an async or lazily loaded handler runs before the page unloads; the Vue and Angular wrappers expose waitUntil on the event detail for a handler to call itself.

@zitadel/sdk-solid

Patch Changes

  • #2178 3b206b2 Thanks @mridang! - The widgets now wait for their listeners before they navigate. The detail of zitadel-flow-redirect, zitadel-flow-complete and zitadel-signout carries a waitUntil(promise) function; the widget navigates once every promise a listener passed has settled, or after two seconds, and at once when none was passed. The React, Solid, Svelte and Qwik wrappers forward each callback's returned promise to waitUntil, so an async or lazily loaded handler runs before the page unloads; the Vue and Angular wrappers expose waitUntil on the event detail for a handler to call itself.

@zitadel/sdk-svelte

Patch Changes

  • #2178 3b206b2 Thanks @mridang! - The widgets now wait for their listeners before they navigate. The detail of zitadel-flow-redirect, zitadel-flow-complete and zitadel-signout carries a waitUntil(promise) function; the widget navigates once every promise a listener passed has settled, or after two seconds, and at once when none was passed. The React, Solid, Svelte and Qwik wrappers forward each callback's returned promise to waitUntil, so an async or lazily loaded handler runs before the page unloads; the Vue and Angular wrappers expose waitUntil on the event detail for a handler to call itself.

@zitadel/sdk-qwik

Patch Changes

  • #2178 3b206b2 Thanks @mridang! - The widgets now wait for their listeners before they navigate. The detail of zitadel-flow-redirect, zitadel-flow-complete and zitadel-signout carries a waitUntil(promise) function; the widget navigates once every promise a listener passed has settled, or after two seconds, and at once when none was passed. The React, Solid, Svelte and Qwik wrappers forward each callback's returned promise to waitUntil, so an async or lazily loaded handler runs before the page unloads; the Vue and Angular wrappers expose waitUntil on the event detail for a handler to call itself.

@zitadel/sdk-tanstack-start

Patch Changes

  • #2170 be439bc Thanks @mridang! - The @tanstack/react-start peer range is now >=1.168.10, the first release that exports createCsrfMiddleware, which the generated src/start.ts imports. Earlier releases fail to build with it.