spfn 0.3.0-beta.2 → 0.3.0-beta.3
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 +15 -8
- package/dist/index.js +76 -48
- package/dist/templates/Dockerfile +6 -3
- package/dist/templates/README.md +1 -1
- package/dist/templates/docker-compose.production.yml +9 -2
- package/dist/templates/modes/full/src/server/router.ts +0 -2
- package/dist/templates/modes/full/src/server/routes/root.ts +1 -1
- package/dist/templates/modes/full/src/server/server.config.ts +0 -2
- package/dist/templates/server/router.ts +0 -2
- package/dist/templates/server/routes/root.ts +1 -1
- package/dist/templates/server/server.config.ts +0 -2
- package/package.json +2 -2
- package/dist/templates/modes/full/src/server/routes/health.ts +0 -9
- package/dist/templates/server/routes/health.ts +0 -18
package/README.md
CHANGED
|
@@ -134,9 +134,8 @@ no pre-build needed.
|
|
|
134
134
|
|--------|-------------|---------|
|
|
135
135
|
| `--server-only` | Run only the SPFN/Hono server (also auto-selected if Next.js isn't a dependency) | off |
|
|
136
136
|
| `--watch` | Restart the server on `src/server` changes (chokidar) | off |
|
|
137
|
-
| `-p, --port <port>` |
|
|
138
|
-
| `-H, --host <host>` |
|
|
139
|
-
| `--routes <path>` | Routes directory path | server default |
|
|
137
|
+
| `-p, --port <port>` | SPFN server port (sets `SPFN_PORT`) | `spfn.config.js` `ports.server`, then `8790` |
|
|
138
|
+
| `-H, --host <host>` | SPFN server host (sets `SPFN_HOST`) | `spfn.config.js` `host`, then `localhost` |
|
|
140
139
|
| `--allow-pending-migrations` | Start even when migrations are pending (they are listed as a warning) | off |
|
|
141
140
|
|
|
142
141
|
Note: hot reload is **off by default** — pass `--watch` to restart on file changes.
|
|
@@ -165,11 +164,19 @@ if `.spfn/server`, `.spfn/prod-server.mjs`, or `.next` are missing.
|
|
|
165
164
|
|--------|-------------|---------|
|
|
166
165
|
| `--server-only` | Run only the SPFN server | off |
|
|
167
166
|
| `--next-only` | Run only Next.js | off |
|
|
168
|
-
| `-p, --port <port>` | SPFN server port (sets `SPFN_PORT`) | `8790` |
|
|
169
|
-
| `-h, --host <host>` | SPFN server host (sets `SPFN_HOST`) | `
|
|
167
|
+
| `-p, --port <port>` | SPFN server port (sets `SPFN_PORT`) | `spfn.config.js` `ports.server`, then `8790` |
|
|
168
|
+
| `-h, --host <host>` | SPFN server host (sets `SPFN_HOST`) | `spfn.config.js` `host`, then `localhost` |
|
|
170
169
|
| `--allow-pending-migrations` | Start even when migrations are pending (they are listed as a warning) | off |
|
|
171
170
|
|
|
172
|
-
|
|
171
|
+
Both run together via `concurrently --kill-others`.
|
|
172
|
+
|
|
173
|
+
Neither flag has a default value, deliberately. A default is indistinguishable
|
|
174
|
+
from a value the operator typed, and it was forwarded as `SPFN_PORT` either way —
|
|
175
|
+
which overrode the app's own configuration. Pass nothing and `spfn.config.js`
|
|
176
|
+
decides; pass a flag and it wins.
|
|
177
|
+
|
|
178
|
+
Next.js is started on the port `spfn.config.js` gives as `ports.next` (`3790` by
|
|
179
|
+
default), overridable with `NEXT_PORT`.
|
|
173
180
|
|
|
174
181
|
Pending migrations stop the boot unless `--allow-pending-migrations` or
|
|
175
182
|
`SPFN_ALLOW_PENDING_MIGRATIONS=true` is set — see [Database](#spfn-db). `--next-only`
|
|
@@ -580,7 +587,7 @@ docker compose -f docker-compose.production.yml up --build -d
|
|
|
580
587
|
|
|
581
588
|
The Dockerfile (`node:22-alpine`) installs with `pnpm --frozen-lockfile`, runs
|
|
582
589
|
`pnpm run spfn:build`, prunes dev deps, exposes `3790`/`8790`, health-checks
|
|
583
|
-
`http://localhost:8790/health`, and starts via `pnpm run spfn:start`.
|
|
590
|
+
`http://localhost:8790/_core/health`, and starts via `pnpm run spfn:start`.
|
|
584
591
|
|
|
585
592
|
Run migrations against the target DB before/with deploy:
|
|
586
593
|
|
|
@@ -595,7 +602,7 @@ the gate is one that never served the 500s. If a rollout has to proceed anyway,
|
|
|
595
602
|
logged as a warning instead.
|
|
596
603
|
|
|
597
604
|
A readiness probe can catch the same drift on a cluster the local gate never sees. When
|
|
598
|
-
detailed health is on, `GET /health` carries a `migrations` object with per-package
|
|
605
|
+
detailed health is on, `GET /_core/health` carries a `migrations` object with per-package
|
|
599
606
|
applied/pending counts — assert `migrations.pending === 0` in the probe to hold a
|
|
600
607
|
drifted pod out of rotation. Reporting drift does not, by itself, change the overall
|
|
601
608
|
health `status`.
|
package/dist/index.js
CHANGED
|
@@ -808,11 +808,33 @@ async function setupDeploymentConfig(cwd, packageJson, packageManager) {
|
|
|
808
808
|
const configContent = `/**
|
|
809
809
|
* SPFN Configuration
|
|
810
810
|
*
|
|
811
|
-
* This file
|
|
811
|
+
* This file describes how your app is served and deployed. It is committed, so
|
|
812
|
+
* keep secrets out of it.
|
|
812
813
|
*
|
|
813
814
|
* @type {import('spfn').SpfnConfig}
|
|
814
815
|
*/
|
|
815
816
|
export default {
|
|
817
|
+
/**
|
|
818
|
+
* Ports the two processes bind.
|
|
819
|
+
*
|
|
820
|
+
* This is the only place either number is written. The Dockerfile, the
|
|
821
|
+
* compose file and \`spfn dev\` / \`spfn start\` all read it, so changing a
|
|
822
|
+
* port here changes it everywhere.
|
|
823
|
+
*
|
|
824
|
+
* Overridable per environment with NEXT_PORT and SPFN_PORT \u2014 a container
|
|
825
|
+
* setting one of those wins over what is written here.
|
|
826
|
+
*/
|
|
827
|
+
ports: {
|
|
828
|
+
next: 3790,
|
|
829
|
+
server: 8790,
|
|
830
|
+
},
|
|
831
|
+
|
|
832
|
+
/**
|
|
833
|
+
* Host the SPFN API server binds. A container sets SPFN_HOST=0.0.0.0; a
|
|
834
|
+
* developer machine has no reason to publish its dev server to the network.
|
|
835
|
+
*/
|
|
836
|
+
host: 'localhost',
|
|
837
|
+
|
|
816
838
|
/**
|
|
817
839
|
* Package manager to use for dependency installation
|
|
818
840
|
* Options: 'npm' | 'yarn' | 'pnpm' | 'bun'
|
|
@@ -922,7 +944,7 @@ var init_deployment_config = __esm({
|
|
|
922
944
|
|
|
923
945
|
// src/utils/version.ts
|
|
924
946
|
function getCliVersion() {
|
|
925
|
-
return "0.3.0-beta.
|
|
947
|
+
return "0.3.0-beta.3";
|
|
926
948
|
}
|
|
927
949
|
function getTagFromVersion(version) {
|
|
928
950
|
const match = version.match(/-([a-z]+)\./i);
|
|
@@ -982,7 +1004,7 @@ async function setupPackageJson(cwd, packageJsonPath, packageJson, packageManage
|
|
|
982
1004
|
}
|
|
983
1005
|
packageJson.scripts["spfn:dev"] = "spfn dev";
|
|
984
1006
|
packageJson.scripts["spfn:server"] = "spfn dev --server-only";
|
|
985
|
-
packageJson.scripts["spfn:next"] = "next dev --turbo
|
|
1007
|
+
packageJson.scripts["spfn:next"] = "next dev --turbo";
|
|
986
1008
|
packageJson.scripts["spfn:start"] = "spfn start";
|
|
987
1009
|
packageJson.scripts["spfn:build"] = "spfn build";
|
|
988
1010
|
packageJson.scripts["codegen"] = "spfn codegen run";
|
|
@@ -1483,7 +1505,7 @@ async function initializeSpfn(options = {}) {
|
|
|
1483
1505
|
console.log(` ${mode === "full" ? "4" : "3"}. Run: ` + chalk4.cyan(`${getRunCommand(pm)} spfn:dev`));
|
|
1484
1506
|
console.log(` ${mode === "full" ? "5" : "4"}. Visit:`);
|
|
1485
1507
|
console.log(" - Next.js: " + chalk4.cyan("http://localhost:3790"));
|
|
1486
|
-
console.log(" - API: " + chalk4.cyan("http://localhost:8790/health"));
|
|
1508
|
+
console.log(" - API: " + chalk4.cyan("http://localhost:8790/_core/health"));
|
|
1487
1509
|
if (mode === "full") {
|
|
1488
1510
|
console.log(" - MCP: " + chalk4.cyan("http://localhost:8790/mcp"));
|
|
1489
1511
|
}
|
|
@@ -2122,6 +2144,7 @@ async function resolveKeychainEnv(cwd) {
|
|
|
2122
2144
|
}
|
|
2123
2145
|
|
|
2124
2146
|
// src/commands/dev.ts
|
|
2147
|
+
import { loadAppConfig, resolvePorts, resolveHost } from "@spfn/core/app-config";
|
|
2125
2148
|
function ignoreDotfilesUnder(root) {
|
|
2126
2149
|
return (watchedPath) => relative(root, watchedPath).split(sep2).some((segment) => segment.startsWith(".") && segment !== "." && segment !== "..");
|
|
2127
2150
|
}
|
|
@@ -2145,13 +2168,22 @@ function waitForReadyFile(filePath, timeoutMs = 3e4) {
|
|
|
2145
2168
|
});
|
|
2146
2169
|
});
|
|
2147
2170
|
}
|
|
2148
|
-
var devCommand = new Command4("dev").description("Start SPFN development server (detects and runs Next.js + Hono)").option("-p, --port <port>", "Server port").option("-H, --host <host>", "Server host").option("--
|
|
2171
|
+
var devCommand = new Command4("dev").description("Start SPFN development server (detects and runs Next.js + Hono)").option("-p, --port <port>", "Server port").option("-H, --host <host>", "Server host").option("--server-only", "Run only Hono server (skip Next.js)").option("--watch", "Enable hot reload (watch mode)").option("--allow-pending-migrations", "Start even when migrations are pending (they are listed as a warning)").action(async (options) => {
|
|
2149
2172
|
process.setMaxListeners(20);
|
|
2150
2173
|
if (!process.env.NODE_ENV) {
|
|
2151
2174
|
process.env.NODE_ENV = "development";
|
|
2152
2175
|
}
|
|
2153
2176
|
const cwd = process.cwd();
|
|
2154
2177
|
const serverDir = join15(cwd, "src", "server");
|
|
2178
|
+
if (options.port) {
|
|
2179
|
+
process.env.SPFN_PORT = String(options.port);
|
|
2180
|
+
}
|
|
2181
|
+
if (options.host) {
|
|
2182
|
+
process.env.SPFN_HOST = String(options.host);
|
|
2183
|
+
}
|
|
2184
|
+
const appConfig = await loadAppConfig(cwd);
|
|
2185
|
+
const { next: nextPort, server: serverPort } = resolvePorts(appConfig);
|
|
2186
|
+
const serverHost = resolveHost(appConfig);
|
|
2155
2187
|
if (!existsSync14(serverDir)) {
|
|
2156
2188
|
logger.error("src/server directory not found.");
|
|
2157
2189
|
logger.info('Run "spfn init" first to initialize SPFN in your project.');
|
|
@@ -2197,11 +2229,7 @@ var devCommand = new Command4("dev").description("Start SPFN development server
|
|
|
2197
2229
|
if (existsSync14(readySignal)) {
|
|
2198
2230
|
unlinkSync(readySignal);
|
|
2199
2231
|
}
|
|
2200
|
-
const configParts = [];
|
|
2201
|
-
if (options.port) configParts.push(`port: ${options.port}`);
|
|
2202
|
-
if (options.host) configParts.push(`host: '${options.host}'`);
|
|
2203
|
-
if (options.routes) configParts.push(`routesPath: '${options.routes}'`);
|
|
2204
|
-
configParts.push("debug: true");
|
|
2232
|
+
const configParts = ["debug: true"];
|
|
2205
2233
|
const readyFile = join15(tempDir, "server-ready");
|
|
2206
2234
|
writeFileSync10(serverEntry, `
|
|
2207
2235
|
import { writeFileSync } from 'fs';
|
|
@@ -2274,9 +2302,7 @@ catch (error)
|
|
|
2274
2302
|
const pm = detectPackageManager(cwd);
|
|
2275
2303
|
if (options.serverOnly || !hasNext) {
|
|
2276
2304
|
const watchMode2 = options.watch === true;
|
|
2277
|
-
|
|
2278
|
-
const port = options.port ?? process.env.PORT ?? "4000";
|
|
2279
|
-
logger.info(`Starting SPFN Server on http://${host}:${port}${watchMode2 ? " (watch mode)" : ""}
|
|
2305
|
+
logger.info(`Starting SPFN Server on http://${serverHost}:${serverPort}${watchMode2 ? " (watch mode)" : ""}
|
|
2280
2306
|
`);
|
|
2281
2307
|
let serverProcess2 = null;
|
|
2282
2308
|
let watcherProcess2 = null;
|
|
@@ -2392,7 +2418,7 @@ catch (error)
|
|
|
2392
2418
|
};
|
|
2393
2419
|
const startNext = () => {
|
|
2394
2420
|
const nextCmd = pm === "npm" ? "npm" : pm;
|
|
2395
|
-
const nextArgs =
|
|
2421
|
+
const nextArgs = ["run", "spfn:next", "--", "--port", String(nextPort)];
|
|
2396
2422
|
nextProcess = execa6(nextCmd, nextArgs, {
|
|
2397
2423
|
cwd,
|
|
2398
2424
|
stdio: "inherit",
|
|
@@ -2498,6 +2524,19 @@ import { execa as execa7 } from "execa";
|
|
|
2498
2524
|
import ora5 from "ora";
|
|
2499
2525
|
import chalk7 from "chalk";
|
|
2500
2526
|
import { build } from "tsup";
|
|
2527
|
+
function renderProdServerEntry() {
|
|
2528
|
+
return `// Load environment variables FIRST (before any imports that depend on them)
|
|
2529
|
+
// Use centralized environment loader for standard dotenv priority
|
|
2530
|
+
await import('@spfn/core/config');
|
|
2531
|
+
|
|
2532
|
+
// Now import server (logger singleton will be created with correct NODE_ENV)
|
|
2533
|
+
const { startServer } = await import('@spfn/core/server');
|
|
2534
|
+
|
|
2535
|
+
// No address here on purpose: SPFN_PORT / SPFN_HOST and spfn.config.js decide,
|
|
2536
|
+
// and startServer reads both.
|
|
2537
|
+
await startServer({ debug: false });
|
|
2538
|
+
`;
|
|
2539
|
+
}
|
|
2501
2540
|
async function buildProject(options) {
|
|
2502
2541
|
if (!process.env.NODE_ENV) {
|
|
2503
2542
|
process.env.NODE_ENV = "production";
|
|
@@ -2571,6 +2610,12 @@ async function buildProject(options) {
|
|
|
2571
2610
|
entry: ["src/server/**/*.ts"],
|
|
2572
2611
|
format: ["esm"],
|
|
2573
2612
|
outDir: ".spfn/server",
|
|
2613
|
+
// Pin the extension. Left to tsup it follows the app's
|
|
2614
|
+
// package.json — `.mjs` normally, `.js` under
|
|
2615
|
+
// `"type": "module"` — and @spfn/core looks the compiled
|
|
2616
|
+
// server.config up by name, so an app that declared that field
|
|
2617
|
+
// shipped a production server running on defaults.
|
|
2618
|
+
outExtension: () => ({ js: ".mjs" }),
|
|
2574
2619
|
clean: true,
|
|
2575
2620
|
splitting: false,
|
|
2576
2621
|
tsconfig: "src/server/tsconfig.json",
|
|
@@ -2590,29 +2635,7 @@ async function buildProject(options) {
|
|
|
2590
2635
|
}
|
|
2591
2636
|
});
|
|
2592
2637
|
const prodServerPath = join16(cwd, ".spfn", "prod-server.mjs");
|
|
2593
|
-
const prodServerContent =
|
|
2594
|
-
// Use centralized environment loader for standard dotenv priority
|
|
2595
|
-
const { env } = await import('@spfn/core/config');
|
|
2596
|
-
|
|
2597
|
-
// Now import server (logger singleton will be created with correct NODE_ENV)
|
|
2598
|
-
const { startServer } = await import('@spfn/core/server');
|
|
2599
|
-
import { join } from 'path';
|
|
2600
|
-
import { fileURLToPath } from 'url';
|
|
2601
|
-
import { dirname } from 'path';
|
|
2602
|
-
|
|
2603
|
-
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
2604
|
-
|
|
2605
|
-
// Environment variables: from .env files OR injected by container/kubernetes
|
|
2606
|
-
const port = env.SPFN_PORT || '8790';
|
|
2607
|
-
const host = env.SPFN_HOST || '0.0.0.0';
|
|
2608
|
-
|
|
2609
|
-
await startServer({
|
|
2610
|
-
port: Number(port),
|
|
2611
|
-
host,
|
|
2612
|
-
routesPath: join(__dirname, 'server', 'routes'),
|
|
2613
|
-
debug: false
|
|
2614
|
-
});
|
|
2615
|
-
`;
|
|
2638
|
+
const prodServerContent = renderProdServerEntry();
|
|
2616
2639
|
writeFileSync11(prodServerPath, prodServerContent);
|
|
2617
2640
|
spinner.succeed(`SPFN server build completed \u2192 .spfn/server`);
|
|
2618
2641
|
const routesDir = join16(cwd, ".spfn", "server", "routes");
|
|
@@ -2676,7 +2699,8 @@ import { existsSync as existsSync16, readFileSync as readFileSync8 } from "fs";
|
|
|
2676
2699
|
import { join as join17 } from "path";
|
|
2677
2700
|
import { execa as execa8 } from "execa";
|
|
2678
2701
|
import chalk8 from "chalk";
|
|
2679
|
-
|
|
2702
|
+
import { loadAppConfig as loadAppConfig2, resolvePorts as resolvePorts2 } from "@spfn/core/app-config";
|
|
2703
|
+
var startCommand = new Command6("start").description("Start SPFN production server (Next.js + Hono)").option("--server-only", "Run only SPFN server (skip Next.js)").option("--next-only", "Run only Next.js (skip SPFN server)").option("-p, --port <port>", "SPFN server port (default: spfn.config.js ports.server, then 8790)").option("-h, --host <host>", "SPFN server host (default: spfn.config.js host, then localhost)").option("--allow-pending-migrations", "Start even when migrations are pending (they are listed as a warning)").action(async (options) => {
|
|
2680
2704
|
if (!process.env.NODE_ENV) {
|
|
2681
2705
|
process.env.NODE_ENV = "production";
|
|
2682
2706
|
}
|
|
@@ -2705,8 +2729,13 @@ var startCommand = new Command6("start").description("Start SPFN production serv
|
|
|
2705
2729
|
logger.error('.spfn/prod-server.mjs not found. Please run "spfn build" first.');
|
|
2706
2730
|
process.exit(1);
|
|
2707
2731
|
}
|
|
2708
|
-
|
|
2709
|
-
|
|
2732
|
+
if (options.port) {
|
|
2733
|
+
process.env.SPFN_PORT = options.port;
|
|
2734
|
+
}
|
|
2735
|
+
if (options.host) {
|
|
2736
|
+
process.env.SPFN_HOST = options.host;
|
|
2737
|
+
}
|
|
2738
|
+
const nextPort = resolvePorts2(await loadAppConfig2(cwd)).next;
|
|
2710
2739
|
if (!options.nextOnly) {
|
|
2711
2740
|
const { checkPendingMigrationsBeforeStart: checkPendingMigrationsBeforeStart2 } = await Promise.resolve().then(() => (init_migration_status(), migration_status_exports));
|
|
2712
2741
|
const migrationCheck = await checkPendingMigrationsBeforeStart2(
|
|
@@ -2722,8 +2751,7 @@ var startCommand = new Command6("start").description("Start SPFN production serv
|
|
|
2722
2751
|
}
|
|
2723
2752
|
}
|
|
2724
2753
|
if (options.serverOnly || !hasNext) {
|
|
2725
|
-
logger.info(
|
|
2726
|
-
`);
|
|
2754
|
+
logger.info("Starting SPFN Server (production)\n");
|
|
2727
2755
|
try {
|
|
2728
2756
|
await execa8("node", [serverEntry], {
|
|
2729
2757
|
stdio: "inherit",
|
|
@@ -2737,9 +2765,10 @@ var startCommand = new Command6("start").description("Start SPFN production serv
|
|
|
2737
2765
|
return;
|
|
2738
2766
|
}
|
|
2739
2767
|
if (options.nextOnly) {
|
|
2740
|
-
logger.info(
|
|
2768
|
+
logger.info(`Starting Next.js (production) on http://0.0.0.0:${nextPort}
|
|
2769
|
+
`);
|
|
2741
2770
|
try {
|
|
2742
|
-
await execa8("npx", ["next", "start", "-H", "0.0.0.0", "-p",
|
|
2771
|
+
await execa8("npx", ["next", "start", "-H", "0.0.0.0", "-p", String(nextPort)], {
|
|
2743
2772
|
stdio: "inherit",
|
|
2744
2773
|
cwd
|
|
2745
2774
|
});
|
|
@@ -2749,12 +2778,11 @@ var startCommand = new Command6("start").description("Start SPFN production serv
|
|
|
2749
2778
|
}
|
|
2750
2779
|
return;
|
|
2751
2780
|
}
|
|
2752
|
-
const nextCmd =
|
|
2781
|
+
const nextCmd = `next start -H 0.0.0.0 -p ${nextPort}`;
|
|
2753
2782
|
const serverCmd = `node "${serverEntry}"`;
|
|
2754
2783
|
console.log(chalk8.blue.bold("\n\u{1F680} Starting SPFN production server...\n"));
|
|
2755
|
-
logger.info(
|
|
2756
|
-
logger.info(
|
|
2757
|
-
`);
|
|
2784
|
+
logger.info(`Next.js: http://0.0.0.0:${nextPort}`);
|
|
2785
|
+
logger.info("SPFN API: announced by the server below\n");
|
|
2758
2786
|
try {
|
|
2759
2787
|
await execa8(
|
|
2760
2788
|
pm === "npm" ? "npx" : pm,
|
|
@@ -24,12 +24,15 @@ RUN pnpm prune --prod
|
|
|
24
24
|
# Environment
|
|
25
25
|
ENV NODE_ENV=production
|
|
26
26
|
|
|
27
|
-
# Expose ports
|
|
27
|
+
# Expose ports. Docker cannot read spfn.config.js, so these two numbers are the
|
|
28
|
+
# one place they are repeated — keep them in step with `ports` in that file, or
|
|
29
|
+
# set NEXT_PORT / SPFN_PORT here and in the compose file instead.
|
|
28
30
|
EXPOSE 3790 8790
|
|
29
31
|
|
|
30
|
-
# Health check
|
|
32
|
+
# Health check. Falls back to the same default the framework uses, so setting
|
|
33
|
+
# SPFN_PORT alone moves both the server and its probe.
|
|
31
34
|
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
|
|
32
|
-
CMD node -e "require('http').get('http://localhost:8790/health', (r) => {process.exit(r.statusCode === 200 ? 0 : 1)})"
|
|
35
|
+
CMD node -e "require('http').get('http://localhost:'+(process.env.SPFN_PORT||8790)+'/_core/health', (r) => {process.exit(r.statusCode === 200 ? 0 : 1)})"
|
|
33
36
|
|
|
34
37
|
# Start application
|
|
35
38
|
CMD ["pnpm", "run", "spfn:start"]
|
package/dist/templates/README.md
CHANGED
|
@@ -22,13 +22,20 @@ services:
|
|
|
22
22
|
context: .
|
|
23
23
|
dockerfile: Dockerfile
|
|
24
24
|
ports:
|
|
25
|
-
- "3790
|
|
26
|
-
- "8790
|
|
25
|
+
- "${NEXT_PORT:-3790}:${NEXT_PORT:-3790}" # Next.js
|
|
26
|
+
- "${SPFN_PORT:-8790}:${SPFN_PORT:-8790}" # SPFN API
|
|
27
27
|
environment:
|
|
28
28
|
# Required: Set these via .env file or environment
|
|
29
29
|
- DATABASE_URL=${DATABASE_URL}
|
|
30
30
|
- REDIS_URL=${REDIS_URL:-redis://redis:6379}
|
|
31
31
|
- NODE_ENV=production
|
|
32
|
+
# Ports: leave unset and spfn.config.js decides. Set either one to
|
|
33
|
+
# override it for this deployment — the mapping above follows.
|
|
34
|
+
- NEXT_PORT=${NEXT_PORT:-}
|
|
35
|
+
- SPFN_PORT=${SPFN_PORT:-}
|
|
36
|
+
# A container has to bind every interface; a developer machine does not,
|
|
37
|
+
# which is why localhost is the default and this line is here.
|
|
38
|
+
- SPFN_HOST=0.0.0.0
|
|
32
39
|
# Optional: Frontend API URL
|
|
33
40
|
- NEXT_PUBLIC_API_URL=${NEXT_PUBLIC_API_URL:-http://localhost:8790}
|
|
34
41
|
restart: unless-stopped
|
|
@@ -8,7 +8,6 @@ import { authRouter, authenticate } from '@spfn/auth/server';
|
|
|
8
8
|
import { defineRouter } from '@spfn/core/route';
|
|
9
9
|
import { mcpRouter } from './mcp';
|
|
10
10
|
import { getRoot } from './routes/root';
|
|
11
|
-
import { getHealth } from './routes/health';
|
|
12
11
|
import {
|
|
13
12
|
listExamples,
|
|
14
13
|
getExample,
|
|
@@ -19,7 +18,6 @@ import {
|
|
|
19
18
|
|
|
20
19
|
export const appRouter = defineRouter({
|
|
21
20
|
getRoot,
|
|
22
|
-
getHealth,
|
|
23
21
|
listExamples,
|
|
24
22
|
getExample,
|
|
25
23
|
createExample,
|
|
@@ -6,7 +6,6 @@
|
|
|
6
6
|
|
|
7
7
|
import { defineRouter } from '@spfn/core/route';
|
|
8
8
|
import { getRoot } from './routes/root';
|
|
9
|
-
import { getHealth } from './routes/health';
|
|
10
9
|
import { listExamples, getExample, createExample, updateExample, deleteExample } from './routes/examples';
|
|
11
10
|
|
|
12
11
|
/**
|
|
@@ -14,7 +13,6 @@ import { listExamples, getExample, createExample, updateExample, deleteExample }
|
|
|
14
13
|
*/
|
|
15
14
|
export const appRouter = defineRouter({
|
|
16
15
|
getRoot,
|
|
17
|
-
getHealth,
|
|
18
16
|
listExamples,
|
|
19
17
|
getExample,
|
|
20
18
|
createExample,
|
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.3",
|
|
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": {
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
"node": ">=20.0.0"
|
|
58
58
|
},
|
|
59
59
|
"peerDependencies": {
|
|
60
|
-
"@spfn/core": ">=0.3.0-beta.
|
|
60
|
+
"@spfn/core": ">=0.3.0-beta.3 <0.4.0",
|
|
61
61
|
"typescript": "^5.3.0"
|
|
62
62
|
},
|
|
63
63
|
"peerDependenciesMeta": {
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Health Check Route
|
|
3
|
-
*
|
|
4
|
-
* Minimal endpoint for monitoring systems, load balancers, and orchestrators.
|
|
5
|
-
* Used by Kubernetes probes, uptime monitors, etc.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { route } from '@spfn/core/route';
|
|
9
|
-
|
|
10
|
-
export const getHealth = route.get('/health')
|
|
11
|
-
.handler(async () =>
|
|
12
|
-
{
|
|
13
|
-
return {
|
|
14
|
-
status: 'ok',
|
|
15
|
-
timestamp: Date.now(),
|
|
16
|
-
uptime: process.uptime(),
|
|
17
|
-
};
|
|
18
|
-
});
|