spfn 0.2.0-beta.6 → 0.2.0-beta.61
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/README.md +371 -211
- package/bin/spfn.js +53 -4
- package/dist/index.js +2928 -2072
- package/dist/templates/.dockerignore +1 -0
- package/dist/templates/Dockerfile +1 -1
- package/dist/templates/README.md +81 -0
- package/dist/templates/lib/api-client.ts +66 -33
- package/dist/templates/server/config/env.config.ts +10 -75
- package/dist/templates/server/entities/config.ts +1 -1
- package/dist/templates/server/entities/example.entity.ts +1 -1
- package/dist/templates/server/repositories/example.repository.ts +1 -1
- package/dist/templates/server/router.ts +2 -3
- package/dist/templates/server/routes/examples.ts +2 -12
- package/dist/templates/server/routes/health.ts +1 -1
- package/dist/templates/server/routes/root.ts +1 -2
- package/dist/templates/server/server.config.ts +2 -2
- package/package.json +29 -18
- package/dist/commands/generate/templates/contract.template +0 -58
- package/dist/commands/generate/templates/entity.template +0 -28
- package/dist/commands/generate/templates/init-migration.template +0 -3
- package/dist/commands/generate/templates/repository.template +0 -56
- package/dist/commands/generate/templates/route.template +0 -65
- package/dist/commands/generate/templates/schema.template +0 -13
- package/dist/templates/Dockerfile.optimized +0 -67
- package/dist/templates/config/index.ts +0 -22
- package/dist/templates/config/schema.ts +0 -42
package/LICENSE
CHANGED
package/README.md
CHANGED
|
@@ -1,260 +1,420 @@
|
|
|
1
|
-
# spfn
|
|
1
|
+
# spfn — the SPFN CLI (backend layer for Next.js)
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
`spfn` scaffolds and runs a Hono-based backend that lives inside a Next.js project.
|
|
4
|
+
It creates the server structure, runs the dev/build/start lifecycle, manages the
|
|
5
|
+
database (Drizzle Kit), generates the RPC route map, and validates environment variables.
|
|
4
6
|
|
|
5
|
-
|
|
7
|
+
> Beta: install with the `@beta` tag (`spfn@beta`). The binary is `spfn`.
|
|
6
8
|
|
|
7
|
-
##
|
|
9
|
+
## Install
|
|
8
10
|
|
|
9
|
-
|
|
11
|
+
No global install needed — run through your package manager's dlx/npx:
|
|
10
12
|
|
|
11
|
-
### Quick Start (New Project)
|
|
12
13
|
```bash
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
cd my-app
|
|
16
|
-
docker compose up -d
|
|
17
|
-
npm run spfn:dev
|
|
14
|
+
npx spfn@beta <command>
|
|
15
|
+
pnpm dlx spfn@beta <command>
|
|
18
16
|
```
|
|
19
17
|
|
|
20
|
-
|
|
18
|
+
Or add it as a project dependency (`spfn init`/`spfn create` do this for you), then
|
|
19
|
+
call it via `pnpm spfn <command>` / `npm run spfn:<script>`.
|
|
20
|
+
|
|
21
|
+
Requirements: Node.js 18.18+, Next.js 15+ (App Router, `src/` dir), PostgreSQL (Redis optional).
|
|
22
|
+
|
|
23
|
+
## Usage
|
|
24
|
+
|
|
21
25
|
```bash
|
|
22
|
-
#
|
|
23
|
-
npx spfn@
|
|
26
|
+
# New project (runs create-next-app + spfn init)
|
|
27
|
+
npx spfn@beta create my-app
|
|
28
|
+
cd my-app
|
|
29
|
+
docker compose up -d # Postgres + Redis
|
|
30
|
+
# .env.local & .env.server are generated — put server secrets in .env.server
|
|
31
|
+
pnpm spfn:dev # Next.js :3790 + SPFN API :8790
|
|
24
32
|
|
|
25
|
-
#
|
|
26
|
-
|
|
27
|
-
spfn init
|
|
33
|
+
# Add SPFN to an existing Next.js project
|
|
34
|
+
npx spfn@beta init
|
|
28
35
|
```
|
|
29
36
|
|
|
37
|
+
The package manager is auto-detected (pnpm > yarn > bun > npm) from lockfiles; override
|
|
38
|
+
with `--pm`. In a pnpm workspace, `create` installs from the workspace root.
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
30
42
|
## Commands
|
|
31
43
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
spfn create <name> # Create new Next.js project with SPFN (all-in-one)
|
|
35
|
-
spfn create my-app # Example: Create project with TypeScript, App Router, SVGR, and SPFN
|
|
36
|
-
spfn create my-app --shadcn # Include shadcn/ui component library
|
|
37
|
-
```
|
|
44
|
+
Registered top-level commands: `create`, `init`, `add`, `dev`, `build`, `start`,
|
|
45
|
+
`codegen`, `db`, `env`, `key`, `setup`.
|
|
38
46
|
|
|
39
|
-
###
|
|
40
|
-
```bash
|
|
41
|
-
spfn init # Initialize SPFN in existing Next.js project
|
|
42
|
-
spfn init -y # Skip prompts, use defaults
|
|
43
|
-
```
|
|
47
|
+
### `spfn create <name>`
|
|
44
48
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
- `src/server/routes/` - Backend route handlers
|
|
48
|
-
- `src/server/entities/` - Database entities (Drizzle ORM)
|
|
49
|
-
- `docker-compose.yml` - PostgreSQL + Redis for local development
|
|
50
|
-
- `Dockerfile`, `.dockerignore`, `docker-compose.production.yml` - Production deployment
|
|
51
|
-
- `.env.local.example` - Environment variable template
|
|
52
|
-
- `spfn.config.js` - Deployment configuration with JSDoc type hints
|
|
53
|
-
- `.spfnrc.json` - Code generation configuration
|
|
54
|
-
|
|
55
|
-
**SPFN's Contract-Based Architecture:**
|
|
56
|
-
- **Contracts** (`src/lib/contracts/`): Define API endpoints with absolute paths (e.g., `/users/:id`)
|
|
57
|
-
- **Handlers** (`src/server/routes/`): Import contracts and implement business logic
|
|
58
|
-
- **Frontend**: Import contracts for type-safe API calls
|
|
59
|
-
- **Auto-generated Client**: `src/lib/api/` is auto-generated from contracts (via `spfn dev` or `spfn build`)
|
|
60
|
-
|
|
61
|
-
### Generate Function Modules
|
|
62
|
-
```bash
|
|
63
|
-
spfn generate fn <name> # Generate new SPFN function module (interactive)
|
|
64
|
-
spfn g fn <name> # Short alias
|
|
49
|
+
Runs `create-next-app` with SPFN-recommended flags (TypeScript, App Router, `src/`,
|
|
50
|
+
Tailwind, import alias `@/*`, no ESLint), sets up SVGR icons, then runs `init`.
|
|
65
51
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
52
|
+
| Option | Description |
|
|
53
|
+
|--------|-------------|
|
|
54
|
+
| `--pm <manager>` | Force package manager: `npm` \| `pnpm` \| `yarn` \| `bun` |
|
|
55
|
+
| `--shadcn` | Also run `shadcn init` |
|
|
56
|
+
| `--skip-install` | Skip dependency install |
|
|
57
|
+
| `--skip-git` | Skip `git init` |
|
|
58
|
+
| `-y, --yes` | Skip prompts, use defaults |
|
|
70
59
|
|
|
71
|
-
|
|
72
|
-
- `packages/<name>/` - New function module in monorepo
|
|
73
|
-
- `src/server/entities/schema.ts` - **Exported schema** (ensures CREATE SCHEMA in migrations)
|
|
74
|
-
- `src/server/entities/*.ts` - Drizzle ORM entity definitions (import schema)
|
|
75
|
-
- `src/server/repositories/` - CRUD repository layer
|
|
76
|
-
- `src/server/routes/` - RESTful API route handlers
|
|
77
|
-
- `src/lib/contracts/` - TypeBox API contracts
|
|
78
|
-
- `package.json` - Pre-configured with SPFN metadata
|
|
79
|
-
- `tsup.config.ts` - Build configuration
|
|
80
|
-
- `drizzle.config.ts` - Database migration setup
|
|
81
|
-
|
|
82
|
-
**Database Schema Naming:**
|
|
83
|
-
- Scope and module name are automatically converted to safe PostgreSQL schema names
|
|
84
|
-
- Examples: `@my-company/blog` → `my_company_blog`, `@spfn/cms` → `spfn_cms`
|
|
85
|
-
- Special characters (`.`, `!`, `-`) are converted to underscores
|
|
86
|
-
- Names starting with numbers get `_` prefix (e.g., `@123company` → `_123company`)
|
|
87
|
-
- Ensures PostgreSQL compatibility (lowercase, numbers, underscores only)
|
|
88
|
-
|
|
89
|
-
**Options:**
|
|
90
|
-
- `-e, --entities <list>` - Comma-separated entity names
|
|
91
|
-
- `-d, --description <text>` - Module description
|
|
92
|
-
- `--skip-routes` - Generate entities without routes
|
|
93
|
-
- `--skip-cache` - Skip cache generation
|
|
94
|
-
- `-y, --yes` - Skip all prompts
|
|
95
|
-
|
|
96
|
-
**Example workflow:**
|
|
97
|
-
```bash
|
|
98
|
-
# 1. Generate module
|
|
99
|
-
spfn g fn blog -e posts,comments -y
|
|
60
|
+
### `spfn init`
|
|
100
61
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
62
|
+
Adds SPFN to an existing Next.js project: copies the server templates, wires the RPC
|
|
63
|
+
proxy route, Docker files, deploy + codegen config, updates `package.json` scripts/deps,
|
|
64
|
+
and installs. With auth enabled it also adds the `/_auth/:path*` → SPFN API rewrite to
|
|
65
|
+
`next.config` (OAuth callbacks return to the app origin; merged manually if a `rewrites()`
|
|
66
|
+
already exists). See [Scaffold structure](#scaffold-structure) for what lands on disk.
|
|
104
67
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
68
|
+
| Option | Description |
|
|
69
|
+
|--------|-------------|
|
|
70
|
+
| `-y, --yes` | Skip prompts, use defaults |
|
|
71
|
+
|
|
72
|
+
Generated projects pin `drizzle-orm` and `drizzle-kit` to `1.0.0-rc.4`, matching
|
|
73
|
+
`@spfn/core` and the rest of the published SPFN database packages.
|
|
74
|
+
|
|
75
|
+
### `spfn add <package>`
|
|
76
|
+
|
|
77
|
+
Installs an SPFN ecosystem package and applies its pre-built migrations. The package
|
|
78
|
+
name must be scoped (contain `/`).
|
|
108
79
|
|
|
109
|
-
### Install Ecosystem Packages
|
|
110
80
|
```bash
|
|
111
|
-
spfn add
|
|
112
|
-
spfn add @spfn
|
|
113
|
-
spfn add @company/plugin # Install third-party SPFN package
|
|
81
|
+
pnpm spfn add @spfn/cms
|
|
82
|
+
pnpm spfn add @mycompany/spfn-analytics
|
|
114
83
|
```
|
|
115
84
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
4. ✅ Shows package-specific setup guide
|
|
85
|
+
How it works: if not already present it installs the package, then reads the package's
|
|
86
|
+
`spfn` field in its `package.json` (`migrations`, `setupMessage`) and applies any
|
|
87
|
+
function migrations to `DATABASE_URL`. If `DATABASE_URL` is unset, migration is skipped
|
|
88
|
+
with a hint to run `spfn db push` later. Works with published and workspace packages.
|
|
121
89
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
90
|
+
### `spfn dev`
|
|
91
|
+
|
|
92
|
+
Starts the SPFN server + Next.js (and a codegen watcher). The server must report ready
|
|
93
|
+
(via a `.spfn/server-ready` signal file) before Next.js launches. Runs through `tsx`,
|
|
94
|
+
no pre-build needed.
|
|
95
|
+
|
|
96
|
+
| Option | Description | Default |
|
|
97
|
+
|--------|-------------|---------|
|
|
98
|
+
| `--server-only` | Run only the SPFN/Hono server (also auto-selected if Next.js isn't a dependency) | off |
|
|
99
|
+
| `--watch` | Restart the server on `src/server` changes (chokidar) | off |
|
|
100
|
+
| `-p, --port <port>` | Server port | from `server.config.ts` / env (`4000` in server-only fallback) |
|
|
101
|
+
| `-H, --host <host>` | Server host | `localhost` |
|
|
102
|
+
| `--routes <path>` | Routes directory path | server default |
|
|
103
|
+
|
|
104
|
+
Note: hot reload is **off by default** — pass `--watch` to restart on file changes.
|
|
105
|
+
|
|
106
|
+
### `spfn build`
|
|
107
|
+
|
|
108
|
+
Runs codegen, builds Next.js (via the project's `build` script), and compiles
|
|
109
|
+
`src/server/**/*.ts` → `.spfn/server` with tsup. Also writes `.spfn/prod-server.mjs`
|
|
110
|
+
(the production entry consumed by `spfn start`).
|
|
111
|
+
|
|
112
|
+
| Option | Description |
|
|
113
|
+
|--------|-------------|
|
|
114
|
+
| `--server-only` | Build only the SPFN server (skip Next.js) |
|
|
115
|
+
| `--next-only` | Build only Next.js (skip the SPFN server) |
|
|
116
|
+
| `--turbo` | Use Turbopack for the Next.js build |
|
|
117
|
+
|
|
118
|
+
### `spfn start`
|
|
119
|
+
|
|
120
|
+
Starts the production servers from build output. Requires `spfn build` first — it errors
|
|
121
|
+
if `.spfn/server`, `.spfn/prod-server.mjs`, or `.next` are missing.
|
|
122
|
+
|
|
123
|
+
| Option | Description | Default |
|
|
124
|
+
|--------|-------------|---------|
|
|
125
|
+
| `--server-only` | Run only the SPFN server | off |
|
|
126
|
+
| `--next-only` | Run only Next.js | off |
|
|
127
|
+
| `-p, --port <port>` | SPFN server port (sets `SPFN_PORT`) | `8790` |
|
|
128
|
+
| `-h, --host <host>` | SPFN server host (sets `SPFN_HOST`) | `0.0.0.0` |
|
|
125
129
|
|
|
126
|
-
|
|
127
|
-
✓ Package installed
|
|
130
|
+
Next.js is started on `0.0.0.0:3790`. Both run together via `concurrently --kill-others`.
|
|
128
131
|
|
|
129
|
-
|
|
130
|
-
✓ Migrations applied
|
|
132
|
+
### `spfn codegen`
|
|
131
133
|
|
|
132
|
-
|
|
134
|
+
Manages code generators driven by `.spfnrc.ts`. The default generator is
|
|
135
|
+
`@spfn/core:route-map`, which emits `src/generated/route-map.ts` from `src/server/router.ts`
|
|
136
|
+
so the RPC proxy can resolve routes without importing server code. Generators also run
|
|
137
|
+
automatically during `spfn dev` and `spfn build`.
|
|
133
138
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
139
|
+
| Subcommand | Description |
|
|
140
|
+
|------------|-------------|
|
|
141
|
+
| `codegen init` | Create `.spfnrc.ts` (`--with-example` shows custom-generator usage) |
|
|
142
|
+
| `codegen list` (`ls`) | List configured generators and their watch patterns |
|
|
143
|
+
| `codegen run` | Run all generators once (no watch) |
|
|
144
|
+
|
|
145
|
+
`package.json` exposes this as the `codegen` script (`spfn codegen run`).
|
|
146
|
+
|
|
147
|
+
To add a custom generator, implement the `Generator` interface from `@spfn/core/codegen`
|
|
148
|
+
and reference it in `.spfnrc.ts`:
|
|
149
|
+
|
|
150
|
+
```ts
|
|
151
|
+
// .spfnrc.ts
|
|
152
|
+
import { defineConfig, defineGenerator } from '@spfn/core/codegen';
|
|
153
|
+
|
|
154
|
+
export default defineConfig({
|
|
155
|
+
generators: [
|
|
156
|
+
defineGenerator({
|
|
157
|
+
name: '@spfn/core:route-map',
|
|
158
|
+
routerPath: './src/server/router.ts',
|
|
159
|
+
outputPath: './src/generated/route-map.ts',
|
|
160
|
+
}),
|
|
161
|
+
],
|
|
162
|
+
});
|
|
138
163
|
```
|
|
139
164
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
165
|
+
### `spfn db`
|
|
166
|
+
|
|
167
|
+
Wraps Drizzle Kit with auto-generated config. Most commands read `DATABASE_URL` from the
|
|
168
|
+
loaded `.env` chain.
|
|
169
|
+
|
|
170
|
+
| Subcommand | Description |
|
|
171
|
+
|------------|-------------|
|
|
172
|
+
| `db generate` (`g`) | Generate migrations from schema changes (timestamp-prefixed) |
|
|
173
|
+
| `db push` | Diff with Drizzle Kit's current PostgreSQL engine and apply the selected DDL atomically. Destructive changes need confirmation; `--force` applies them, `--dry-run` previews |
|
|
174
|
+
| `db migrate` (`m`) | Run pending migrations. `--with-backup` snapshots first |
|
|
175
|
+
| `db studio` | Open Drizzle Studio. `-p, --port` (auto-finds a free port) |
|
|
176
|
+
| `db check` | Verify the database connection |
|
|
177
|
+
| `db drop` | Drop all tables — **destructive**, double-prompts (see [Pitfalls](#pitfalls)) |
|
|
178
|
+
| `db backup` | Create a backup (`-f sql|custom`, `-o`, `-s`, `--data-only`, `--schema-only`, `--tag`, `--env`) |
|
|
179
|
+
| `db restore [file]` | Restore from a backup (`--drop`, `-s`, `--data-only`, `--schema-only`, `-v`) |
|
|
180
|
+
| `db backup:list` | List backups |
|
|
181
|
+
| `db backup:clean` | Prune backups (`-k, --keep <n>`, `-o, --older-than <days>`) |
|
|
182
|
+
| `db reindex` | Convert sequential migration prefixes to timestamps (`--dry-run`) |
|
|
183
|
+
|
|
184
|
+
> `db push` is for development. For production, use `db generate` + `db migrate` to keep
|
|
185
|
+
> migration history.
|
|
186
|
+
|
|
187
|
+
`db push` and `db migrate` also replay migrations shipped by installed SPFN function
|
|
188
|
+
packages (`@spfn/auth`, `@spfn/cms`, …) into per-package tracking tables
|
|
189
|
+
(`drizzle.__spfn_fn_<pkg>_migrations`). The CLI applies these with a built-in runner
|
|
190
|
+
that reads both migration layouts — drizzle-kit ≤0.31 (`NNNN_name.sql` +
|
|
191
|
+
`meta/_journal.json`) and drizzle-kit 1.0 (`<timestamp>_name/migration.sql`) — so a
|
|
192
|
+
package's layout never has to match the CLI's bundled drizzle version. `db push`
|
|
193
|
+
validates every package's migration folder before applying the project schema, and a
|
|
194
|
+
function-migration failure after a successful schema apply exits 1 with a message
|
|
195
|
+
making clear the project schema was already committed.
|
|
196
|
+
|
|
197
|
+
Database TLS is controlled by `DATABASE_URL`. Loopback URLs (`localhost`, `127.0.0.1`,
|
|
198
|
+
and `::1`) default to `ssl: false`; add an explicit `sslmode` when the local server uses
|
|
199
|
+
TLS. For a TLS connection with a self-signed certificate, set
|
|
200
|
+
`SPFN_DB_INSECURE_TLS=1` to disable certificate verification. This opt-in never enables
|
|
201
|
+
TLS by itself and `sslmode=disable` remains authoritative.
|
|
202
|
+
|
|
203
|
+
### `spfn env`
|
|
204
|
+
|
|
205
|
+
Schema-driven environment variable tooling (schema comes from a package's `envSchema`,
|
|
206
|
+
default `@spfn/core`). Routes vars to the right file: `NEXT_PUBLIC_*` → `.env`/`.env.local`,
|
|
207
|
+
server vars → `.env.server`.
|
|
208
|
+
|
|
209
|
+
| Subcommand | Description |
|
|
210
|
+
|------------|-------------|
|
|
211
|
+
| `env list` | List vars from the schema (`-g` groups by target file) |
|
|
212
|
+
| `env stats` | Show variable statistics |
|
|
213
|
+
| `env search <query>` | Search vars by key or description |
|
|
214
|
+
| `env init` | Generate `.env` template files (`-e <env>` for per-env, `-f` to overwrite) |
|
|
215
|
+
| `env check` | Check `.env` files against the schema (`-e <env>` for a full env chain) |
|
|
216
|
+
| `env validate` | Validate `process.env` against the schema — for CI/CD (`-e <env>`, `-s` strict) |
|
|
217
|
+
|
|
218
|
+
All accept `-p, --package <pkg>` (`env validate` uses `-p, --packages <pkgs...>`).
|
|
219
|
+
|
|
220
|
+
### `spfn key [preset]`
|
|
221
|
+
|
|
222
|
+
Generate cryptographically random secrets (base64url, 256-bit default).
|
|
223
|
+
|
|
157
224
|
```bash
|
|
158
|
-
#
|
|
159
|
-
spfn
|
|
160
|
-
spfn
|
|
161
|
-
spfn
|
|
162
|
-
|
|
163
|
-
# Production
|
|
164
|
-
spfn build # Build Next.js + compile server
|
|
165
|
-
spfn start # Start production server
|
|
166
|
-
spfn start --server-only # Start API server only (no Next.js)
|
|
225
|
+
spfn key # generic 256-bit secret
|
|
226
|
+
spfn key auth-encryption -c # preset key, copy to clipboard
|
|
227
|
+
spfn key --list # list presets
|
|
228
|
+
spfn key gen -b 64 # raw value only, no metadata (alias of `key generate`)
|
|
167
229
|
```
|
|
168
230
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
spfn
|
|
175
|
-
|
|
176
|
-
|
|
231
|
+
Presets: `auth-encryption`, `nextauth-secret`, `jwt-secret`, `session-secret`, `api-key`.
|
|
232
|
+
Options: `-l, --list`, `-b, --bytes <n>` (1–128), `-e, --env <name>`, `-c, --copy`.
|
|
233
|
+
The command prints the value to stdout for you to paste into an env file — it does **not**
|
|
234
|
+
write any file.
|
|
235
|
+
|
|
236
|
+
### `spfn secret`
|
|
237
|
+
|
|
238
|
+
Unified secret management: local secrets live in the OS keychain, deployed secrets in
|
|
239
|
+
encrypted SOPS files. The runtime never sees a reference — `spfn dev` injects local
|
|
240
|
+
values into the server process and GitOps injects them in production, so the app always
|
|
241
|
+
reads plain `process.env`.
|
|
242
|
+
|
|
243
|
+
| Subcommand | Description |
|
|
244
|
+
|------------|-------------|
|
|
245
|
+
| `secret set [key]` | Store a value (masked prompt). `--env local` → keychain; other envs → SOPS |
|
|
246
|
+
| `secret list` | List declared secrets and their status per env (never prints values) |
|
|
247
|
+
| `secret generate [key]` | Mint values for schema secrets with a `generate` strategy (`-a/--all`) |
|
|
248
|
+
| `secret rotate [key]` | Rotate values; external secrets are flagged for manual reissue (`-a/--all`) |
|
|
249
|
+
| `secret keygen` | Generate an age key pair for the SOPS no-cloud backend |
|
|
250
|
+
| `secret recipients <add\|remove\|list> [age1…]` | Manage `.sops.yaml` recipients + re-encrypt |
|
|
251
|
+
| `secret check` | Static lint — flag plaintext secret leaks |
|
|
252
|
+
|
|
253
|
+
Options: `-e, --env <env>` (`local` default; also `development`/`staging`/`production`),
|
|
254
|
+
`-p, --package <pkg>` (schema source, default `@spfn/core`).
|
|
255
|
+
|
|
256
|
+
**Local (keychain).** `spfn secret set DB_URL` stores the value in the OS keychain
|
|
257
|
+
(macOS `security`, Windows Credential Manager via optional `@napi-rs/keyring`, Linux
|
|
258
|
+
libsecret) and writes a `secret:keychain:spfn_DB_URL` reference into `.env.server`. The
|
|
259
|
+
reference is not sensitive; the real value never lands in the repo. `spfn dev` resolves
|
|
260
|
+
and injects it. Note: injection happens only when the server is started via `spfn dev` —
|
|
261
|
+
running the app another way (a bare `node`, tests) would see the raw reference, so use
|
|
262
|
+
`spfn dev` locally (a runtime resolver for other runners is planned).
|
|
263
|
+
|
|
264
|
+
**Deployed (SOPS).** `spfn secret set DB_URL --env production` writes the value into
|
|
265
|
+
`secrets/production.enc.json`, encrypted by SOPS. The backend (age / GCP KMS / AWS KMS)
|
|
266
|
+
is chosen by `.sops.yaml` creation rules — KMS needs no local key file (IAM + cloud
|
|
267
|
+
auth), age is the no-cloud fallback (`secret keygen` + `secret recipients add`). Commit
|
|
268
|
+
the encrypted file; your GitOps step decrypts it into env at deploy time. `sops`/`age`
|
|
269
|
+
are needed only for the deployed envs, never for local keychain use.
|
|
270
|
+
|
|
271
|
+
Schema-driven: a secret declared with `envSecret({ generate: 'base64url32' })` can be
|
|
272
|
+
minted/rotated automatically (`secret generate`/`rotate`); one without `generate` is an
|
|
273
|
+
external value you paste in (`secret set`).
|
|
274
|
+
|
|
275
|
+
### `spfn setup icons`
|
|
276
|
+
|
|
277
|
+
Install and configure SVGR for SVG-as-component imports (Next.js only).
|
|
278
|
+
|
|
279
|
+
---
|
|
280
|
+
|
|
281
|
+
## Scaffold structure
|
|
282
|
+
|
|
283
|
+
`spfn init` (and `create`, which calls it) produces:
|
|
284
|
+
|
|
285
|
+
```
|
|
286
|
+
src/
|
|
287
|
+
app/api/rpc/[routeName]/route.ts # RPC proxy — re-exports { GET, POST } from @spfn/core/nextjs/server
|
|
288
|
+
generated/route-map.ts # generated by codegen (run `spfn codegen run` if missing)
|
|
289
|
+
lib/
|
|
290
|
+
api-client.ts # createApi<AppRouter>() — the type-safe client
|
|
291
|
+
server/
|
|
292
|
+
router.ts # defineRouter({ ...routes }) → export type AppRouter
|
|
293
|
+
server.config.ts # defineServerConfig().port(8790).host('0.0.0.0').routes(appRouter)
|
|
294
|
+
config/env.config.ts # environment schema
|
|
295
|
+
entities/ # Drizzle tables (example.entity.ts, config.ts)
|
|
296
|
+
repositories/ # BaseRepository subclasses (example.repository.ts)
|
|
297
|
+
routes/ # route DSL handlers: root.ts, health.ts, examples.ts
|
|
298
|
+
tsconfig.json, tsup.config.ts
|
|
299
|
+
.spfnrc.ts # codegen config (route-map generator)
|
|
300
|
+
spfn.config.js # deployment config (subdomain/region/domains) — committed
|
|
301
|
+
docker-compose.yml # Postgres + Redis (dev)
|
|
302
|
+
docker-compose.production.yml
|
|
303
|
+
Dockerfile, .dockerignore
|
|
304
|
+
next.config.ts # patched when auth is enabled: /_auth/:path* rewrite → SPFN API
|
|
305
|
+
.env.example # committed reference — every key, placeholder values
|
|
306
|
+
.env.local # generated, gitignored (Next.js-facing URLs)
|
|
307
|
+
.env.server # generated, gitignored (server secrets: DB, cache)
|
|
177
308
|
```
|
|
178
309
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
310
|
+
`init` also patches `package.json` (scripts: `spfn:dev`, `spfn:server`, `spfn:next`,
|
|
311
|
+
`spfn:build`, `spfn:start`, `codegen`; deps: `@spfn/core`, `spfn`, `drizzle-orm`,
|
|
312
|
+
`@sinclair/typebox`, `concurrently`, etc.), excludes `src/server` from the root
|
|
313
|
+
`tsconfig.json` (Vercel compat), and adds `.spfn/`, `.env.local`, `.env.server` to
|
|
314
|
+
`.gitignore`.
|
|
315
|
+
|
|
316
|
+
### Route DSL (the current architecture)
|
|
317
|
+
|
|
318
|
+
Routes are defined with the `route` builder and collected by `defineRouter`. There is no
|
|
319
|
+
separate "contract" layer — the router's type *is* the contract; the client infers from it.
|
|
320
|
+
|
|
321
|
+
```ts
|
|
322
|
+
// src/server/routes/examples.ts
|
|
323
|
+
import { route } from '@spfn/core/route';
|
|
324
|
+
import { Type } from '@sinclair/typebox';
|
|
325
|
+
|
|
326
|
+
export const getExample = route.get('/examples/:id')
|
|
327
|
+
.input({ params: Type.Object({ id: Type.String() }) })
|
|
328
|
+
.handler(async (c) =>
|
|
329
|
+
{
|
|
330
|
+
const { params } = await c.data();
|
|
331
|
+
return { id: params.id };
|
|
332
|
+
});
|
|
182
333
|
```
|
|
183
334
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
335
|
+
```ts
|
|
336
|
+
// src/server/router.ts
|
|
337
|
+
import { defineRouter } from '@spfn/core/route';
|
|
338
|
+
import { getExample } from './routes/examples';
|
|
339
|
+
|
|
340
|
+
export const appRouter = defineRouter({ getExample });
|
|
341
|
+
export type AppRouter = typeof appRouter;
|
|
187
342
|
```
|
|
188
343
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
```javascript
|
|
197
|
-
/**
|
|
198
|
-
* @type {import('spfn').SpfnConfig}
|
|
199
|
-
*/
|
|
200
|
-
export default {
|
|
201
|
-
packageManager: 'pnpm',
|
|
202
|
-
deployment: {
|
|
203
|
-
// Your app's subdomain on spfn.app
|
|
204
|
-
// Creates region-specific domains:
|
|
205
|
-
// - myapp.us.spfn.app (Next.js)
|
|
206
|
-
// - api-myapp.us.spfn.app (API)
|
|
207
|
-
subdomain: 'myapp',
|
|
208
|
-
|
|
209
|
-
// Optional: Deployment region (defaults to 'us')
|
|
210
|
-
// Available: 'us' (Virginia, default), 'kr' (Seoul), 'jp', 'sg', 'eu' (coming soon)
|
|
211
|
-
region: 'us',
|
|
212
|
-
|
|
213
|
-
// Optional: Add custom domains
|
|
214
|
-
customDomains: {
|
|
215
|
-
nextjs: ['www.example.com', 'example.com'],
|
|
216
|
-
spfn: ['api.example.com']
|
|
217
|
-
},
|
|
218
|
-
|
|
219
|
-
// Optional: Environment variables for both Next.js and SPFN backend
|
|
220
|
-
// ⚠️ WARNING: These values are committed to Git
|
|
221
|
-
// Do NOT put sensitive credentials here!
|
|
222
|
-
env: {
|
|
223
|
-
NEXT_PUBLIC_API_URL: 'https://api-myapp.us.spfn.app',
|
|
224
|
-
NODE_ENV: 'production'
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
}
|
|
344
|
+
```ts
|
|
345
|
+
// src/lib/api-client.ts
|
|
346
|
+
import { createApi } from '@spfn/core/nextjs';
|
|
347
|
+
import type { AppRouter } from '@/server/router';
|
|
348
|
+
|
|
349
|
+
export const api = createApi<AppRouter>();
|
|
350
|
+
const example = await api.getExample.call({ params: { id: '123' } });
|
|
228
351
|
```
|
|
229
352
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
- **Environment Variables** - Shared between Next.js and SPFN backend
|
|
236
|
-
- **ESM/CJS Support** - Works with both module systems
|
|
353
|
+
Client calls go through the Next.js RPC proxy (`/api/rpc/[routeName]`), which forwards to
|
|
354
|
+
the SPFN API with cookie forwarding and interceptors, resolving routes via the generated
|
|
355
|
+
`route-map.ts`.
|
|
356
|
+
|
|
357
|
+
---
|
|
237
358
|
|
|
238
|
-
|
|
239
|
-
- `spfn.config.js` is committed to Git
|
|
240
|
-
- Only use for non-sensitive configuration
|
|
241
|
-
- For secrets (DB passwords, API keys), use CI/CD secrets management
|
|
359
|
+
## Deployment
|
|
242
360
|
|
|
243
|
-
|
|
361
|
+
`spfn build` then `spfn start`, or use the generated Docker files.
|
|
244
362
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
363
|
+
```bash
|
|
364
|
+
# Build + run locally
|
|
365
|
+
pnpm spfn:build
|
|
366
|
+
pnpm spfn:start # Next.js :3790 + SPFN API :8790
|
|
248
367
|
|
|
249
|
-
|
|
368
|
+
# Docker (single image runs both)
|
|
369
|
+
docker compose -f docker-compose.production.yml up --build -d
|
|
370
|
+
```
|
|
250
371
|
|
|
251
|
-
-
|
|
252
|
-
|
|
253
|
-
|
|
372
|
+
The Dockerfile (`node:22-alpine`) installs with `pnpm --frozen-lockfile`, runs
|
|
373
|
+
`pnpm run spfn:build`, prunes dev deps, exposes `3790`/`8790`, health-checks
|
|
374
|
+
`http://localhost:8790/health`, and starts via `pnpm run spfn:start`.
|
|
254
375
|
|
|
255
|
-
|
|
376
|
+
Run migrations against the target DB before/with deploy:
|
|
377
|
+
|
|
378
|
+
```bash
|
|
379
|
+
docker exec <container> npx spfn db migrate
|
|
380
|
+
```
|
|
256
381
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
382
|
+
`spfn.config.js` (committed) configures the managed `*.spfn.app` deployment: `subdomain`,
|
|
383
|
+
`region` (`us` default, `kr`, …), `customDomains`, and non-secret `env`. Its `SpfnConfig`
|
|
384
|
+
type ships from `spfn` (`@type {import('spfn').SpfnConfig}`).
|
|
385
|
+
|
|
386
|
+
---
|
|
387
|
+
|
|
388
|
+
## Pitfalls
|
|
389
|
+
|
|
390
|
+
- **`.env.server` is gitignored and server-only.** Put DB/secret values there, not in
|
|
391
|
+
`.env` (committed) and not in `.env.local` (that's Next.js's local file). There is no
|
|
392
|
+
`.env.server.local`. `spfn init` generates `.env.server`; put DB/secret values there.
|
|
393
|
+
Load order is the standard dotenv chain ending with `.env.server`.
|
|
394
|
+
- **Never commit secrets in `spfn.config.js`.** It's checked into Git; its `env` block is
|
|
395
|
+
for non-sensitive values only. Use CI/CD secret management for credentials.
|
|
396
|
+
- **`spfn dev` does not hot-reload by default.** Add `--watch` to restart on `src/server`
|
|
397
|
+
changes.
|
|
398
|
+
- **`spfn start` needs a prior `spfn build`.** It hard-fails without `.spfn/server`,
|
|
399
|
+
`.spfn/prod-server.mjs`, and `.next`.
|
|
400
|
+
- **Destructive DB commands are guarded.** `db drop` double-confirms (and verifies the
|
|
401
|
+
target); `db push` applies the selected statements in one transaction and withholds
|
|
402
|
+
destructive ones unless `--force`/confirmed. Prefer `db generate` + `db migrate` for
|
|
403
|
+
production; `db push` is dev-only.
|
|
404
|
+
- **`spfn add` requires a scoped package name** (must contain `/`) and only applies
|
|
405
|
+
migrations when `DATABASE_URL` is set — otherwise it skips with a hint.
|
|
406
|
+
- **Package manager is auto-detected from lockfiles.** If detection is wrong (e.g. mixed
|
|
407
|
+
lockfiles), pass `--pm` to `create`. In a pnpm workspace, `create` installs from the
|
|
408
|
+
workspace root, not the new project dir.
|
|
409
|
+
- **Regenerate the route map after route changes outside dev.** If
|
|
410
|
+
`src/generated/route-map.ts` is missing or stale, run `spfn codegen run` — the RPC proxy
|
|
411
|
+
depends on it.
|
|
412
|
+
|
|
413
|
+
---
|
|
414
|
+
|
|
415
|
+
## Related
|
|
416
|
+
|
|
417
|
+
- `@spfn/core` — server, route DSL, codegen, db, client runtime.
|
|
418
|
+
- Project root README — framework overview and getting started.
|
|
419
|
+
</content>
|
|
420
|
+
</invoke>
|