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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bosia",
3
- "version": "0.8.2",
3
+ "version": "0.8.3",
4
4
  "type": "module",
5
5
  "description": "A fast, batteries-included fullstack framework — SSR · Svelte 5 Runes · Bun · ElysiaJS. File-based routing No Node.js, no Vite, no adapters.",
6
6
  "keywords": [
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) return; // re-entry from process-group signals or impatient ^C — drain is already running
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
- // Safety net: if any stray handle still holds the loop, force clean exit.
586
- // .unref() so the timer itself doesn't keep the loop alive when drain succeeds.
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);
@@ -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) return;
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) {