spfn 0.3.0-beta.12 → 0.3.0-beta.13

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.
Files changed (3) hide show
  1. package/README.md +100 -8
  2. package/dist/index.js +1346 -518
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -396,9 +396,29 @@ TLS by itself and `sslmode=disable` remains authoritative.
396
396
 
397
397
  ### `spfn env`
398
398
 
399
- Schema-driven environment variable tooling (schema comes from a package's `envSchema`,
400
- default `@spfn/core`). Routes vars to the right file: `NEXT_PUBLIC_*` → `.env`/`.env.local`,
401
- server vars → `.env.server`.
399
+ Schema-driven environment variable tooling. Routes vars to the right file: `NEXT_PUBLIC_*`
400
+ → `.env`/`.env.local`, server vars → `.env.server`.
401
+
402
+ Without `-p`, every subcommand reads the **whole-app list**: the app's own schema modules
403
+ named in `spfn.config.js` under `env.schemas`, then every installed `@spfn/*` package whose
404
+ `./config` exports `envSchema` (found through the app's `package.json`, resolved from the
405
+ project root). Output is grouped by the source that declares each variable.
406
+
407
+ ```js
408
+ // spfn.config.js
409
+ export default {
410
+ env: {
411
+ // Modules exporting `envSchema`, relative to the project root.
412
+ // TypeScript and tsconfig path aliases load the way `spfn dev` loads them (tsx).
413
+ schemas: ['src/server/config/env.config.ts'],
414
+ },
415
+ };
416
+ ```
417
+
418
+ A variable declared by two sources alike is listed once. Two sources that declare it with a
419
+ different type, `required` or `sensitive` are an error naming the key and both sources. A schema path that does not exist, or a
420
+ module without an `envSchema` export, is an error naming the path. Top-level `env` is not
421
+ `deployment.env`: the latter holds values to inject, the former says where the schemas are.
402
422
 
403
423
  | Subcommand | Description |
404
424
  |------------|-------------|
@@ -408,8 +428,41 @@ server vars → `.env.server`.
408
428
  | `env init` | Generate `.env` template files (`-e <env>` for per-env, `-f` to overwrite) |
409
429
  | `env check` | Check `.env` files against the schema (`-e <env>` for a full env chain) |
410
430
  | `env validate` | Validate `process.env` against the schema — for CI/CD (`-e <env>`, `-s` strict) |
431
+ | `env audit` | Find direct `process.env` reads and app variables nothing reads; exits 1 on findings |
432
+
433
+ All but `env audit` accept `-p, --package <pkg>` to read that one package only (`env validate` uses
434
+ `-p, --packages <pkgs...>`).
435
+
436
+ **Audit.** `spfn env audit` prints `file:line NAME reason` — names only — and exits 1 when
437
+ it finds any of:
438
+
439
+ - a direct read — `process.env.X`, `process.env['X']`, `process.env[expr]`,
440
+ `const { X } = process.env` — outside the `env.schemas` modules, root-level
441
+ `*.config.{js,cjs,mjs,ts}` files and `env.audit.ignore` (`NODE_ENV` is always allowed);
442
+ - a name in the app's own schemas that no scanned file reads as a property (`env.X`), a
443
+ destructured key or a string literal, and that has no `readBy` (see the `@spfn/core/env`
444
+ README); a `readBy` file that does not exist or is not scanned. Skipped, with a notice,
445
+ when `env.schemas` is not set.
446
+
447
+ A list that cannot be built — a missing schema module, or a name declared differently by an
448
+ app schema and a package — is reported and exits 1.
449
+
450
+ ```js
451
+ // spfn.config.js
452
+ export default {
453
+ env: {
454
+ schemas: ['src/server/config/env.config.ts'],
455
+ audit: {
456
+ include: ['src'], // default — directories or globs from the project root
457
+ ignore: ['src/**/*.test.ts'],
458
+ },
459
+ },
460
+ };
461
+ ```
411
462
 
412
- All accept `-p, --package <pkg>` (`env validate` uses `-p, --packages <pkgs...>`).
463
+ `node_modules`, build output, `*.d.ts` and symbolic links are never scanned. Sources —
464
+ `.ts`, `.tsx`, `.js`, `.jsx`, `.mjs`, `.cjs`, `.mts`, `.cts` — are parsed with TypeScript,
465
+ taken from the app, else from the CLI's optional peer, else from `@spfn/core`.
413
466
 
414
467
  ### `spfn key [preset]`
415
468
 
@@ -436,16 +489,55 @@ reads plain `process.env`.
436
489
 
437
490
  | Subcommand | Description |
438
491
  |------------|-------------|
439
- | `secret set [key]` | Store a value (masked prompt). `--env local` → keychain; other envs → SOPS |
440
- | `secret list` | List declared secrets and their status per env (never prints values) |
492
+ | `secret set [key]` | Store a value (masked prompt, or `--stdin`). `--env local` → keychain; other envs → SOPS |
493
+ | `secret list` | List declared secrets and their status per env; names in a deployed file that the list lacks show as "not in list" (never prints values) |
441
494
  | `secret generate [key]` | Mint values for schema secrets with a `generate` strategy (`-a/--all`) |
442
495
  | `secret rotate [key]` | Rotate values; external secrets are flagged for manual reissue (`-a/--all`) |
443
496
  | `secret keygen` | Generate an age key pair for the SOPS no-cloud backend |
444
497
  | `secret recipients <add\|remove\|list> [age1…]` | Manage `.sops.yaml` recipients + re-encrypt |
445
- | `secret check` | Static lint — flag plaintext secret leaks |
498
+ | `secret check` | Static lint — flag plaintext secret leaks; with a deployed `--env`, names in the wrong layer's file |
499
+ | `secret export` | Decrypt and merge one deployment's layers into a 0600 dotenv file (prints names only) |
446
500
 
447
501
  Options: `-e, --env <env>` (`local` default; also `development`/`staging`/`production`),
448
- `-p, --package <pkg>` (schema source, default `@spfn/core`).
502
+ `-i, --instance <name>` (one instance of a deployed environment: `secrets/<env>.<instance>.enc.json`;
503
+ lowercase letters, digits and dashes), `-p, --package <pkg>` (read that one package's
504
+ schema; without it, the whole-app list described under `spfn env`).
505
+
506
+ **`--stdin`.** `spfn secret set --stdin --env staging SIGNING_KEY < key.pem` stores the
507
+ whole of stdin as the value — one trailing newline dropped, inner newlines kept — for
508
+ local and deployed targets alike. It never prompts, and refuses a terminal on stdin.
509
+ Shaped values such as key pairs come from the app's own command, piped in.
510
+
511
+ **Layers.** A schema entry's `layer` (`'environment'` default, or `'instance'`) says where a
512
+ deployment's value comes from. Without `--instance`, `set`/`generate`/`rotate`/`list` on a
513
+ deployed environment work on the `environment` names in `secrets/<env>.enc.json`; with
514
+ `--instance blue`, on the `instance` names in `secrets/<env>.blue.enc.json`. A named key of
515
+ the other layer is refused.
516
+
517
+ **Export.**
518
+
519
+ ```bash
520
+ spfn secret export --env <env> [--instance <name>] [--with <file>]... --out <file>
521
+ ```
522
+
523
+ Decrypts `secrets/<env>.enc.json` and — with `--instance`, when it exists —
524
+ `secrets/<env>.<instance>.enc.json`, reads each plaintext `--with` dotenv file (computed
525
+ instance values such as port and URLs), and writes the merge to `--out`. Only names in the
526
+ whole-app list are written; an unlisted name in an encrypted file is a warning by name.
527
+ The export fails, writing nothing, when a required name is in no layer, a name is in two
528
+ layers, an `instance` name is in the environment file, an `environment` name is in an
529
+ instance layer, a value fails its validator (the error names the variable and the
530
+ reason, never the value), or decryption fails. `--out` is required and its directory must
531
+ exist; relative paths resolve from the working directory. The file is created 0600 beside
532
+ the destination and renamed over it, so a failure leaves the previous file untouched
533
+ (POSIX only — Windows does not enforce the mode).
534
+
535
+ **SOPS rules for instance files.** The rule `spfn secret recipients add` writes,
536
+ `secrets/.*\.enc\.json$`, already covers `secrets/<env>.<instance>.enc.json`. A project
537
+ with per-environment rules needs each to accept the instance form too, e.g.
538
+ `path_regex: secrets/production(\.[a-z0-9-]+)?\.enc\.json$`. Writes need sops 3.10 or
539
+ later (`sops encrypt` from stdin, `sops set --value-stdin`), so a value never appears as a
540
+ process argument.
449
541
 
450
542
  **Local (keychain).** `spfn secret set DB_URL` stores the value in the OS keychain
451
543
  (macOS `security`, Windows Credential Manager via optional `@napi-rs/keyring`, Linux