spfn 0.3.0-beta.7 → 0.3.0-beta.8
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 +16 -2
- package/dist/index.js +21 -29
- package/dist/templates/README.md +8 -1
- package/package.json +1 -1
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
|
|
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`
|
|
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
|
@@ -948,7 +948,7 @@ import { dirname as dirname2, join as join10 } from "path";
|
|
|
948
948
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
949
949
|
function getCliVersion() {
|
|
950
950
|
if (true) {
|
|
951
|
-
return "0.3.0-beta.
|
|
951
|
+
return "0.3.0-beta.8";
|
|
952
952
|
}
|
|
953
953
|
return fromPackageJson ?? (fromPackageJson = readPackageVersion());
|
|
954
954
|
}
|
|
@@ -1169,14 +1169,6 @@ import { randomBytes } from "crypto";
|
|
|
1169
1169
|
import { spawnSync } from "child_process";
|
|
1170
1170
|
import { join as join12 } from "path";
|
|
1171
1171
|
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
1172
|
function envServerExampleTemplate(mode) {
|
|
1181
1173
|
return withPlaceholderCreds(
|
|
1182
1174
|
`${ENV_SERVER_TEMPLATE}${mode === "full" ? FULL_ENV_SERVER_EXAMPLE : ""}`
|
|
@@ -1195,7 +1187,9 @@ NEXT_PUBLIC_SPFN_APP_URL=http://localhost:3790
|
|
|
1195
1187
|
`;
|
|
1196
1188
|
}
|
|
1197
1189
|
function envLocalExampleTemplate(mode) {
|
|
1198
|
-
return
|
|
1190
|
+
return withPlaceholderCreds(
|
|
1191
|
+
`${ENV_LOCAL_TEMPLATE}${mode === "full" ? FULL_ENV_LOCAL_EXAMPLE : ""}`
|
|
1192
|
+
);
|
|
1199
1193
|
}
|
|
1200
1194
|
function envServerTemplate(mode) {
|
|
1201
1195
|
if (mode === "bare") {
|
|
@@ -1261,20 +1255,16 @@ export default defineConfig({
|
|
|
1261
1255
|
updateTsconfig(cwd);
|
|
1262
1256
|
}
|
|
1263
1257
|
function generateEnvFiles(cwd, mode) {
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1258
|
+
writeReferenceEnv(cwd, ".env.local.example", envLocalExampleTemplate(mode));
|
|
1259
|
+
writeReferenceEnv(cwd, ".env.server.example", envServerExampleTemplate(mode));
|
|
1260
|
+
writeLocalEnv(cwd, envLocalTemplate(mode));
|
|
1261
|
+
writeServerEnv(cwd, envServerTemplate(mode));
|
|
1267
1262
|
}
|
|
1268
|
-
function writeLocalEnv(cwd, template
|
|
1263
|
+
function writeLocalEnv(cwd, template) {
|
|
1269
1264
|
const filename = ".env.local";
|
|
1270
1265
|
const filePath = join12(cwd, filename);
|
|
1271
1266
|
if (isGitTracked(cwd, filename)) {
|
|
1272
|
-
|
|
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`);
|
|
1267
|
+
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
1268
|
return;
|
|
1279
1269
|
}
|
|
1280
1270
|
writeEnvFile(cwd, filename, template);
|
|
@@ -1287,7 +1277,7 @@ function isGitTracked(cwd, filename) {
|
|
|
1287
1277
|
});
|
|
1288
1278
|
return result.status === 0;
|
|
1289
1279
|
}
|
|
1290
|
-
function writeServerEnv(cwd, template
|
|
1280
|
+
function writeServerEnv(cwd, template) {
|
|
1291
1281
|
const filePath = join12(cwd, ".env.server");
|
|
1292
1282
|
if (!existsSync12(filePath)) {
|
|
1293
1283
|
writeFileSync8(filePath, template);
|
|
@@ -1295,16 +1285,15 @@ function writeServerEnv(cwd, template, exampleTemplate) {
|
|
|
1295
1285
|
logger.success("Created .env.server");
|
|
1296
1286
|
return;
|
|
1297
1287
|
}
|
|
1298
|
-
|
|
1299
|
-
logger.warn(".env.server already exists \u2014 left it untouched; wrote SPFN's reference to .env.server.example, add any missing keys manually");
|
|
1288
|
+
logger.warn(".env.server already exists \u2014 left it untouched; SPFN's reference is in .env.server.example, add any missing keys manually");
|
|
1300
1289
|
}
|
|
1301
|
-
function
|
|
1302
|
-
const filePath = join12(cwd,
|
|
1290
|
+
function writeReferenceEnv(cwd, filename, content) {
|
|
1291
|
+
const filePath = join12(cwd, filename);
|
|
1303
1292
|
if (existsSync12(filePath)) {
|
|
1304
1293
|
return;
|
|
1305
1294
|
}
|
|
1306
1295
|
writeFileSync8(filePath, content);
|
|
1307
|
-
logger.success(
|
|
1296
|
+
logger.success(`Created ${filename} (committed reference)`);
|
|
1308
1297
|
}
|
|
1309
1298
|
function writeEnvFile(cwd, filename, content) {
|
|
1310
1299
|
const filePath = join12(cwd, filename);
|
|
@@ -1362,8 +1351,11 @@ function pendingIgnoreRules(content) {
|
|
|
1362
1351
|
if (!gitignoreCovers(lines, ".env.server")) {
|
|
1363
1352
|
rules.push("\n# spfn server env (secrets)\n.env.server\n");
|
|
1364
1353
|
}
|
|
1365
|
-
if (!lines.some((line) => line.trim() === "!.env.example")) {
|
|
1366
|
-
rules.push("\n# spfn env reference (committed)\n!.env.example\n");
|
|
1354
|
+
if (!lines.some((line) => line.trim() === "!.env.local.example")) {
|
|
1355
|
+
rules.push("\n# spfn env reference, Next.js keys (committed)\n!.env.local.example\n");
|
|
1356
|
+
}
|
|
1357
|
+
if (!lines.some((line) => line.trim() === "!.env.server.example")) {
|
|
1358
|
+
rules.push("\n# spfn env reference, backend keys (committed)\n!.env.server.example\n");
|
|
1367
1359
|
}
|
|
1368
1360
|
return rules;
|
|
1369
1361
|
}
|
|
@@ -7148,7 +7140,7 @@ init_logger();
|
|
|
7148
7140
|
import { join as join32 } from "path";
|
|
7149
7141
|
import chalk43 from "chalk";
|
|
7150
7142
|
init_env_file();
|
|
7151
|
-
var COMMITTED_FILES = [".env", ".env.example"];
|
|
7143
|
+
var COMMITTED_FILES = [".env", ".env.example", ".env.local.example", ".env.server.example"];
|
|
7152
7144
|
var PLACEHOLDER = /(your-|changeme|placeholder|example|<.*>)/i;
|
|
7153
7145
|
async function secretCheck(options) {
|
|
7154
7146
|
const cwd = process.cwd();
|
package/dist/templates/README.md
CHANGED
|
@@ -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
|
|
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.
|
|
3
|
+
"version": "0.3.0-beta.8",
|
|
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": {
|