bosia 0.2.3 → 0.3.0
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 +39 -39
- package/package.json +56 -54
- package/src/ambient.d.ts +31 -0
- package/src/cli/add.ts +120 -114
- package/src/cli/build.ts +10 -10
- package/src/cli/create.ts +142 -137
- package/src/cli/dev.ts +8 -8
- package/src/cli/feat.ts +266 -258
- package/src/cli/index.ts +51 -42
- package/src/cli/registry.ts +136 -115
- package/src/cli/start.ts +17 -17
- package/src/cli/test.ts +25 -0
- package/src/core/build.ts +72 -56
- package/src/core/client/App.svelte +177 -156
- package/src/core/client/appState.svelte.ts +33 -31
- package/src/core/client/enhance.ts +83 -78
- package/src/core/client/hydrate.ts +95 -81
- package/src/core/client/prefetch.ts +101 -94
- package/src/core/client/router.svelte.ts +64 -51
- package/src/core/cookies.ts +70 -66
- package/src/core/cors.ts +44 -35
- package/src/core/csrf.ts +38 -38
- package/src/core/dedup.ts +17 -17
- package/src/core/dev.ts +165 -168
- package/src/core/env.ts +155 -148
- package/src/core/envCodegen.ts +73 -73
- package/src/core/errors.ts +48 -49
- package/src/core/hooks.ts +50 -50
- package/src/core/html.ts +184 -145
- package/src/core/matcher.ts +130 -121
- package/src/core/paths.ts +8 -10
- package/src/core/plugin.ts +113 -107
- package/src/core/prerender.ts +191 -122
- package/src/core/renderer.ts +359 -286
- package/src/core/routeFile.ts +140 -127
- package/src/core/routeTypes.ts +144 -83
- package/src/core/scanner.ts +125 -95
- package/src/core/server.ts +538 -424
- package/src/core/types.ts +25 -20
- package/src/lib/index.ts +8 -8
- package/src/lib/utils.ts +44 -30
- package/templates/default/.prettierignore +5 -0
- package/templates/default/.prettierrc.json +9 -0
- package/templates/default/README.md +5 -5
- package/templates/default/package.json +22 -18
- package/templates/default/src/app.css +80 -80
- package/templates/default/src/app.d.ts +3 -3
- package/templates/default/src/routes/+error.svelte +7 -10
- package/templates/default/src/routes/+layout.svelte +2 -2
- package/templates/default/src/routes/+page.svelte +31 -29
- package/templates/default/src/routes/about/+page.svelte +3 -3
- package/templates/default/tsconfig.json +20 -20
- package/templates/demo/.prettierignore +5 -0
- package/templates/demo/.prettierrc.json +9 -0
- package/templates/demo/README.md +9 -9
- package/templates/demo/package.json +22 -17
- package/templates/demo/src/app.css +80 -80
- package/templates/demo/src/app.d.ts +3 -3
- package/templates/demo/src/hooks.server.ts +9 -9
- package/templates/demo/src/routes/(public)/+layout.svelte +45 -23
- package/templates/demo/src/routes/(public)/+page.svelte +96 -67
- package/templates/demo/src/routes/(public)/about/+page.svelte +13 -25
- package/templates/demo/src/routes/(public)/all/[...catchall]/+page.svelte +24 -28
- package/templates/demo/src/routes/(public)/blog/+page.svelte +55 -46
- package/templates/demo/src/routes/(public)/blog/[slug]/+page.server.ts +36 -38
- package/templates/demo/src/routes/(public)/blog/[slug]/+page.svelte +60 -42
- package/templates/demo/src/routes/+error.svelte +10 -7
- package/templates/demo/src/routes/+layout.server.ts +4 -4
- package/templates/demo/src/routes/+layout.svelte +2 -2
- package/templates/demo/src/routes/actions-test/+page.server.ts +16 -16
- package/templates/demo/src/routes/actions-test/+page.svelte +49 -49
- package/templates/demo/src/routes/api/hello/+server.ts +25 -25
- package/templates/demo/tsconfig.json +20 -20
- package/templates/todo/.prettierignore +5 -0
- package/templates/todo/.prettierrc.json +9 -0
- package/templates/todo/README.md +9 -9
- package/templates/todo/package.json +22 -17
- package/templates/todo/src/app.css +80 -80
- package/templates/todo/src/app.d.ts +7 -7
- package/templates/todo/src/hooks.server.ts +9 -9
- package/templates/todo/src/routes/+error.svelte +10 -7
- package/templates/todo/src/routes/+layout.server.ts +4 -4
- package/templates/todo/src/routes/+layout.svelte +2 -2
- package/templates/todo/src/routes/+page.svelte +44 -44
- package/templates/todo/template.json +1 -1
- package/templates/todo/tsconfig.json +20 -20
package/src/core/routeFile.ts
CHANGED
|
@@ -8,106 +8,115 @@ import type { RouteManifest } from "./types.ts";
|
|
|
8
8
|
// apiRoutes — used by API handler
|
|
9
9
|
|
|
10
10
|
function routePriority(pattern: string): number {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
if (!pattern.includes("[")) return 0; // exact
|
|
12
|
+
if (!pattern.includes("[...")) return 1; // dynamic
|
|
13
|
+
return 2; // catch-all
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
function sortRoutes<T extends { pattern: string }>(routes: T[]): T[] {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
17
|
+
return [...routes].sort((a, b) => {
|
|
18
|
+
const pa = routePriority(a.pattern);
|
|
19
|
+
const pb = routePriority(b.pattern);
|
|
20
|
+
if (pa !== pb) return pa - pb;
|
|
21
|
+
// same tier: more segments first, then alphabetical
|
|
22
|
+
const sa = a.pattern.split("/").length;
|
|
23
|
+
const sb = b.pattern.split("/").length;
|
|
24
|
+
if (sa !== sb) return sb - sa;
|
|
25
|
+
return a.pattern.localeCompare(b.pattern);
|
|
26
|
+
});
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
export function generateRoutesFile(manifest: RouteManifest): void {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
30
|
+
const lines: string[] = ["// AUTO-GENERATED by bosia build — do not edit\n"];
|
|
31
|
+
|
|
32
|
+
const pages = sortRoutes(manifest.pages);
|
|
33
|
+
const apis = sortRoutes(manifest.apis);
|
|
34
|
+
|
|
35
|
+
// clientRoutes
|
|
36
|
+
lines.push("export const clientRoutes: Array<{");
|
|
37
|
+
lines.push(" pattern: string;");
|
|
38
|
+
lines.push(" page: () => Promise<any>;");
|
|
39
|
+
lines.push(" layouts: (() => Promise<any>)[];");
|
|
40
|
+
lines.push(" hasServerData: boolean;");
|
|
41
|
+
lines.push(' trailingSlash: "never" | "always" | "ignore";');
|
|
42
|
+
lines.push("}> = [");
|
|
43
|
+
for (const r of pages) {
|
|
44
|
+
const layoutImports = r.layouts
|
|
45
|
+
.map((l) => `() => import(${JSON.stringify(toImportPath(l))})`)
|
|
46
|
+
.join(", ");
|
|
47
|
+
const hasServerData = !!(r.pageServer || r.layoutServers.length > 0);
|
|
48
|
+
lines.push(" {");
|
|
49
|
+
lines.push(` pattern: ${JSON.stringify(r.pattern)},`);
|
|
50
|
+
lines.push(` page: () => import(${JSON.stringify(toImportPath(r.page))}),`);
|
|
51
|
+
lines.push(` layouts: [${layoutImports}],`);
|
|
52
|
+
lines.push(` hasServerData: ${hasServerData},`);
|
|
53
|
+
lines.push(` trailingSlash: ${JSON.stringify(r.trailingSlash)},`);
|
|
54
|
+
lines.push(" },");
|
|
55
|
+
}
|
|
56
|
+
lines.push("];\n");
|
|
57
|
+
|
|
58
|
+
// serverRoutes
|
|
59
|
+
lines.push("export const serverRoutes: Array<{");
|
|
60
|
+
lines.push(" pattern: string;");
|
|
61
|
+
lines.push(" pageModule: () => Promise<any>;");
|
|
62
|
+
lines.push(" layoutModules: (() => Promise<any>)[];");
|
|
63
|
+
lines.push(" pageServer: (() => Promise<any>) | null;");
|
|
64
|
+
lines.push(" layoutServers: { loader: () => Promise<any>; depth: number }[];");
|
|
65
|
+
lines.push(' trailingSlash: "never" | "always" | "ignore";');
|
|
66
|
+
lines.push("}> = [");
|
|
67
|
+
for (const r of pages) {
|
|
68
|
+
const layoutImports = r.layouts
|
|
69
|
+
.map((l) => `() => import(${JSON.stringify(toImportPath(l))})`)
|
|
70
|
+
.join(", ");
|
|
71
|
+
const layoutServerImports = r.layoutServers
|
|
72
|
+
.map(
|
|
73
|
+
(ls) =>
|
|
74
|
+
`{ loader: () => import(${JSON.stringify(toImportPath(ls.path))}), depth: ${ls.depth} }`,
|
|
75
|
+
)
|
|
76
|
+
.join(", ");
|
|
77
|
+
lines.push(" {");
|
|
78
|
+
lines.push(` pattern: ${JSON.stringify(r.pattern)},`);
|
|
79
|
+
lines.push(` pageModule: () => import(${JSON.stringify(toImportPath(r.page))}),`);
|
|
80
|
+
lines.push(` layoutModules: [${layoutImports}],`);
|
|
81
|
+
lines.push(
|
|
82
|
+
` pageServer: ${r.pageServer ? `() => import(${JSON.stringify(toImportPath(r.pageServer))})` : "null"},`,
|
|
83
|
+
);
|
|
84
|
+
lines.push(` layoutServers: [${layoutServerImports}],`);
|
|
85
|
+
lines.push(` trailingSlash: ${JSON.stringify(r.trailingSlash)},`);
|
|
86
|
+
lines.push(" },");
|
|
87
|
+
}
|
|
88
|
+
lines.push("];\n");
|
|
89
|
+
|
|
90
|
+
// apiRoutes
|
|
91
|
+
lines.push("export const apiRoutes: Array<{");
|
|
92
|
+
lines.push(" pattern: string;");
|
|
93
|
+
lines.push(" module: () => Promise<any>;");
|
|
94
|
+
lines.push("}> = [");
|
|
95
|
+
for (const r of apis) {
|
|
96
|
+
lines.push(" {");
|
|
97
|
+
lines.push(` pattern: ${JSON.stringify(r.pattern)},`);
|
|
98
|
+
lines.push(` module: () => import(${JSON.stringify(toImportPath(r.server))}),`);
|
|
99
|
+
lines.push(" },");
|
|
100
|
+
}
|
|
101
|
+
lines.push("];\n");
|
|
102
|
+
|
|
103
|
+
// errorPage
|
|
104
|
+
const ep = manifest.errorPage;
|
|
105
|
+
lines.push(
|
|
106
|
+
`export const errorPage: (() => Promise<any>) | null = ${
|
|
107
|
+
ep ? `() => import(${JSON.stringify(toImportPath(ep))})` : "null"
|
|
108
|
+
};\n`,
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
mkdirSync(".bosia", { recursive: true });
|
|
112
|
+
writeFileSync(".bosia/routes.ts", lines.join("\n"));
|
|
113
|
+
|
|
114
|
+
// Generate client-only routes file (no server imports that could pull in Node builtins)
|
|
115
|
+
generateClientRoutesFile(pages, manifest.errorPage);
|
|
116
|
+
|
|
117
|
+
const pagePatterns = pages.map((p) => p.pattern).join(", ") || "(none)";
|
|
118
|
+
console.log(`✅ Routes generated: .bosia/routes.ts`);
|
|
119
|
+
console.log(` Found ${pages.length} page route(s): ${pagePatterns}`);
|
|
111
120
|
}
|
|
112
121
|
|
|
113
122
|
// ─── Client-only routes file ─────────────────────────────
|
|
@@ -116,42 +125,46 @@ export function generateRoutesFile(manifest: RouteManifest): void {
|
|
|
116
125
|
// (e.g. +page.server.ts → drizzle-orm → postgres → Node builtins).
|
|
117
126
|
|
|
118
127
|
function generateClientRoutesFile(
|
|
119
|
-
|
|
120
|
-
|
|
128
|
+
pages: RouteManifest["pages"],
|
|
129
|
+
errorPage: RouteManifest["errorPage"],
|
|
121
130
|
): void {
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
131
|
+
const lines: string[] = [
|
|
132
|
+
"// AUTO-GENERATED by bosia build — client-only routes (no server imports)\n",
|
|
133
|
+
];
|
|
134
|
+
|
|
135
|
+
lines.push("export const clientRoutes: Array<{");
|
|
136
|
+
lines.push(" pattern: string;");
|
|
137
|
+
lines.push(" page: () => Promise<any>;");
|
|
138
|
+
lines.push(" layouts: (() => Promise<any>)[];");
|
|
139
|
+
lines.push(" hasServerData: boolean;");
|
|
140
|
+
lines.push(' trailingSlash: "never" | "always" | "ignore";');
|
|
141
|
+
lines.push("}> = [");
|
|
142
|
+
for (const r of pages) {
|
|
143
|
+
const layoutImports = r.layouts
|
|
144
|
+
.map((l) => `() => import(${JSON.stringify(toImportPath(l))})`)
|
|
145
|
+
.join(", ");
|
|
146
|
+
const hasServerData = !!(r.pageServer || r.layoutServers.length > 0);
|
|
147
|
+
lines.push(" {");
|
|
148
|
+
lines.push(` pattern: ${JSON.stringify(r.pattern)},`);
|
|
149
|
+
lines.push(` page: () => import(${JSON.stringify(toImportPath(r.page))}),`);
|
|
150
|
+
lines.push(` layouts: [${layoutImports}],`);
|
|
151
|
+
lines.push(` hasServerData: ${hasServerData},`);
|
|
152
|
+
lines.push(` trailingSlash: ${JSON.stringify(r.trailingSlash)},`);
|
|
153
|
+
lines.push(" },");
|
|
154
|
+
}
|
|
155
|
+
lines.push("];\n");
|
|
156
|
+
|
|
157
|
+
const ep = errorPage;
|
|
158
|
+
lines.push(
|
|
159
|
+
`export const errorPage: (() => Promise<any>) | null = ${
|
|
160
|
+
ep ? `() => import(${JSON.stringify(toImportPath(ep))})` : "null"
|
|
161
|
+
};\n`,
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
writeFileSync(".bosia/routes.client.ts", lines.join("\n"));
|
|
152
165
|
}
|
|
153
166
|
|
|
154
167
|
// Import path from .bosia/routes.ts to src/routes/<routePath>
|
|
155
168
|
function toImportPath(routePath: string): string {
|
|
156
|
-
|
|
169
|
+
return "../src/routes/" + routePath.replace(/\\/g, "/");
|
|
157
170
|
}
|
package/src/core/routeTypes.ts
CHANGED
|
@@ -9,71 +9,127 @@ import type { RouteManifest } from "./types.ts";
|
|
|
9
9
|
// work in +page.svelte files — identical to SvelteKit's API.
|
|
10
10
|
|
|
11
11
|
function routeDirOf(filePath: string): string {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
const parts = filePath.replace(/\\/g, "/").split("/");
|
|
13
|
+
parts.pop();
|
|
14
|
+
return parts.join("/") || ".";
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function paramsForDir(dir: string): string[] {
|
|
18
|
+
if (dir === ".") return [];
|
|
19
|
+
const segs = dir
|
|
20
|
+
.split("/")
|
|
21
|
+
.filter(Boolean)
|
|
22
|
+
.filter((s) => !/^\(.*\)$/.test(s));
|
|
23
|
+
const out: string[] = [];
|
|
24
|
+
for (const seg of segs) {
|
|
25
|
+
const catchAll = seg.match(/^\[\.\.\.(\w+)\]$/);
|
|
26
|
+
if (catchAll) {
|
|
27
|
+
out.push(catchAll[1]!);
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
const dyn = seg.match(/^\[(\w+)\]$/);
|
|
31
|
+
if (dyn) out.push(dyn[1]!);
|
|
32
|
+
}
|
|
33
|
+
return out;
|
|
15
34
|
}
|
|
16
35
|
|
|
17
36
|
export function generateRouteTypes(manifest: RouteManifest): void {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
37
|
+
// Collect { dir → { pageServer?, layoutServer? } }
|
|
38
|
+
const dirs = new Map<string, { pageServer?: string; layoutServer?: string }>();
|
|
39
|
+
|
|
40
|
+
for (const route of manifest.pages) {
|
|
41
|
+
const pageDir = routeDirOf(route.page);
|
|
42
|
+
if (!dirs.has(pageDir)) dirs.set(pageDir, {});
|
|
43
|
+
if (route.pageServer) {
|
|
44
|
+
dirs.get(pageDir)!.pageServer = route.pageServer;
|
|
45
|
+
}
|
|
46
|
+
for (const ls of route.layoutServers) {
|
|
47
|
+
const lsDir = routeDirOf(ls.path);
|
|
48
|
+
if (!dirs.has(lsDir)) dirs.set(lsDir, {});
|
|
49
|
+
dirs.get(lsDir)!.layoutServer = ls.path;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
for (const [dir, info] of dirs) {
|
|
54
|
+
// Path segments of the route dir (empty array for root ".")
|
|
55
|
+
const segments = dir === "." ? [] : dir.split("/").filter(Boolean);
|
|
56
|
+
|
|
57
|
+
// Depth of the generated file from project root:
|
|
58
|
+
// .bosia/ + types/ + src/ + routes/ + ...segments
|
|
59
|
+
const depth = 4 + segments.length;
|
|
60
|
+
const up = "../".repeat(depth);
|
|
61
|
+
const srcBase = `${up}src/routes/${segments.length ? segments.join("/") + "/" : ""}`;
|
|
62
|
+
|
|
63
|
+
const params = paramsForDir(dir);
|
|
64
|
+
const paramsType =
|
|
65
|
+
params.length === 0 ? `{}` : `{ ${params.map((p) => `${p}: string`).join("; ")} }`;
|
|
66
|
+
|
|
67
|
+
const lines: string[] = ["// AUTO-GENERATED by bosia — do not edit\n"];
|
|
68
|
+
lines.push(
|
|
69
|
+
`import type { LoadEvent, MetadataEvent, RequestEvent, Metadata } from 'bosia';`,
|
|
70
|
+
);
|
|
71
|
+
lines.push(``);
|
|
72
|
+
lines.push(`export type Params = ${paramsType};`);
|
|
73
|
+
lines.push(``);
|
|
74
|
+
lines.push(`type _LoadEvent = Omit<LoadEvent, 'params'> & { params: Params };`);
|
|
75
|
+
lines.push(`type _MetadataEvent = Omit<MetadataEvent, 'params'> & { params: Params };`);
|
|
76
|
+
lines.push(`type _RequestEvent = Omit<RequestEvent, 'params'> & { params: Params };`);
|
|
77
|
+
|
|
78
|
+
if (info.pageServer) {
|
|
79
|
+
lines.push(``);
|
|
80
|
+
lines.push(`import type { load as _pageLoad } from '${srcBase}+page.server.ts';`);
|
|
81
|
+
lines.push(`export type PageServerLoad = (event: _LoadEvent) => any;`);
|
|
82
|
+
lines.push(
|
|
83
|
+
`export type PageMetadataLoad = (event: _MetadataEvent) => Metadata | Promise<Metadata>;`,
|
|
84
|
+
);
|
|
85
|
+
lines.push(`export type Action = (event: _RequestEvent) => any;`);
|
|
86
|
+
lines.push(
|
|
87
|
+
`export type PageData = Awaited<ReturnType<typeof _pageLoad>> & { params: Params };`,
|
|
88
|
+
);
|
|
89
|
+
} else {
|
|
90
|
+
lines.push(``);
|
|
91
|
+
lines.push(
|
|
92
|
+
`export type PageMetadataLoad = (event: _MetadataEvent) => Metadata | Promise<Metadata>;`,
|
|
93
|
+
);
|
|
94
|
+
lines.push(`export type Action = (event: _RequestEvent) => any;`);
|
|
95
|
+
lines.push(`export type PageData = { params: Params };`);
|
|
96
|
+
}
|
|
97
|
+
lines.push(`export type PageProps = { data: PageData };`);
|
|
98
|
+
|
|
99
|
+
// ActionData — union of all action return types, unwrapping ActionFailure
|
|
100
|
+
if (info.pageServer) {
|
|
101
|
+
lines.push(``);
|
|
102
|
+
lines.push(`import type { actions as _actions } from '${srcBase}+page.server.ts';`);
|
|
103
|
+
lines.push(
|
|
104
|
+
`type _ActionReturn<T> = T extends (...args: any[]) => infer R ? Awaited<R> : never;`,
|
|
105
|
+
);
|
|
106
|
+
lines.push(
|
|
107
|
+
`type _UnwrapFailure<T> = T extends { status: number; data: infer D } ? D : T;`,
|
|
108
|
+
);
|
|
109
|
+
lines.push(
|
|
110
|
+
`export type ActionData = _actions extends Record<string, (...args: any[]) => any>`,
|
|
111
|
+
);
|
|
112
|
+
lines.push(` ? _UnwrapFailure<_ActionReturn<_actions[keyof _actions]>> | null`);
|
|
113
|
+
lines.push(` : null;`);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (info.layoutServer) {
|
|
117
|
+
lines.push(`\nimport type { load as _layoutLoad } from '${srcBase}+layout.server.ts';`);
|
|
118
|
+
lines.push(`export type LayoutServerLoad = (event: _LoadEvent) => any;`);
|
|
119
|
+
lines.push(
|
|
120
|
+
`export type LayoutData = Awaited<ReturnType<typeof _layoutLoad>> & { params: Params };`,
|
|
121
|
+
);
|
|
122
|
+
lines.push(`export type LayoutProps = { data: LayoutData; children: any };`);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const outDir = join(process.cwd(), ".bosia", "types", "src", "routes", ...segments);
|
|
126
|
+
mkdirSync(outDir, { recursive: true });
|
|
127
|
+
writeFileSync(join(outDir, "$types.d.ts"), lines.join("\n") + "\n");
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
console.log(
|
|
131
|
+
`✅ Route types generated: .bosia/types/ (${dirs.size} route director${dirs.size === 1 ? "y" : "ies"})`,
|
|
132
|
+
);
|
|
77
133
|
}
|
|
78
134
|
|
|
79
135
|
// ─── Ensure tsconfig rootDirs ─────────────────────────────
|
|
@@ -82,25 +138,30 @@ export function generateRouteTypes(manifest: RouteManifest): void {
|
|
|
82
138
|
// Only patches the file if rootDirs is not already set.
|
|
83
139
|
|
|
84
140
|
export function ensureRootDirs(): void {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
141
|
+
const tsconfigPath = join(process.cwd(), "tsconfig.json");
|
|
142
|
+
if (!existsSync(tsconfigPath)) return;
|
|
143
|
+
|
|
144
|
+
let tsconfig: any;
|
|
145
|
+
try {
|
|
146
|
+
tsconfig = JSON.parse(readFileSync(tsconfigPath, "utf-8"));
|
|
147
|
+
} catch {
|
|
148
|
+
console.warn(
|
|
149
|
+
"⚠️ Could not parse tsconfig.json — add rootDirs manually:\n" +
|
|
150
|
+
' "rootDirs": [".", ".bosia/types"]',
|
|
151
|
+
);
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const rootDirs: string[] = tsconfig.compilerOptions?.rootDirs ?? [];
|
|
156
|
+
if (rootDirs.includes(".bosia/types")) return;
|
|
157
|
+
|
|
158
|
+
tsconfig.compilerOptions ??= {};
|
|
159
|
+
tsconfig.compilerOptions.rootDirs = [
|
|
160
|
+
".",
|
|
161
|
+
".bosia/types",
|
|
162
|
+
...rootDirs.filter((d: string) => d !== "."),
|
|
163
|
+
];
|
|
164
|
+
|
|
165
|
+
writeFileSync(tsconfigPath, JSON.stringify(tsconfig, null, 2) + "\n");
|
|
166
|
+
console.log("✅ tsconfig.json: added .bosia/types to rootDirs");
|
|
106
167
|
}
|