bosia 0.6.20 → 0.6.22
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 +2 -2
- package/src/cli/add.ts +5 -5
- package/src/cli/addRouter.ts +53 -0
- package/src/cli/block.ts +19 -12
- package/src/cli/create.ts +6 -11
- package/src/cli/feat.ts +19 -22
- package/src/cli/index.ts +25 -23
- package/src/cli/manifest.ts +1 -1
- package/src/cli/registry.ts +40 -2
- package/src/core/build.ts +1 -3
- package/src/core/client/App.svelte +3 -8
- package/src/core/client/page.svelte.ts +28 -0
- package/src/core/client/router.svelte.ts +3 -8
- package/src/core/config.ts +1 -4
- package/src/core/cookies.ts +1 -2
- package/src/core/dev-500.ts +1 -1
- package/src/core/html.ts +1 -2
- package/src/core/plugin.ts +1 -3
- package/src/core/plugins/inspector/bun-plugin.ts +1 -4
- package/src/core/plugins/inspector/index.ts +45 -59
- package/src/core/renderer.ts +3 -10
- package/src/core/routeTypes.ts +3 -9
- package/src/core/scanner.ts +1 -3
- package/src/core/server.ts +9 -34
- package/src/core/staticManifest.ts +1 -3
- package/src/core/svelteAudit.ts +2 -5
- package/src/core/svelteCompiler.ts +2 -8
- package/src/lib/client.ts +1 -0
- package/templates/default/.prettierignore +1 -0
- package/templates/demo/.prettierignore +1 -0
- package/templates/shop/.env.example +12 -0
- package/templates/shop/.prettierignore +7 -0
- package/templates/shop/.prettierrc.json +9 -0
- package/templates/shop/README.md +62 -0
- package/templates/shop/_gitignore +12 -0
- package/templates/shop/bosia.config.ts +10 -0
- package/templates/shop/instructions.txt +8 -0
- package/templates/shop/package.json +27 -0
- package/templates/shop/public/favicon.svg +14 -0
- package/templates/shop/public/logo-dark.svg +14 -0
- package/templates/shop/public/logo-light.svg +14 -0
- package/templates/shop/src/app.css +132 -0
- package/templates/shop/src/app.d.ts +14 -0
- package/templates/shop/src/app.html +11 -0
- package/templates/shop/src/hooks.server.ts +21 -0
- package/templates/shop/src/lib/utils.ts +1 -0
- package/templates/shop/src/routes/(private)/+layout.server.ts +10 -0
- package/templates/shop/src/routes/(private)/+layout.svelte +14 -0
- package/templates/shop/src/routes/(private)/dashboard/+page.svelte +11 -0
- package/templates/shop/src/routes/(public)/+layout.svelte +13 -0
- package/templates/shop/src/routes/(public)/+page.svelte +30 -0
- package/templates/shop/src/routes/+error.svelte +19 -0
- package/templates/shop/src/routes/+layout.server.ts +9 -0
- package/templates/shop/src/routes/+layout.svelte +6 -0
- package/templates/shop/template.json +10 -0
- package/templates/shop/tsconfig.json +22 -0
- package/templates/todo/.prettierignore +1 -0
- package/templates/todo/template.json +4 -1
|
@@ -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
|
-
|
|
200
|
-
{
|
|
201
|
-
|
|
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
|
-
|
|
250
|
-
{
|
|
251
|
-
|
|
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
|
-
|
|
271
|
-
{
|
|
272
|
-
|
|
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
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
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(":
|
|
302
|
+
ctrl.enqueue(encode(":ping\n\n"));
|
|
308
303
|
} catch {
|
|
304
|
+
clearInterval(ping);
|
|
309
305
|
sseClients.delete(ctrl);
|
|
310
|
-
return;
|
|
311
306
|
}
|
|
312
|
-
|
|
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
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
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;
|
package/src/core/renderer.ts
CHANGED
|
@@ -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
|
}
|
package/src/core/routeTypes.ts
CHANGED
|
@@ -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);
|
package/src/core/scanner.ts
CHANGED
|
@@ -93,9 +93,7 @@ export function scanRoutes(): RouteManifest {
|
|
|
93
93
|
? join(dir, "+page.server.ts")
|
|
94
94
|
: null;
|
|
95
95
|
|
|
96
|
-
const pageTs = pageServerFile
|
|
97
|
-
? readTrailingSlash(join(ROUTES_DIR, pageServerFile))
|
|
98
|
-
: null;
|
|
96
|
+
const pageTs = pageServerFile ? readTrailingSlash(join(ROUTES_DIR, pageServerFile)) : null;
|
|
99
97
|
const effectiveTs: TrailingSlash = pageTs ?? currentTrailingSlash;
|
|
100
98
|
|
|
101
99
|
pages.push({
|
package/src/core/server.ts
CHANGED
|
@@ -234,21 +234,11 @@ async function resolve(event: RequestEvent): Promise<Response> {
|
|
|
234
234
|
const mask = invalidatedBits
|
|
235
235
|
? buildMaskFromBits(
|
|
236
236
|
invalidatedBits,
|
|
237
|
-
pageMatch?.route
|
|
238
|
-
? ((pageMatch.route as any).layoutModules?.length ?? 0)
|
|
239
|
-
: 0,
|
|
237
|
+
pageMatch?.route ? ((pageMatch.route as any).layoutModules?.length ?? 0) : 0,
|
|
240
238
|
)
|
|
241
239
|
: undefined;
|
|
242
240
|
const runLoad = async () => {
|
|
243
|
-
const data = await loadRouteData(
|
|
244
|
-
routeUrl,
|
|
245
|
-
locals,
|
|
246
|
-
request,
|
|
247
|
-
cookies,
|
|
248
|
-
null,
|
|
249
|
-
pageMatch,
|
|
250
|
-
mask,
|
|
251
|
-
);
|
|
241
|
+
const data = await loadRouteData(routeUrl, locals, request, cookies, null, pageMatch, mask);
|
|
252
242
|
|
|
253
243
|
let metadata = null;
|
|
254
244
|
if (pageMatch) {
|
|
@@ -278,14 +268,10 @@ async function resolve(event: RequestEvent): Promise<Response> {
|
|
|
278
268
|
? `${dedupKey(routeUrl)}|m=${invalidatedBits}`
|
|
279
269
|
: dedupKey(routeUrl);
|
|
280
270
|
const result =
|
|
281
|
-
pageMatch?.route.scope === "private"
|
|
282
|
-
? await runLoad()
|
|
283
|
-
: await dedup(dedupK, runLoad);
|
|
271
|
+
pageMatch?.route.scope === "private" ? await runLoad() : await dedup(dedupK, runLoad);
|
|
284
272
|
|
|
285
273
|
const cookiesWereAccessed = (cookies as CookieJar).accessed || result.cookiesAccessed;
|
|
286
|
-
const cc = cookiesWereAccessed
|
|
287
|
-
? "private, no-cache"
|
|
288
|
-
: "public, max-age=0, must-revalidate";
|
|
274
|
+
const cc = cookiesWereAccessed ? "private, no-cache" : "public, max-age=0, must-revalidate";
|
|
289
275
|
|
|
290
276
|
if (!result.data) {
|
|
291
277
|
return compress(
|
|
@@ -461,9 +447,7 @@ async function resolve(event: RequestEvent): Promise<Response> {
|
|
|
461
447
|
if (hit) {
|
|
462
448
|
return new Response(
|
|
463
449
|
Bun.file(hit.absPath),
|
|
464
|
-
hit.cacheControl
|
|
465
|
-
? { headers: { "Cache-Control": hit.cacheControl } }
|
|
466
|
-
: undefined,
|
|
450
|
+
hit.cacheControl ? { headers: { "Cache-Control": hit.cacheControl } } : undefined,
|
|
467
451
|
);
|
|
468
452
|
}
|
|
469
453
|
return new Response("Not Found", { status: 404 });
|
|
@@ -510,9 +494,7 @@ async function resolve(event: RequestEvent): Promise<Response> {
|
|
|
510
494
|
if (!isDev) {
|
|
511
495
|
// Try both `<path>/index.html` (always/ignore mode) and `<path>.html` (never mode)
|
|
512
496
|
const prerenderCandidates =
|
|
513
|
-
path === "/"
|
|
514
|
-
? ["index.html"]
|
|
515
|
-
: [`${path}/index.html`, `${path.replace(/\/$/, "")}.html`];
|
|
497
|
+
path === "/" ? ["index.html"] : [`${path}/index.html`, `${path.replace(/\/$/, "")}.html`];
|
|
516
498
|
for (const candidate of prerenderCandidates) {
|
|
517
499
|
const prerenderPath = safePath(`${OUT_DIR}/prerendered`, candidate);
|
|
518
500
|
if (!prerenderPath) continue;
|
|
@@ -533,10 +515,7 @@ async function resolve(event: RequestEvent): Promise<Response> {
|
|
|
533
515
|
|
|
534
516
|
// Trailing-slash canonicalization — 308 preserves method (form POSTs included)
|
|
535
517
|
if (pageMatch) {
|
|
536
|
-
const canonical = canonicalPathname(
|
|
537
|
-
path,
|
|
538
|
-
(pageMatch.route as any).trailingSlash ?? "never",
|
|
539
|
-
);
|
|
518
|
+
const canonical = canonicalPathname(path, (pageMatch.route as any).trailingSlash ?? "never");
|
|
540
519
|
if (canonical !== null) {
|
|
541
520
|
return new Response(null, {
|
|
542
521
|
status: 308,
|
|
@@ -854,15 +833,11 @@ async function handleRequest(request: Request, url: URL): Promise<Response> {
|
|
|
854
833
|
function parseCorsMaxAge(value?: string): number | undefined {
|
|
855
834
|
if (!value) return undefined;
|
|
856
835
|
if (!/^\d+$/.test(value)) {
|
|
857
|
-
throw new Error(
|
|
858
|
-
`Invalid CORS_MAX_AGE: "${value}" — must be a non-negative integer (seconds)`,
|
|
859
|
-
);
|
|
836
|
+
throw new Error(`Invalid CORS_MAX_AGE: "${value}" — must be a non-negative integer (seconds)`);
|
|
860
837
|
}
|
|
861
838
|
const n = parseInt(value, 10);
|
|
862
839
|
if (!Number.isFinite(n) || n > Number.MAX_SAFE_INTEGER) {
|
|
863
|
-
throw new Error(
|
|
864
|
-
`Invalid CORS_MAX_AGE: "${value}" — must be a non-negative integer (seconds)`,
|
|
865
|
-
);
|
|
840
|
+
throw new Error(`Invalid CORS_MAX_AGE: "${value}" — must be a non-negative integer (seconds)`);
|
|
866
841
|
}
|
|
867
842
|
return n;
|
|
868
843
|
}
|
|
@@ -50,9 +50,7 @@ export function buildStaticManifest(outDir: string): StaticManifest {
|
|
|
50
50
|
const clientRoot = join(outAbs, "client");
|
|
51
51
|
if (existsSync(clientRoot)) {
|
|
52
52
|
for (const { abs, rel } of walk(clientRoot)) {
|
|
53
|
-
const cacheControl = HASHED_BASENAME.test(basename(rel))
|
|
54
|
-
? IMMUTABLE_CACHE
|
|
55
|
-
: DEFAULT_CACHE;
|
|
53
|
+
const cacheControl = HASHED_BASENAME.test(basename(rel)) ? IMMUTABLE_CACHE : DEFAULT_CACHE;
|
|
56
54
|
addOnce(manifest, `/dist/client/${rel}`, { absPath: abs, cacheControl });
|
|
57
55
|
}
|
|
58
56
|
}
|
package/src/core/svelteAudit.ts
CHANGED
|
@@ -121,9 +121,7 @@ function extractBindings(ast: AnyNode): Binding[] {
|
|
|
121
121
|
case "ImportDeclaration": {
|
|
122
122
|
const sourceNode = stmt.source as AnyNode | undefined;
|
|
123
123
|
const source =
|
|
124
|
-
sourceNode && typeof sourceNode.value === "string"
|
|
125
|
-
? (sourceNode.value as string)
|
|
126
|
-
: "";
|
|
124
|
+
sourceNode && typeof sourceNode.value === "string" ? (sourceNode.value as string) : "";
|
|
127
125
|
const specs = stmt.specifiers as AnyNode[] | undefined;
|
|
128
126
|
if (!Array.isArray(specs)) break;
|
|
129
127
|
for (const spec of specs) {
|
|
@@ -344,8 +342,7 @@ function collectTemplateRefs(source: string, fragment: AnyNode): TemplateRef[] {
|
|
|
344
342
|
// name into the surrounding scope so `<MySnippet/>` doesn't false-
|
|
345
343
|
// positive. The expression's name is the snippet's identifier.
|
|
346
344
|
const expr = n.expression as AnyNode | undefined;
|
|
347
|
-
const snippetName =
|
|
348
|
-
expr && typeof expr.name === "string" ? (expr.name as string) : null;
|
|
345
|
+
const snippetName = expr && typeof expr.name === "string" ? (expr.name as string) : null;
|
|
349
346
|
if (snippetName && scopeStack.length > 0) {
|
|
350
347
|
scopeStack[scopeStack.length - 1].add(snippetName);
|
|
351
348
|
} else if (snippetName) {
|
|
@@ -113,10 +113,7 @@ export function makeBosiaSvelteCompiler(target: "browser" | "bun"): BunPlugin {
|
|
|
113
113
|
// Server (Bun) compile output has different line numbers and would
|
|
114
114
|
// clobber the client entry under the same cache key.
|
|
115
115
|
if (dev && target === "browser" && result.js.map) {
|
|
116
|
-
const m =
|
|
117
|
-
typeof result.js.map === "string"
|
|
118
|
-
? JSON.parse(result.js.map)
|
|
119
|
-
: result.js.map;
|
|
116
|
+
const m = typeof result.js.map === "string" ? JSON.parse(result.js.map) : result.js.map;
|
|
120
117
|
svelteMapCache.set(args.path, m);
|
|
121
118
|
}
|
|
122
119
|
const contents = dev ? fixBindShadow(result.js.code) : result.js.code;
|
|
@@ -134,10 +131,7 @@ export function makeBosiaSvelteCompiler(target: "browser" | "bun"): BunPlugin {
|
|
|
134
131
|
filename: args.path,
|
|
135
132
|
});
|
|
136
133
|
if (dev && target === "browser" && result.js.map) {
|
|
137
|
-
const m =
|
|
138
|
-
typeof result.js.map === "string"
|
|
139
|
-
? JSON.parse(result.js.map)
|
|
140
|
-
: result.js.map;
|
|
134
|
+
const m = typeof result.js.map === "string" ? JSON.parse(result.js.map) : result.js.map;
|
|
141
135
|
svelteMapCache.set(args.path, m);
|
|
142
136
|
}
|
|
143
137
|
return { contents: result.js.code, loader: "js" };
|
package/src/lib/client.ts
CHANGED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# {{PROJECT_NAME}}
|
|
2
|
+
|
|
3
|
+
An online-store starter built with [Bosia](https://github.com/bosapi/bosia) — auth, RBAC, S3-backed uploads, and the shop domain (products / orders / cart).
|
|
4
|
+
|
|
5
|
+
## Prerequisites
|
|
6
|
+
|
|
7
|
+
- [Bun](https://bun.sh/) v1.1+
|
|
8
|
+
- PostgreSQL running locally or remotely
|
|
9
|
+
- An S3-compatible bucket (AWS S3, Cloudflare R2, MinIO, ...)
|
|
10
|
+
|
|
11
|
+
## Getting Started
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
cp .env.example .env
|
|
15
|
+
# fill DATABASE_URL, SESSION_SECRET, and S3_* in .env
|
|
16
|
+
|
|
17
|
+
bun run db:generate
|
|
18
|
+
bun run db:migrate
|
|
19
|
+
bun run db:seed
|
|
20
|
+
|
|
21
|
+
bun x bosia dev
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Visit [http://localhost:9000](http://localhost:9000). The **first account you register becomes the admin** (gets `('*','*')` via the RBAC bootstrap seed).
|
|
25
|
+
|
|
26
|
+
## What ships
|
|
27
|
+
|
|
28
|
+
| Feature | Path |
|
|
29
|
+
| ------------- | ---------------------------------------------------------------------- |
|
|
30
|
+
| `auth` | `src/features/auth/`, `(public)/login`, `(public)/register`, `/logout` |
|
|
31
|
+
| `rbac` | `src/features/rbac/`, `locals.can(r,a,scope?)` |
|
|
32
|
+
| `file-upload` | `src/features/file-upload/`, `POST /api/files` (S3 via `Bun.s3`) |
|
|
33
|
+
| `shop` | `src/features/shop/` (products / orders / cart services) |
|
|
34
|
+
|
|
35
|
+
## Routes
|
|
36
|
+
|
|
37
|
+
- `/` — public landing
|
|
38
|
+
- `/login`, `/register`, `POST /logout`
|
|
39
|
+
- `/dashboard` — gated; redirects to `/login` if unauthenticated
|
|
40
|
+
|
|
41
|
+
## Scripts
|
|
42
|
+
|
|
43
|
+
| Command | Description |
|
|
44
|
+
| --------------------- | --------------------------------------------- |
|
|
45
|
+
| `bun x bosia dev` | Dev server with HMR |
|
|
46
|
+
| `bun x bosia build` | Production build |
|
|
47
|
+
| `bun run db:generate` | Generate migration from schema changes |
|
|
48
|
+
| `bun run db:migrate` | Apply pending migrations |
|
|
49
|
+
| `bun run db:seed` | Run pending seed files (incl. RBAC bootstrap) |
|
|
50
|
+
|
|
51
|
+
## S3 storage
|
|
52
|
+
|
|
53
|
+
Uses native `Bun.s3` (no `@aws-sdk/*` dependency). Set the standard env vars:
|
|
54
|
+
|
|
55
|
+
```
|
|
56
|
+
STORAGE_DRIVER=s3
|
|
57
|
+
S3_BUCKET=...
|
|
58
|
+
S3_REGION=...
|
|
59
|
+
S3_ACCESS_KEY_ID=...
|
|
60
|
+
S3_SECRET_ACCESS_KEY=...
|
|
61
|
+
S3_ENDPOINT= # optional, for R2/MinIO
|
|
62
|
+
```
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { defineConfig } from "bosia";
|
|
2
|
+
import { inspector } from "bosia/plugins/inspector";
|
|
3
|
+
|
|
4
|
+
export default defineConfig({
|
|
5
|
+
plugins: [
|
|
6
|
+
// Dev-only: Alt+click any element on the page to open its source in your editor.
|
|
7
|
+
// Change `editor` to "cursor" or "zed" if you don't use VS Code.
|
|
8
|
+
inspector({ editor: "code" }),
|
|
9
|
+
],
|
|
10
|
+
});
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{PROJECT_NAME}}",
|
|
3
|
+
"private": true,
|
|
4
|
+
"type": "module",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"dev": "bosia dev",
|
|
7
|
+
"build": "bosia build",
|
|
8
|
+
"start": "bosia start",
|
|
9
|
+
"check": "tsc --noEmit && prettier --check .",
|
|
10
|
+
"format": "prettier --write .",
|
|
11
|
+
"format:check": "prettier --check ."
|
|
12
|
+
},
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"bosia": "^{{BOSIA_VERSION}}",
|
|
15
|
+
"svelte": "^5.20.0",
|
|
16
|
+
"tailwind-merge": "^3.5.0",
|
|
17
|
+
"drizzle-orm": "^0.44.0",
|
|
18
|
+
"postgres": "^3.4.0"
|
|
19
|
+
},
|
|
20
|
+
"devDependencies": {
|
|
21
|
+
"@types/bun": "latest",
|
|
22
|
+
"prettier": "^3.3.0",
|
|
23
|
+
"prettier-plugin-svelte": "^3.2.0",
|
|
24
|
+
"typescript": "^5",
|
|
25
|
+
"drizzle-kit": "^0.31.0"
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<svg width="200" height="200" viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<!-- Top block -->
|
|
3
|
+
<rect fill="currentColor" x="50" y="50" width="28" height="28" rx="6"/>
|
|
4
|
+
<rect fill="currentColor" x="86" y="50" width="60" height="28" rx="6"/>
|
|
5
|
+
|
|
6
|
+
<!-- Middle block -->
|
|
7
|
+
<rect fill="currentColor" x="86" y="86" width="72" height="28" rx="6"/>
|
|
8
|
+
|
|
9
|
+
<!-- Bottom block -->
|
|
10
|
+
<rect fill="currentColor" x="86" y="122" width="60" height="28" rx="6"/>
|
|
11
|
+
|
|
12
|
+
<!-- Connector bar on left -->
|
|
13
|
+
<rect fill="currentColor" x="50" y="50" width="28" height="100" rx="6"/>
|
|
14
|
+
</svg>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<svg width="200" height="200" viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<!-- Top block -->
|
|
3
|
+
<rect fill="#f0f0f0" x="50" y="50" width="28" height="28" rx="6"/>
|
|
4
|
+
<rect fill="#f0f0f0" x="86" y="50" width="60" height="28" rx="6"/>
|
|
5
|
+
|
|
6
|
+
<!-- Middle block -->
|
|
7
|
+
<rect fill="#f0f0f0" x="86" y="86" width="72" height="28" rx="6"/>
|
|
8
|
+
|
|
9
|
+
<!-- Bottom block -->
|
|
10
|
+
<rect fill="#f0f0f0" x="86" y="122" width="60" height="28" rx="6"/>
|
|
11
|
+
|
|
12
|
+
<!-- Connector bar on left -->
|
|
13
|
+
<rect fill="#f0f0f0" x="50" y="50" width="28" height="100" rx="6"/>
|
|
14
|
+
</svg>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<svg width="200" height="200" viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<!-- Top block -->
|
|
3
|
+
<rect fill="#1a1a1a" x="50" y="50" width="28" height="28" rx="6"/>
|
|
4
|
+
<rect fill="#1a1a1a" x="86" y="50" width="60" height="28" rx="6"/>
|
|
5
|
+
|
|
6
|
+
<!-- Middle block -->
|
|
7
|
+
<rect fill="#1a1a1a" x="86" y="86" width="72" height="28" rx="6"/>
|
|
8
|
+
|
|
9
|
+
<!-- Bottom block -->
|
|
10
|
+
<rect fill="#1a1a1a" x="86" y="122" width="60" height="28" rx="6"/>
|
|
11
|
+
|
|
12
|
+
<!-- Connector bar on left -->
|
|
13
|
+
<rect fill="#1a1a1a" x="50" y="50" width="28" height="100" rx="6"/>
|
|
14
|
+
</svg>
|