bosia 0.5.4 → 0.5.6
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/start.ts +3 -3
- package/src/core/build.ts +25 -13
- package/src/core/client/enhance.ts +8 -5
- package/src/core/dev.ts +12 -4
- package/src/core/html.ts +2 -1
- package/src/core/paths.ts +5 -0
- package/src/core/prerender.ts +14 -14
- package/src/core/server.ts +6 -5
- package/templates/default/src/routes/+page.svelte +3 -6
- package/templates/demo/src/routes/(public)/+layout.svelte +4 -11
- package/templates/demo/src/routes/(public)/blog/+page.svelte +2 -4
- package/templates/demo/src/routes/(public)/blog/[slug]/+page.svelte +3 -5
- package/templates/todo/src/routes/+page.svelte +1 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bosia",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.6",
|
|
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": [
|
package/src/cli/start.ts
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
import { spawn } from "bun";
|
|
2
2
|
import { loadEnv } from "../core/env.ts";
|
|
3
|
-
import { BOSIA_NODE_PATH } from "../core/paths.ts";
|
|
3
|
+
import { BOSIA_NODE_PATH, OUT_DIR } from "../core/paths.ts";
|
|
4
4
|
|
|
5
5
|
export async function runStart() {
|
|
6
6
|
loadEnv("production");
|
|
7
7
|
|
|
8
8
|
let serverEntry = "index.js";
|
|
9
9
|
try {
|
|
10
|
-
const manifest = await Bun.file(
|
|
10
|
+
const manifest = await Bun.file(`${OUT_DIR}/manifest.json`).json();
|
|
11
11
|
serverEntry = manifest.serverEntry ?? "index.js";
|
|
12
12
|
} catch {}
|
|
13
13
|
|
|
14
|
-
const proc = spawn(["bun", "run",
|
|
14
|
+
const proc = spawn(["bun", "run", `${OUT_DIR}/server/${serverEntry}`], {
|
|
15
15
|
stdout: "inherit",
|
|
16
16
|
stderr: "inherit",
|
|
17
17
|
cwd: process.cwd(),
|
package/src/core/build.ts
CHANGED
|
@@ -9,7 +9,7 @@ import { makeBosiaSvelteCompiler } from "./svelteCompiler.ts";
|
|
|
9
9
|
import { prerenderStaticRoutes, generateStaticSite } from "./prerender.ts";
|
|
10
10
|
import { loadEnv, classifyEnvVars } from "./env.ts";
|
|
11
11
|
import { generateEnvModules } from "./envCodegen.ts";
|
|
12
|
-
import { BOSIA_NODE_PATH, resolveBosiaBin } from "./paths.ts";
|
|
12
|
+
import { BOSIA_NODE_PATH, OUT_DIR, resolveBosiaBin } from "./paths.ts";
|
|
13
13
|
import { loadPlugins } from "./config.ts";
|
|
14
14
|
import type { BuildContext } from "./types/plugin.ts";
|
|
15
15
|
|
|
@@ -45,13 +45,25 @@ const envMode = isProduction ? "production" : "development";
|
|
|
45
45
|
const envVars = loadEnv(envMode);
|
|
46
46
|
const classifiedEnv = classifyEnvVars(envVars);
|
|
47
47
|
|
|
48
|
-
// 0b. Clean
|
|
48
|
+
// 0b. Clean generated output. Only OUT_DIR (this build's artifacts) and the
|
|
49
|
+
// codegen files inside .bosia/ that this build owns. A blanket wipe of .bosia/
|
|
50
|
+
// would clobber a concurrently-running `bosia dev` whose compiled server lives
|
|
51
|
+
// at .bosia/dev/ — the codegen files (routes*.ts, env.*.ts, types/) are the
|
|
52
|
+
// only things this build needs to clear to avoid stale entries on route renames.
|
|
49
53
|
try {
|
|
50
|
-
rmSync(
|
|
51
|
-
} catch {}
|
|
52
|
-
try {
|
|
53
|
-
rmSync("./.bosia", { recursive: true, force: true });
|
|
54
|
+
rmSync(OUT_DIR, { recursive: true, force: true });
|
|
54
55
|
} catch {}
|
|
56
|
+
for (const p of [
|
|
57
|
+
".bosia/routes.ts",
|
|
58
|
+
".bosia/routes.client.ts",
|
|
59
|
+
".bosia/env.server.ts",
|
|
60
|
+
".bosia/env.client.ts",
|
|
61
|
+
".bosia/types",
|
|
62
|
+
]) {
|
|
63
|
+
try {
|
|
64
|
+
rmSync(p, { recursive: true, force: true });
|
|
65
|
+
} catch {}
|
|
66
|
+
}
|
|
55
67
|
|
|
56
68
|
// 1. Scan routes
|
|
57
69
|
const manifest = scanRoutes();
|
|
@@ -125,7 +137,7 @@ for (const [key, value] of Object.entries(classifiedEnv.privateStatic)) {
|
|
|
125
137
|
console.log("\n📦 Building Tailwind + client + server...");
|
|
126
138
|
const clientPromise = Bun.build({
|
|
127
139
|
entrypoints: [join(CORE_DIR, "client", "hydrate.ts")],
|
|
128
|
-
outdir:
|
|
140
|
+
outdir: `${OUT_DIR}/client`,
|
|
129
141
|
target: "browser",
|
|
130
142
|
splitting: true,
|
|
131
143
|
naming: { chunk: "[name]-[hash].[ext]" },
|
|
@@ -139,7 +151,7 @@ const clientPromise = Bun.build({
|
|
|
139
151
|
|
|
140
152
|
const serverPromise = Bun.build({
|
|
141
153
|
entrypoints: [join(CORE_DIR, "server.ts")],
|
|
142
|
-
outdir:
|
|
154
|
+
outdir: `${OUT_DIR}/server`,
|
|
143
155
|
target: "bun",
|
|
144
156
|
splitting: true,
|
|
145
157
|
naming: { entry: "index.[ext]", chunk: "[name]-[hash].[ext]" },
|
|
@@ -177,7 +189,7 @@ if (!serverResult.success) {
|
|
|
177
189
|
const jsFiles: string[] = [];
|
|
178
190
|
const cssFiles: string[] = [];
|
|
179
191
|
for (const output of clientResult.outputs) {
|
|
180
|
-
const rel = relative(
|
|
192
|
+
const rel = relative(`${OUT_DIR}/client`, output.path);
|
|
181
193
|
if (output.path.endsWith(".js")) jsFiles.push(rel);
|
|
182
194
|
if (output.path.endsWith(".css")) cssFiles.push(rel);
|
|
183
195
|
}
|
|
@@ -190,7 +202,7 @@ const serverEntry =
|
|
|
190
202
|
.pop() ?? "index.js";
|
|
191
203
|
|
|
192
204
|
// 8. Write dist/manifest.json
|
|
193
|
-
mkdirSync(
|
|
205
|
+
mkdirSync(OUT_DIR, { recursive: true });
|
|
194
206
|
const distManifest = {
|
|
195
207
|
js: jsFiles,
|
|
196
208
|
css: cssFiles,
|
|
@@ -200,12 +212,12 @@ const distManifest = {
|
|
|
200
212
|
"hydrate.js",
|
|
201
213
|
serverEntry,
|
|
202
214
|
};
|
|
203
|
-
writeFileSync(
|
|
215
|
+
writeFileSync(`${OUT_DIR}/manifest.json`, JSON.stringify(distManifest, null, 2));
|
|
204
216
|
console.log(`✅ Client bundle: ${jsFiles.join(", ")}`);
|
|
205
|
-
console.log(`✅ Server entry:
|
|
217
|
+
console.log(`✅ Server entry: ${OUT_DIR}/server/${serverEntry}`);
|
|
206
218
|
|
|
207
219
|
// 8b. Persist route manifest for runtime plugins (backend.after consumers like OpenAPI).
|
|
208
|
-
writeFileSync(
|
|
220
|
+
writeFileSync(`${OUT_DIR}/route-manifest.json`, JSON.stringify(manifest, null, 2));
|
|
209
221
|
|
|
210
222
|
// 9. Prerender static routes
|
|
211
223
|
await prerenderStaticRoutes(manifest);
|
|
@@ -68,11 +68,14 @@ export function enhance(form: HTMLFormElement, submit?: SubmitFunction) {
|
|
|
68
68
|
const submitter = (event.submitter as HTMLElement | null) ?? null;
|
|
69
69
|
const formData = new FormData(form, submitter as HTMLElement | undefined);
|
|
70
70
|
|
|
71
|
-
// Resolve action URL — preserve `?/actionName` if the submitter or form sets it
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
71
|
+
// Resolve action URL — preserve `?/actionName` if the submitter or form sets it.
|
|
72
|
+
// `submitter.formAction` reflects the document URL when no `formaction` attribute
|
|
73
|
+
// is set (HTML spec), which would silently drop the form's `action="?/foo"`. So
|
|
74
|
+
// only honor the submitter override when it actually has the attribute.
|
|
75
|
+
const submitterEl = submitter as HTMLButtonElement | HTMLInputElement | null;
|
|
76
|
+
const submitterAction =
|
|
77
|
+
submitterEl && submitterEl.hasAttribute("formaction") ? submitterEl.formAction : "";
|
|
78
|
+
const actionAttr = submitterAction || form.action || window.location.href;
|
|
76
79
|
const action = new URL(actionAttr, window.location.href);
|
|
77
80
|
|
|
78
81
|
let cancelled = false;
|
package/src/core/dev.ts
CHANGED
|
@@ -2,6 +2,13 @@ import { spawn, type Subprocess } from "bun";
|
|
|
2
2
|
import { watch } from "fs";
|
|
3
3
|
import { join } from "path";
|
|
4
4
|
import { loadEnv, resetDeclaredKeys } from "./env.ts";
|
|
5
|
+
import { BOSIA_NODE_PATH } from "./paths.ts";
|
|
6
|
+
|
|
7
|
+
// Dev always writes to .bosia/dev so a parallel `bun run build` (writing to ./dist)
|
|
8
|
+
// can't clobber the live preview. Hardcoded — BOSIA_OUT_DIR is a build-mode knob,
|
|
9
|
+
// not a dev knob; we pass it to spawned children to redirect build.ts/server output,
|
|
10
|
+
// but dev.ts itself never reads it.
|
|
11
|
+
const DEV_OUT_DIR = ".bosia/dev";
|
|
5
12
|
|
|
6
13
|
// Snapshot pure shell env BEFORE any loadEnv call pollutes process.env.
|
|
7
14
|
// On `.env*` change we restore from this snapshot, then re-run loadEnv,
|
|
@@ -49,8 +56,6 @@ function broadcastReload() {
|
|
|
49
56
|
|
|
50
57
|
// ─── Build ────────────────────────────────────────────────
|
|
51
58
|
|
|
52
|
-
import { BOSIA_NODE_PATH } from "./paths.ts";
|
|
53
|
-
|
|
54
59
|
const BUILD_SCRIPT = join(import.meta.dir, "build.ts");
|
|
55
60
|
|
|
56
61
|
async function runBuild(): Promise<boolean> {
|
|
@@ -59,6 +64,7 @@ async function runBuild(): Promise<boolean> {
|
|
|
59
64
|
stdout: "inherit",
|
|
60
65
|
stderr: "inherit",
|
|
61
66
|
cwd: process.cwd(),
|
|
67
|
+
env: { ...process.env, BOSIA_OUT_DIR: DEV_OUT_DIR },
|
|
62
68
|
});
|
|
63
69
|
return (await proc.exited) === 0;
|
|
64
70
|
}
|
|
@@ -79,11 +85,11 @@ async function startAppServer() {
|
|
|
79
85
|
// Read the server entry filename from the manifest written by build.ts
|
|
80
86
|
let serverEntry = "index.js";
|
|
81
87
|
try {
|
|
82
|
-
const manifest = await Bun.file(
|
|
88
|
+
const manifest = await Bun.file(`${DEV_OUT_DIR}/manifest.json`).json();
|
|
83
89
|
serverEntry = manifest.serverEntry ?? "index.js";
|
|
84
90
|
} catch {}
|
|
85
91
|
|
|
86
|
-
appProcess = spawn(["bun", "run",
|
|
92
|
+
appProcess = spawn(["bun", "run", `${DEV_OUT_DIR}/server/${serverEntry}`], {
|
|
87
93
|
stdout: "inherit",
|
|
88
94
|
stderr: "inherit",
|
|
89
95
|
cwd: process.cwd(),
|
|
@@ -94,6 +100,8 @@ async function startAppServer() {
|
|
|
94
100
|
PORT: String(APP_PORT),
|
|
95
101
|
// Allow externalized deps (elysia, etc.) to resolve from bosia's node_modules
|
|
96
102
|
NODE_PATH: BOSIA_NODE_PATH,
|
|
103
|
+
// Point the server child at dev's output dir so its OUT_DIR reads match what build wrote.
|
|
104
|
+
BOSIA_OUT_DIR: DEV_OUT_DIR,
|
|
97
105
|
// Dev proxy injects X-Forwarded-Host/Proto reflecting the public DEV_PORT, so CSRF
|
|
98
106
|
// origin checks must honour them. Safe in dev because the proxy controls these
|
|
99
107
|
// headers — no untrusted client can spoof them.
|
package/src/core/html.ts
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from "fs";
|
|
2
2
|
import { getDeclaredEnvKeys } from "./env.ts";
|
|
3
3
|
import { nonceAttr } from "./csp.ts";
|
|
4
|
+
import { OUT_DIR } from "./paths.ts";
|
|
4
5
|
|
|
5
6
|
// ─── Dist Manifest ───────────────────────────────────────
|
|
6
7
|
// Maps hashed filenames → script/link tags.
|
|
7
8
|
// Cached at startup; server restarts on rebuild in dev anyway.
|
|
8
9
|
|
|
9
10
|
export const distManifest: { js: string[]; css: string[]; entry: string } = (() => {
|
|
10
|
-
const p =
|
|
11
|
+
const p = `${OUT_DIR}/manifest.json`;
|
|
11
12
|
return existsSync(p)
|
|
12
13
|
? JSON.parse(readFileSync(p, "utf-8"))
|
|
13
14
|
: { js: [], css: [], entry: "hydrate.js" };
|
package/src/core/paths.ts
CHANGED
|
@@ -18,6 +18,11 @@ const HOISTED_NM = isInstalledAsDep ? parentDir : null;
|
|
|
18
18
|
/** NODE_PATH value covering both nested and hoisted dependency locations */
|
|
19
19
|
export const BOSIA_NODE_PATH = HOISTED_NM ? [NESTED_NM, HOISTED_NM].join(":") : NESTED_NM;
|
|
20
20
|
|
|
21
|
+
// On-disk output directory. URL namespace (/dist/client/...) stays stable;
|
|
22
|
+
// only the on-disk location moves so dev (.bosia/dev) and a parallel
|
|
23
|
+
// `bun run build` (./dist) don't clobber each other.
|
|
24
|
+
export const OUT_DIR = process.env.BOSIA_OUT_DIR ?? "./dist";
|
|
25
|
+
|
|
21
26
|
/** Find a binary from bosia's dependencies (handles hoisting) */
|
|
22
27
|
export function resolveBosiaBin(name: string): string {
|
|
23
28
|
const nested = join(NESTED_NM, ".bin", name);
|
package/src/core/prerender.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { createServer } from "net";
|
|
|
3
3
|
import { join } from "path";
|
|
4
4
|
import type { RouteManifest, TrailingSlash } from "./types.ts";
|
|
5
5
|
|
|
6
|
-
import { BOSIA_NODE_PATH } from "./paths.ts";
|
|
6
|
+
import { BOSIA_NODE_PATH, OUT_DIR } from "./paths.ts";
|
|
7
7
|
|
|
8
8
|
/** Acquire an OS-assigned ephemeral port. Tiny TOCTOU race window; acceptable for build-time use. */
|
|
9
9
|
export function getEphemeralPort(): Promise<number> {
|
|
@@ -78,9 +78,9 @@ export function canonicalRouteFor(routePath: string, ts: TrailingSlash): string
|
|
|
78
78
|
* mode so static hosts serve the right file on direct URL hits.
|
|
79
79
|
*/
|
|
80
80
|
export function prerenderOutPath(routePath: string, ts: TrailingSlash): string {
|
|
81
|
-
if (routePath === "/") return
|
|
82
|
-
if (ts === "never") return
|
|
83
|
-
return
|
|
81
|
+
if (routePath === "/") return `${OUT_DIR}/prerendered/index.html`;
|
|
82
|
+
if (ts === "never") return `${OUT_DIR}/prerendered${routePath.replace(/\/$/, "")}.html`;
|
|
83
|
+
return `${OUT_DIR}/prerendered${routePath.replace(/\/$/, "")}/index.html`;
|
|
84
84
|
}
|
|
85
85
|
|
|
86
86
|
/** Data-payload filename for a prerendered route — matches client `dataUrl()`. */
|
|
@@ -94,7 +94,7 @@ export function prerenderDataPath(routePath: string): string {
|
|
|
94
94
|
* `/api/foo.json` regardless of the request URL's slash).
|
|
95
95
|
*/
|
|
96
96
|
export function prerenderApiOutPath(routePath: string): string {
|
|
97
|
-
return
|
|
97
|
+
return `${OUT_DIR}/prerendered${routePath.replace(/\/$/, "")}.json`;
|
|
98
98
|
}
|
|
99
99
|
|
|
100
100
|
async function detectPrerenderRoutes(manifest: RouteManifest): Promise<PrerenderTarget[]> {
|
|
@@ -179,7 +179,7 @@ export async function prerenderStaticRoutes(manifest: RouteManifest): Promise<vo
|
|
|
179
179
|
console.log(`\n🖨️ Prerendering ${targets.length} route(s)...`);
|
|
180
180
|
|
|
181
181
|
const port = await getEphemeralPort();
|
|
182
|
-
const child = Bun.spawn(["bun", "run",
|
|
182
|
+
const child = Bun.spawn(["bun", "run", `${OUT_DIR}/server/index.js`], {
|
|
183
183
|
env: {
|
|
184
184
|
...process.env,
|
|
185
185
|
NODE_ENV: "production",
|
|
@@ -219,7 +219,7 @@ export async function prerenderStaticRoutes(manifest: RouteManifest): Promise<vo
|
|
|
219
219
|
return;
|
|
220
220
|
}
|
|
221
221
|
|
|
222
|
-
mkdirSync(
|
|
222
|
+
mkdirSync(`${OUT_DIR}/prerendered`, { recursive: true });
|
|
223
223
|
|
|
224
224
|
for (const { path: routePath, kind, trailingSlash: ts } of targets) {
|
|
225
225
|
try {
|
|
@@ -260,7 +260,7 @@ export async function prerenderStaticRoutes(manifest: RouteManifest): Promise<vo
|
|
|
260
260
|
});
|
|
261
261
|
if (dataRes.ok) {
|
|
262
262
|
const dataJson = await dataRes.text();
|
|
263
|
-
const dataOutPath =
|
|
263
|
+
const dataOutPath = `${OUT_DIR}/prerendered/__bosia/data${dataPath}`;
|
|
264
264
|
mkdirSync(dataOutPath.substring(0, dataOutPath.lastIndexOf("/")), {
|
|
265
265
|
recursive: true,
|
|
266
266
|
});
|
|
@@ -295,24 +295,24 @@ export async function prerenderStaticRoutes(manifest: RouteManifest): Promise<vo
|
|
|
295
295
|
// ─── Static Site Output ──────────────────────────────────
|
|
296
296
|
|
|
297
297
|
export function generateStaticSite(): void {
|
|
298
|
-
if (!existsSync(
|
|
298
|
+
if (!existsSync(`${OUT_DIR}/prerendered`)) {
|
|
299
299
|
console.log("\n⏭️ No prerendered pages — skipping static site output");
|
|
300
300
|
return;
|
|
301
301
|
}
|
|
302
302
|
|
|
303
303
|
console.log("\n📦 Generating static site...");
|
|
304
|
-
mkdirSync(
|
|
304
|
+
mkdirSync(`${OUT_DIR}/static`, { recursive: true });
|
|
305
305
|
|
|
306
306
|
// 1. HTML files from prerendering
|
|
307
|
-
cpSync(
|
|
307
|
+
cpSync(`${OUT_DIR}/prerendered`, `${OUT_DIR}/static`, { recursive: true });
|
|
308
308
|
|
|
309
309
|
// 2. Client JS/CSS — preserves /dist/client/... absolute paths used in HTML
|
|
310
|
-
cpSync(
|
|
310
|
+
cpSync(`${OUT_DIR}/client`, `${OUT_DIR}/static/dist/client`, { recursive: true });
|
|
311
311
|
|
|
312
312
|
// 3. Public assets (bosia-tw.css, favicon, etc.) — preserves /bosia-tw.css path
|
|
313
313
|
if (existsSync("./public")) {
|
|
314
|
-
cpSync("./public",
|
|
314
|
+
cpSync("./public", `${OUT_DIR}/static`, { recursive: true });
|
|
315
315
|
}
|
|
316
316
|
|
|
317
|
-
console.log(
|
|
317
|
+
console.log(`✅ Static site generated: ${OUT_DIR}/static/`);
|
|
318
318
|
}
|
package/src/core/server.ts
CHANGED
|
@@ -22,6 +22,7 @@ import { applyCorsVary, getCorsHeaders, handlePreflight } from "./cors.ts";
|
|
|
22
22
|
import type { CorsConfig } from "./cors.ts";
|
|
23
23
|
import { buildCspHeader, CSP_DIRECTIVES_TEMPLATE, CSP_ENABLED, generateNonce } from "./csp.ts";
|
|
24
24
|
import { isDev, compress, isStaticPath } from "./html.ts";
|
|
25
|
+
import { OUT_DIR } from "./paths.ts";
|
|
25
26
|
import { dedup, dedupKey } from "./dedup.ts";
|
|
26
27
|
import {
|
|
27
28
|
loadRouteData,
|
|
@@ -288,7 +289,7 @@ async function resolve(event: RequestEvent): Promise<Response> {
|
|
|
288
289
|
// dist/client: serve with cache headers based on whether filename is hashed
|
|
289
290
|
if (path.startsWith("/dist/client/")) {
|
|
290
291
|
const resolved = safePath(
|
|
291
|
-
|
|
292
|
+
`${OUT_DIR}/client`,
|
|
292
293
|
path.split("?")[0].slice("/dist/client".length),
|
|
293
294
|
);
|
|
294
295
|
if (resolved) {
|
|
@@ -308,12 +309,12 @@ async function resolve(event: RequestEvent): Promise<Response> {
|
|
|
308
309
|
const pub = Bun.file(pubPath);
|
|
309
310
|
if (await pub.exists()) return new Response(pub);
|
|
310
311
|
}
|
|
311
|
-
const distPath = safePath(
|
|
312
|
+
const distPath = safePath(OUT_DIR, path);
|
|
312
313
|
if (distPath) {
|
|
313
314
|
const dist = Bun.file(distPath);
|
|
314
315
|
if (await dist.exists()) return new Response(dist);
|
|
315
316
|
}
|
|
316
|
-
const staticPath = safePath(
|
|
317
|
+
const staticPath = safePath(`${OUT_DIR}/static`, path);
|
|
317
318
|
if (staticPath) {
|
|
318
319
|
const staticFile = Bun.file(staticPath);
|
|
319
320
|
if (await staticFile.exists()) return new Response(staticFile);
|
|
@@ -326,7 +327,7 @@ async function resolve(event: RequestEvent): Promise<Response> {
|
|
|
326
327
|
const prerenderCandidates =
|
|
327
328
|
path === "/" ? ["index.html"] : [`${path}/index.html`, `${path.replace(/\/$/, "")}.html`];
|
|
328
329
|
for (const candidate of prerenderCandidates) {
|
|
329
|
-
const prerenderPath = safePath(
|
|
330
|
+
const prerenderPath = safePath(`${OUT_DIR}/prerendered`, candidate);
|
|
330
331
|
if (!prerenderPath) continue;
|
|
331
332
|
const prerenderFile = Bun.file(prerenderPath);
|
|
332
333
|
if (await prerenderFile.exists()) {
|
|
@@ -724,7 +725,7 @@ if (plugins.length > 0) {
|
|
|
724
725
|
|
|
725
726
|
// Read the build-time route manifest so plugins.backend.after can introspect routes.
|
|
726
727
|
function loadBuiltManifest(): RouteManifest {
|
|
727
|
-
const path =
|
|
728
|
+
const path = `${OUT_DIR}/route-manifest.json`;
|
|
728
729
|
if (existsSync(path)) {
|
|
729
730
|
try {
|
|
730
731
|
return JSON.parse(readFileSync(path, "utf-8"));
|
|
@@ -2,9 +2,7 @@
|
|
|
2
2
|
<div class="flex flex-col items-center gap-3 text-center">
|
|
3
3
|
<img src="/favicon.svg" alt="" class="size-16" />
|
|
4
4
|
<h1 class="text-4xl font-bold tracking-tight">Welcome to Bosia</h1>
|
|
5
|
-
<p class="text-muted-foreground text-lg">
|
|
6
|
-
A Bosia project — SSR + Svelte 5 + Bun + ElysiaJS
|
|
7
|
-
</p>
|
|
5
|
+
<p class="text-muted-foreground text-lg">A Bosia project — SSR + Svelte 5 + Bun + ElysiaJS</p>
|
|
8
6
|
</div>
|
|
9
7
|
|
|
10
8
|
<div class="mt-4 flex gap-3">
|
|
@@ -27,8 +25,7 @@
|
|
|
27
25
|
</div>
|
|
28
26
|
|
|
29
27
|
<p class="text-muted-foreground mt-6 text-sm">
|
|
30
|
-
Edit <code class="bg-muted rounded px-1 py-0.5 font-mono text-xs"
|
|
31
|
-
|
|
32
|
-
> to get started
|
|
28
|
+
Edit <code class="bg-muted rounded px-1 py-0.5 font-mono text-xs">src/routes/+page.svelte</code> to
|
|
29
|
+
get started
|
|
33
30
|
</p>
|
|
34
31
|
</main>
|
|
@@ -10,25 +10,18 @@
|
|
|
10
10
|
<a href="/" class="font-bold tracking-tight flex items-center gap-2"
|
|
11
11
|
><img src="/favicon.svg" alt="" class="size-5" /> {data.appName}</a
|
|
12
12
|
>
|
|
13
|
-
<a
|
|
14
|
-
href="/"
|
|
15
|
-
class="text-sm text-muted-foreground hover:text-foreground transition-colors"
|
|
13
|
+
<a href="/" class="text-sm text-muted-foreground hover:text-foreground transition-colors"
|
|
16
14
|
>Home</a
|
|
17
15
|
>
|
|
18
|
-
<a
|
|
19
|
-
href="/about"
|
|
20
|
-
class="text-sm text-muted-foreground hover:text-foreground transition-colors"
|
|
16
|
+
<a href="/about" class="text-sm text-muted-foreground hover:text-foreground transition-colors"
|
|
21
17
|
>About</a
|
|
22
18
|
>
|
|
23
|
-
<a
|
|
24
|
-
href="/blog"
|
|
25
|
-
class="text-sm text-muted-foreground hover:text-foreground transition-colors"
|
|
19
|
+
<a href="/blog" class="text-sm text-muted-foreground hover:text-foreground transition-colors"
|
|
26
20
|
>Blog</a
|
|
27
21
|
>
|
|
28
22
|
<a
|
|
29
23
|
href="/all/foo/bar"
|
|
30
|
-
class="text-sm text-muted-foreground hover:text-foreground transition-colors"
|
|
31
|
-
>Catch-all</a
|
|
24
|
+
class="text-sm text-muted-foreground hover:text-foreground transition-colors">Catch-all</a
|
|
32
25
|
>
|
|
33
26
|
<a
|
|
34
27
|
href="/api/hello"
|
|
@@ -11,8 +11,7 @@
|
|
|
11
11
|
slug: "route-groups",
|
|
12
12
|
title: "Route Groups Explained",
|
|
13
13
|
date: "2026-03-04",
|
|
14
|
-
excerpt:
|
|
15
|
-
"How (public), (auth), (admin) groups work — invisible in URLs, share layouts.",
|
|
14
|
+
excerpt: "How (public), (auth), (admin) groups work — invisible in URLs, share layouts.",
|
|
16
15
|
tags: ["routing", "layouts"],
|
|
17
16
|
},
|
|
18
17
|
{
|
|
@@ -50,8 +49,7 @@
|
|
|
50
49
|
</div>
|
|
51
50
|
<div class="flex gap-1 shrink-0">
|
|
52
51
|
{#each post.tags as tag}
|
|
53
|
-
<span
|
|
54
|
-
class="rounded-full bg-secondary px-2 py-0.5 text-xs text-secondary-foreground"
|
|
52
|
+
<span class="rounded-full bg-secondary px-2 py-0.5 text-xs text-secondary-foreground"
|
|
55
53
|
>{tag}</span
|
|
56
54
|
>
|
|
57
55
|
{/each}
|
|
@@ -13,16 +13,14 @@
|
|
|
13
13
|
|
|
14
14
|
{#if post}
|
|
15
15
|
<article class="space-y-6 max-w-2xl">
|
|
16
|
-
<a
|
|
17
|
-
|
|
18
|
-
class="text-sm text-muted-foreground hover:text-foreground transition-colors">← Blog</a
|
|
16
|
+
<a href="/blog" class="text-sm text-muted-foreground hover:text-foreground transition-colors"
|
|
17
|
+
>← Blog</a
|
|
19
18
|
>
|
|
20
19
|
|
|
21
20
|
<div class="space-y-2">
|
|
22
21
|
<div class="flex flex-wrap gap-1">
|
|
23
22
|
{#each post.tags as tag}
|
|
24
|
-
<span
|
|
25
|
-
class="rounded-full bg-secondary px-2 py-0.5 text-xs text-secondary-foreground"
|
|
23
|
+
<span class="rounded-full bg-secondary px-2 py-0.5 text-xs text-secondary-foreground"
|
|
26
24
|
>{tag}</span
|
|
27
25
|
>
|
|
28
26
|
{/each}
|