bosia 0.8.2 → 0.8.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/package.json +1 -1
- package/src/cli/dev.ts +4 -0
- package/src/cli/start.ts +4 -0
- package/src/cli/sync.ts +11 -0
- package/src/core/brandGuard.ts +36 -0
- package/src/core/dev.ts +10 -4
- package/src/core/server.ts +10 -1
package/package.json
CHANGED
package/src/cli/dev.ts
CHANGED
|
@@ -8,5 +8,9 @@ export async function runDev() {
|
|
|
8
8
|
stderr: "inherit",
|
|
9
9
|
cwd: process.cwd(),
|
|
10
10
|
});
|
|
11
|
+
// Survive ^C so we keep waiting for the child instead of orphaning it
|
|
12
|
+
// mid-shutdown. The terminal delivers SIGINT to the whole process group,
|
|
13
|
+
// so the child already gets the signal — no forwarding needed.
|
|
14
|
+
for (const sig of ["SIGINT", "SIGTERM"] as const) process.on(sig, () => {});
|
|
11
15
|
await proc.exited;
|
|
12
16
|
}
|
package/src/cli/start.ts
CHANGED
|
@@ -22,5 +22,9 @@ export async function runStart() {
|
|
|
22
22
|
},
|
|
23
23
|
});
|
|
24
24
|
|
|
25
|
+
// Survive ^C so we keep waiting for the child instead of orphaning it
|
|
26
|
+
// mid-drain. The terminal delivers SIGINT to the whole process group,
|
|
27
|
+
// so the child already gets the signal — no forwarding needed.
|
|
28
|
+
for (const sig of ["SIGINT", "SIGTERM"] as const) process.on(sig, () => {});
|
|
25
29
|
await proc.exited;
|
|
26
30
|
}
|
package/src/cli/sync.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { generateRoutesFile } from "../core/routeFile.ts";
|
|
|
3
3
|
import { generateRouteTypes, ensureRootDirs } from "../core/routeTypes.ts";
|
|
4
4
|
import { loadEnv, classifyEnvVars } from "../core/env.ts";
|
|
5
5
|
import { generateEnvModules } from "../core/envCodegen.ts";
|
|
6
|
+
import { findBrandPlaceholders, BRAND_SENTINEL } from "../core/brandGuard.ts";
|
|
6
7
|
|
|
7
8
|
export async function runSync() {
|
|
8
9
|
const envMode = process.env.NODE_ENV === "production" ? "production" : "development";
|
|
@@ -13,4 +14,14 @@ export async function runSync() {
|
|
|
13
14
|
ensureRootDirs();
|
|
14
15
|
generateEnvModules(classifiedEnv);
|
|
15
16
|
console.log("✅ Bosia codegen ready (.bosia/routes.ts, types, env modules)");
|
|
17
|
+
|
|
18
|
+
const brandHits = findBrandPlaceholders();
|
|
19
|
+
if (brandHits.length > 0) {
|
|
20
|
+
console.error(
|
|
21
|
+
`\n❌ ${BRAND_SENTINEL} placeholder left un-replaced in ${brandHits.length} spot(s):`,
|
|
22
|
+
);
|
|
23
|
+
for (const { file, line } of brandHits) console.error(` • ${file}:${line}`);
|
|
24
|
+
console.error(`\nReplace ${BRAND_SENTINEL} with the app's name (nav/header/footer).\n`);
|
|
25
|
+
process.exit(1);
|
|
26
|
+
}
|
|
16
27
|
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { readdirSync, readFileSync } from "fs";
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
|
|
4
|
+
// Registry blocks (navbars, storefront header/footer) ship the brand name as the
|
|
5
|
+
// literal `__BRAND__` sentinel so it's unmistakably a placeholder. This guard fails
|
|
6
|
+
// `bosia sync` (and therefore `bun run check`) if any survive un-replaced in an app —
|
|
7
|
+
// otherwise a fresh scaffold ships someone else's brand in its nav/header/footer.
|
|
8
|
+
export const BRAND_SENTINEL = "__BRAND__";
|
|
9
|
+
|
|
10
|
+
const SRC_DIR = "./src";
|
|
11
|
+
const TEXT_EXT = /\.(svelte|ts|js|html|md|json)$/;
|
|
12
|
+
|
|
13
|
+
export function findBrandPlaceholders(srcDir = SRC_DIR): { file: string; line: number }[] {
|
|
14
|
+
const hits: { file: string; line: number }[] = [];
|
|
15
|
+
let entries: string[];
|
|
16
|
+
try {
|
|
17
|
+
entries = readdirSync(srcDir, { recursive: true }) as string[];
|
|
18
|
+
} catch {
|
|
19
|
+
return hits; // no src/ (e.g. non-app cwd) → nothing to guard
|
|
20
|
+
}
|
|
21
|
+
for (const rel of entries) {
|
|
22
|
+
if (!TEXT_EXT.test(rel)) continue;
|
|
23
|
+
const path = join(srcDir, rel);
|
|
24
|
+
let contents: string;
|
|
25
|
+
try {
|
|
26
|
+
contents = readFileSync(path, "utf-8");
|
|
27
|
+
} catch {
|
|
28
|
+
continue; // directory or unreadable
|
|
29
|
+
}
|
|
30
|
+
if (!contents.includes(BRAND_SENTINEL)) continue;
|
|
31
|
+
contents.split("\n").forEach((l, i) => {
|
|
32
|
+
if (l.includes(BRAND_SENTINEL)) hits.push({ file: path, line: i + 1 });
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
return hits;
|
|
36
|
+
}
|
package/src/core/dev.ts
CHANGED
|
@@ -564,9 +564,16 @@ console.log(
|
|
|
564
564
|
// a second ^C.
|
|
565
565
|
|
|
566
566
|
let shuttingDown = false;
|
|
567
|
+
let firstSignalAt = 0;
|
|
567
568
|
async function shutdown() {
|
|
568
|
-
if (shuttingDown)
|
|
569
|
+
if (shuttingDown) {
|
|
570
|
+
// One ^C arrives multiple times (process group + `bun run` forwarding
|
|
571
|
+
// to its child) — only a genuinely later signal is a second ^C.
|
|
572
|
+
if (Date.now() - firstSignalAt > 200) process.exit(130); // second ^C = force quit
|
|
573
|
+
return;
|
|
574
|
+
}
|
|
569
575
|
shuttingDown = true;
|
|
576
|
+
firstSignalAt = Date.now();
|
|
570
577
|
intentionalKill = true;
|
|
571
578
|
|
|
572
579
|
if (buildTimer) clearTimeout(buildTimer);
|
|
@@ -582,9 +589,8 @@ async function shutdown() {
|
|
|
582
589
|
await Promise.race([appProcess.exited, Bun.sleep(2_500)]);
|
|
583
590
|
}
|
|
584
591
|
|
|
585
|
-
//
|
|
586
|
-
|
|
587
|
-
setTimeout(() => process.exit(0), 1_500).unref();
|
|
592
|
+
// Everything is stopped — exit now rather than waiting for the loop to drain.
|
|
593
|
+
process.exit(0);
|
|
588
594
|
}
|
|
589
595
|
|
|
590
596
|
process.on("SIGINT", shutdown);
|
package/src/core/server.ts
CHANGED
|
@@ -978,6 +978,7 @@ if (Number.isFinite(MAX_INFLIGHT)) {
|
|
|
978
978
|
// ─── Graceful Shutdown State ──────────────────────────────
|
|
979
979
|
|
|
980
980
|
let shuttingDown = false;
|
|
981
|
+
let firstSignalAt = 0;
|
|
981
982
|
let inFlight = 0;
|
|
982
983
|
let drainResolve: (() => void) | null = null;
|
|
983
984
|
|
|
@@ -1097,8 +1098,16 @@ app.listen(PORT, () => {
|
|
|
1097
1098
|
});
|
|
1098
1099
|
|
|
1099
1100
|
async function shutdown() {
|
|
1100
|
-
if (shuttingDown)
|
|
1101
|
+
if (shuttingDown) {
|
|
1102
|
+
// One ^C arrives multiple times (process group + `bun run` forwarding
|
|
1103
|
+
// to its child) — only a genuinely later signal is a second ^C.
|
|
1104
|
+
if (Date.now() - firstSignalAt > 200) process.exit(130); // second ^C = force quit
|
|
1105
|
+
return;
|
|
1106
|
+
}
|
|
1101
1107
|
shuttingDown = true;
|
|
1108
|
+
firstSignalAt = Date.now();
|
|
1109
|
+
// Dev: nothing worth draining — exit instantly so ^C feels immediate.
|
|
1110
|
+
if (isDev) process.exit(0);
|
|
1102
1111
|
console.log("⏳ Shutting down — draining in-flight requests...");
|
|
1103
1112
|
|
|
1104
1113
|
if (inFlight > 0) {
|