spfn 0.3.0-beta.7 → 0.3.0-beta.9

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 CHANGED
@@ -703,11 +703,25 @@ docker-compose.yml # Postgres + Redis (dev)
703
703
  docker-compose.production.yml
704
704
  Dockerfile, .dockerignore
705
705
  next.config.ts # patched when auth is enabled: /_auth/:path* rewrite → SPFN API
706
- .env.example # committed reference — every key, placeholder values
706
+ .env.local.example # committed reference — Next.js keys, placeholder values
707
+ .env.server.example # committed reference — backend keys, placeholder values
707
708
  .env.local # generated, gitignored (values loaded by Next.js)
708
709
  .env.server # generated, gitignored (server secrets: DB, cache)
709
710
  ```
710
711
 
712
+ The reference is split by consumer, never combined, and a key's file follows who
713
+ reads it rather than whether it is secret:
714
+
715
+ - `.env.local` / `.env.local.example` — the Next.js process (server routes, proxy,
716
+ SSR) and, through `NEXT_PUBLIC_*`, the browser: `SPFN_API_URL`, `SPFN_APP_URL`,
717
+ `SPFN_AUTH_SESSION_SECRET`, `SPFN_AUTH_SESSION_TTL`, `SPFN_AUTH_CSRF`.
718
+ - `.env.server` / `.env.server.example` — the SPFN backend only, never loaded by
719
+ Next.js: `NODE_ENV`, `SPFN_LOG_LEVEL`, `DATABASE_URL`, `CACHE_URL`, `DB_POOL_*`,
720
+ and every OAuth, token, and admin secret.
721
+
722
+ So the session secret is secret and still lives in `.env.local` — the Next.js proxy
723
+ verifies cookies with it — while `DATABASE_URL` never appears there at all.
724
+
711
725
  Full mode overlays the Prototype-to-Production baseline:
712
726
 
713
727
  ```
@@ -726,7 +740,7 @@ next.config.ts # /_auth/* callback rewrite
726
740
 
727
741
  The full RPC proxy imports the auth interceptor and merges `authRouteMap`. Internal auth
728
742
  keys are generated with cryptographic randomness in ignored local env files;
729
- `.env.example` contains placeholders only. Add only the provider keys you use, then run
743
+ `.env.local.example` and `.env.server.example` contain placeholders only. Add only the provider keys you use, then run
730
744
  `pnpm spfn db migrate`.
731
745
 
732
746
  Operating the app is [`spfn ops`](#spfn-ops), not a dashboard: the starter
package/dist/index.js CHANGED
@@ -72,6 +72,9 @@ function detectPackageManager(cwd) {
72
72
  function getRunCommand(pm) {
73
73
  return pm === "npm" ? "npm run" : `${pm} run`;
74
74
  }
75
+ function getRunScriptArgs(pm, script, args) {
76
+ return pm === "npm" ? ["run", script, "--", ...args] : ["run", script, ...args];
77
+ }
75
78
  var init_package_manager = __esm({
76
79
  "src/utils/package-manager.ts"() {
77
80
  "use strict";
@@ -948,7 +951,7 @@ import { dirname as dirname2, join as join10 } from "path";
948
951
  import { fileURLToPath as fileURLToPath2 } from "url";
949
952
  function getCliVersion() {
950
953
  if (true) {
951
- return "0.3.0-beta.7";
954
+ return "0.3.0-beta.9";
952
955
  }
953
956
  return fromPackageJson ?? (fromPackageJson = readPackageVersion());
954
957
  }
@@ -1169,14 +1172,6 @@ import { randomBytes } from "crypto";
1169
1172
  import { spawnSync } from "child_process";
1170
1173
  import { join as join12 } from "path";
1171
1174
  import fse8 from "fs-extra";
1172
- function envExampleTemplate(mode) {
1173
- return withPlaceholderCreds(`# Example environment \u2014 committed reference for the variables SPFN uses.
1174
- # Real values live in .env.local (Next.js) and .env.server (backend secrets),
1175
- # both gitignored. This file documents the keys; it is not loaded by anything.
1176
-
1177
- ${ENV_LOCAL_TEMPLATE}${mode === "full" ? FULL_ENV_LOCAL_EXAMPLE : ""}
1178
- ${ENV_SERVER_TEMPLATE}${mode === "full" ? FULL_ENV_SERVER_EXAMPLE : ""}`);
1179
- }
1180
1175
  function envServerExampleTemplate(mode) {
1181
1176
  return withPlaceholderCreds(
1182
1177
  `${ENV_SERVER_TEMPLATE}${mode === "full" ? FULL_ENV_SERVER_EXAMPLE : ""}`
@@ -1195,7 +1190,9 @@ NEXT_PUBLIC_SPFN_APP_URL=http://localhost:3790
1195
1190
  `;
1196
1191
  }
1197
1192
  function envLocalExampleTemplate(mode) {
1198
- return `${ENV_LOCAL_TEMPLATE}${mode === "full" ? FULL_ENV_LOCAL_EXAMPLE : ""}`;
1193
+ return withPlaceholderCreds(
1194
+ `${ENV_LOCAL_TEMPLATE}${mode === "full" ? FULL_ENV_LOCAL_EXAMPLE : ""}`
1195
+ );
1199
1196
  }
1200
1197
  function envServerTemplate(mode) {
1201
1198
  if (mode === "bare") {
@@ -1261,20 +1258,16 @@ export default defineConfig({
1261
1258
  updateTsconfig(cwd);
1262
1259
  }
1263
1260
  function generateEnvFiles(cwd, mode) {
1264
- writeLocalEnv(cwd, envLocalTemplate(mode), envLocalExampleTemplate(mode));
1265
- writeServerEnv(cwd, envServerTemplate(mode), envServerExampleTemplate(mode));
1266
- writeExampleEnv(cwd, envExampleTemplate(mode));
1261
+ writeReferenceEnv(cwd, ".env.local.example", envLocalExampleTemplate(mode));
1262
+ writeReferenceEnv(cwd, ".env.server.example", envServerExampleTemplate(mode));
1263
+ writeLocalEnv(cwd, envLocalTemplate(mode));
1264
+ writeServerEnv(cwd, envServerTemplate(mode));
1267
1265
  }
1268
- function writeLocalEnv(cwd, template, exampleTemplate) {
1266
+ function writeLocalEnv(cwd, template) {
1269
1267
  const filename = ".env.local";
1270
1268
  const filePath = join12(cwd, filename);
1271
1269
  if (isGitTracked(cwd, filename)) {
1272
- const referenceName = ".env.local.spfn.example";
1273
- const referencePath = join12(cwd, referenceName);
1274
- if (!existsSync12(referencePath)) {
1275
- writeFileSync8(referencePath, exampleTemplate);
1276
- }
1277
- logger.warn(`${filename} is tracked by Git \u2014 left it untouched; wrote ${referenceName}. Untrack ${filename}, then add the missing keys with freshly generated values`);
1270
+ logger.warn(`${filename} is tracked by Git \u2014 left it untouched; its keys are listed in .env.local.example. Untrack ${filename}, then add the missing keys with freshly generated values`);
1278
1271
  return;
1279
1272
  }
1280
1273
  writeEnvFile(cwd, filename, template);
@@ -1287,7 +1280,7 @@ function isGitTracked(cwd, filename) {
1287
1280
  });
1288
1281
  return result.status === 0;
1289
1282
  }
1290
- function writeServerEnv(cwd, template, exampleTemplate) {
1283
+ function writeServerEnv(cwd, template) {
1291
1284
  const filePath = join12(cwd, ".env.server");
1292
1285
  if (!existsSync12(filePath)) {
1293
1286
  writeFileSync8(filePath, template);
@@ -1295,16 +1288,15 @@ function writeServerEnv(cwd, template, exampleTemplate) {
1295
1288
  logger.success("Created .env.server");
1296
1289
  return;
1297
1290
  }
1298
- writeFileSync8(join12(cwd, ".env.server.example"), exampleTemplate);
1299
- logger.warn(".env.server already exists \u2014 left it untouched; wrote SPFN's reference to .env.server.example, add any missing keys manually");
1291
+ logger.warn(".env.server already exists \u2014 left it untouched; SPFN's reference is in .env.server.example, add any missing keys manually");
1300
1292
  }
1301
- function writeExampleEnv(cwd, content) {
1302
- const filePath = join12(cwd, ".env.example");
1293
+ function writeReferenceEnv(cwd, filename, content) {
1294
+ const filePath = join12(cwd, filename);
1303
1295
  if (existsSync12(filePath)) {
1304
1296
  return;
1305
1297
  }
1306
1298
  writeFileSync8(filePath, content);
1307
- logger.success("Created .env.example (committed reference)");
1299
+ logger.success(`Created ${filename} (committed reference)`);
1308
1300
  }
1309
1301
  function writeEnvFile(cwd, filename, content) {
1310
1302
  const filePath = join12(cwd, filename);
@@ -1362,8 +1354,11 @@ function pendingIgnoreRules(content) {
1362
1354
  if (!gitignoreCovers(lines, ".env.server")) {
1363
1355
  rules.push("\n# spfn server env (secrets)\n.env.server\n");
1364
1356
  }
1365
- if (!lines.some((line) => line.trim() === "!.env.example")) {
1366
- rules.push("\n# spfn env reference (committed)\n!.env.example\n");
1357
+ if (!lines.some((line) => line.trim() === "!.env.local.example")) {
1358
+ rules.push("\n# spfn env reference, Next.js keys (committed)\n!.env.local.example\n");
1359
+ }
1360
+ if (!lines.some((line) => line.trim() === "!.env.server.example")) {
1361
+ rules.push("\n# spfn env reference, backend keys (committed)\n!.env.server.example\n");
1367
1362
  }
1368
1363
  return rules;
1369
1364
  }
@@ -2658,7 +2653,7 @@ catch (error)
2658
2653
  };
2659
2654
  const startNext = () => {
2660
2655
  const nextCmd = pm === "npm" ? "npm" : pm;
2661
- const nextArgs = ["run", "spfn:next", "--", "--port", String(nextPort)];
2656
+ const nextArgs = getRunScriptArgs(pm, "spfn:next", ["--port", String(nextPort)]);
2662
2657
  nextProcess = execa6(nextCmd, nextArgs, {
2663
2658
  cwd,
2664
2659
  stdio: "inherit",
@@ -7148,7 +7143,7 @@ init_logger();
7148
7143
  import { join as join32 } from "path";
7149
7144
  import chalk43 from "chalk";
7150
7145
  init_env_file();
7151
- var COMMITTED_FILES = [".env", ".env.example"];
7146
+ var COMMITTED_FILES = [".env", ".env.example", ".env.local.example", ".env.server.example"];
7152
7147
  var PLACEHOLDER = /(your-|changeme|placeholder|example|<.*>)/i;
7153
7148
  async function secretCheck(options) {
7154
7149
  const cwd = process.cwd();
@@ -32,7 +32,8 @@ src/
32
32
  generated/ codegen output (route map) — do not edit by hand
33
33
  .env.local Next.js runtime env (gitignored)
34
34
  .env.server server secrets (gitignored)
35
- .env.example committed reference — keys only, placeholder values
35
+ .env.local.example committed reference — the Next.js keys, placeholder values
36
+ .env.server.example committed reference — the backend keys, placeholder values
36
37
  .spfnrc.ts codegen configuration
37
38
  spfn.config.js deployment config
38
39
  ```
@@ -54,6 +55,12 @@ A feature is a vertical slice: `Entity` (Drizzle table) → `Repository` →
54
55
 
55
56
  ## Environment & secrets
56
57
 
58
+ The env reference is split by consumer: `.env.local.example` lists what the Next.js
59
+ process reads (`SPFN_API_URL`, `NEXT_PUBLIC_*`, the auth session secret), and
60
+ `.env.server.example` lists what only the backend reads (`DATABASE_URL`, `CACHE_URL`,
61
+ OAuth secrets). Which file a key belongs in follows who consumes it, not whether it
62
+ is secret.
63
+
57
64
  `.env.server` holds backend-only secrets and is gitignored — Next.js never loads it.
58
65
  For a managed workflow use `spfn secret`: local values go to the OS keychain (only a
59
66
  `secret:keychain:` reference lands in `.env.server`), and deployed secrets are stored
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spfn",
3
- "version": "0.3.0-beta.7",
3
+ "version": "0.3.0-beta.9",
4
4
  "description": "Scaffold a full-stack TypeScript backend onto a Next.js app built with an AI coding agent: auth, database, typed routes and codegen, one fixed vertical slice per feature",
5
5
  "type": "module",
6
6
  "bin": {