bosia 0.6.21 → 0.6.23

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.
Files changed (58) hide show
  1. package/package.json +2 -2
  2. package/src/cli/add.ts +3 -4
  3. package/src/cli/block.ts +16 -10
  4. package/src/cli/create.ts +6 -11
  5. package/src/cli/feat.ts +19 -22
  6. package/src/cli/index.ts +1 -2
  7. package/src/cli/manifest.ts +1 -1
  8. package/src/cli/registry.ts +3 -1
  9. package/src/core/build.ts +1 -3
  10. package/src/core/client/App.svelte +3 -8
  11. package/src/core/client/router.svelte.ts +3 -8
  12. package/src/core/config.ts +1 -4
  13. package/src/core/cookies.ts +1 -2
  14. package/src/core/dev-500.ts +1 -1
  15. package/src/core/html.ts +1 -2
  16. package/src/core/plugin.ts +1 -3
  17. package/src/core/plugins/inspector/bun-plugin.ts +1 -4
  18. package/src/core/plugins/inspector/index.ts +45 -59
  19. package/src/core/renderer.ts +3 -10
  20. package/src/core/routeTypes.ts +3 -9
  21. package/src/core/scanner.ts +1 -3
  22. package/src/core/server.ts +30 -35
  23. package/src/core/staticManifest.ts +1 -3
  24. package/src/core/svelteAudit.ts +2 -5
  25. package/src/core/svelteCompiler.ts +2 -8
  26. package/templates/default/.prettierignore +1 -0
  27. package/templates/default/src/app.css +2 -0
  28. package/templates/demo/.prettierignore +1 -0
  29. package/templates/demo/src/app.css +2 -0
  30. package/templates/shop/.env.example +12 -0
  31. package/templates/shop/.prettierignore +7 -0
  32. package/templates/shop/.prettierrc.json +9 -0
  33. package/templates/shop/README.md +62 -0
  34. package/templates/shop/_gitignore +12 -0
  35. package/templates/shop/bosia.config.ts +10 -0
  36. package/templates/shop/instructions.txt +8 -0
  37. package/templates/shop/package.json +26 -0
  38. package/templates/shop/public/favicon.svg +14 -0
  39. package/templates/shop/public/logo-dark.svg +14 -0
  40. package/templates/shop/public/logo-light.svg +14 -0
  41. package/templates/shop/src/app.css +134 -0
  42. package/templates/shop/src/app.d.ts +14 -0
  43. package/templates/shop/src/app.html +11 -0
  44. package/templates/shop/src/hooks.server.ts +21 -0
  45. package/templates/shop/src/lib/utils.ts +1 -0
  46. package/templates/shop/src/routes/(private)/+layout.server.ts +10 -0
  47. package/templates/shop/src/routes/(private)/+layout.svelte +44 -0
  48. package/templates/shop/src/routes/(private)/dashboard/+page.svelte +11 -0
  49. package/templates/shop/src/routes/(public)/+layout.svelte +13 -0
  50. package/templates/shop/src/routes/(public)/+page.svelte +38 -0
  51. package/templates/shop/src/routes/+error.svelte +19 -0
  52. package/templates/shop/src/routes/+layout.server.ts +9 -0
  53. package/templates/shop/src/routes/+layout.svelte +6 -0
  54. package/templates/shop/template.json +10 -0
  55. package/templates/shop/tsconfig.json +22 -0
  56. package/templates/todo/.prettierignore +1 -0
  57. package/templates/todo/src/app.css +2 -0
  58. package/templates/todo/template.json +4 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bosia",
3
- "version": "0.6.21",
3
+ "version": "0.6.23",
4
4
  "type": "module",
5
5
  "description": "A fast, batteries-included fullstack framework — SSR · Svelte 5 Runes · Bun · ElysiaJS. File-based routing inspired by SvelteKit. No Node.js, no Vite, no adapters.",
6
6
  "keywords": [
@@ -56,7 +56,7 @@
56
56
  "@tailwindcss/cli": "^4.2.1",
57
57
  "elysia": "^1.4.26",
58
58
  "magic-string": "^0.30.0",
59
- "svelte": "^5.53.6",
59
+ "svelte": "^5.56.3",
60
60
  "tailwind-merge": "^3.5.0",
61
61
  "tailwindcss": "^4.2.1"
62
62
  }
package/src/cli/add.ts CHANGED
@@ -159,8 +159,7 @@ export async function addComponent(name: string, root = false, options?: Install
159
159
  if (Object.keys(meta.npmDeps).length > 0) {
160
160
  if (options?.skipInstall) {
161
161
  const { addedDeps } = mergePkgJson(cwd, { deps: meta.npmDeps });
162
- if (addedDeps.length > 0)
163
- console.log(` 📥 Added to package.json: ${addedDeps.join(", ")}`);
162
+ if (addedDeps.length > 0) console.log(` 📥 Added to package.json: ${addedDeps.join(", ")}`);
164
163
  } else {
165
164
  await bunAdd(cwd, meta.npmDeps);
166
165
  }
@@ -239,8 +238,8 @@ export function cn(...inputs: ClassValue[]) {
239
238
  }
240
239
  `;
241
240
 
242
- export function ensureUtils() {
243
- const utilsPath = join(process.cwd(), "src", "lib", "utils.ts");
241
+ export function ensureUtils(cwd: string = process.cwd()) {
242
+ const utilsPath = join(cwd, "src", "lib", "utils.ts");
244
243
  if (!existsSync(utilsPath)) {
245
244
  mkdirSync(dirname(utilsPath), { recursive: true });
246
245
  writeFileSync(utilsPath, UTILS_CONTENT, "utf-8");
package/src/cli/block.ts CHANGED
@@ -44,24 +44,33 @@ export async function runAddBlock(
44
44
 
45
45
  const local = flags.includes("--local");
46
46
  const flagYes = flags.includes("-y") || flags.includes("--yes");
47
- const registryRoot = local ? resolveLocalRegistryOrExit() : null;
48
- if (local) console.log(`⬡ Using local registry: ${registryRoot}\n`);
47
+ // Honor an inherited registry root from options (e.g. when called from feat.ts in --local mode).
48
+ const inheritedRoot = options?.registryRoot ?? null;
49
+ const registryRoot = inheritedRoot ?? (local ? resolveLocalRegistryOrExit() : null);
50
+ if (local && !inheritedRoot) console.log(`⬡ Using local registry: ${registryRoot}\n`);
49
51
 
50
52
  const resolvedOptions: InstallOptions = {
51
53
  ...(options ?? {}),
54
+ registryRoot,
52
55
  skipPrompts: options?.skipPrompts ?? flagYes,
53
56
  };
54
57
 
55
58
  await initAddRegistry(registryRoot);
56
- ensureUtils();
59
+ ensureUtils(resolvedOptions.cwd);
57
60
 
58
61
  console.log(`⬡ Installing block: ${name}\n`);
59
62
 
60
63
  const meta = await readRegistryJSON<BlockMeta>(registryRoot, "blocks", name, "meta.json");
61
64
 
62
- // 1. Install primitive dependencies first
65
+ // 1. Install primitive dependencies first.
66
+ // Component deps (e.g. "ui/button") go through addComponent.
67
+ // Block deps (e.g. "blocks/files/upload-area") recurse into runAddBlock.
63
68
  for (const dep of meta.dependencies ?? []) {
64
- await addComponent(dep, false, resolvedOptions);
69
+ if (dep.startsWith("blocks/")) {
70
+ await runAddBlock(dep.slice("blocks/".length), [], resolvedOptions);
71
+ } else {
72
+ await addComponent(dep, false, resolvedOptions);
73
+ }
65
74
  }
66
75
 
67
76
  // 2. Copy block files to src/lib/blocks/<path>/
@@ -103,8 +112,7 @@ export async function runAddBlock(
103
112
  if (meta.npmDeps && Object.keys(meta.npmDeps).length > 0) {
104
113
  if (resolvedOptions.skipInstall) {
105
114
  const { addedDeps } = mergePkgJson(cwd, { deps: meta.npmDeps });
106
- if (addedDeps.length > 0)
107
- console.log(` 📥 Added to package.json: ${addedDeps.join(", ")}`);
115
+ if (addedDeps.length > 0) console.log(` 📥 Added to package.json: ${addedDeps.join(", ")}`);
108
116
  } else {
109
117
  await bunAdd(cwd, meta.npmDeps);
110
118
  }
@@ -119,9 +127,7 @@ export async function runAddBlock(
119
127
  ...(meta.dependencies && meta.dependencies.length > 0
120
128
  ? { dependencies: meta.dependencies }
121
129
  : {}),
122
- ...(meta.fonts && Object.keys(meta.fonts).length > 0
123
- ? { fonts: Object.keys(meta.fonts) }
124
- : {}),
130
+ ...(meta.fonts && Object.keys(meta.fonts).length > 0 ? { fonts: Object.keys(meta.fonts) } : {}),
125
131
  });
126
132
 
127
133
  console.log(`\n✅ ${name} installed at src/lib/blocks/${name}/`);
package/src/cli/create.ts CHANGED
@@ -15,13 +15,12 @@ const TEMPLATE_DESCRIPTIONS: Record<string, string> = {
15
15
  default: "Minimal starter with routing and Tailwind",
16
16
  demo: "Full-featured demo with hooks, API routes, form actions, and more",
17
17
  todo: "Todo app with PostgreSQL + Drizzle ORM",
18
+ shop: "Online store starter with auth, RBAC, S3 uploads, products/orders/cart",
18
19
  };
19
20
 
20
21
  export async function runCreate(name: string | undefined, args: string[] = []) {
21
22
  if (!name) {
22
- console.error(
23
- "❌ Please provide a project name.\n Usage: bun x bosia@latest create my-app",
24
- );
23
+ console.error("❌ Please provide a project name.\n Usage: bun x bosia@latest create my-app");
25
24
  process.exit(1);
26
25
  }
27
26
 
@@ -68,10 +67,7 @@ export async function runCreate(name: string | undefined, args: string[] = []) {
68
67
  copyDir(templateDir, targetDir, name, isLocal);
69
68
 
70
69
  if (existsSync(join(targetDir, ".env.example"))) {
71
- writeFileSync(
72
- join(targetDir, ".env"),
73
- readFileSync(join(targetDir, ".env.example"), "utf-8"),
74
- );
70
+ writeFileSync(join(targetDir, ".env"), readFileSync(join(targetDir, ".env.example"), "utf-8"));
75
71
  }
76
72
 
77
73
  // Install template features from registry
@@ -89,11 +85,13 @@ export async function runCreate(name: string | undefined, args: string[] = []) {
89
85
  await initAddRegistry(localRegistry);
90
86
  initFeatRegistry(localRegistry);
91
87
 
88
+ const featureOptions: Record<string, string> = config.featureOptions ?? {};
92
89
  for (const feat of config.features) {
93
90
  await installFeature(feat, true, {
94
91
  skipInstall: true,
95
92
  skipPrompts: true,
96
93
  cwd: targetDir,
94
+ featureOptions,
97
95
  });
98
96
  }
99
97
  }
@@ -177,10 +175,7 @@ function copyDir(src: string, dest: string, projectName: string, isLocal: boolea
177
175
  if (entry.isDirectory()) {
178
176
  copyDir(srcPath, destPath, projectName, isLocal);
179
177
  } else {
180
- let content = readFileSync(srcPath, "utf-8").replaceAll(
181
- "{{PROJECT_NAME}}",
182
- projectName,
183
- );
178
+ let content = readFileSync(srcPath, "utf-8").replaceAll("{{PROJECT_NAME}}", projectName);
184
179
 
185
180
  if (entry.name === "package.json" && isLocal) {
186
181
  const bosiaPath = resolve(import.meta.dir, "../../");
package/src/cli/feat.ts CHANGED
@@ -109,8 +109,11 @@ async function resolveFeatureOptions(
109
109
  options: FeatureOption[],
110
110
  args: string[],
111
111
  skipPrompts: boolean,
112
+ seed: Record<string, string> = {},
112
113
  ): Promise<Record<string, string>> {
113
- const values: Record<string, string> = {};
114
+ // Seed values come from inherited featureOptions (e.g. template-level defaults).
115
+ // They beat per-feature `default` but lose to explicit CLI args.
116
+ const values: Record<string, string> = { ...seed };
114
117
  const byFlag = new Map<string, FeatureOption>();
115
118
  for (const opt of options) {
116
119
  if (opt.flag) byFlag.set(opt.flag, opt);
@@ -153,9 +156,7 @@ async function resolveFeatureOptions(
153
156
  continue;
154
157
  }
155
158
  if (opt.required) {
156
- console.error(
157
- `❌ Feature "${featName}" requires "${opt.flag ?? opt.long ?? opt.name}".`,
158
- );
159
+ console.error(`❌ Feature "${featName}" requires "${opt.flag ?? opt.long ?? opt.name}".`);
159
160
  process.exit(1);
160
161
  }
161
162
  continue;
@@ -217,19 +218,22 @@ export async function installFeature(name: string, isRoot: boolean, options?: In
217
218
  const inheritedOptions = options?.featureOptions ?? {};
218
219
  let myOptions: Record<string, string> = {};
219
220
  if (meta.options && meta.options.length > 0) {
221
+ // Extract this feature's seed values from the namespaced inherited map.
222
+ const seed: Record<string, string> = {};
223
+ for (const [k, v] of Object.entries(inheritedOptions)) {
224
+ const [feat, optName] = k.split(".");
225
+ if (feat === name) seed[optName] = v;
226
+ }
220
227
  myOptions = isRoot
221
228
  ? await resolveFeatureOptions(
222
229
  name,
223
230
  meta.options,
224
231
  options?.featureArgs ?? [],
225
232
  options?.skipPrompts ?? false,
233
+ seed,
226
234
  )
227
235
  : // Dependency features inherit any caller-provided values; prompt only for unresolved required opts.
228
- await resolveFeatureOptions(name, meta.options, [], options?.skipPrompts ?? false);
229
- for (const [k, v] of Object.entries(inheritedOptions)) {
230
- const [feat, optName] = k.split(".");
231
- if (feat === name && !(optName in myOptions)) myOptions[optName] = v;
232
- }
236
+ await resolveFeatureOptions(name, meta.options, [], options?.skipPrompts ?? false, seed);
233
237
  }
234
238
 
235
239
  // Merge into the namespaced map for downstream dependency features.
@@ -262,7 +266,7 @@ export async function installFeature(name: string, isRoot: boolean, options?: In
262
266
  if (meta.blocks && meta.blocks.length > 0) {
263
267
  console.log("🧱 Installing required blocks...");
264
268
  for (const blockName of meta.blocks) {
265
- await runAddBlock(blockName, [], options);
269
+ await runAddBlock(blockName, [], { ...options, registryRoot });
266
270
  }
267
271
  console.log("");
268
272
  }
@@ -308,16 +312,13 @@ export async function installFeature(name: string, isRoot: boolean, options?: In
308
312
  devDeps: meta.npmDevDeps,
309
313
  scripts: meta.scripts,
310
314
  });
311
- if (addedDeps.length > 0)
312
- console.log(`\n📥 Added to package.json: ${addedDeps.join(", ")}`);
313
- if (addedScripts.length > 0)
314
- console.log(`\n📜 Added scripts: ${addedScripts.join(", ")}`);
315
+ if (addedDeps.length > 0) console.log(`\n📥 Added to package.json: ${addedDeps.join(", ")}`);
316
+ if (addedScripts.length > 0) console.log(`\n📜 Added scripts: ${addedScripts.join(", ")}`);
315
317
  } else {
316
318
  await bunAdd(cwd, meta.npmDeps, meta.npmDevDeps);
317
319
  if (hasScripts) {
318
320
  const { addedScripts } = mergePkgJson(cwd, { scripts: meta.scripts });
319
- if (addedScripts.length > 0)
320
- console.log(`\n📜 Added scripts: ${addedScripts.join(", ")}`);
321
+ if (addedScripts.length > 0) console.log(`\n📜 Added scripts: ${addedScripts.join(", ")}`);
321
322
  }
322
323
  }
323
324
  } else if (hasScripts) {
@@ -359,9 +360,7 @@ export async function installFeature(name: string, isRoot: boolean, options?: In
359
360
  (meta.blocks && meta.blocks.length > 0)
360
361
  ? {
361
362
  deps: {
362
- ...(meta.features && meta.features.length > 0
363
- ? { features: meta.features }
364
- : {}),
363
+ ...(meta.features && meta.features.length > 0 ? { features: meta.features } : {}),
365
364
  ...(meta.components.length > 0 ? { components: meta.components } : {}),
366
365
  ...(meta.blocks && meta.blocks.length > 0 ? { blocks: meta.blocks } : {}),
367
366
  },
@@ -439,9 +438,7 @@ async function applyStrategy(args: StrategyArgs): Promise<void> {
439
438
 
440
439
  const nl = existing.length > 0 && !existing.endsWith("\n") ? "\n" : "";
441
440
  writeFileSync(dest, existing + nl + newLines.join("\n") + "\n", "utf-8");
442
- console.log(
443
- ` ➕ ${target} (+${newLines.length} line${newLines.length === 1 ? "" : "s"})`,
444
- );
441
+ console.log(` ➕ ${target} (+${newLines.length} line${newLines.length === 1 ? "" : "s"})`);
445
442
  return;
446
443
  }
447
444
 
package/src/cli/index.ts CHANGED
@@ -96,8 +96,7 @@ async function main() {
96
96
  // First non-flag token is the feature name; everything else flows through to the
97
97
  // feature's own option parser. Global flags (-y, --local) are also accepted here
98
98
  // and get split out inside runFeat.
99
- const rest =
100
- nameIdx === -1 ? args : [...args.slice(0, nameIdx), ...args.slice(nameIdx + 1)];
99
+ const rest = nameIdx === -1 ? args : [...args.slice(0, nameIdx), ...args.slice(nameIdx + 1)];
101
100
  await runFeat(featName, rest);
102
101
  break;
103
102
  }
@@ -67,7 +67,7 @@ export function readManifest(cwd: string = process.cwd()): Manifest {
67
67
 
68
68
  export function writeManifest(manifest: Manifest, cwd: string = process.cwd()): void {
69
69
  const path = join(cwd, MANIFEST_FILE);
70
- writeFileSync(path, JSON.stringify(manifest, null, 2) + "\n", "utf-8");
70
+ writeFileSync(path, JSON.stringify(manifest, null, "\t") + "\n", "utf-8");
71
71
  }
72
72
 
73
73
  export function recordFeature(
@@ -10,6 +10,8 @@ export interface InstallOptions {
10
10
  skipInstall?: boolean; // write deps to package.json instead of `bun add`
11
11
  skipPrompts?: boolean; // auto-overwrite, no interactive prompts
12
12
  cwd?: string; // override process.cwd() for file operations
13
+ /** When set, use this absolute path as the local registry root instead of fetching from GitHub. */
14
+ registryRoot?: string | null;
13
15
  /** Pre-resolved feature-specific option values, keyed by `featureName.optionName`. */
14
16
  featureOptions?: Record<string, string>;
15
17
  /** Remaining argv tokens to be parsed as the root feature's own options. */
@@ -174,7 +176,7 @@ export function mergePkgJson(
174
176
  }
175
177
 
176
178
  if (changed) {
177
- writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf-8");
179
+ writeFileSync(pkgPath, JSON.stringify(pkg, null, "\t") + "\n", "utf-8");
178
180
  }
179
181
 
180
182
  return { addedDeps, addedScripts };
package/src/core/build.ts CHANGED
@@ -88,9 +88,7 @@ let appHtml: any;
88
88
  try {
89
89
  appHtml = loadAppHtmlTemplate(process.cwd());
90
90
  console.log(
91
- "📄 Loaded src/app.html (favicon override: " +
92
- (appHtml.hasCustomFavicon ? "yes" : "no") +
93
- ")",
91
+ "📄 Loaded src/app.html (favicon override: " + (appHtml.hasCustomFavicon ? "yes" : "no") + ")",
94
92
  );
95
93
  } catch (err) {
96
94
  console.error(`❌ src/app.html validation failed:\n${(err as Error).message}`);
@@ -114,8 +114,7 @@
114
114
  // We always issue the fetch (even when all loaders skip) so page-level
115
115
  // metadata stays fresh on every navigation; only the loaders flagged in
116
116
  // the mask actually run server-side.
117
- const maskBits =
118
- (pageRun ? "1" : "0") + layoutRunFlags.map((b) => (b ? "1" : "0")).join("");
117
+ const maskBits = (pageRun ? "1" : "0") + layoutRunFlags.map((b) => (b ? "1" : "0")).join("");
119
118
 
120
119
  // Clear dirty set now — we've baked it into the mask.
121
120
  clearDirty();
@@ -173,9 +172,7 @@
173
172
  ? errInfo
174
173
  : "Internal Server Error";
175
174
  const errDepth: number =
176
- typeof result?.errorDepth === "number"
177
- ? result.errorDepth
178
- : match.route.layouts.length;
175
+ typeof result?.errorDepth === "number" ? result.errorDepth : match.route.layouts.length;
179
176
  const errOrigin = result?.errorOrigin === "layout" ? "layout" : "page";
180
177
  const picked = pickErrorPage(match.route.errorPages ?? [], errDepth, errOrigin);
181
178
  if (!picked) {
@@ -309,9 +306,7 @@
309
306
  if (result?.metadata) {
310
307
  if (result.metadata.title) document.title = result.metadata.title;
311
308
  if (result.metadata.description) {
312
- let meta = document.querySelector(
313
- 'meta[name="description"]',
314
- ) as HTMLMetaElement | null;
309
+ let meta = document.querySelector('meta[name="description"]') as HTMLMetaElement | null;
315
310
  if (!meta) {
316
311
  meta = document.createElement("meta");
317
312
  meta.name = "description";
@@ -57,10 +57,7 @@ export const router = new (class Router {
57
57
  return;
58
58
  }
59
59
  // Canonicalize trailing slash before navigating (matches server 308 behavior)
60
- const canonical = canonicalPathname(
61
- pathname,
62
- (match.route as any).trailingSlash ?? "never",
63
- );
60
+ const canonical = canonicalPathname(pathname, (match.route as any).trailingSlash ?? "never");
64
61
  const finalPath = canonical !== null ? canonical + queryHash : path;
65
62
 
66
63
  const navType: NavType = opts.source ?? "link";
@@ -108,8 +105,7 @@ export const router = new (class Router {
108
105
  // Same-page hash navigation: skip page reload, just update URL and scroll
109
106
  // to the target element. Mirrors browser default for in-page anchors.
110
107
  const samePage =
111
- anchor.pathname === window.location.pathname &&
112
- anchor.search === window.location.search;
108
+ anchor.pathname === window.location.pathname && anchor.search === window.location.search;
113
109
  if (samePage && anchor.hash) {
114
110
  e.preventDefault();
115
111
  const finalPath = anchor.pathname + anchor.search + anchor.hash;
@@ -127,8 +123,7 @@ export const router = new (class Router {
127
123
 
128
124
  // Browser back/forward
129
125
  window.addEventListener("popstate", () => {
130
- const finalPath =
131
- window.location.pathname + window.location.search + window.location.hash;
126
+ const finalPath = window.location.pathname + window.location.search + window.location.hash;
132
127
  // Fire beforeNavigate listeners; popstate can't be reliably cancelled
133
128
  // (browser history already advanced), so we surface the event for
134
129
  // observation only — `cancel()` is a no-op for this source.
@@ -70,10 +70,7 @@ export async function loadBosiaConfig(cwd: string = process.cwd()): Promise<Bosi
70
70
  // the project's own node_modules. /tmp would have no node_modules to walk into.
71
71
  const cacheDir = join(cwd, ".bosia");
72
72
  mkdirSync(cacheDir, { recursive: true });
73
- const tmpFile = join(
74
- cacheDir,
75
- `config.${Date.now()}.${Math.random().toString(36).slice(2)}.mjs`,
76
- );
73
+ const tmpFile = join(cacheDir, `config.${Date.now()}.${Math.random().toString(36).slice(2)}.mjs`);
77
74
  await Bun.write(tmpFile, code);
78
75
 
79
76
  let mod: { default?: BosiaConfig };
@@ -98,8 +98,7 @@ export class CookieJar implements Cookies {
98
98
  }
99
99
  let header = `${name}=${encodeURIComponent(value)}`;
100
100
  if (opts.path) {
101
- if (UNSAFE_COOKIE_VALUE.test(opts.path))
102
- throw new Error(`Invalid cookie path: ${opts.path}`);
101
+ if (UNSAFE_COOKIE_VALUE.test(opts.path)) throw new Error(`Invalid cookie path: ${opts.path}`);
103
102
  header += `; Path=${opts.path}`;
104
103
  }
105
104
  if (opts.domain) {
@@ -115,7 +115,7 @@ export function dev500Response({
115
115
  message,
116
116
  stack: detail,
117
117
  },
118
- ])}</script>
118
+ ])}</script>
119
119
  ${bodyEndExtras?.join("\n") ?? ""}
120
120
  </body>
121
121
  </html>`;
package/src/core/html.ts CHANGED
@@ -122,8 +122,7 @@ export function buildHtml(
122
122
  ? `window.__BOSIA_PAGE_DEPS__=${safeJsonStringify(pageDeps)};window.__BOSIA_LAYOUT_DEPS__=${safeJsonStringify(layoutDeps ?? [])};`
123
123
  : "";
124
124
 
125
- const sysScript =
126
- ssrFlag || depsScript ? `\n <script${n}>${ssrFlag}${depsScript}</script>` : "";
125
+ const sysScript = ssrFlag || depsScript ? `\n <script${n}>${ssrFlag}${depsScript}</script>` : "";
127
126
 
128
127
  const dataIslands = csr
129
128
  ? `\n <script${n} type="application/json" id="__bosia-page-data__">${safeJsonForScript(pageData)}</script>` +
@@ -60,9 +60,7 @@ export function makeBosiaPlugin(target: "browser" | "bun" = "bun") {
60
60
  let svelteBrowserEntry: string | null = null;
61
61
  if (target === "browser") {
62
62
  try {
63
- const svelteDir = dirname(
64
- require.resolve("svelte/package.json", { paths: [appDir] }),
65
- );
63
+ const svelteDir = dirname(require.resolve("svelte/package.json", { paths: [appDir] }));
66
64
  const pkg = require(join(svelteDir, "package.json"));
67
65
  const dotExport = pkg.exports?.["."];
68
66
  const browserPath = typeof dotExport === "object" ? dotExport.browser : null;
@@ -160,10 +160,7 @@ export function createInspectorBunPlugin(opts: InspectorBunPluginOptions): BunPl
160
160
  // line numbers differ. The resolver translates browser-side stack
161
161
  // frames (delivered via SSE), which run client code.
162
162
  if (dev && generate === "client" && result.js.map) {
163
- const m =
164
- typeof result.js.map === "string"
165
- ? JSON.parse(result.js.map)
166
- : result.js.map;
163
+ const m = typeof result.js.map === "string" ? JSON.parse(result.js.map) : result.js.map;
167
164
  svelteMapCache.set(args.path, m);
168
165
  }
169
166
 
@@ -174,9 +174,7 @@ export function inspector(options: InspectorOptions = {}): BosiaPlugin | false {
174
174
  name: "inspector",
175
175
 
176
176
  build: {
177
- bunPlugins: (target) => [
178
- createInspectorBunPlugin({ cwd: process.cwd(), target, dev: true }),
179
- ],
177
+ bunPlugins: (target) => [createInspectorBunPlugin({ cwd: process.cwd(), target, dev: true })],
180
178
  },
181
179
 
182
180
  backend: {
@@ -195,13 +193,10 @@ export function inspector(options: InspectorOptions = {}): BosiaPlugin | false {
195
193
  const line = Number.isFinite(data.line) ? Number(data.line) : null;
196
194
  const col = Number.isFinite(data.col) ? Number(data.col) : 1;
197
195
  if (!file || line === null) {
198
- return new Response(
199
- JSON.stringify({ ok: false, error: "missing file/line" }),
200
- {
201
- status: 400,
202
- headers: { "content-type": "application/json" },
203
- },
204
- );
196
+ return new Response(JSON.stringify({ ok: false, error: "missing file/line" }), {
197
+ status: 400,
198
+ headers: { "content-type": "application/json" },
199
+ });
205
200
  }
206
201
 
207
202
  const comment = typeof data.comment === "string" ? data.comment.trim() : "";
@@ -245,13 +240,10 @@ export function inspector(options: InspectorOptions = {}): BosiaPlugin | false {
245
240
  return { ok: true, mode: "ai" as const };
246
241
  } catch (err) {
247
242
  console.error("[inspector] aiEndpoint POST failed:", err);
248
- return new Response(
249
- JSON.stringify({ ok: false, error: "ai endpoint failed" }),
250
- {
251
- status: 502,
252
- headers: { "content-type": "application/json" },
253
- },
254
- );
243
+ return new Response(JSON.stringify({ ok: false, error: "ai endpoint failed" }), {
244
+ status: 502,
245
+ headers: { "content-type": "application/json" },
246
+ });
255
247
  }
256
248
  }
257
249
 
@@ -266,13 +258,10 @@ export function inspector(options: InspectorOptions = {}): BosiaPlugin | false {
266
258
  });
267
259
  } catch (err) {
268
260
  console.error(`[inspector] failed to launch "${editor}":`, err);
269
- return new Response(
270
- JSON.stringify({ ok: false, error: "editor launch failed" }),
271
- {
272
- status: 500,
273
- headers: { "content-type": "application/json" },
274
- },
275
- );
261
+ return new Response(JSON.stringify({ ok: false, error: "editor launch failed" }), {
262
+ status: 500,
263
+ headers: { "content-type": "application/json" },
264
+ });
276
265
  }
277
266
  return { ok: true, mode: "editor" as const };
278
267
  });
@@ -297,46 +286,43 @@ export function inspector(options: InspectorOptions = {}): BosiaPlugin | false {
297
286
  // Live SSE stream. New clients also get a flush of the bounded
298
287
  // replay buffer so errors that fired during a failing render
299
288
  // (before the 500 page's overlay could subscribe) are visible.
300
- chained = chained.get(
301
- "/__bosia/errors",
302
- ({ request }: { request: Request }) => {
303
- const stream = new ReadableStream<Uint8Array>({
304
- start(ctrl) {
305
- sseClients.add(ctrl);
289
+ chained = chained.get("/__bosia/errors", ({ request }: { request: Request }) => {
290
+ const stream = new ReadableStream<Uint8Array>({
291
+ start(ctrl) {
292
+ sseClients.add(ctrl);
293
+ try {
294
+ ctrl.enqueue(encode(":ok\n\n"));
295
+ } catch {
296
+ sseClients.delete(ctrl);
297
+ return;
298
+ }
299
+ flushReplay(ctrl);
300
+ const ping = setInterval(() => {
306
301
  try {
307
- ctrl.enqueue(encode(":ok\n\n"));
302
+ ctrl.enqueue(encode(":ping\n\n"));
308
303
  } catch {
304
+ clearInterval(ping);
309
305
  sseClients.delete(ctrl);
310
- return;
311
306
  }
312
- flushReplay(ctrl);
313
- const ping = setInterval(() => {
314
- try {
315
- ctrl.enqueue(encode(":ping\n\n"));
316
- } catch {
317
- clearInterval(ping);
318
- sseClients.delete(ctrl);
319
- }
320
- }, 25_000);
307
+ }, 25_000);
321
308
 
322
- request.signal.addEventListener("abort", () => {
323
- clearInterval(ping);
324
- sseClients.delete(ctrl);
325
- try {
326
- ctrl.close();
327
- } catch {}
328
- });
329
- },
330
- });
331
- return new Response(stream, {
332
- headers: {
333
- "Content-Type": "text/event-stream; charset=utf-8",
334
- "Cache-Control": "no-cache",
335
- Connection: "keep-alive",
336
- },
337
- });
338
- },
339
- ) as unknown as Elysia;
309
+ request.signal.addEventListener("abort", () => {
310
+ clearInterval(ping);
311
+ sseClients.delete(ctrl);
312
+ try {
313
+ ctrl.close();
314
+ } catch {}
315
+ });
316
+ },
317
+ });
318
+ return new Response(stream, {
319
+ headers: {
320
+ "Content-Type": "text/event-stream; charset=utf-8",
321
+ "Cache-Control": "no-cache",
322
+ Connection: "keep-alive",
323
+ },
324
+ });
325
+ }) as unknown as Elysia;
340
326
  }
341
327
 
342
328
  return chained;
@@ -148,8 +148,7 @@ function makeFetch(req: Request, url: URL) {
148
148
 
149
149
  const headers = new Headers(init?.headers);
150
150
  const trusted =
151
- targetOrigin !== null &&
152
- (targetOrigin === sameOrigin || INTERNAL_HOSTS.has(targetOrigin));
151
+ targetOrigin !== null && (targetOrigin === sameOrigin || INTERNAL_HOSTS.has(targetOrigin));
153
152
  if (cookie && trusted && !headers.has("cookie")) headers.set("cookie", cookie);
154
153
 
155
154
  return globalThis.fetch(resolved, { ...init, headers });
@@ -927,9 +926,7 @@ export async function renderErrorPage(
927
926
  const K = picked.depth;
928
927
  const [errorMod, layoutMods] = await Promise.all([
929
928
  picked.loader(),
930
- Promise.all(
931
- route.layoutModules.slice(0, K).map((l: () => Promise<any>) => l()),
932
- ),
929
+ Promise.all(route.layoutModules.slice(0, K).map((l: () => Promise<any>) => l())),
933
930
  ]);
934
931
  const layoutData: Record<string, any>[] = [];
935
932
  for (let i = 0; i < K; i++) layoutData.push(partialLayoutData?.[i] ?? {});
@@ -962,11 +959,7 @@ export async function renderErrorPage(
962
959
  return compress(html, "text/html; charset=utf-8", req, status);
963
960
  } catch (err) {
964
961
  if (isDev) console.error("Nested error page render failed:", err);
965
- else
966
- console.error(
967
- "Nested error page render failed:",
968
- (err as Error).message ?? err,
969
- );
962
+ else console.error("Nested error page render failed:", (err as Error).message ?? err);
970
963
  if (isDev) reportDevErrorFromCatch(err);
971
964
  // fall through to global / text fallback
972
965
  }
@@ -78,9 +78,7 @@ export function generateRouteTypes(manifest: RouteManifest): void {
78
78
  params.length === 0 ? `{}` : `{ ${params.map((p) => `${p}: string`).join("; ")} }`;
79
79
 
80
80
  const lines: string[] = ["// AUTO-GENERATED by bosia — do not edit\n"];
81
- lines.push(
82
- `import type { LoadEvent, MetadataEvent, RequestEvent, Metadata } from 'bosia';`,
83
- );
81
+ lines.push(`import type { LoadEvent, MetadataEvent, RequestEvent, Metadata } from 'bosia';`);
84
82
  lines.push(``);
85
83
  lines.push(`export type Params = ${paramsType};`);
86
84
  lines.push(``);
@@ -126,9 +124,7 @@ export function generateRouteTypes(manifest: RouteManifest): void {
126
124
  lines.push(
127
125
  `type _ActionReturn<T> = T extends (...args: any[]) => infer R ? Awaited<R> : never;`,
128
126
  );
129
- lines.push(
130
- `type _UnwrapFailure<T> = T extends { status: number; data: infer D } ? D : T;`,
131
- );
127
+ lines.push(`type _UnwrapFailure<T> = T extends { status: number; data: infer D } ? D : T;`);
132
128
  lines.push(
133
129
  `export type ActionData = _actions extends Record<string, (...args: any[]) => any>`,
134
130
  );
@@ -140,9 +136,7 @@ export function generateRouteTypes(manifest: RouteManifest): void {
140
136
  lines.push(`\nimport type { load as _layoutLoad } from '${srcBase}+layout.server.ts';`);
141
137
  lines.push(`export type LayoutServerLoad = (event: _LoadEvent) => any;`);
142
138
  lines.push(`export type LayoutData = Awaited<ReturnType<typeof _layoutLoad>>;`);
143
- lines.push(
144
- `export type LayoutProps = { data: LayoutData; params: Params; children: any };`,
145
- );
139
+ lines.push(`export type LayoutProps = { data: LayoutData; params: Params; children: any };`);
146
140
  }
147
141
 
148
142
  const outDir = join(process.cwd(), ".bosia", "types", "src", "routes", ...segments);