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 +16 -2
- package/dist/index.js +25 -30
- 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
|
@@ -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.
|
|
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
|
|
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
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
1302
|
-
const filePath = join12(cwd,
|
|
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(
|
|
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 =
|
|
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();
|
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.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": {
|