business-stack 0.1.1 → 0.1.5
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 -3
- package/bin/business-stack.cjs +206 -34
- package/gateway/README.md +14 -4
- package/gateway/package.json +12 -9
- package/gateway/src/auth.ts +2 -2
- package/gateway/src/index.ts +7 -4
- package/gateway/src/integrations/store.ts +15 -14
- package/gateway/src/stack-secrets.ts +5 -5
- package/gateway/tsconfig.json +1 -1
- package/package.json +1 -2
package/README.md
CHANGED
|
@@ -8,8 +8,9 @@ Install these on your machine and ensure they are on your `PATH`:
|
|
|
8
8
|
|
|
9
9
|
| Tool | Purpose |
|
|
10
10
|
|------|---------|
|
|
11
|
-
| [Node.js](https://nodejs.org/) 20+ | Runs the `business-stack` CLI
|
|
12
|
-
|
|
|
11
|
+
| [Node.js](https://nodejs.org/) 20+ | Runs the `business-stack` CLI |
|
|
12
|
+
| **npm** (comes with Node) | `business-stack start` / `dev` use **`npm install`** and **`npm run`** for workspaces and Turborepo |
|
|
13
|
+
| *(optional)* [Bun](https://bun.sh) | Only if you develop the **full monorepo** from git (`bun run dev` at repo root); **not** required for the published `business-stack` package |
|
|
13
14
|
| [uv](https://docs.astral.sh/uv/) | Installs and runs the Python backend |
|
|
14
15
|
| Python **3.12** | Required by the backend (managed via `uv`) |
|
|
15
16
|
|
|
@@ -19,6 +20,18 @@ Check your environment:
|
|
|
19
20
|
npx business-stack doctor
|
|
20
21
|
```
|
|
21
22
|
|
|
23
|
+
### Using `npx` (no install, or one-off runs)
|
|
24
|
+
|
|
25
|
+
`npx` runs the same `business-stack` binary as a global or local install. Use a version tag so npm does not cache an old release:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npx --yes business-stack@latest doctor
|
|
29
|
+
npx --yes business-stack@latest setup --yes
|
|
30
|
+
npx --yes business-stack@latest start
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
On **Windows Command Prompt**, use the same commands; if `npx` is slow the first time, it is downloading the package.
|
|
34
|
+
|
|
22
35
|
## Installation (recommended: project-level)
|
|
23
36
|
|
|
24
37
|
**Prefer a local (project) install** so the stack, generated `.env` files, and SQLite database live under your project’s `node_modules/business-stack/`, match npm’s default `npm i business-stack`, and avoid permission or PATH issues from global installs.
|
|
@@ -49,7 +62,7 @@ npx business-stack start
|
|
|
49
62
|
```
|
|
50
63
|
|
|
51
64
|
- **`setup`** generates secrets, stores them in SQLite (`node_modules/business-stack/gateway/auth.sqlite`, table `stack_secrets`), and writes `gateway/.env`, `frontend/web/.env.local`, and `backend/.env` under that package path. You do not need to create `.env` files by hand.
|
|
52
|
-
- **`start`** runs
|
|
65
|
+
- **`start`** runs **`npm install`** (workspaces), **`uv sync`**, **`npm run build`**, Alembic migrations, then **`npm run start`** (Turborepo: Next, gateway, backend). First run can take several minutes.
|
|
53
66
|
|
|
54
67
|
Optional flags for `start`: `--skip-install`, `--skip-build`, `--skip-migrate`.
|
|
55
68
|
|
package/bin/business-stack.cjs
CHANGED
|
@@ -111,18 +111,108 @@ function writeEnvFile(filePath, content) {
|
|
|
111
111
|
fs.writeFileSync(filePath, `${content.trim()}\n`, "utf8");
|
|
112
112
|
}
|
|
113
113
|
|
|
114
|
+
function getCliPackageVersion() {
|
|
115
|
+
try {
|
|
116
|
+
const p = path.join(__dirname, "..", "package.json");
|
|
117
|
+
return JSON.parse(fs.readFileSync(p, "utf8")).version;
|
|
118
|
+
} catch {
|
|
119
|
+
return "?";
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function logPhase(msg) {
|
|
124
|
+
const line = `business-stack: ${msg}`;
|
|
125
|
+
console.log(line);
|
|
126
|
+
console.error(line);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** Microsoft Store / WindowsApps shims often exit 0 immediately with no real Bun — skip them. */
|
|
130
|
+
function isWindowsAppsStub(exePath) {
|
|
131
|
+
const n = exePath.toLowerCase().replace(/\//g, "\\");
|
|
132
|
+
return (
|
|
133
|
+
n.includes("\\windowsapps\\") ||
|
|
134
|
+
n.includes("microsoft\\windowsapps") ||
|
|
135
|
+
n.endsWith("appinstaller.exe")
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function whereAll(cmd) {
|
|
140
|
+
const r = spawnSync("where.exe", [cmd], {
|
|
141
|
+
encoding: "utf8",
|
|
142
|
+
windowsHide: true,
|
|
143
|
+
});
|
|
144
|
+
if (r.status !== 0 || !r.stdout) {
|
|
145
|
+
return [];
|
|
146
|
+
}
|
|
147
|
+
return r.stdout
|
|
148
|
+
.split(/\r?\n/)
|
|
149
|
+
.map((s) => s.trim())
|
|
150
|
+
.filter(Boolean);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function executableResponds(exe, args) {
|
|
154
|
+
const r = spawnSync(exe, args, {
|
|
155
|
+
encoding: "utf8",
|
|
156
|
+
windowsHide: true,
|
|
157
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
158
|
+
timeout: 20_000,
|
|
159
|
+
});
|
|
160
|
+
if (r.error) {
|
|
161
|
+
return false;
|
|
162
|
+
}
|
|
163
|
+
if (r.status !== 0) {
|
|
164
|
+
return false;
|
|
165
|
+
}
|
|
166
|
+
const out = `${String(r.stdout || "")}${String(r.stderr || "")}`.trim();
|
|
167
|
+
return out.length >= 2;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Resolve `npm` / `bun` / `uv` on Windows: `where` can list a useless WindowsApps stub first.
|
|
172
|
+
* Pick a path that successfully runs `--version`.
|
|
173
|
+
*/
|
|
174
|
+
function resolveExecutable(cmd) {
|
|
175
|
+
if (process.platform !== "win32") {
|
|
176
|
+
return cmd;
|
|
177
|
+
}
|
|
178
|
+
const all = whereAll(cmd);
|
|
179
|
+
if (all.length === 0) {
|
|
180
|
+
return cmd;
|
|
181
|
+
}
|
|
182
|
+
const preferred = all.filter((p) => !isWindowsAppsStub(p));
|
|
183
|
+
const order = preferred.length > 0 ? preferred : all;
|
|
184
|
+
for (let i = order.length - 1; i >= 0; i--) {
|
|
185
|
+
const exe = order[i];
|
|
186
|
+
if (isWindowsAppsStub(exe)) {
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
if (executableResponds(exe, ["--version"])) {
|
|
190
|
+
return exe;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
for (let i = order.length - 1; i >= 0; i--) {
|
|
194
|
+
const exe = order[i];
|
|
195
|
+
if (executableResponds(exe, ["--version"])) {
|
|
196
|
+
return exe;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
return order[order.length - 1] || cmd;
|
|
200
|
+
}
|
|
201
|
+
|
|
114
202
|
function cmdExists(cmd) {
|
|
115
203
|
const isWin = process.platform === "win32";
|
|
116
204
|
const which = isWin ? "where" : "which";
|
|
117
|
-
const r = spawnSync(which, [cmd], { encoding: "utf8" });
|
|
205
|
+
const r = spawnSync(which, [cmd], { encoding: "utf8", windowsHide: isWin });
|
|
118
206
|
return r.status === 0;
|
|
119
207
|
}
|
|
120
208
|
|
|
121
209
|
function runDoctor() {
|
|
122
210
|
const ok = [];
|
|
123
211
|
const bad = [];
|
|
124
|
-
if (cmdExists("
|
|
125
|
-
else bad.push("
|
|
212
|
+
if (cmdExists("node")) ok.push("node");
|
|
213
|
+
else bad.push("node (https://nodejs.org/)");
|
|
214
|
+
if (cmdExists("npm")) ok.push("npm");
|
|
215
|
+
else bad.push("npm (ships with Node.js)");
|
|
126
216
|
if (cmdExists("uv")) ok.push("uv");
|
|
127
217
|
else bad.push("uv (https://docs.astral.sh/uv/)");
|
|
128
218
|
if (cmdExists("python") || cmdExists("python3")) ok.push("python");
|
|
@@ -136,6 +226,9 @@ function runDoctor() {
|
|
|
136
226
|
return;
|
|
137
227
|
}
|
|
138
228
|
console.log("\nAll common toolchain binaries are on PATH.");
|
|
229
|
+
console.log(
|
|
230
|
+
"\nThe gateway runs on **Node** (tsx + better-sqlite3). Bun is optional for monorepo contributors only.",
|
|
231
|
+
);
|
|
139
232
|
}
|
|
140
233
|
|
|
141
234
|
async function promptDefaults(rl, defaults) {
|
|
@@ -264,29 +357,86 @@ BACKEND_GATEWAY_SECRET=${backendGatewaySecret}
|
|
|
264
357
|
}
|
|
265
358
|
|
|
266
359
|
function envFileLooksConfigured(stackRoot) {
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
360
|
+
try {
|
|
361
|
+
const g = path.join(stackRoot, "gateway", ".env");
|
|
362
|
+
if (fs.existsSync(g)) {
|
|
363
|
+
const t = fs.readFileSync(g, "utf8");
|
|
364
|
+
if (
|
|
365
|
+
/^BETTER_AUTH_SECRET=./m.test(t) ||
|
|
366
|
+
/^INTEGRATIONS_ENCRYPTION_KEY=./m.test(t)
|
|
367
|
+
) {
|
|
368
|
+
return true;
|
|
369
|
+
}
|
|
272
370
|
}
|
|
371
|
+
return hasExistingSecrets(stackRoot);
|
|
372
|
+
} catch (e) {
|
|
373
|
+
console.error(
|
|
374
|
+
"business-stack: could not read setup state:",
|
|
375
|
+
e instanceof Error ? e.message : e,
|
|
376
|
+
);
|
|
377
|
+
return false;
|
|
273
378
|
}
|
|
274
|
-
return hasExistingSecrets(stackRoot);
|
|
275
379
|
}
|
|
276
380
|
|
|
277
|
-
function runCmd(cmd, args, cwd, extraEnv) {
|
|
278
|
-
|
|
381
|
+
function runCmd(label, cmd, args, cwd, extraEnv) {
|
|
382
|
+
logPhase(`${label}…`);
|
|
383
|
+
const exe = resolveExecutable(cmd);
|
|
384
|
+
if (process.platform === "win32") {
|
|
385
|
+
logPhase(`using ${cmd} → ${exe}`);
|
|
386
|
+
}
|
|
387
|
+
const r = spawnSync(exe, args, {
|
|
279
388
|
cwd,
|
|
280
389
|
stdio: "inherit",
|
|
281
390
|
env: { ...process.env, ...extraEnv },
|
|
282
391
|
shell: false,
|
|
392
|
+
windowsHide: process.platform === "win32",
|
|
283
393
|
});
|
|
394
|
+
if (r.error) {
|
|
395
|
+
console.error(`business-stack: ${label} failed:`, r.error.message);
|
|
396
|
+
console.error(` Command: ${exe} ${args.join(" ")}`);
|
|
397
|
+
console.error(` cwd: ${cwd}`);
|
|
398
|
+
process.exit(1);
|
|
399
|
+
}
|
|
400
|
+
if (r.signal) {
|
|
401
|
+
console.error(`business-stack: ${label} killed (${r.signal})`);
|
|
402
|
+
process.exit(1);
|
|
403
|
+
}
|
|
284
404
|
if (r.status !== 0) {
|
|
285
405
|
process.exit(r.status ?? 1);
|
|
286
406
|
}
|
|
287
407
|
}
|
|
288
408
|
|
|
289
|
-
|
|
409
|
+
/**
|
|
410
|
+
* Wait for turbo + servers. Async so Commander keeps the Node event loop alive on Windows
|
|
411
|
+
* until the child exits (avoids exiting immediately with no output).
|
|
412
|
+
*/
|
|
413
|
+
function waitForChild(child, label) {
|
|
414
|
+
return new Promise((resolve) => {
|
|
415
|
+
let settled = false;
|
|
416
|
+
const finish = (code) => {
|
|
417
|
+
if (settled) return;
|
|
418
|
+
settled = true;
|
|
419
|
+
resolve(code ?? 0);
|
|
420
|
+
};
|
|
421
|
+
|
|
422
|
+
child.on("error", (err) => {
|
|
423
|
+
console.error(`business-stack: ${label} — failed to spawn process:`, err.message);
|
|
424
|
+
console.error(" Run: business-stack doctor");
|
|
425
|
+
finish(1);
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
child.on("exit", (code, sig) => {
|
|
429
|
+
if (sig) {
|
|
430
|
+
console.error(`business-stack: ${label} — child exited with signal ${sig}`);
|
|
431
|
+
finish(1);
|
|
432
|
+
return;
|
|
433
|
+
}
|
|
434
|
+
finish(code ?? 0);
|
|
435
|
+
});
|
|
436
|
+
});
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
async function runStart(opts) {
|
|
290
440
|
const stackRoot = getStackRoot();
|
|
291
441
|
if (!envFileLooksConfigured(stackRoot)) {
|
|
292
442
|
console.error(
|
|
@@ -295,6 +445,10 @@ function runStart(opts) {
|
|
|
295
445
|
process.exit(1);
|
|
296
446
|
}
|
|
297
447
|
|
|
448
|
+
logPhase("starting (install → sync → build → migrate → servers)");
|
|
449
|
+
logPhase(`stack root ${stackRoot}`);
|
|
450
|
+
logPhase(`CLI package v${getCliPackageVersion()} — if there is no output below, run: npm i -g business-stack@latest`);
|
|
451
|
+
|
|
298
452
|
const fromDb = readStackSecretsEnv(stackRoot);
|
|
299
453
|
const childEnv = { ...process.env };
|
|
300
454
|
for (const [k, v] of Object.entries(fromDb)) {
|
|
@@ -304,43 +458,61 @@ function runStart(opts) {
|
|
|
304
458
|
}
|
|
305
459
|
|
|
306
460
|
if (!opts.skipInstall) {
|
|
307
|
-
runCmd(
|
|
461
|
+
runCmd(
|
|
462
|
+
"npm install (workspaces)",
|
|
463
|
+
"npm",
|
|
464
|
+
["install", "--no-fund", "--no-audit"],
|
|
465
|
+
stackRoot,
|
|
466
|
+
childEnv,
|
|
467
|
+
);
|
|
308
468
|
}
|
|
309
|
-
runCmd("uv", ["sync"], path.join(stackRoot, "backend"), childEnv);
|
|
469
|
+
runCmd("uv sync (backend)", "uv", ["sync"], path.join(stackRoot, "backend"), childEnv);
|
|
310
470
|
if (!opts.skipBuild) {
|
|
311
|
-
runCmd("
|
|
471
|
+
runCmd("npm run build", "npm", ["run", "build"], stackRoot, childEnv);
|
|
312
472
|
}
|
|
313
473
|
if (!opts.skipMigrate) {
|
|
314
|
-
runCmd(
|
|
474
|
+
runCmd(
|
|
475
|
+
"alembic upgrade head",
|
|
476
|
+
"uv",
|
|
477
|
+
["run", "alembic", "upgrade", "head"],
|
|
478
|
+
path.join(stackRoot, "backend"),
|
|
479
|
+
childEnv,
|
|
480
|
+
);
|
|
315
481
|
}
|
|
316
482
|
|
|
317
|
-
|
|
483
|
+
logPhase("launching production servers (turbo start via npm)");
|
|
484
|
+
const npmExe = resolveExecutable("npm");
|
|
485
|
+
if (process.platform === "win32") {
|
|
486
|
+
logPhase(`using npm → ${npmExe}`);
|
|
487
|
+
}
|
|
488
|
+
const child = spawn(npmExe, ["run", "start"], {
|
|
318
489
|
cwd: stackRoot,
|
|
319
490
|
stdio: "inherit",
|
|
320
491
|
env: childEnv,
|
|
321
492
|
shell: false,
|
|
493
|
+
windowsHide: process.platform === "win32",
|
|
322
494
|
});
|
|
323
495
|
|
|
324
|
-
|
|
496
|
+
const shutdown = () => {
|
|
325
497
|
try {
|
|
326
|
-
if (process.platform === "win32") {
|
|
498
|
+
if (process.platform === "win32" && child.pid) {
|
|
327
499
|
spawnSync("taskkill", ["/PID", String(child.pid), "/T", "/F"], {
|
|
328
500
|
stdio: "ignore",
|
|
501
|
+
windowsHide: true,
|
|
329
502
|
});
|
|
330
|
-
} else {
|
|
331
|
-
child.kill(
|
|
503
|
+
} else if (child.pid) {
|
|
504
|
+
child.kill("SIGINT");
|
|
332
505
|
}
|
|
333
506
|
} catch {
|
|
334
507
|
/* ignore */
|
|
335
508
|
}
|
|
336
|
-
}
|
|
509
|
+
};
|
|
337
510
|
|
|
338
|
-
process.
|
|
339
|
-
process.
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
});
|
|
511
|
+
process.once("SIGINT", shutdown);
|
|
512
|
+
process.once("SIGTERM", shutdown);
|
|
513
|
+
|
|
514
|
+
const code = await waitForChild(child, "turbo start");
|
|
515
|
+
process.exit(code);
|
|
344
516
|
}
|
|
345
517
|
|
|
346
518
|
function runDev() {
|
|
@@ -352,17 +524,17 @@ function runDev() {
|
|
|
352
524
|
childEnv[k] = String(v);
|
|
353
525
|
}
|
|
354
526
|
}
|
|
355
|
-
runCmd("
|
|
527
|
+
runCmd("npm run dev", "npm", ["run", "dev"], stackRoot, childEnv);
|
|
356
528
|
}
|
|
357
529
|
|
|
358
530
|
program
|
|
359
531
|
.name("business-stack")
|
|
360
532
|
.description("Run the business-stack monorepo (Next + Hono gateway + FastAPI)")
|
|
361
|
-
.version("0.1.
|
|
533
|
+
.version("0.1.5");
|
|
362
534
|
|
|
363
535
|
program
|
|
364
536
|
.command("doctor")
|
|
365
|
-
.description("Check for
|
|
537
|
+
.description("Check for node, npm, uv, and python on PATH")
|
|
366
538
|
.action(runDoctor);
|
|
367
539
|
|
|
368
540
|
program
|
|
@@ -382,12 +554,12 @@ program
|
|
|
382
554
|
program
|
|
383
555
|
.command("start")
|
|
384
556
|
.description("Install deps, build, migrate, run production stack (turbo start)")
|
|
385
|
-
.option("--skip-install", "Skip
|
|
557
|
+
.option("--skip-install", "Skip npm install")
|
|
386
558
|
.option("--skip-build", "Skip turbo build")
|
|
387
559
|
.option("--skip-migrate", "Skip alembic upgrade")
|
|
388
|
-
.action((o) => {
|
|
560
|
+
.action(async (o) => {
|
|
389
561
|
try {
|
|
390
|
-
runStart(o);
|
|
562
|
+
await runStart(o);
|
|
391
563
|
} catch (e) {
|
|
392
564
|
console.error(e instanceof Error ? e.message : e);
|
|
393
565
|
process.exit(1);
|
package/gateway/README.md
CHANGED
|
@@ -1,13 +1,23 @@
|
|
|
1
|
-
|
|
1
|
+
Install dependencies from the **repo root** (workspaces) or in this folder:
|
|
2
|
+
|
|
2
3
|
```sh
|
|
3
|
-
|
|
4
|
+
npm install
|
|
4
5
|
```
|
|
5
6
|
|
|
6
|
-
|
|
7
|
+
Run (development with reload):
|
|
8
|
+
|
|
7
9
|
```sh
|
|
8
|
-
|
|
10
|
+
npm run dev
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Production-style (no watch):
|
|
14
|
+
|
|
15
|
+
```sh
|
|
16
|
+
npm run start
|
|
9
17
|
```
|
|
10
18
|
|
|
11
19
|
The gateway listens on **port 3001** by default (`PORT`). Open `http://127.0.0.1:3001` (or set `PORT`).
|
|
12
20
|
|
|
21
|
+
Stack: **Hono**, **Node** via **tsx**, **better-sqlite3** for auth + integration settings, **Better Auth**.
|
|
22
|
+
|
|
13
23
|
Better Auth needs a public **base URL** (where `/api/auth` is reached in the browser). With the Next.js app proxying auth, set `BETTER_AUTH_URL` to that origin (e.g. `http://localhost:3000`). See `.env.example`. If unset, the gateway defaults to `http://localhost:3000`.
|
package/gateway/package.json
CHANGED
|
@@ -1,24 +1,27 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@business-stack/gateway",
|
|
3
3
|
"private": true,
|
|
4
|
-
"packageManager": "bun@1.3.1",
|
|
5
4
|
"scripts": {
|
|
6
|
-
"dev": "
|
|
7
|
-
"start": "
|
|
8
|
-
"migrate": "
|
|
9
|
-
"build": "
|
|
5
|
+
"dev": "tsx watch src/index.ts",
|
|
6
|
+
"start": "tsx src/index.ts",
|
|
7
|
+
"migrate": "npx @better-auth/cli@latest migrate --yes",
|
|
8
|
+
"build": "node -e \"require('fs').mkdirSync('dist',{recursive:true}); require('fs').writeFileSync('dist/.buildstamp','')\"",
|
|
10
9
|
"lint": "biome check .",
|
|
11
10
|
"lint:fix": "biome check --write .",
|
|
12
11
|
"typecheck": "tsc --noEmit",
|
|
13
|
-
"test": "
|
|
14
|
-
"clean": "
|
|
12
|
+
"test": "node -e \"process.exit(0)\"",
|
|
13
|
+
"clean": "npx rimraf@6 dist"
|
|
15
14
|
},
|
|
16
15
|
"dependencies": {
|
|
16
|
+
"@hono/node-server": "^1.19.9",
|
|
17
17
|
"better-auth": "1.5.6",
|
|
18
|
-
"
|
|
18
|
+
"better-sqlite3": "^11.7.0",
|
|
19
|
+
"hono": "^4.12.10",
|
|
20
|
+
"tsx": "^4.19.3"
|
|
19
21
|
},
|
|
20
22
|
"devDependencies": {
|
|
21
|
-
"@types/
|
|
23
|
+
"@types/better-sqlite3": "^7.6.12",
|
|
24
|
+
"@types/node": "^20",
|
|
22
25
|
"typescript": "^5.8.3"
|
|
23
26
|
}
|
|
24
27
|
}
|
package/gateway/src/auth.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { Database } from "bun:sqlite";
|
|
2
1
|
import { betterAuth } from "better-auth";
|
|
2
|
+
import Database from "better-sqlite3";
|
|
3
3
|
import { loadStackSecretsIntoEnv } from "./stack-secrets";
|
|
4
4
|
|
|
5
5
|
loadStackSecretsIntoEnv();
|
|
6
6
|
|
|
7
7
|
const dbPath = process.env.BETTER_AUTH_DATABASE_PATH ?? "auth.sqlite";
|
|
8
|
-
const sqlite = new Database(dbPath
|
|
8
|
+
const sqlite = new Database(dbPath);
|
|
9
9
|
|
|
10
10
|
function parseTrustedOrigins(): string[] {
|
|
11
11
|
const raw = process.env.BETTER_AUTH_TRUSTED_ORIGINS;
|
package/gateway/src/index.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { serve } from "@hono/node-server";
|
|
1
2
|
import { Hono } from "hono";
|
|
2
3
|
import { cors } from "hono/cors";
|
|
3
4
|
import { proxy } from "hono/proxy";
|
|
@@ -130,12 +131,14 @@ app.get("/session", (c) => {
|
|
|
130
131
|
});
|
|
131
132
|
|
|
132
133
|
const port = Number(process.env.PORT ?? 3001);
|
|
134
|
+
const hostname = process.env.HOST?.trim() || "127.0.0.1";
|
|
133
135
|
|
|
134
|
-
|
|
135
|
-
port,
|
|
136
|
+
serve({
|
|
136
137
|
fetch: app.fetch,
|
|
137
|
-
|
|
138
|
+
port,
|
|
139
|
+
hostname,
|
|
140
|
+
});
|
|
138
141
|
|
|
139
142
|
console.log(
|
|
140
|
-
`Gateway listening on http
|
|
143
|
+
`Gateway listening on http://${hostname}:${port} (backend ${backendOrigin}, prefix ${apiGatewayPrefix})`,
|
|
141
144
|
);
|
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
import
|
|
1
|
+
import Database from "better-sqlite3";
|
|
2
2
|
import { decryptIntegrationValue, encryptIntegrationValue } from "./crypto";
|
|
3
3
|
import { INTEGRATION_DB_KEYS, type IntegrationDbKey } from "./keys";
|
|
4
4
|
|
|
5
5
|
const dbPath = process.env.BETTER_AUTH_DATABASE_PATH ?? "auth.sqlite";
|
|
6
6
|
|
|
7
|
-
let _db: Database | null = null;
|
|
7
|
+
let _db: Database.Database | null = null;
|
|
8
8
|
|
|
9
|
-
function getDb(): Database {
|
|
9
|
+
function getDb(): Database.Database {
|
|
10
10
|
if (!_db) {
|
|
11
|
-
_db = new Database(dbPath
|
|
12
|
-
_db.
|
|
11
|
+
_db = new Database(dbPath);
|
|
12
|
+
_db.exec(`
|
|
13
13
|
CREATE TABLE IF NOT EXISTS integration_settings (
|
|
14
14
|
key TEXT PRIMARY KEY NOT NULL,
|
|
15
15
|
ciphertext TEXT NOT NULL,
|
|
@@ -22,8 +22,8 @@ function getDb(): Database {
|
|
|
22
22
|
|
|
23
23
|
export function getEncryptedRow(key: IntegrationDbKey): string | null {
|
|
24
24
|
const row = getDb()
|
|
25
|
-
.
|
|
26
|
-
.get(key) as { ciphertext: string } |
|
|
25
|
+
.prepare("SELECT ciphertext FROM integration_settings WHERE key = ?")
|
|
26
|
+
.get(key) as { ciphertext: string } | undefined;
|
|
27
27
|
return row?.ciphertext ?? null;
|
|
28
28
|
}
|
|
29
29
|
|
|
@@ -33,16 +33,17 @@ export function setEncryptedRow(
|
|
|
33
33
|
): void {
|
|
34
34
|
const ciphertext = encryptIntegrationValue(plaintext);
|
|
35
35
|
const updatedAt = new Date().toISOString();
|
|
36
|
-
getDb()
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
36
|
+
getDb()
|
|
37
|
+
.prepare(
|
|
38
|
+
`INSERT INTO integration_settings (key, ciphertext, updated_at)
|
|
39
|
+
VALUES (?, ?, ?)
|
|
40
|
+
ON CONFLICT(key) DO UPDATE SET ciphertext = excluded.ciphertext, updated_at = excluded.updated_at`,
|
|
41
|
+
)
|
|
42
|
+
.run(key, ciphertext, updatedAt);
|
|
42
43
|
}
|
|
43
44
|
|
|
44
45
|
export function deleteRow(key: IntegrationDbKey): void {
|
|
45
|
-
getDb().
|
|
46
|
+
getDb().prepare("DELETE FROM integration_settings WHERE key = ?").run(key);
|
|
46
47
|
}
|
|
47
48
|
|
|
48
49
|
export function getPlaintext(key: IntegrationDbKey): string | null {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import Database from "better-sqlite3";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* If an env var is unset or whitespace-only, fill it from `stack_secrets` in the auth DB.
|
|
@@ -6,20 +6,20 @@ import { Database } from "bun:sqlite";
|
|
|
6
6
|
*/
|
|
7
7
|
export function loadStackSecretsIntoEnv(): void {
|
|
8
8
|
const dbPath = process.env.BETTER_AUTH_DATABASE_PATH ?? "auth.sqlite";
|
|
9
|
-
let db: Database;
|
|
9
|
+
let db: Database.Database;
|
|
10
10
|
try {
|
|
11
|
-
db = new Database(dbPath
|
|
11
|
+
db = new Database(dbPath);
|
|
12
12
|
} catch {
|
|
13
13
|
return;
|
|
14
14
|
}
|
|
15
15
|
try {
|
|
16
|
-
db.
|
|
16
|
+
db.exec(`
|
|
17
17
|
CREATE TABLE IF NOT EXISTS stack_secrets (
|
|
18
18
|
key TEXT PRIMARY KEY NOT NULL,
|
|
19
19
|
value TEXT NOT NULL
|
|
20
20
|
)
|
|
21
21
|
`);
|
|
22
|
-
const rows = db.
|
|
22
|
+
const rows = db.prepare("SELECT key, value FROM stack_secrets").all() as {
|
|
23
23
|
key: string;
|
|
24
24
|
value: string;
|
|
25
25
|
}[];
|
package/gateway/tsconfig.json
CHANGED
package/package.json
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "business-stack",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"description": "Next.js + Hono gateway + FastAPI monorepo",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
|
-
"packageManager": "bun@1.3.1",
|
|
7
6
|
"workspaces": ["frontend/web", "gateway", "backend"],
|
|
8
7
|
"scripts": {
|
|
9
8
|
"prepublishOnly": "node ../scripts/sync-npm-package.cjs",
|