create-userkit-app
v0.7.0
Published
Scaffold a Next.js app with UserKit already wired: sign-in, a protected page, and billing.
Maintainers
Readme
create-userkit-app
One command, and you have a Next.js app whose sign-in works, whose protected page reads the session on the server before it renders, and whose session token is unreachable from script.
npx create-userkit-app my-appIt asks three things — where, which publishable key, which API — writes the
project and .env.local, and stops. It installs nothing.
npx create-userkit-app [directory] [options]
-y, --yes Take the defaults; ask nothing.
--publishable-key <key> The environment's uk_pk_ key.
--api-url <url> Default https://api.userkit.dev
-h, --help
-v, --versionPiped, or run by an agent, it is non-interactive on its own: prompts appear only when both ends are a terminal.
What it asks npm for
@userkit/nextjs, @userkit/react and @userkit/js are published from this
repository in lockstep with this package — one version number, one tag, one
release — and the template asks for them by range. That is what makes the
npm install this command tells you to run the one that works: the range names
the version this very command was published at, so a starter cannot ask npm for
an SDK older than itself. Next and React are pinned rather than ranged, because
a starter is read as a statement about what works and a caret on the framework
hands somebody a version this template was never tried against.
What it deliberately never writes is a tarball URL, a git ref or a file: path
into a checkout you do not have. Those make an install succeed and produce a
project that runs once and cannot be updated. If the SDKs are ever unavailable,
this file says so — it does not point the template somewhere else.
Everything else is finished. The generated project is not a set of stubs; it is the app.
What it generates
A Next.js App Router project in the arrangement @userkit/nextjs exists for:
browser ──httpOnly cookie──▶ /api/userkit/* ──Bearer──▶ UserKit APIapp/api/userkit/[...userkit]/route.ts— the server half, three lines.proxy.ts— the route guard: the cookie exists, checked on navigation.app/sign-in/— the form, rendering whatever the environment has turned on.app/dashboard/— protected,getSession()in a Server Component, with<HelpWidget />: the published help centre and the support inbox, behind one launcher, drawn only where one of the two has something behind it.app/billing/— the public pricing document, with no buy button and the reason written down..env.local— written from the answers.
The template is inside this package
template/ ships in the tarball npm already downloaded. Nothing in this
command opens a socket.
A scaffolder that fetches its template over the network fails exactly when
somebody's network is the problem they were trying to route around, and it
fails in the worst way available: half a project on disk and a stack trace
about DNS. It also means the version of the template is the version of the
command, which is what makes npx [email protected] mean anything.
The blank line that is not a missing one
If you skip the publishable key, .env.local is written with
USERKIT_PUBLISHABLE_KEY=and six lines of comment above it, rather than with the variable left out. Leaving it out reads as nothing is needed here; the app then throws from inside a route handler at the first sign-in, and the person debugs the library instead of filling in a line. A variable that is visibly blank sends them to the panel.
Working on it
pnpm --filter create-userkit-app test # generate, then tsc the result
pnpm --filter create-userkit-app build
pnpm --filter create-userkit-app check-typesThe suite's centre is template.test.ts: it generates a project into a
temporary directory and runs tsc --noEmit inside it, with the three SDKs
resolved to their workspace sources through paths rather than to the
published tarballs. The published SDK is what the template agreed with when it
shipped; what is worth failing on is whether it still agrees with the code in
this repository, while a breaking rename is still cheap. A template is the one
kind of code nothing else type-checks: it is data to the
package that holds it, so without this test a broken import ships and is
discovered by a stranger.
versions.test.ts derives the published range from the workspace SDK versions,
so bumping them past the range the template asks for fails here instead of
shipping a starter that installs the wrong one.
