bosia 0.9.8 → 0.9.10
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 +3 -3
- package/package.json +2 -4
- package/src/ambient.d.ts +7 -0
- package/src/cli/build.ts +6 -1
- package/src/cli/index.ts +3 -3
- package/src/cli/start.ts +9 -1
- package/src/core/appHtml.ts +3 -11
- package/src/core/artifacts.ts +21 -0
- package/src/core/backend.ts +238 -0
- package/src/core/build.ts +97 -6
- package/src/core/cache.ts +10 -6
- package/src/core/client/App.svelte +5 -2
- package/src/core/client/prefetch.ts +3 -1
- package/src/core/config.ts +20 -9
- package/src/core/dev.ts +2 -3
- package/src/core/env.ts +0 -20
- package/src/core/hooks.ts +28 -0
- package/src/core/html.ts +29 -24
- package/src/core/paths.ts +4 -2
- package/src/core/platform.ts +24 -0
- package/src/core/plugin.ts +26 -1
- package/src/core/plugins/inspector/index.ts +6 -10
- package/src/core/plugins/server-timing.ts +1 -1
- package/src/core/prerender.ts +6 -6
- package/src/core/renderer.ts +15 -8
- package/src/core/routeFile.ts +4 -0
- package/src/core/scanner.ts +30 -0
- package/src/core/server.bun.ts +129 -0
- package/src/core/server.ts +108 -204
- package/src/core/server.workers.ts +46 -0
- package/src/core/types/plugin.ts +10 -6
- package/src/core/types.ts +2 -0
- package/src/core/workersCodegen.ts +110 -0
- package/src/core/workersGuard.ts +107 -0
- package/src/lib/index.ts +3 -0
- package/src/lib/server.ts +1 -0
- package/templates/default/README.md +1 -1
- package/templates/default/_gitignore +2 -0
- package/templates/default/src/routes/(public)/+page.svelte +1 -1
- package/templates/demo/_gitignore +2 -0
- package/templates/demo/src/routes/(public)/+page.svelte +2 -2
- package/templates/demo/src/routes/(public)/about/+page.svelte +2 -2
- package/templates/shop/_gitignore +2 -0
- package/templates/store/_gitignore +2 -0
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
2
|
+
import { basename, join } from "path";
|
|
3
|
+
|
|
4
|
+
import { readArtifact } from "./artifacts.ts";
|
|
5
|
+
import { findConfigPath } from "./config.ts";
|
|
6
|
+
import { OUT_DIR, toPosix } from "./paths.ts";
|
|
7
|
+
|
|
8
|
+
// ─── Workers Codegen ─────────────────────────────────────
|
|
9
|
+
// Cloudflare Workers has no filesystem and no dynamic `import(<path>)`, so
|
|
10
|
+
// everything the Bun server reads or imports off disk at boot is generated
|
|
11
|
+
// into `.bosia/` as static code the worker bundle can inline.
|
|
12
|
+
|
|
13
|
+
// Build artifacts the runtime reads at boot. Keep in sync with readArtifact() callers.
|
|
14
|
+
export const RUNTIME_ARTIFACTS = ["manifest.json", "app-html.json", "route-manifest.json"];
|
|
15
|
+
|
|
16
|
+
const HEADER = "// Auto-generated by Bosia. Do not edit.\n";
|
|
17
|
+
|
|
18
|
+
function writeBosiaFile(cwd: string, name: string, code: string): string {
|
|
19
|
+
const target = join(cwd, ".bosia", name);
|
|
20
|
+
mkdirSync(join(cwd, ".bosia"), { recursive: true });
|
|
21
|
+
writeFileSync(target, HEADER + code);
|
|
22
|
+
return target;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Write `.bosia/artifacts.ts` — a filesystem-free drop-in for artifacts.ts with
|
|
27
|
+
* every runtime artifact inlined. Call after the build has written them.
|
|
28
|
+
*/
|
|
29
|
+
export function generateArtifactsModule(outDir: string = OUT_DIR, cwd = process.cwd()): string {
|
|
30
|
+
const files: Record<string, unknown> = {};
|
|
31
|
+
for (const name of RUNTIME_ARTIFACTS) {
|
|
32
|
+
const value = readArtifact(name, outDir);
|
|
33
|
+
if (value !== undefined) files[name] = value;
|
|
34
|
+
}
|
|
35
|
+
return writeBosiaFile(
|
|
36
|
+
cwd,
|
|
37
|
+
"artifacts.ts",
|
|
38
|
+
`const files: Record<string, unknown> = ${JSON.stringify(files)};\n\n` +
|
|
39
|
+
"export function readArtifact<T>(name: string, _dir?: string): T | undefined {\n" +
|
|
40
|
+
"\treturn files[name] as T | undefined;\n" +
|
|
41
|
+
"}\n",
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Write `.bosia/runtime.workers.ts` (`bosia:workers-runtime`) — static imports
|
|
47
|
+
* of the app's `src/hooks.server.ts` and `bosia.config.*`, which the Bun server
|
|
48
|
+
* loads with a dynamic `import()` of an absolute path.
|
|
49
|
+
*/
|
|
50
|
+
export function generateWorkersRuntime(cwd = process.cwd()): string {
|
|
51
|
+
const hooksPath = join(cwd, "src", "hooks.server.ts");
|
|
52
|
+
const configPath = findConfigPath(cwd);
|
|
53
|
+
const lines: string[] = [];
|
|
54
|
+
if (existsSync(hooksPath)) {
|
|
55
|
+
lines.push(`import * as hooks from ${JSON.stringify(toPosix(hooksPath))};`);
|
|
56
|
+
lines.push(`export const handle = typeof hooks.handle === "function" ? hooks.handle : null;`);
|
|
57
|
+
} else {
|
|
58
|
+
lines.push("export const handle = null;");
|
|
59
|
+
}
|
|
60
|
+
if (configPath) {
|
|
61
|
+
lines.push(`export { default as config } from ${JSON.stringify(toPosix(configPath))};`);
|
|
62
|
+
} else {
|
|
63
|
+
lines.push("export const config = {};");
|
|
64
|
+
}
|
|
65
|
+
return writeBosiaFile(cwd, "runtime.workers.ts", lines.join("\n") + "\n");
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Write `wrangler.jsonc` at the app root, unless one exists — it's where the
|
|
70
|
+
* user adds bindings (D1, KV, vars), so a rebuild must never overwrite it.
|
|
71
|
+
* Returns the path when written, null when left alone.
|
|
72
|
+
*/
|
|
73
|
+
export function generateWranglerConfig(
|
|
74
|
+
cwd = process.cwd(),
|
|
75
|
+
outDir: string = OUT_DIR,
|
|
76
|
+
): string | null {
|
|
77
|
+
const target = join(cwd, "wrangler.jsonc");
|
|
78
|
+
if (
|
|
79
|
+
existsSync(target) ||
|
|
80
|
+
existsSync(join(cwd, "wrangler.json")) ||
|
|
81
|
+
existsSync(join(cwd, "wrangler.toml"))
|
|
82
|
+
)
|
|
83
|
+
return null;
|
|
84
|
+
let name = basename(cwd);
|
|
85
|
+
try {
|
|
86
|
+
name = JSON.parse(readFileSync(join(cwd, "package.json"), "utf-8")).name || name;
|
|
87
|
+
} catch {}
|
|
88
|
+
// Worker names: lowercase letters, digits and dashes. Drops an npm @scope/.
|
|
89
|
+
name =
|
|
90
|
+
name
|
|
91
|
+
.replace(/^@[^/]+\//, "")
|
|
92
|
+
.toLowerCase()
|
|
93
|
+
.replace(/[^a-z0-9-]+/g, "-") || "bosia-app";
|
|
94
|
+
const dir = toPosix(outDir).replace(/^\.\//, "");
|
|
95
|
+
const config = {
|
|
96
|
+
name,
|
|
97
|
+
main: `./${dir}/worker/index.js`,
|
|
98
|
+
compatibility_date: "2025-09-01",
|
|
99
|
+
compatibility_flags: ["nodejs_compat"],
|
|
100
|
+
// Served before the worker runs; misses fall through to it.
|
|
101
|
+
assets: { directory: `./${dir}/static` },
|
|
102
|
+
};
|
|
103
|
+
writeFileSync(
|
|
104
|
+
target,
|
|
105
|
+
"// Generated once by `bosia build --target=workers`. Yours to edit — add bindings here.\n" +
|
|
106
|
+
JSON.stringify(config, null, "\t") +
|
|
107
|
+
"\n",
|
|
108
|
+
);
|
|
109
|
+
return target;
|
|
110
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { readdirSync, readFileSync } from "fs";
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
|
|
4
|
+
import { toPosix } from "./paths.ts";
|
|
5
|
+
|
|
6
|
+
// Cloudflare Workers has no `Bun` global and no real filesystem, so server code
|
|
7
|
+
// that uses either builds fine and then throws on the first request. This scan
|
|
8
|
+
// fails the workers build first.
|
|
9
|
+
//
|
|
10
|
+
// Scans the user's server files only, by path — a Bun call in a plain
|
|
11
|
+
// src/lib/*.ts helper that a loader imports is missed. Upgrade path: run the
|
|
12
|
+
// check from the workers plugin's onLoad, which sees every bundled file.
|
|
13
|
+
|
|
14
|
+
export interface WorkersGuardHit {
|
|
15
|
+
file: string;
|
|
16
|
+
line: number;
|
|
17
|
+
col: number;
|
|
18
|
+
message: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const SERVER_FILE = /(^|\/)(hooks\.server\.(ts|js)|\+server\.(ts|js)|\+[^/]*\.server\.(ts|js))$/;
|
|
22
|
+
const LIB_SERVER = /^lib\/server\/.*\.(ts|js)$/;
|
|
23
|
+
const FS_MODULES = new Set(["fs", "node:fs", "fs/promises", "node:fs/promises"]);
|
|
24
|
+
|
|
25
|
+
function isServerFile(rel: string): boolean {
|
|
26
|
+
return (
|
|
27
|
+
(rel.startsWith("routes/") && SERVER_FILE.test(rel)) ||
|
|
28
|
+
rel === "hooks.server.ts" ||
|
|
29
|
+
rel === "hooks.server.js" ||
|
|
30
|
+
LIB_SERVER.test(rel)
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function badImport(spec: string): string | null {
|
|
35
|
+
if (FS_MODULES.has(spec)) return `imports "${spec}" — Workers has no filesystem`;
|
|
36
|
+
if (spec === "bun" || spec.startsWith("bun:")) return `imports "${spec}" — Bun-only module`;
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function findWorkersIncompatible(srcDir = "./src"): WorkersGuardHit[] {
|
|
41
|
+
const hits: WorkersGuardHit[] = [];
|
|
42
|
+
let entries: string[];
|
|
43
|
+
try {
|
|
44
|
+
entries = readdirSync(srcDir, { recursive: true }) as string[];
|
|
45
|
+
} catch {
|
|
46
|
+
return hits;
|
|
47
|
+
}
|
|
48
|
+
for (const entry of entries) {
|
|
49
|
+
const rel = toPosix(entry);
|
|
50
|
+
if (!isServerFile(rel)) continue;
|
|
51
|
+
// Posix on every OS, so reports and callers see one path shape.
|
|
52
|
+
const file = toPosix(join(srcDir, entry));
|
|
53
|
+
const source = readFileSync(file, "utf-8");
|
|
54
|
+
const lines = source.split("\n");
|
|
55
|
+
|
|
56
|
+
let imports: { path: string }[] = [];
|
|
57
|
+
try {
|
|
58
|
+
imports = new Bun.Transpiler({ loader: rel.endsWith(".js") ? "js" : "ts" }).scan(
|
|
59
|
+
source,
|
|
60
|
+
).imports;
|
|
61
|
+
} catch {
|
|
62
|
+
continue; // syntax error — the real build reports it better
|
|
63
|
+
}
|
|
64
|
+
for (const { path } of imports) {
|
|
65
|
+
const message = badImport(path);
|
|
66
|
+
if (!message) continue;
|
|
67
|
+
const quoted = new RegExp(`["']${path.replace(/[.*+?^${}()|[\]\\/]/g, "\\$&")}["']`);
|
|
68
|
+
const i = Math.max(
|
|
69
|
+
0,
|
|
70
|
+
lines.findIndex((l) => quoted.test(l)),
|
|
71
|
+
);
|
|
72
|
+
hits.push({ file, line: i + 1, col: (lines[i]?.search(quoted) ?? 0) + 1, message });
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Line-based, so a `Bun.` inside a string or a multi-line block comment
|
|
76
|
+
// also trips it. `typeof Bun` guards on the same line are allowed.
|
|
77
|
+
lines.forEach((l, i) => {
|
|
78
|
+
const t = l.trimStart();
|
|
79
|
+
if (t.startsWith("//") || t.startsWith("*") || l.includes("typeof Bun")) return;
|
|
80
|
+
const m = /\bBun\.(\w+)/.exec(l);
|
|
81
|
+
if (m)
|
|
82
|
+
hits.push({
|
|
83
|
+
file,
|
|
84
|
+
line: i + 1,
|
|
85
|
+
col: m.index + 1,
|
|
86
|
+
message: `uses Bun.${m[1]} — no Bun global on Workers`,
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
return hits;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Formatted failure text, or null when clean. BOSIA_WORKERS_GUARD=0 downgrades to a warning. */
|
|
94
|
+
export function workersGuardReport(srcDir = "./src"): string | null {
|
|
95
|
+
const hits = findWorkersIncompatible(srcDir);
|
|
96
|
+
if (hits.length === 0) return null;
|
|
97
|
+
const body = hits.map((h) => ` ${h.file}:${h.line}:${h.col} ${h.message}`).join("\n");
|
|
98
|
+
const text =
|
|
99
|
+
`Workers guard: server code uses APIs Cloudflare Workers doesn't have\n\n${body}\n\n` +
|
|
100
|
+
"Use fetch/Web APIs or bindings (event.platform.env) instead. " +
|
|
101
|
+
"Set BOSIA_WORKERS_GUARD=0 to downgrade this to a warning.";
|
|
102
|
+
if (process.env.BOSIA_WORKERS_GUARD === "0") {
|
|
103
|
+
console.warn(`⚠️ ${text}`);
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
return text;
|
|
107
|
+
}
|
package/src/lib/index.ts
CHANGED
|
@@ -30,6 +30,8 @@ export type {
|
|
|
30
30
|
ResolveFunction,
|
|
31
31
|
Cookies,
|
|
32
32
|
CookieOptions,
|
|
33
|
+
Platform,
|
|
34
|
+
PlatformEnv,
|
|
33
35
|
} from "../core/hooks.ts";
|
|
34
36
|
export type { CsrfConfig } from "../core/csrf.ts";
|
|
35
37
|
export type { CorsConfig } from "../core/cors.ts";
|
|
@@ -41,4 +43,5 @@ export type {
|
|
|
41
43
|
DevContext,
|
|
42
44
|
RenderContext,
|
|
43
45
|
BuildTarget,
|
|
46
|
+
RuntimeTarget,
|
|
44
47
|
} from "../core/types/plugin.ts";
|
package/src/lib/server.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# {{PROJECT_NAME}}
|
|
2
2
|
|
|
3
|
-
A [Bosia](https://github.com/bosapi/bosia) project — SSR · Svelte 5 · Bun
|
|
3
|
+
A [Bosia](https://github.com/bosapi/bosia) project — SSR · Svelte 5 · Bun.
|
|
4
4
|
|
|
5
5
|
Bosia is **production-ready out of the box** — security (CSRF, XSS, secure cookies, headers), performance (response cache, gzip, prerendering), and reliability (graceful shutdown, request backpressure, crash backoff) are all built in.
|
|
6
6
|
|
|
@@ -2,7 +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">A Bosia project — SSR + Svelte 5 + Bun
|
|
5
|
+
<p class="text-muted-foreground text-lg">A Bosia project — SSR + Svelte 5 + Bun</p>
|
|
6
6
|
</div>
|
|
7
7
|
|
|
8
8
|
<div class="mt-4 flex gap-3">
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
|
|
39
39
|
<svelte:head>
|
|
40
40
|
<title>Bosia Demo</title>
|
|
41
|
-
<meta name="description" content="Bosia Demo — SSR + Svelte 5 + Bun
|
|
41
|
+
<meta name="description" content="Bosia Demo — SSR + Svelte 5 + Bun" />
|
|
42
42
|
</svelte:head>
|
|
43
43
|
|
|
44
44
|
<div class="space-y-12">
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
Bosia Demo
|
|
50
50
|
</h1>
|
|
51
51
|
<p class="text-xl text-muted-foreground max-w-xl">
|
|
52
|
-
A minimalist fullstack framework — SSR, Svelte 5 Runes,
|
|
52
|
+
A minimalist fullstack framework — SSR, Svelte 5 Runes, and Bun.
|
|
53
53
|
</p>
|
|
54
54
|
</div>
|
|
55
55
|
|
|
@@ -5,11 +5,11 @@
|
|
|
5
5
|
<div class="space-y-6 max-w-2xl">
|
|
6
6
|
<h1 class="text-4xl font-bold tracking-tight">About Bosia Demo</h1>
|
|
7
7
|
<p class="text-muted-foreground text-lg">
|
|
8
|
-
A minimalist fullstack framework built on Bun
|
|
8
|
+
A minimalist fullstack framework built on Bun and Svelte 5.
|
|
9
9
|
</p>
|
|
10
10
|
|
|
11
11
|
<ul class="space-y-2 text-foreground">
|
|
12
|
-
{#each ["Bun runtime — fast builds, native TypeScript", "
|
|
12
|
+
{#each ["Bun runtime — fast builds, native TypeScript", "Svelte 5 Runes — fine-grained reactivity", "Isomorphic SSR with client hydration", "File-based routing (SvelteKit-compatible conventions)", "Nested layouts and route groups (public), (auth), (admin)", "Dynamic params [slug] and catch-all [...rest]", "Server loaders with parent() data threading", "Hooks — sequence() middleware for auth, logging, etc.", "Component registry — bun x bosia@latest add button", "Feature registry — bosia feat login"] as item}
|
|
13
13
|
<li class="flex items-start gap-2">
|
|
14
14
|
<span class="text-primary mt-0.5">✓</span>
|
|
15
15
|
<span>{item}</span>
|