spfn 0.2.0-beta.52 → 0.2.0-beta.54
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/README.md +45 -6
- package/bin/spfn.js +10 -3
- package/dist/index.js +1275 -275
- package/dist/templates/README.md +81 -0
- package/package.json +12 -6
package/README.md
CHANGED
|
@@ -27,7 +27,7 @@ Requirements: Node.js 18.18+, Next.js 15+ (App Router, `src/` dir), PostgreSQL (
|
|
|
27
27
|
npx spfn@beta create my-app
|
|
28
28
|
cd my-app
|
|
29
29
|
docker compose up -d # Postgres + Redis
|
|
30
|
-
|
|
30
|
+
# .env.local & .env.server are generated — put server secrets in .env.server
|
|
31
31
|
pnpm spfn:dev # Next.js :3790 + SPFN API :8790
|
|
32
32
|
|
|
33
33
|
# Add SPFN to an existing Next.js project
|
|
@@ -212,6 +212,45 @@ Options: `-l, --list`, `-b, --bytes <n>` (1–128), `-e, --env <name>`, `-c, --c
|
|
|
212
212
|
The command prints the value to stdout for you to paste into an env file — it does **not**
|
|
213
213
|
write any file.
|
|
214
214
|
|
|
215
|
+
### `spfn secret`
|
|
216
|
+
|
|
217
|
+
Unified secret management: local secrets live in the OS keychain, deployed secrets in
|
|
218
|
+
encrypted SOPS files. The runtime never sees a reference — `spfn dev` injects local
|
|
219
|
+
values into the server process and GitOps injects them in production, so the app always
|
|
220
|
+
reads plain `process.env`.
|
|
221
|
+
|
|
222
|
+
| Subcommand | Description |
|
|
223
|
+
|------------|-------------|
|
|
224
|
+
| `secret set [key]` | Store a value (masked prompt). `--env local` → keychain; other envs → SOPS |
|
|
225
|
+
| `secret list` | List declared secrets and their status per env (never prints values) |
|
|
226
|
+
| `secret generate [key]` | Mint values for schema secrets with a `generate` strategy (`-a/--all`) |
|
|
227
|
+
| `secret rotate [key]` | Rotate values; external secrets are flagged for manual reissue (`-a/--all`) |
|
|
228
|
+
| `secret keygen` | Generate an age key pair for the SOPS no-cloud backend |
|
|
229
|
+
| `secret recipients <add\|remove\|list> [age1…]` | Manage `.sops.yaml` recipients + re-encrypt |
|
|
230
|
+
| `secret check` | Static lint — flag plaintext secret leaks |
|
|
231
|
+
|
|
232
|
+
Options: `-e, --env <env>` (`local` default; also `development`/`staging`/`production`),
|
|
233
|
+
`-p, --package <pkg>` (schema source, default `@spfn/core`).
|
|
234
|
+
|
|
235
|
+
**Local (keychain).** `spfn secret set DB_URL` stores the value in the OS keychain
|
|
236
|
+
(macOS `security`, Windows Credential Manager via optional `@napi-rs/keyring`, Linux
|
|
237
|
+
libsecret) and writes a `secret:keychain:spfn_DB_URL` reference into `.env.server`. The
|
|
238
|
+
reference is not sensitive; the real value never lands in the repo. `spfn dev` resolves
|
|
239
|
+
and injects it. Note: injection happens only when the server is started via `spfn dev` —
|
|
240
|
+
running the app another way (a bare `node`, tests) would see the raw reference, so use
|
|
241
|
+
`spfn dev` locally (a runtime resolver for other runners is planned).
|
|
242
|
+
|
|
243
|
+
**Deployed (SOPS).** `spfn secret set DB_URL --env production` writes the value into
|
|
244
|
+
`secrets/production.enc.json`, encrypted by SOPS. The backend (age / GCP KMS / AWS KMS)
|
|
245
|
+
is chosen by `.sops.yaml` creation rules — KMS needs no local key file (IAM + cloud
|
|
246
|
+
auth), age is the no-cloud fallback (`secret keygen` + `secret recipients add`). Commit
|
|
247
|
+
the encrypted file; your GitOps step decrypts it into env at deploy time. `sops`/`age`
|
|
248
|
+
are needed only for the deployed envs, never for local keychain use.
|
|
249
|
+
|
|
250
|
+
Schema-driven: a secret declared with `envSecret({ generate: 'base64url32' })` can be
|
|
251
|
+
minted/rotated automatically (`secret generate`/`rotate`); one without `generate` is an
|
|
252
|
+
external value you paste in (`secret set`).
|
|
253
|
+
|
|
215
254
|
### `spfn setup icons`
|
|
216
255
|
|
|
217
256
|
Install and configure SVGR for SVG-as-component imports (Next.js only).
|
|
@@ -241,9 +280,9 @@ spfn.config.js # deployment config (subdomain/region/domai
|
|
|
241
280
|
docker-compose.yml # Postgres + Redis (dev)
|
|
242
281
|
docker-compose.production.yml
|
|
243
282
|
Dockerfile, .dockerignore
|
|
244
|
-
.env.example # committed,
|
|
245
|
-
.env.local
|
|
246
|
-
.env.server
|
|
283
|
+
.env.example # committed reference — every key, placeholder values
|
|
284
|
+
.env.local # generated, gitignored (Next.js-facing URLs)
|
|
285
|
+
.env.server # generated, gitignored (server secrets: DB, cache)
|
|
247
286
|
```
|
|
248
287
|
|
|
249
288
|
`init` also patches `package.json` (scripts: `spfn:dev`, `spfn:server`, `spfn:next`,
|
|
@@ -328,8 +367,8 @@ type ships from `spfn` (`@type {import('spfn').SpfnConfig}`).
|
|
|
328
367
|
|
|
329
368
|
- **`.env.server` is gitignored and server-only.** Put DB/secret values there, not in
|
|
330
369
|
`.env` (committed) and not in `.env.local` (that's Next.js's local file). There is no
|
|
331
|
-
`.env.server.local`.
|
|
332
|
-
standard dotenv chain ending with `.env.server`.
|
|
370
|
+
`.env.server.local`. `spfn init` generates `.env.server`; put DB/secret values there.
|
|
371
|
+
Load order is the standard dotenv chain ending with `.env.server`.
|
|
333
372
|
- **Never commit secrets in `spfn.config.js`.** It's checked into Git; its `env` block is
|
|
334
373
|
for non-sensitive values only. Use CI/CD secret management for credentials.
|
|
335
374
|
- **`spfn dev` does not hot-reload by default.** Add `--watch` to restart on `src/server`
|
package/bin/spfn.js
CHANGED
|
@@ -28,13 +28,20 @@ else
|
|
|
28
28
|
|
|
29
29
|
async function tryRelaunchWithTsx()
|
|
30
30
|
{
|
|
31
|
-
//
|
|
32
|
-
|
|
31
|
+
// Resolve tsx to an absolute URL from THIS package's location. A bare
|
|
32
|
+
// '--import tsx' is resolved by Node against the child's CWD, so running
|
|
33
|
+
// outside a project (no node_modules/tsx up the tree) crashes the child
|
|
34
|
+
// with ERR_MODULE_NOT_FOUND even though the guard import above succeeds
|
|
35
|
+
// (it resolves package-relative). Resolving here also doubles as the
|
|
36
|
+
// availability check — a throw falls back to running without tsx.
|
|
37
|
+
const { createRequire } = await import('module');
|
|
38
|
+
const { pathToFileURL } = await import('url');
|
|
39
|
+
const tsxUrl = pathToFileURL(createRequire(import.meta.url).resolve(TSX_MODULE)).href;
|
|
33
40
|
|
|
34
41
|
const { spawn } = await import('child_process');
|
|
35
42
|
const child = spawn(
|
|
36
43
|
process.execPath,
|
|
37
|
-
[TSX_FLAG,
|
|
44
|
+
[TSX_FLAG, tsxUrl, ...process.execArgv, process.argv[1], ...process.argv.slice(2)],
|
|
38
45
|
{ stdio: 'inherit' },
|
|
39
46
|
);
|
|
40
47
|
|