raktajs 0.1.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 +90 -0
- package/dist/auto-import/generator.d.ts +4 -0
- package/dist/auto-import/generator.d.ts.map +1 -0
- package/dist/auto-import/index.d.ts +5 -0
- package/dist/auto-import/index.d.ts.map +1 -0
- package/dist/auto-import/index.js +153 -0
- package/dist/auto-import/index.js.map +11 -0
- package/dist/auto-import/scanner.d.ts +9 -0
- package/dist/auto-import/scanner.d.ts.map +1 -0
- package/dist/auto-import/types.d.ts +20 -0
- package/dist/auto-import/types.d.ts.map +1 -0
- package/dist/cli/build.d.ts +2 -0
- package/dist/cli/build.d.ts.map +1 -0
- package/dist/cli/dev.d.ts +2 -0
- package/dist/cli/dev.d.ts.map +1 -0
- package/dist/cli/doctor.d.ts +2 -0
- package/dist/cli/doctor.d.ts.map +1 -0
- package/dist/cli/imports.d.ts +2 -0
- package/dist/cli/imports.d.ts.map +1 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/make.d.ts +4 -0
- package/dist/cli/make.d.ts.map +1 -0
- package/dist/cli/rakta.d.ts +3 -0
- package/dist/cli/rakta.d.ts.map +1 -0
- package/dist/cli/rakta.js +1511 -0
- package/dist/cli/rakta.js.map +33 -0
- package/dist/cli/routes.d.ts +2 -0
- package/dist/cli/routes.d.ts.map +1 -0
- package/dist/cli/rpcTypes.d.ts +2 -0
- package/dist/cli/rpcTypes.d.ts.map +1 -0
- package/dist/cli/seo.d.ts +2 -0
- package/dist/cli/seo.d.ts.map +1 -0
- package/dist/cli/start.d.ts +2 -0
- package/dist/cli/start.d.ts.map +1 -0
- package/dist/components/Click.d.ts +11 -0
- package/dist/components/Click.d.ts.map +1 -0
- package/dist/components/Picture.d.ts +22 -0
- package/dist/components/Picture.d.ts.map +1 -0
- package/dist/components/index.d.ts +5 -0
- package/dist/components/index.d.ts.map +1 -0
- package/dist/components/index.js +1383 -0
- package/dist/components/index.js.map +15 -0
- package/dist/config/defineConfig.d.ts +64 -0
- package/dist/config/defineConfig.d.ts.map +1 -0
- package/dist/config/index.d.ts +4 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +104 -0
- package/dist/config/index.js.map +11 -0
- package/dist/config/loadConfig.d.ts +4 -0
- package/dist/config/loadConfig.d.ts.map +1 -0
- package/dist/forge/build.d.ts +7 -0
- package/dist/forge/build.d.ts.map +1 -0
- package/dist/forge/devServer.d.ts +7 -0
- package/dist/forge/devServer.d.ts.map +1 -0
- package/dist/forge/index.d.ts +6 -0
- package/dist/forge/index.d.ts.map +1 -0
- package/dist/forge/index.js +579 -0
- package/dist/forge/index.js.map +17 -0
- package/dist/forge/inspect.d.ts +9 -0
- package/dist/forge/inspect.d.ts.map +1 -0
- package/dist/forge/types.d.ts +55 -0
- package/dist/forge/types.d.ts.map +1 -0
- package/dist/http/errors.d.ts +17 -0
- package/dist/http/errors.d.ts.map +1 -0
- package/dist/http/index.d.ts +4 -0
- package/dist/http/index.d.ts.map +1 -0
- package/dist/http/index.js +154 -0
- package/dist/http/index.js.map +11 -0
- package/dist/http/panturaFetch.d.ts +33 -0
- package/dist/http/panturaFetch.d.ts.map +1 -0
- package/dist/http/types.d.ts +24 -0
- package/dist/http/types.d.ts.map +1 -0
- package/dist/index.d.ts +30 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3872 -0
- package/dist/index.js.map +50 -0
- package/dist/pwa/cache.d.ts +6 -0
- package/dist/pwa/cache.d.ts.map +1 -0
- package/dist/pwa/index.d.ts +5 -0
- package/dist/pwa/index.d.ts.map +1 -0
- package/dist/pwa/index.js +136 -0
- package/dist/pwa/index.js.map +12 -0
- package/dist/pwa/manifest.d.ts +11 -0
- package/dist/pwa/manifest.d.ts.map +1 -0
- package/dist/pwa/serviceWorker.d.ts +11 -0
- package/dist/pwa/serviceWorker.d.ts.map +1 -0
- package/dist/pwa/types.d.ts +45 -0
- package/dist/pwa/types.d.ts.map +1 -0
- package/dist/render/index.d.ts +5 -0
- package/dist/render/index.d.ts.map +1 -0
- package/dist/render/index.js +183 -0
- package/dist/render/index.js.map +11 -0
- package/dist/render/modes.d.ts +13 -0
- package/dist/render/modes.d.ts.map +1 -0
- package/dist/render/renderer.d.ts +15 -0
- package/dist/render/renderer.d.ts.map +1 -0
- package/dist/render/types.d.ts +72 -0
- package/dist/render/types.d.ts.map +1 -0
- package/dist/router/index.d.ts +5 -0
- package/dist/router/index.d.ts.map +1 -0
- package/dist/router/index.js +228 -0
- package/dist/router/index.js.map +12 -0
- package/dist/router/manifest.d.ts +6 -0
- package/dist/router/manifest.d.ts.map +1 -0
- package/dist/router/matcher.d.ts +5 -0
- package/dist/router/matcher.d.ts.map +1 -0
- package/dist/router/scanner.d.ts +6 -0
- package/dist/router/scanner.d.ts.map +1 -0
- package/dist/router/types.d.ts +45 -0
- package/dist/router/types.d.ts.map +1 -0
- package/dist/rpc/client.d.ts +24 -0
- package/dist/rpc/client.d.ts.map +1 -0
- package/dist/rpc/index.d.ts +6 -0
- package/dist/rpc/index.d.ts.map +1 -0
- package/dist/rpc/index.js +179 -0
- package/dist/rpc/index.js.map +13 -0
- package/dist/rpc/procedure.d.ts +28 -0
- package/dist/rpc/procedure.d.ts.map +1 -0
- package/dist/rpc/router.d.ts +12 -0
- package/dist/rpc/router.d.ts.map +1 -0
- package/dist/rpc/types.d.ts +48 -0
- package/dist/rpc/types.d.ts.map +1 -0
- package/dist/schema/array.d.ts +15 -0
- package/dist/schema/array.d.ts.map +1 -0
- package/dist/schema/boolean.d.ts +7 -0
- package/dist/schema/boolean.d.ts.map +1 -0
- package/dist/schema/errors.d.ts +21 -0
- package/dist/schema/errors.d.ts.map +1 -0
- package/dist/schema/index.d.ts +11 -0
- package/dist/schema/index.d.ts.map +1 -0
- package/dist/schema/index.js +393 -0
- package/dist/schema/index.js.map +16 -0
- package/dist/schema/number.d.ts +17 -0
- package/dist/schema/number.d.ts.map +1 -0
- package/dist/schema/object.d.ts +19 -0
- package/dist/schema/object.d.ts.map +1 -0
- package/dist/schema/string.d.ts +18 -0
- package/dist/schema/string.d.ts.map +1 -0
- package/dist/schema/types.d.ts +27 -0
- package/dist/schema/types.d.ts.map +1 -0
- package/dist/seo/head.d.ts +8 -0
- package/dist/seo/head.d.ts.map +1 -0
- package/dist/seo/index.d.ts +9 -0
- package/dist/seo/index.d.ts.map +1 -0
- package/dist/seo/index.js +1578 -0
- package/dist/seo/index.js.map +17 -0
- package/dist/seo/metadata.d.ts +106 -0
- package/dist/seo/metadata.d.ts.map +1 -0
- package/dist/seo/robots.d.ts +14 -0
- package/dist/seo/robots.d.ts.map +1 -0
- package/dist/seo/sitemap.d.ts +27 -0
- package/dist/seo/sitemap.d.ts.map +1 -0
- package/dist/store/createStore.d.ts +18 -0
- package/dist/store/createStore.d.ts.map +1 -0
- package/dist/store/index.d.ts +3 -0
- package/dist/store/index.d.ts.map +1 -0
- package/dist/store/index.js +925 -0
- package/dist/store/index.js.map +12 -0
- package/dist/store/types.d.ts +14 -0
- package/dist/store/types.d.ts.map +1 -0
- package/dist/tide/adapter.d.ts +8 -0
- package/dist/tide/adapter.d.ts.map +1 -0
- package/dist/tide/index.d.ts +4 -0
- package/dist/tide/index.d.ts.map +1 -0
- package/dist/tide/index.js +477 -0
- package/dist/tide/index.js.map +16 -0
- package/dist/tide/runtime.d.ts +8 -0
- package/dist/tide/runtime.d.ts.map +1 -0
- package/dist/tide/types.d.ts +40 -0
- package/dist/tide/types.d.ts.map +1 -0
- package/package.json +128 -0
|
@@ -0,0 +1,1511 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
// @bun
|
|
3
|
+
|
|
4
|
+
// src/cli/rakta.ts
|
|
5
|
+
import { join as join17 } from "path";
|
|
6
|
+
|
|
7
|
+
// src/config/loadConfig.ts
|
|
8
|
+
import { existsSync } from "fs";
|
|
9
|
+
import { join, resolve } from "path";
|
|
10
|
+
|
|
11
|
+
// src/config/defineConfig.ts
|
|
12
|
+
var defaultConfig = {
|
|
13
|
+
appName: "Rakta.js App",
|
|
14
|
+
appDir: "app",
|
|
15
|
+
publicDir: "public",
|
|
16
|
+
port: 3000,
|
|
17
|
+
css: {
|
|
18
|
+
framework: "tailwind"
|
|
19
|
+
},
|
|
20
|
+
seo: {
|
|
21
|
+
defaultTitle: "Rakta.js App",
|
|
22
|
+
titleTemplate: "%s | Rakta.js App",
|
|
23
|
+
defaultDescription: "Built with Rakta.js",
|
|
24
|
+
siteUrl: "http://localhost:3000"
|
|
25
|
+
},
|
|
26
|
+
server: {
|
|
27
|
+
port: 3000,
|
|
28
|
+
hostname: "0.0.0.0",
|
|
29
|
+
cors: true,
|
|
30
|
+
compression: false,
|
|
31
|
+
trustProxy: false
|
|
32
|
+
},
|
|
33
|
+
build: {
|
|
34
|
+
entryPoint: "entry.client.tsx",
|
|
35
|
+
outDir: "dist",
|
|
36
|
+
sourcemap: false,
|
|
37
|
+
minify: true,
|
|
38
|
+
splitting: false,
|
|
39
|
+
target: "browser"
|
|
40
|
+
},
|
|
41
|
+
autoImport: {
|
|
42
|
+
enabled: false,
|
|
43
|
+
directories: ["components", "lib", "stores", "schemas"],
|
|
44
|
+
outputDirectory: ".rakta",
|
|
45
|
+
extensions: [".ts", ".tsx", ".js", ".jsx"],
|
|
46
|
+
dts: true
|
|
47
|
+
},
|
|
48
|
+
rpc: {
|
|
49
|
+
enabled: false,
|
|
50
|
+
basePath: "/rpc",
|
|
51
|
+
maxBodySize: 1048576
|
|
52
|
+
},
|
|
53
|
+
render: {
|
|
54
|
+
defaultMode: "csr",
|
|
55
|
+
routes: {}
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
// src/config/loadConfig.ts
|
|
60
|
+
var CONFIG_FILENAMES = [
|
|
61
|
+
"rakta.config.ts",
|
|
62
|
+
"rakta.config.js",
|
|
63
|
+
"rakta.config.mts",
|
|
64
|
+
"rakta.config.mjs"
|
|
65
|
+
];
|
|
66
|
+
async function loadConfig(cwd = process.cwd()) {
|
|
67
|
+
const resolvedCwd = resolve(cwd);
|
|
68
|
+
for (const filename of CONFIG_FILENAMES) {
|
|
69
|
+
const configPath = join(resolvedCwd, filename);
|
|
70
|
+
if (!existsSync(configPath)) {
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
const configModule = await import(configPath);
|
|
74
|
+
const userConfig = configModule.default ?? {};
|
|
75
|
+
return mergeConfig(defaultConfig, userConfig);
|
|
76
|
+
}
|
|
77
|
+
return defaultConfig;
|
|
78
|
+
}
|
|
79
|
+
function mergeConfig(baseConfig, overrideConfig) {
|
|
80
|
+
return {
|
|
81
|
+
appName: overrideConfig.appName ?? baseConfig.appName,
|
|
82
|
+
appDir: overrideConfig.appDir ?? baseConfig.appDir,
|
|
83
|
+
publicDir: overrideConfig.publicDir ?? baseConfig.publicDir,
|
|
84
|
+
port: overrideConfig.port ?? baseConfig.port,
|
|
85
|
+
css: overrideConfig.css !== undefined ? { ...baseConfig.css, ...overrideConfig.css } : baseConfig.css,
|
|
86
|
+
seo: overrideConfig.seo !== undefined ? { ...baseConfig.seo, ...overrideConfig.seo } : baseConfig.seo,
|
|
87
|
+
server: overrideConfig.server !== undefined ? { ...baseConfig.server, ...overrideConfig.server } : baseConfig.server,
|
|
88
|
+
build: overrideConfig.build !== undefined ? { ...baseConfig.build, ...overrideConfig.build } : baseConfig.build,
|
|
89
|
+
autoImport: overrideConfig.autoImport !== undefined ? { ...baseConfig.autoImport, ...overrideConfig.autoImport } : baseConfig.autoImport,
|
|
90
|
+
rpc: overrideConfig.rpc !== undefined ? { ...baseConfig.rpc, ...overrideConfig.rpc } : baseConfig.rpc,
|
|
91
|
+
render: overrideConfig.render !== undefined ? {
|
|
92
|
+
defaultMode: overrideConfig.render.defaultMode ?? baseConfig.render.defaultMode,
|
|
93
|
+
routes: {
|
|
94
|
+
...baseConfig.render.routes,
|
|
95
|
+
...overrideConfig.render.routes
|
|
96
|
+
}
|
|
97
|
+
} : baseConfig.render
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// src/forge/inspect.ts
|
|
102
|
+
import { existsSync as existsSync4, readdirSync as readdirSync2, statSync as statSync2 } from "fs";
|
|
103
|
+
import { join as join3 } from "path";
|
|
104
|
+
|
|
105
|
+
// src/render/modes.ts
|
|
106
|
+
var RENDER_MODE_DESCRIPTORS = {
|
|
107
|
+
csr: {
|
|
108
|
+
mode: "csr",
|
|
109
|
+
label: "Client-Side Rendering",
|
|
110
|
+
shortLabel: "CSR",
|
|
111
|
+
description: "React renders entirely in the browser. The server sends a minimal HTML shell with a JS bundle. No server required at runtime.",
|
|
112
|
+
serverRequired: false,
|
|
113
|
+
buildTimeGenerated: false,
|
|
114
|
+
clientHydration: true,
|
|
115
|
+
roadmap: false
|
|
116
|
+
},
|
|
117
|
+
ssr: {
|
|
118
|
+
mode: "ssr",
|
|
119
|
+
label: "Server-Side Rendering",
|
|
120
|
+
shortLabel: "SSR",
|
|
121
|
+
description: "React renders to HTML on the server per request. The client receives full HTML and then hydrates. Requires a running Node/Bun server.",
|
|
122
|
+
serverRequired: true,
|
|
123
|
+
buildTimeGenerated: false,
|
|
124
|
+
clientHydration: true,
|
|
125
|
+
roadmap: true
|
|
126
|
+
},
|
|
127
|
+
ssg: {
|
|
128
|
+
mode: "ssg",
|
|
129
|
+
label: "Static Site Generation",
|
|
130
|
+
shortLabel: "SSG",
|
|
131
|
+
description: "Pages are fully rendered at build time. Output is static HTML files. No server required at runtime.",
|
|
132
|
+
serverRequired: false,
|
|
133
|
+
buildTimeGenerated: true,
|
|
134
|
+
clientHydration: false,
|
|
135
|
+
roadmap: true
|
|
136
|
+
},
|
|
137
|
+
csg: {
|
|
138
|
+
mode: "csg",
|
|
139
|
+
label: "Client-Side Generation",
|
|
140
|
+
shortLabel: "CSG",
|
|
141
|
+
description: "A static shell is generated at build time, data is fetched and rendered on the client. Combines SSG speed with dynamic data.",
|
|
142
|
+
serverRequired: false,
|
|
143
|
+
buildTimeGenerated: true,
|
|
144
|
+
clientHydration: true,
|
|
145
|
+
roadmap: true
|
|
146
|
+
},
|
|
147
|
+
spa: {
|
|
148
|
+
mode: "spa",
|
|
149
|
+
label: "Single Page Application",
|
|
150
|
+
shortLabel: "SPA",
|
|
151
|
+
description: "A single HTML entry point is served for all routes. React handles all routing client-side. Equivalent to a standard Vite SPA.",
|
|
152
|
+
serverRequired: false,
|
|
153
|
+
buildTimeGenerated: false,
|
|
154
|
+
clientHydration: true,
|
|
155
|
+
roadmap: false
|
|
156
|
+
},
|
|
157
|
+
hybrid: {
|
|
158
|
+
mode: "hybrid",
|
|
159
|
+
label: "Hybrid Rendering",
|
|
160
|
+
shortLabel: "Hybrid",
|
|
161
|
+
description: "Different routes use different render modes. Configured via the `render.routes` map in rakta.config.ts. The top-level mode is the fallback.",
|
|
162
|
+
serverRequired: false,
|
|
163
|
+
buildTimeGenerated: false,
|
|
164
|
+
clientHydration: true,
|
|
165
|
+
roadmap: false
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
function pathMatchesPattern(pathname, pattern) {
|
|
169
|
+
if (pattern === pathname)
|
|
170
|
+
return true;
|
|
171
|
+
const patternParts = pattern.split("/");
|
|
172
|
+
const pathParts = pathname.split("/");
|
|
173
|
+
if (patternParts.length !== pathParts.length)
|
|
174
|
+
return false;
|
|
175
|
+
for (let i = 0;i < patternParts.length; i++) {
|
|
176
|
+
const pp = patternParts[i];
|
|
177
|
+
const path = pathParts[i];
|
|
178
|
+
if (pp === undefined || path === undefined)
|
|
179
|
+
return false;
|
|
180
|
+
if (pp.startsWith(":"))
|
|
181
|
+
continue;
|
|
182
|
+
if (pp !== path)
|
|
183
|
+
return false;
|
|
184
|
+
}
|
|
185
|
+
return true;
|
|
186
|
+
}
|
|
187
|
+
function resolveRouteMode(pathname, config) {
|
|
188
|
+
const patterns = Object.keys(config.routes).sort((a, b) => b.length - a.length);
|
|
189
|
+
for (const pattern of patterns) {
|
|
190
|
+
const overrideMode = config.routes[pattern];
|
|
191
|
+
if (overrideMode !== undefined && pathMatchesPattern(pathname, pattern)) {
|
|
192
|
+
return {
|
|
193
|
+
routePath: pathname,
|
|
194
|
+
mode: overrideMode,
|
|
195
|
+
source: "route-override"
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
return {
|
|
200
|
+
routePath: pathname,
|
|
201
|
+
mode: config.defaultMode,
|
|
202
|
+
source: "default"
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
function isRoadmapMode(mode) {
|
|
206
|
+
return RENDER_MODE_DESCRIPTORS[mode].roadmap;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// src/router/manifest.ts
|
|
210
|
+
import { existsSync as existsSync3, readFileSync, writeFileSync } from "fs";
|
|
211
|
+
|
|
212
|
+
// src/router/scanner.ts
|
|
213
|
+
import { existsSync as existsSync2, readdirSync, statSync } from "fs";
|
|
214
|
+
import { join as join2, relative } from "path";
|
|
215
|
+
var FILE_TO_KIND = {
|
|
216
|
+
"page.tsx": "page",
|
|
217
|
+
"page.ts": "page",
|
|
218
|
+
"page.jsx": "page",
|
|
219
|
+
"page.js": "page",
|
|
220
|
+
"layout.tsx": "layout",
|
|
221
|
+
"layout.ts": "layout",
|
|
222
|
+
"layout.jsx": "layout",
|
|
223
|
+
"layout.js": "layout",
|
|
224
|
+
"loading.tsx": "loading",
|
|
225
|
+
"loading.ts": "loading",
|
|
226
|
+
"loading.jsx": "loading",
|
|
227
|
+
"loading.js": "loading",
|
|
228
|
+
"notFound.tsx": "notFound",
|
|
229
|
+
"notFound.ts": "notFound",
|
|
230
|
+
"notFound.jsx": "notFound",
|
|
231
|
+
"notFound.js": "notFound",
|
|
232
|
+
"error.tsx": "error",
|
|
233
|
+
"error.ts": "error",
|
|
234
|
+
"error.jsx": "error",
|
|
235
|
+
"error.js": "error",
|
|
236
|
+
"route.ts": "api",
|
|
237
|
+
"route.js": "api"
|
|
238
|
+
};
|
|
239
|
+
function parseSegment(raw) {
|
|
240
|
+
const isDynamic = raw.startsWith("[") && raw.endsWith("]");
|
|
241
|
+
const paramName = isDynamic ? raw.slice(1, -1) : "";
|
|
242
|
+
return { raw, isDynamic, paramName };
|
|
243
|
+
}
|
|
244
|
+
function collectParamNames(segments) {
|
|
245
|
+
return segments.filter((segment) => segment.isDynamic && segment.paramName.length > 0).map((segment) => segment.paramName);
|
|
246
|
+
}
|
|
247
|
+
function segmentsToUrlPattern(segments) {
|
|
248
|
+
if (segments.length === 0)
|
|
249
|
+
return "/";
|
|
250
|
+
const parts = segments.map((segment) => segment.isDynamic && segment.paramName ? `:${segment.paramName}` : segment.raw);
|
|
251
|
+
return `/${parts.join("/")}`;
|
|
252
|
+
}
|
|
253
|
+
function scanDirectory(dirPath, appRoot, results) {
|
|
254
|
+
if (!existsSync2(dirPath))
|
|
255
|
+
return;
|
|
256
|
+
const entries = readdirSync(dirPath);
|
|
257
|
+
for (const entryName of entries) {
|
|
258
|
+
const fullPath = join2(dirPath, entryName);
|
|
259
|
+
const stats = statSync(fullPath);
|
|
260
|
+
if (stats.isDirectory()) {
|
|
261
|
+
scanDirectory(fullPath, appRoot, results);
|
|
262
|
+
continue;
|
|
263
|
+
}
|
|
264
|
+
if (!stats.isFile())
|
|
265
|
+
continue;
|
|
266
|
+
const kind = FILE_TO_KIND[entryName];
|
|
267
|
+
if (!kind)
|
|
268
|
+
continue;
|
|
269
|
+
const relativePath = relative(appRoot, fullPath).replace(/\\/g, "/");
|
|
270
|
+
const dirRelative = relative(appRoot, dirPath).replace(/\\/g, "/");
|
|
271
|
+
const rawSegments = dirRelative === "" ? [] : dirRelative.split("/");
|
|
272
|
+
const segments = rawSegments.map(parseSegment);
|
|
273
|
+
const urlPattern = segmentsToUrlPattern(segments);
|
|
274
|
+
const paramNames = collectParamNames(segments);
|
|
275
|
+
const isDynamic = paramNames.length > 0;
|
|
276
|
+
results.push({
|
|
277
|
+
filePath: relativePath,
|
|
278
|
+
urlPattern,
|
|
279
|
+
kind,
|
|
280
|
+
segments,
|
|
281
|
+
isDynamic,
|
|
282
|
+
paramNames
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
function scanRoutes(options) {
|
|
287
|
+
const results = [];
|
|
288
|
+
scanDirectory(options.appDir, options.appDir, results);
|
|
289
|
+
results.sort((routeA, routeB) => {
|
|
290
|
+
if (routeA.isDynamic !== routeB.isDynamic) {
|
|
291
|
+
return routeA.isDynamic ? 1 : -1;
|
|
292
|
+
}
|
|
293
|
+
return routeA.urlPattern.localeCompare(routeB.urlPattern);
|
|
294
|
+
});
|
|
295
|
+
return results;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// src/router/manifest.ts
|
|
299
|
+
function generateManifest(appDir) {
|
|
300
|
+
const routes = scanRoutes({ appDir });
|
|
301
|
+
const manifest = {
|
|
302
|
+
version: "1",
|
|
303
|
+
generatedAt: new Date().toISOString(),
|
|
304
|
+
routes
|
|
305
|
+
};
|
|
306
|
+
return manifest;
|
|
307
|
+
}
|
|
308
|
+
function writeManifest(manifest, outputPath) {
|
|
309
|
+
writeFileSync(outputPath, JSON.stringify(manifest, null, 2), "utf-8");
|
|
310
|
+
}
|
|
311
|
+
function readManifest(manifestPath) {
|
|
312
|
+
if (!existsSync3(manifestPath))
|
|
313
|
+
return null;
|
|
314
|
+
const raw = readFileSync(manifestPath, "utf-8");
|
|
315
|
+
const parsed = JSON.parse(raw);
|
|
316
|
+
if (typeof parsed !== "object" || parsed === null || !("version" in parsed) || !("routes" in parsed)) {
|
|
317
|
+
return null;
|
|
318
|
+
}
|
|
319
|
+
return parsed;
|
|
320
|
+
}
|
|
321
|
+
function printManifest(manifest) {
|
|
322
|
+
const pageRoutes = manifest.routes.filter((route) => route.kind === "page");
|
|
323
|
+
const apiRoutes = manifest.routes.filter((route) => route.kind === "api");
|
|
324
|
+
const layoutRoutes = manifest.routes.filter((route) => route.kind === "layout");
|
|
325
|
+
const specialRoutes = manifest.routes.filter((route) => ["loading", "not-found", "error"].includes(route.kind));
|
|
326
|
+
const line = "\u2500".repeat(56);
|
|
327
|
+
console.log(`
|
|
328
|
+
Rakta.js Route Manifest`);
|
|
329
|
+
console.log(` ${line}`);
|
|
330
|
+
console.log(` Generated: ${manifest.generatedAt}`);
|
|
331
|
+
console.log(` Total: ${manifest.routes.length} routes
|
|
332
|
+
`);
|
|
333
|
+
if (pageRoutes.length > 0) {
|
|
334
|
+
console.log(" Pages:");
|
|
335
|
+
for (const route of pageRoutes) {
|
|
336
|
+
const dynamic = route.isDynamic ? ` [params: ${route.paramNames.join(", ")}]` : "";
|
|
337
|
+
console.log(` GET ${route.urlPattern}${dynamic}`);
|
|
338
|
+
}
|
|
339
|
+
console.log("");
|
|
340
|
+
}
|
|
341
|
+
if (apiRoutes.length > 0) {
|
|
342
|
+
console.log(" API Routes:");
|
|
343
|
+
for (const route of apiRoutes) {
|
|
344
|
+
const dynamic = route.isDynamic ? ` [params: ${route.paramNames.join(", ")}]` : "";
|
|
345
|
+
console.log(` ANY ${route.urlPattern}${dynamic}`);
|
|
346
|
+
}
|
|
347
|
+
console.log("");
|
|
348
|
+
}
|
|
349
|
+
if (layoutRoutes.length > 0) {
|
|
350
|
+
console.log(" Layouts:");
|
|
351
|
+
for (const route of layoutRoutes) {
|
|
352
|
+
console.log(` ${route.urlPattern === "/" ? "root" : route.urlPattern}`);
|
|
353
|
+
}
|
|
354
|
+
console.log("");
|
|
355
|
+
}
|
|
356
|
+
if (specialRoutes.length > 0) {
|
|
357
|
+
console.log(" Special:");
|
|
358
|
+
for (const route of specialRoutes) {
|
|
359
|
+
console.log(` [${route.kind}] ${route.filePath}`);
|
|
360
|
+
}
|
|
361
|
+
console.log("");
|
|
362
|
+
}
|
|
363
|
+
console.log(` ${line}
|
|
364
|
+
`);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// src/forge/inspect.ts
|
|
368
|
+
function detectArtifactKind(filename) {
|
|
369
|
+
if (filename === "route-manifest.json")
|
|
370
|
+
return "manifest";
|
|
371
|
+
if (filename.endsWith(".css"))
|
|
372
|
+
return "stylesheet";
|
|
373
|
+
if (filename.endsWith(".js") || filename.endsWith(".mjs"))
|
|
374
|
+
return "script";
|
|
375
|
+
return "asset";
|
|
376
|
+
}
|
|
377
|
+
function formatBytes(bytes) {
|
|
378
|
+
if (bytes < 1024)
|
|
379
|
+
return `${bytes} B`;
|
|
380
|
+
if (bytes < 1048576)
|
|
381
|
+
return `${(bytes / 1024).toFixed(1)} KB`;
|
|
382
|
+
return `${(bytes / 1048576).toFixed(2)} MB`;
|
|
383
|
+
}
|
|
384
|
+
function scanDirectory2(dirPath) {
|
|
385
|
+
if (!existsSync4(dirPath))
|
|
386
|
+
return [];
|
|
387
|
+
const collected = [];
|
|
388
|
+
function walk(current) {
|
|
389
|
+
const entries = readdirSync2(current, { withFileTypes: true });
|
|
390
|
+
for (const entry of entries) {
|
|
391
|
+
const full = join3(current, entry.name);
|
|
392
|
+
if (entry.isDirectory()) {
|
|
393
|
+
walk(full);
|
|
394
|
+
} else if (entry.isFile()) {
|
|
395
|
+
collected.push({
|
|
396
|
+
outputPath: full,
|
|
397
|
+
sizeBytes: statSync2(full).size,
|
|
398
|
+
kind: detectArtifactKind(entry.name)
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
walk(dirPath);
|
|
404
|
+
return collected;
|
|
405
|
+
}
|
|
406
|
+
function inspectBuild(options) {
|
|
407
|
+
const artifacts = scanDirectory2(options.outDir);
|
|
408
|
+
const manifestPath = join3(options.outDir, "route-manifest.json");
|
|
409
|
+
const manifest = readManifest(manifestPath);
|
|
410
|
+
const emptyManifest = {
|
|
411
|
+
version: "1",
|
|
412
|
+
generatedAt: new Date().toISOString(),
|
|
413
|
+
routes: []
|
|
414
|
+
};
|
|
415
|
+
const routeModes = (manifest ?? emptyManifest).routes.filter((r) => r.kind === "page" || r.kind === "api").map((r) => {
|
|
416
|
+
const resolved = resolveRouteMode(r.urlPattern, options.renderConfig);
|
|
417
|
+
return {
|
|
418
|
+
pattern: r.urlPattern,
|
|
419
|
+
mode: resolved.mode,
|
|
420
|
+
source: resolved.source
|
|
421
|
+
};
|
|
422
|
+
});
|
|
423
|
+
return {
|
|
424
|
+
buildDir: options.outDir,
|
|
425
|
+
artifacts,
|
|
426
|
+
manifest: manifest ?? emptyManifest,
|
|
427
|
+
routeModes,
|
|
428
|
+
totalSizeBytes: artifacts.reduce((sum, a) => sum + a.sizeBytes, 0),
|
|
429
|
+
inspectedAt: new Date().toISOString()
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
function printInspectReport(report) {
|
|
433
|
+
const sep = "\u2500".repeat(58);
|
|
434
|
+
console.log(`
|
|
435
|
+
|
|
436
|
+
Rakta.js Forge - Build Inspection
|
|
437
|
+
`);
|
|
438
|
+
console.log(`
|
|
439
|
+
${sep}
|
|
440
|
+
`);
|
|
441
|
+
console.log(`
|
|
442
|
+
Directory: ${report.buildDir}
|
|
443
|
+
`);
|
|
444
|
+
console.log(`
|
|
445
|
+
Inspected: ${report.inspectedAt}
|
|
446
|
+
`);
|
|
447
|
+
console.log(`
|
|
448
|
+
Total size: ${formatBytes(report.totalSizeBytes)}
|
|
449
|
+
|
|
450
|
+
`);
|
|
451
|
+
if (report.artifacts.length > 0) {
|
|
452
|
+
console.log("Artifacts:");
|
|
453
|
+
for (const artifact of report.artifacts) {
|
|
454
|
+
const rel = artifact.outputPath.replace(report.buildDir, ".").replace(/\\/g, "/");
|
|
455
|
+
console.log(`
|
|
456
|
+
[${artifact.kind.padEnd(10)}]
|
|
457
|
+
${rel.padEnd(38)}
|
|
458
|
+
${formatBytes(artifact.sizeBytes)}`);
|
|
459
|
+
}
|
|
460
|
+
console.log("");
|
|
461
|
+
}
|
|
462
|
+
if (report.routeModes.length > 0) {
|
|
463
|
+
console.log("Route Render Modes:");
|
|
464
|
+
for (const rm of report.routeModes) {
|
|
465
|
+
const src = rm.source === "route-override" ? "(override)" : "(default) ";
|
|
466
|
+
console.log(`
|
|
467
|
+
${rm.mode.toUpperCase().padEnd(8)}
|
|
468
|
+
${rm.pattern.padEnd(36)} ${src}`);
|
|
469
|
+
}
|
|
470
|
+
console.log("");
|
|
471
|
+
}
|
|
472
|
+
console.log(`${sep}
|
|
473
|
+
`);
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
// src/cli/build.ts
|
|
477
|
+
import { isAbsolute, join as join5 } from "path";
|
|
478
|
+
|
|
479
|
+
// src/forge/build.ts
|
|
480
|
+
import { mkdirSync } from "fs";
|
|
481
|
+
import { join as join4, resolve as resolve2 } from "path";
|
|
482
|
+
async function buildProject(options) {
|
|
483
|
+
const startMs = Date.now();
|
|
484
|
+
const artifacts = [];
|
|
485
|
+
const errors = [];
|
|
486
|
+
mkdirSync(resolve2(options.outDir), { recursive: true });
|
|
487
|
+
const manifest = generateManifest(options.appDir);
|
|
488
|
+
const manifestPath = join4(options.outDir, "route-manifest.json");
|
|
489
|
+
writeManifest(manifest, manifestPath);
|
|
490
|
+
const manifestContent = JSON.stringify(manifest);
|
|
491
|
+
artifacts.push({
|
|
492
|
+
outputPath: manifestPath,
|
|
493
|
+
sizeBytes: new TextEncoder().encode(manifestContent).byteLength,
|
|
494
|
+
kind: "manifest"
|
|
495
|
+
});
|
|
496
|
+
const buildResult = await Bun.build({
|
|
497
|
+
entrypoints: [options.entryPoint],
|
|
498
|
+
outdir: options.outDir,
|
|
499
|
+
target: options.target,
|
|
500
|
+
minify: options.minify,
|
|
501
|
+
sourcemap: options.sourcemap ? "external" : "none",
|
|
502
|
+
splitting: options.splitting,
|
|
503
|
+
naming: {
|
|
504
|
+
entry: "[name].[ext]",
|
|
505
|
+
chunk: "chunks/[name]-[hash].[ext]",
|
|
506
|
+
asset: "assets/[name]-[hash].[ext]"
|
|
507
|
+
}
|
|
508
|
+
});
|
|
509
|
+
if (!buildResult.success) {
|
|
510
|
+
for (const log of buildResult.logs) {
|
|
511
|
+
errors.push(log.message);
|
|
512
|
+
}
|
|
513
|
+
return {
|
|
514
|
+
success: false,
|
|
515
|
+
artifacts,
|
|
516
|
+
manifest,
|
|
517
|
+
buildMs: Date.now() - startMs,
|
|
518
|
+
errors
|
|
519
|
+
};
|
|
520
|
+
}
|
|
521
|
+
for (const output of buildResult.outputs) {
|
|
522
|
+
artifacts.push({
|
|
523
|
+
outputPath: output.path,
|
|
524
|
+
sizeBytes: output.size,
|
|
525
|
+
kind: output.path.endsWith(".css") ? "stylesheet" : "script"
|
|
526
|
+
});
|
|
527
|
+
}
|
|
528
|
+
return {
|
|
529
|
+
success: true,
|
|
530
|
+
artifacts,
|
|
531
|
+
manifest,
|
|
532
|
+
buildMs: Date.now() - startMs,
|
|
533
|
+
errors: []
|
|
534
|
+
};
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
// src/cli/build.ts
|
|
538
|
+
function resolveProjectPath(cwd, pathValue) {
|
|
539
|
+
if (isAbsolute(pathValue)) {
|
|
540
|
+
return pathValue;
|
|
541
|
+
}
|
|
542
|
+
return join5(cwd, pathValue);
|
|
543
|
+
}
|
|
544
|
+
async function buildCommand(cwd = process.cwd()) {
|
|
545
|
+
const projectConfig = await loadConfig(cwd);
|
|
546
|
+
const appDirectory = projectConfig.appDir ?? "app";
|
|
547
|
+
const publicDirectory = projectConfig.publicDir ?? "public";
|
|
548
|
+
const outputDirectory = projectConfig.build.outDir ?? "dist";
|
|
549
|
+
const entryFile = projectConfig.build.entryPoint ?? "entry.client.tsx";
|
|
550
|
+
const entryPoint = resolveProjectPath(join5(cwd, appDirectory), entryFile);
|
|
551
|
+
const outDir = resolveProjectPath(cwd, outputDirectory);
|
|
552
|
+
console.log(`
|
|
553
|
+
Building Rakta.js application...
|
|
554
|
+
`);
|
|
555
|
+
const buildResult = await buildProject({
|
|
556
|
+
entryPoint,
|
|
557
|
+
outDir,
|
|
558
|
+
appDir: join5(cwd, appDirectory),
|
|
559
|
+
publicDir: join5(cwd, publicDirectory),
|
|
560
|
+
appName: projectConfig.appName,
|
|
561
|
+
sourcemap: projectConfig.build.sourcemap ?? false,
|
|
562
|
+
minify: projectConfig.build.minify ?? true,
|
|
563
|
+
splitting: projectConfig.build.splitting ?? false,
|
|
564
|
+
target: projectConfig.build.target ?? "browser",
|
|
565
|
+
renderConfig: projectConfig.render
|
|
566
|
+
});
|
|
567
|
+
if (!buildResult.success) {
|
|
568
|
+
console.error("Build failed:");
|
|
569
|
+
for (const buildError of buildResult.errors) {
|
|
570
|
+
console.error(`${buildError}`);
|
|
571
|
+
}
|
|
572
|
+
process.exit(1);
|
|
573
|
+
}
|
|
574
|
+
console.log(`Build complete in ${buildResult.buildMs}ms`);
|
|
575
|
+
console.log(`Output: ${outDir}`);
|
|
576
|
+
console.log(`Artifacts: ${buildResult.artifacts.length}
|
|
577
|
+
`);
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
// src/cli/dev.ts
|
|
581
|
+
import { join as join7 } from "path";
|
|
582
|
+
|
|
583
|
+
// src/forge/devServer.ts
|
|
584
|
+
import { existsSync as existsSync5, readFileSync as readFileSync2 } from "fs";
|
|
585
|
+
import { join as join6 } from "path";
|
|
586
|
+
|
|
587
|
+
// src/render/renderer.ts
|
|
588
|
+
function buildHtmlShell(options) {
|
|
589
|
+
return `
|
|
590
|
+
<!DOCTYPE html>
|
|
591
|
+
<html lang="${options.lang}">
|
|
592
|
+
<head>
|
|
593
|
+
<meta charset="UTF-8" />
|
|
594
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
595
|
+
<title>
|
|
596
|
+
${options.appName}
|
|
597
|
+
</title>
|
|
598
|
+
<link rel="stylesheet" href="${options.cssPath}" />
|
|
599
|
+
</head>
|
|
600
|
+
<body>
|
|
601
|
+
<div id="rakta-root"></div>
|
|
602
|
+
<script type="module" src="${options.scriptPath}"></script>
|
|
603
|
+
</body>
|
|
604
|
+
</html>`;
|
|
605
|
+
}
|
|
606
|
+
function makeSuccess(html, mode, renderMs, httpStatus = 200, fromCache = false) {
|
|
607
|
+
return {
|
|
608
|
+
kind: "success",
|
|
609
|
+
html,
|
|
610
|
+
mode,
|
|
611
|
+
httpStatus,
|
|
612
|
+
responseHeaders: { "Content-Type": "text/html; charset=utf-8" },
|
|
613
|
+
fromCache,
|
|
614
|
+
renderMs
|
|
615
|
+
};
|
|
616
|
+
}
|
|
617
|
+
function makeFailure(reason, mode, httpStatus = 500) {
|
|
618
|
+
return { kind: "failure", reason, mode, httpStatus };
|
|
619
|
+
}
|
|
620
|
+
async function render(context, options) {
|
|
621
|
+
const startMs = Date.now();
|
|
622
|
+
if (isRoadmapMode(context.mode)) {
|
|
623
|
+
console.warn(`[Rakta.js] Render mode "${context.mode}" is a roadmap feature (v0.2.0). ` + `Falling back to CSR for: ${context.routePath}`);
|
|
624
|
+
return makeSuccess(buildHtmlShell(options), "csr", Date.now() - startMs);
|
|
625
|
+
}
|
|
626
|
+
switch (context.mode) {
|
|
627
|
+
case "csr":
|
|
628
|
+
case "spa":
|
|
629
|
+
return makeSuccess(buildHtmlShell(options), context.mode, Date.now() - startMs);
|
|
630
|
+
case "hybrid":
|
|
631
|
+
return makeSuccess(buildHtmlShell(options), "csr", Date.now() - startMs);
|
|
632
|
+
case "ssr":
|
|
633
|
+
case "ssg":
|
|
634
|
+
case "csg":
|
|
635
|
+
return makeFailure(`Render mode "${context.mode}" is not yet implemented.`, context.mode, 501);
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
// src/router/matcher.ts
|
|
640
|
+
function escapeRegex(text) {
|
|
641
|
+
return text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
642
|
+
}
|
|
643
|
+
function buildPatternRegex(segments) {
|
|
644
|
+
const paramNames = [];
|
|
645
|
+
const parts = segments.map((segment) => {
|
|
646
|
+
if (segment.isDynamic && segment.paramName) {
|
|
647
|
+
paramNames.push(segment.paramName);
|
|
648
|
+
return "([^/]+)";
|
|
649
|
+
}
|
|
650
|
+
return escapeRegex(segment.raw);
|
|
651
|
+
});
|
|
652
|
+
const pattern = parts.length === 0 ? "" : `/${parts.join("/")}`;
|
|
653
|
+
const regex = new RegExp(`^${pattern || "/"}$`);
|
|
654
|
+
return { regex, paramNames };
|
|
655
|
+
}
|
|
656
|
+
function matchRoute(pathname, routes) {
|
|
657
|
+
const normalized = pathname !== "/" && pathname.endsWith("/") ? pathname.slice(0, -1) : pathname;
|
|
658
|
+
for (const entry of routes) {
|
|
659
|
+
if (entry.kind !== "page" && entry.kind !== "api")
|
|
660
|
+
continue;
|
|
661
|
+
const { regex, paramNames } = buildPatternRegex(entry.segments);
|
|
662
|
+
const match = regex.exec(normalized);
|
|
663
|
+
if (!match)
|
|
664
|
+
continue;
|
|
665
|
+
const params = {};
|
|
666
|
+
paramNames.forEach((name, index) => {
|
|
667
|
+
const captured = match[index + 1];
|
|
668
|
+
if (captured !== undefined) {
|
|
669
|
+
params[name] = decodeURIComponent(captured);
|
|
670
|
+
}
|
|
671
|
+
});
|
|
672
|
+
return { entry, params };
|
|
673
|
+
}
|
|
674
|
+
return null;
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
// src/forge/devServer.ts
|
|
678
|
+
var DEFAULT_DEV_PORT = 3000;
|
|
679
|
+
var MIME_MAP = {
|
|
680
|
+
".html": "text/html; charset=utf-8",
|
|
681
|
+
".js": "application/javascript; charset=utf-8",
|
|
682
|
+
".mjs": "application/javascript; charset=utf-8",
|
|
683
|
+
".css": "text/css; charset=utf-8",
|
|
684
|
+
".json": "application/json; charset=utf-8",
|
|
685
|
+
".png": "image/png",
|
|
686
|
+
".jpg": "image/jpeg",
|
|
687
|
+
".jpeg": "image/jpeg",
|
|
688
|
+
".webp": "image/webp",
|
|
689
|
+
".svg": "image/svg+xml",
|
|
690
|
+
".ico": "image/x-icon",
|
|
691
|
+
".woff": "font/woff",
|
|
692
|
+
".woff2": "font/woff2",
|
|
693
|
+
".ttf": "font/ttf"
|
|
694
|
+
};
|
|
695
|
+
function resolveMime(filePath) {
|
|
696
|
+
const ext = filePath.slice(filePath.lastIndexOf(".")).toLowerCase();
|
|
697
|
+
return MIME_MAP[ext] ?? "application/octet-stream";
|
|
698
|
+
}
|
|
699
|
+
function resolveDevPort(port) {
|
|
700
|
+
return port > 0 ? port : DEFAULT_DEV_PORT;
|
|
701
|
+
}
|
|
702
|
+
function startDevServer(options) {
|
|
703
|
+
const manifest = generateManifest(options.appDir);
|
|
704
|
+
const resolvedPort = resolveDevPort(options.port);
|
|
705
|
+
const server = Bun.serve({
|
|
706
|
+
port: resolvedPort,
|
|
707
|
+
hostname: options.host,
|
|
708
|
+
async fetch(request) {
|
|
709
|
+
const url = new URL(request.url);
|
|
710
|
+
const { pathname } = url;
|
|
711
|
+
const publicPath = join6(options.publicDir, pathname);
|
|
712
|
+
if (existsSync5(publicPath) && !publicPath.endsWith("/")) {
|
|
713
|
+
return new Response(readFileSync2(publicPath), {
|
|
714
|
+
headers: { "Content-Type": resolveMime(pathname) }
|
|
715
|
+
});
|
|
716
|
+
}
|
|
717
|
+
const apiMatch = matchRoute(pathname, manifest.routes.filter((route) => route.kind === "api"));
|
|
718
|
+
if (apiMatch) {
|
|
719
|
+
const modulePath = join6(options.appDir, apiMatch.entry.filePath);
|
|
720
|
+
const routeModule = await import(modulePath);
|
|
721
|
+
const method = request.method.toUpperCase();
|
|
722
|
+
const handler = routeModule[method];
|
|
723
|
+
if (typeof handler !== "function") {
|
|
724
|
+
return new Response("Method not allowed", { status: 405 });
|
|
725
|
+
}
|
|
726
|
+
return await handler(request);
|
|
727
|
+
}
|
|
728
|
+
const resolved = resolveRouteMode(pathname, options.renderConfig);
|
|
729
|
+
const searchParams = {};
|
|
730
|
+
url.searchParams.forEach((value, key) => {
|
|
731
|
+
searchParams[key] = value;
|
|
732
|
+
});
|
|
733
|
+
const requestHeaders = {};
|
|
734
|
+
request.headers.forEach((value, key) => {
|
|
735
|
+
requestHeaders[key] = value;
|
|
736
|
+
});
|
|
737
|
+
const result = await render({
|
|
738
|
+
routePath: pathname,
|
|
739
|
+
mode: resolved.mode,
|
|
740
|
+
params: {},
|
|
741
|
+
searchParams,
|
|
742
|
+
requestHeaders,
|
|
743
|
+
timestampMs: Date.now()
|
|
744
|
+
}, {
|
|
745
|
+
appName: options.appName,
|
|
746
|
+
scriptPath: "/app.js",
|
|
747
|
+
cssPath: "/globals.css",
|
|
748
|
+
lang: "en"
|
|
749
|
+
});
|
|
750
|
+
if (result.kind === "failure") {
|
|
751
|
+
return new Response(result.reason, { status: result.httpStatus });
|
|
752
|
+
}
|
|
753
|
+
return new Response(result.html, {
|
|
754
|
+
status: result.httpStatus,
|
|
755
|
+
headers: result.responseHeaders
|
|
756
|
+
});
|
|
757
|
+
}
|
|
758
|
+
});
|
|
759
|
+
const serverPort = typeof server.port === "number" ? server.port : resolvedPort;
|
|
760
|
+
return {
|
|
761
|
+
port: serverPort,
|
|
762
|
+
host: options.host,
|
|
763
|
+
url: `http://${options.host}:${serverPort}`,
|
|
764
|
+
stop: () => server.stop()
|
|
765
|
+
};
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
// src/cli/dev.ts
|
|
769
|
+
async function devCommand(cwd = process.cwd()) {
|
|
770
|
+
const config = await loadConfig(cwd);
|
|
771
|
+
console.log(`
|
|
772
|
+
Starting Rakta.js dev server...
|
|
773
|
+
`);
|
|
774
|
+
startDevServer({
|
|
775
|
+
port: config.port,
|
|
776
|
+
host: config.server.hostname ?? "0.0.0.0",
|
|
777
|
+
appDir: join7(cwd, config.appDir),
|
|
778
|
+
publicDir: join7(cwd, config.publicDir),
|
|
779
|
+
appName: config.appName,
|
|
780
|
+
renderConfig: config.render
|
|
781
|
+
});
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
// src/cli/doctor.ts
|
|
785
|
+
import { existsSync as existsSync6 } from "fs";
|
|
786
|
+
import { join as join8 } from "path";
|
|
787
|
+
async function doctorCommand(cwd = process.cwd()) {
|
|
788
|
+
const config = await loadConfig(cwd);
|
|
789
|
+
const checks = [
|
|
790
|
+
{
|
|
791
|
+
label: "Bun runtime",
|
|
792
|
+
passed: typeof Bun !== "undefined",
|
|
793
|
+
note: typeof Bun !== "undefined" ? `v${Bun.version}` : "not found"
|
|
794
|
+
},
|
|
795
|
+
{
|
|
796
|
+
label: "rakta.config.ts",
|
|
797
|
+
passed: existsSync6(join8(cwd, "rakta.config.ts")) || existsSync6(join8(cwd, "rakta.config.js"))
|
|
798
|
+
},
|
|
799
|
+
{
|
|
800
|
+
label: `app/ directory (${config.appDir})`,
|
|
801
|
+
passed: existsSync6(join8(cwd, config.appDir))
|
|
802
|
+
},
|
|
803
|
+
{
|
|
804
|
+
label: "app/page.tsx (root page)",
|
|
805
|
+
passed: existsSync6(join8(cwd, config.appDir, "page.tsx"))
|
|
806
|
+
},
|
|
807
|
+
{
|
|
808
|
+
label: "app/layout.tsx (root layout)",
|
|
809
|
+
passed: existsSync6(join8(cwd, config.appDir, "layout.tsx"))
|
|
810
|
+
},
|
|
811
|
+
{
|
|
812
|
+
label: `public/ directory (${config.publicDir})`,
|
|
813
|
+
passed: existsSync6(join8(cwd, config.publicDir))
|
|
814
|
+
},
|
|
815
|
+
{
|
|
816
|
+
label: "package.json",
|
|
817
|
+
passed: existsSync6(join8(cwd, "package.json"))
|
|
818
|
+
},
|
|
819
|
+
{
|
|
820
|
+
label: "tsconfig.json",
|
|
821
|
+
passed: existsSync6(join8(cwd, "tsconfig.json"))
|
|
822
|
+
}
|
|
823
|
+
];
|
|
824
|
+
const sep = "\u2500".repeat(52);
|
|
825
|
+
console.log(`
|
|
826
|
+
Rakta.js Doctor`);
|
|
827
|
+
console.log(` ${sep}`);
|
|
828
|
+
let allPassed = true;
|
|
829
|
+
for (const check of checks) {
|
|
830
|
+
const icon = check.passed ? "pass" : "FAIL";
|
|
831
|
+
const note = check.note ? ` (${check.note})` : "";
|
|
832
|
+
console.log(` [${icon}] ${check.label}${note}`);
|
|
833
|
+
if (!check.passed)
|
|
834
|
+
allPassed = false;
|
|
835
|
+
}
|
|
836
|
+
console.log(` ${sep}`);
|
|
837
|
+
if (allPassed) {
|
|
838
|
+
console.log(` All checks passed.
|
|
839
|
+
`);
|
|
840
|
+
} else {
|
|
841
|
+
console.log(` Some checks failed. Review the output above.
|
|
842
|
+
`);
|
|
843
|
+
process.exit(1);
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
// src/auto-import/generator.ts
|
|
848
|
+
import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
849
|
+
import { join as join10 } from "path";
|
|
850
|
+
|
|
851
|
+
// src/auto-import/scanner.ts
|
|
852
|
+
import { existsSync as existsSync7, readdirSync as readdirSync3 } from "fs";
|
|
853
|
+
import { basename, extname, join as join9, relative as relative2 } from "path";
|
|
854
|
+
var DEFAULT_EXTENSIONS = [
|
|
855
|
+
".ts",
|
|
856
|
+
".tsx",
|
|
857
|
+
".js",
|
|
858
|
+
".jsx"
|
|
859
|
+
];
|
|
860
|
+
function detectKind(filePath) {
|
|
861
|
+
const lower = filePath.replace(/\\/g, "/").toLowerCase();
|
|
862
|
+
if (lower.includes("/stores/") || lower.endsWith(".store.ts") || lower.endsWith(".store.tsx")) {
|
|
863
|
+
return "store";
|
|
864
|
+
}
|
|
865
|
+
if (lower.includes("/schemas/") || lower.endsWith(".schema.ts") || lower.endsWith(".schema.tsx")) {
|
|
866
|
+
return "schema";
|
|
867
|
+
}
|
|
868
|
+
if (lower.includes("/components/")) {
|
|
869
|
+
return "component";
|
|
870
|
+
}
|
|
871
|
+
const fileName = basename(lower);
|
|
872
|
+
if (fileName.startsWith("use")) {
|
|
873
|
+
return "hook";
|
|
874
|
+
}
|
|
875
|
+
if (lower.includes("/lib/") || lower.includes("/utils/") || lower.includes("/helpers/")) {
|
|
876
|
+
return "utils";
|
|
877
|
+
}
|
|
878
|
+
return "unknown";
|
|
879
|
+
}
|
|
880
|
+
function toPascalCase(text) {
|
|
881
|
+
return text.replace(/[-_.]/g, " ").replace(/\s+(.)/g, (_match, char) => char.toUpperCase()).replace(/^(.)/, (_match, char) => char.toUpperCase());
|
|
882
|
+
}
|
|
883
|
+
function deriveExportName(filePath, dirPath) {
|
|
884
|
+
const relativePath = relative2(dirPath, filePath).replace(/\\/g, "/");
|
|
885
|
+
const withoutExtension = relativePath.replace(/\.(ts|tsx|js|jsx)$/, "").replace(/\/index$/, "");
|
|
886
|
+
return toPascalCase(withoutExtension.replace(/\//g, "_"));
|
|
887
|
+
}
|
|
888
|
+
function walkDirectory(dirPath, extensions) {
|
|
889
|
+
if (!existsSync7(dirPath)) {
|
|
890
|
+
return [];
|
|
891
|
+
}
|
|
892
|
+
const collected = [];
|
|
893
|
+
function walk(currentPath) {
|
|
894
|
+
const entries = readdirSync3(currentPath, {
|
|
895
|
+
withFileTypes: true
|
|
896
|
+
});
|
|
897
|
+
for (const entry of entries) {
|
|
898
|
+
if (entry.name.startsWith(".") || entry.name === "node_modules") {
|
|
899
|
+
continue;
|
|
900
|
+
}
|
|
901
|
+
const fullPath = join9(currentPath, entry.name);
|
|
902
|
+
if (entry.isDirectory()) {
|
|
903
|
+
walk(fullPath);
|
|
904
|
+
continue;
|
|
905
|
+
}
|
|
906
|
+
if (entry.isFile() && extensions.includes(extname(entry.name))) {
|
|
907
|
+
collected.push(fullPath);
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
walk(dirPath);
|
|
912
|
+
return collected;
|
|
913
|
+
}
|
|
914
|
+
function scanForExports(options) {
|
|
915
|
+
const extensions = options.extensions ?? DEFAULT_EXTENSIONS;
|
|
916
|
+
const discovered = [];
|
|
917
|
+
const outputAbs = join9(options.frontendRoot, options.outputDirectory);
|
|
918
|
+
for (const directory of options.directories) {
|
|
919
|
+
const directoryAbs = join9(options.frontendRoot, directory);
|
|
920
|
+
const files = walkDirectory(directoryAbs, extensions);
|
|
921
|
+
for (const filePath of files) {
|
|
922
|
+
const relativeFromOutput = relative2(outputAbs, filePath).replace(/\\/g, "/").replace(/\.(ts|tsx)$/, "");
|
|
923
|
+
const importPath = relativeFromOutput.startsWith(".") ? relativeFromOutput : `./${relativeFromOutput}`;
|
|
924
|
+
discovered.push({
|
|
925
|
+
name: deriveExportName(filePath, directoryAbs),
|
|
926
|
+
filePath: relative2(options.frontendRoot, filePath).replace(/\\/g, "/"),
|
|
927
|
+
importPath,
|
|
928
|
+
kind: detectKind(filePath)
|
|
929
|
+
});
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
return discovered;
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
// src/auto-import/generator.ts
|
|
936
|
+
var GENERATED_HEADER = `// This file is auto-generated by: bun rakta imports:generate
|
|
937
|
+
// Do not edit manually. Re-generate whenever you add or remove files.
|
|
938
|
+
// Note: Rakta Auto Import v0.1.0 produces a typed re-export layer.
|
|
939
|
+
// Import from this file in your app code:
|
|
940
|
+
// import { useCounterStore, http } from "./.rakta/auto-imports";
|
|
941
|
+
`;
|
|
942
|
+
function buildImportLines(exports) {
|
|
943
|
+
if (exports.length === 0) {
|
|
944
|
+
return `// No exports discovered. Check your autoImport.directories config.
|
|
945
|
+
`;
|
|
946
|
+
}
|
|
947
|
+
return exports.map((discoveredExport) => `export * from "${discoveredExport.importPath}";`).join(`
|
|
948
|
+
`);
|
|
949
|
+
}
|
|
950
|
+
function generateAutoImports(options) {
|
|
951
|
+
const exports = scanForExports({
|
|
952
|
+
frontendRoot: options.frontendRoot,
|
|
953
|
+
directories: options.directories,
|
|
954
|
+
outputDirectory: options.outputDirectory,
|
|
955
|
+
...options.extensions ? {
|
|
956
|
+
extensions: options.extensions
|
|
957
|
+
} : {}
|
|
958
|
+
});
|
|
959
|
+
const outputDir = join10(options.frontendRoot, options.outputDirectory);
|
|
960
|
+
mkdirSync2(outputDir, {
|
|
961
|
+
recursive: true
|
|
962
|
+
});
|
|
963
|
+
const tsContent = `${GENERATED_HEADER}
|
|
964
|
+
${buildImportLines(exports)}
|
|
965
|
+
`;
|
|
966
|
+
writeFileSync2(join10(outputDir, "auto-imports.ts"), tsContent, "utf-8");
|
|
967
|
+
if (options.generateDts) {
|
|
968
|
+
const dtsContent = tsContent.replace("// This file is auto-generated by: bun rakta imports:generate", "// Type declarations \u2014 auto-generated by: bun rakta imports:generate");
|
|
969
|
+
writeFileSync2(join10(outputDir, "auto-imports.d.ts"), dtsContent, "utf-8");
|
|
970
|
+
}
|
|
971
|
+
return {
|
|
972
|
+
generatedAt: new Date().toISOString(),
|
|
973
|
+
sourceDirectories: options.directories,
|
|
974
|
+
exports
|
|
975
|
+
};
|
|
976
|
+
}
|
|
977
|
+
function printAutoImportSummary(manifest) {
|
|
978
|
+
console.log(`
|
|
979
|
+
Rakta Auto Import`);
|
|
980
|
+
console.log(` ${"\u2500".repeat(50)}`);
|
|
981
|
+
console.log(`Generated: ${manifest.generatedAt}`);
|
|
982
|
+
console.log(`Exports: ${manifest.exports.length}`);
|
|
983
|
+
if (manifest.exports.length > 0) {
|
|
984
|
+
console.log("");
|
|
985
|
+
for (const discoveredExport of manifest.exports) {
|
|
986
|
+
console.log(`[${discoveredExport.kind.padEnd(10)}] ${discoveredExport.name} <- ${discoveredExport.filePath}`);
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
console.log(` ${"\u2500".repeat(50)}
|
|
990
|
+
`);
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
// src/cli/imports.ts
|
|
994
|
+
async function importsGenerateCommand(cwd = process.cwd()) {
|
|
995
|
+
const config = await loadConfig(cwd);
|
|
996
|
+
if (!config.autoImport.enabled) {
|
|
997
|
+
console.warn(" Auto Import is disabled in rakta.config.ts (autoImport.enabled = false).");
|
|
998
|
+
return;
|
|
999
|
+
}
|
|
1000
|
+
const manifest = generateAutoImports({
|
|
1001
|
+
frontendRoot: cwd,
|
|
1002
|
+
directories: config.autoImport.directories,
|
|
1003
|
+
outputDirectory: config.autoImport.outputDirectory,
|
|
1004
|
+
extensions: config.autoImport.extensions ?? [".ts", ".tsx", ".js", ".jsx"],
|
|
1005
|
+
generateDts: config.autoImport.dts ?? true
|
|
1006
|
+
});
|
|
1007
|
+
printAutoImportSummary(manifest);
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
// src/cli/make.ts
|
|
1011
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
1012
|
+
import { dirname, join as join11 } from "path";
|
|
1013
|
+
function toPascalCase2(name) {
|
|
1014
|
+
return name.split(/[-_/]/).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
|
|
1015
|
+
}
|
|
1016
|
+
function writeIfNew(filePath, content) {
|
|
1017
|
+
if (existsSync8(filePath)) {
|
|
1018
|
+
console.warn(` Already exists: ${filePath}`);
|
|
1019
|
+
return;
|
|
1020
|
+
}
|
|
1021
|
+
mkdirSync3(dirname(filePath), { recursive: true });
|
|
1022
|
+
writeFileSync3(filePath, content, "utf-8");
|
|
1023
|
+
console.log(` Created: ${filePath}`);
|
|
1024
|
+
}
|
|
1025
|
+
function getRelativeFilePath(filePath, cwd) {
|
|
1026
|
+
return filePath.replace(`${cwd}/`, "");
|
|
1027
|
+
}
|
|
1028
|
+
async function makeCommand(target, name, cwd = process.cwd()) {
|
|
1029
|
+
const config = await loadConfig(cwd);
|
|
1030
|
+
const appDir = join11(cwd, config.appDir);
|
|
1031
|
+
const componentName = toPascalCase2(name);
|
|
1032
|
+
switch (target) {
|
|
1033
|
+
case "page": {
|
|
1034
|
+
const filePath = join11(appDir, name, "page.tsx");
|
|
1035
|
+
writeIfNew(filePath, `// ${getRelativeFilePath(filePath, cwd)}
|
|
1036
|
+
import React from "react";
|
|
1037
|
+
import type { Metadata } from "rakta/seo";
|
|
1038
|
+
|
|
1039
|
+
export const metadata: Metadata = {
|
|
1040
|
+
title: "${componentName}",
|
|
1041
|
+
};
|
|
1042
|
+
|
|
1043
|
+
export default function ${componentName}Page() {
|
|
1044
|
+
return (
|
|
1045
|
+
<main>
|
|
1046
|
+
<h1>${componentName}</h1>
|
|
1047
|
+
</main>
|
|
1048
|
+
);
|
|
1049
|
+
}
|
|
1050
|
+
`);
|
|
1051
|
+
break;
|
|
1052
|
+
}
|
|
1053
|
+
case "layout": {
|
|
1054
|
+
const filePath = join11(appDir, name, "layout.tsx");
|
|
1055
|
+
writeIfNew(filePath, `// ${getRelativeFilePath(filePath, cwd)}
|
|
1056
|
+
import React from "react";
|
|
1057
|
+
import type { LayoutProps } from "rakta/router";
|
|
1058
|
+
|
|
1059
|
+
export default function ${componentName}Layout({ children }: LayoutProps) {
|
|
1060
|
+
return <section>{children}</section>;
|
|
1061
|
+
}
|
|
1062
|
+
`);
|
|
1063
|
+
break;
|
|
1064
|
+
}
|
|
1065
|
+
case "component": {
|
|
1066
|
+
const filePath = join11(cwd, "components", "ui", `${componentName}.tsx`);
|
|
1067
|
+
writeIfNew(filePath, `// components/ui/${componentName}.tsx
|
|
1068
|
+
import React from "react";
|
|
1069
|
+
|
|
1070
|
+
export interface ${componentName}Props {
|
|
1071
|
+
children?: React.ReactNode;
|
|
1072
|
+
className?: string;
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
export function ${componentName}({ children, className }: ${componentName}Props) {
|
|
1076
|
+
return <div className={className}>{children}</div>;
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
export default ${componentName};
|
|
1080
|
+
`);
|
|
1081
|
+
break;
|
|
1082
|
+
}
|
|
1083
|
+
case "api": {
|
|
1084
|
+
const filePath = join11(appDir, "api", name, "route.ts");
|
|
1085
|
+
writeIfNew(filePath, `// ${getRelativeFilePath(filePath, cwd)}
|
|
1086
|
+
import type { RouteContext } from "rakta/router";
|
|
1087
|
+
|
|
1088
|
+
export async function GET(_request: Request, context: RouteContext): Promise<Response> {
|
|
1089
|
+
return Response.json({ message: "Hello from /api/${name}", pathname: context.pathname });
|
|
1090
|
+
}
|
|
1091
|
+
`);
|
|
1092
|
+
break;
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
// src/cli/routes.ts
|
|
1098
|
+
import { join as join12 } from "path";
|
|
1099
|
+
async function routesCommand(cwd = process.cwd()) {
|
|
1100
|
+
const config = await loadConfig(cwd);
|
|
1101
|
+
const appDir = join12(cwd, config.appDir);
|
|
1102
|
+
const manifest = generateManifest(appDir);
|
|
1103
|
+
printManifest(manifest);
|
|
1104
|
+
}
|
|
1105
|
+
|
|
1106
|
+
// src/cli/rpcTypes.ts
|
|
1107
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync4, writeFileSync as writeFileSync4 } from "fs";
|
|
1108
|
+
import { join as join13 } from "path";
|
|
1109
|
+
async function rpcTypesCommand(cwd = process.cwd()) {
|
|
1110
|
+
const sharedTypesDir = join13(cwd, "..", "shared", "types");
|
|
1111
|
+
if (!existsSync9(sharedTypesDir)) {
|
|
1112
|
+
mkdirSync4(sharedTypesDir, { recursive: true });
|
|
1113
|
+
}
|
|
1114
|
+
const outputPath = join13(sharedTypesDir, "rpc.ts");
|
|
1115
|
+
const content = `// shared/types/rpc.ts
|
|
1116
|
+
// Auto-generated by: bun rakta rpc:types
|
|
1117
|
+
// Re-exports AppRouter type from the backend for type-safe frontend RPC calls.
|
|
1118
|
+
// Re-run this command after modifying backend/src/rpc/router.ts.
|
|
1119
|
+
|
|
1120
|
+
export type { AppRouter } from "../../backend/src/rpc/router.js";
|
|
1121
|
+
`;
|
|
1122
|
+
writeFileSync4(outputPath, content, "utf-8");
|
|
1123
|
+
console.log(`
|
|
1124
|
+
Generated: shared/types/rpc.ts
|
|
1125
|
+
`);
|
|
1126
|
+
console.log(` Import in frontend:`);
|
|
1127
|
+
console.log(` import type { AppRouter } from "../../shared/types/rpc";
|
|
1128
|
+
`);
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1131
|
+
// src/cli/seo.ts
|
|
1132
|
+
import { existsSync as existsSync10, mkdirSync as mkdirSync5, writeFileSync as writeFileSync5 } from "fs";
|
|
1133
|
+
import { join as join14 } from "path";
|
|
1134
|
+
async function seoGenerateCommand(cwd = process.cwd()) {
|
|
1135
|
+
const config = await loadConfig(cwd);
|
|
1136
|
+
const appDir = join14(cwd, config.appDir);
|
|
1137
|
+
const sitemapDir = join14(appDir, "api", "sitemap.xml");
|
|
1138
|
+
const robotsDir = join14(appDir, "api", "robots.txt");
|
|
1139
|
+
if (!existsSync10(sitemapDir)) {
|
|
1140
|
+
mkdirSync5(sitemapDir, { recursive: true });
|
|
1141
|
+
writeFileSync5(join14(sitemapDir, "route.ts"), `// app/api/sitemap.xml/route.ts \u2014 generated by: bun rakta seo:generate
|
|
1142
|
+
import { createSitemapHandler } from "rakta/seo";
|
|
1143
|
+
|
|
1144
|
+
export const GET = createSitemapHandler({
|
|
1145
|
+
baseUrl: process.env["BASE_URL"] ?? "${config.seo.siteUrl ?? "http://localhost:3000"}",
|
|
1146
|
+
entries: [
|
|
1147
|
+
{ loc: "/", changefreq: "daily", priority: 1.0 },
|
|
1148
|
+
{ loc: "/about", changefreq: "monthly", priority: 0.8 },
|
|
1149
|
+
],
|
|
1150
|
+
});
|
|
1151
|
+
`, "utf-8");
|
|
1152
|
+
console.log(" Created: app/api/sitemap.xml/route.ts");
|
|
1153
|
+
} else {
|
|
1154
|
+
console.warn(" Already exists: app/api/sitemap.xml/route.ts");
|
|
1155
|
+
}
|
|
1156
|
+
if (!existsSync10(robotsDir)) {
|
|
1157
|
+
mkdirSync5(robotsDir, { recursive: true });
|
|
1158
|
+
writeFileSync5(join14(robotsDir, "route.ts"), `// app/api/robots.txt/route.ts \u2014 generated by: bun rakta seo:generate
|
|
1159
|
+
import { createRobotsHandler } from "rakta/seo";
|
|
1160
|
+
|
|
1161
|
+
export const GET = createRobotsHandler({
|
|
1162
|
+
rules: [
|
|
1163
|
+
{
|
|
1164
|
+
userAgent: "*",
|
|
1165
|
+
allow: "/",
|
|
1166
|
+
disallow: ["/api/"],
|
|
1167
|
+
},
|
|
1168
|
+
],
|
|
1169
|
+
sitemap: "${config.seo.siteUrl ?? "http://localhost:3000"}/api/sitemap.xml",
|
|
1170
|
+
});
|
|
1171
|
+
`, "utf-8");
|
|
1172
|
+
console.log(" Created: app/api/robots.txt/route.ts");
|
|
1173
|
+
} else {
|
|
1174
|
+
console.warn(" Already exists: app/api/robots.txt/route.ts");
|
|
1175
|
+
}
|
|
1176
|
+
console.log(`
|
|
1177
|
+
SEO route handlers generated.
|
|
1178
|
+
`);
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1181
|
+
// src/cli/start.ts
|
|
1182
|
+
import { join as join16 } from "path";
|
|
1183
|
+
|
|
1184
|
+
// src/tide/adapter.ts
|
|
1185
|
+
import { existsSync as existsSync11, readFileSync as readFileSync3 } from "fs";
|
|
1186
|
+
import { join as join15 } from "path";
|
|
1187
|
+
// src/tide/runtime.ts
|
|
1188
|
+
function createRuntimeContext(request, url, params, resolvedMode) {
|
|
1189
|
+
const searchParams = {};
|
|
1190
|
+
url.searchParams.forEach((value, key) => {
|
|
1191
|
+
searchParams[key] = value;
|
|
1192
|
+
});
|
|
1193
|
+
const requestHeaders = {};
|
|
1194
|
+
request.headers.forEach((value, key) => {
|
|
1195
|
+
requestHeaders[key] = value;
|
|
1196
|
+
});
|
|
1197
|
+
return {
|
|
1198
|
+
url,
|
|
1199
|
+
params,
|
|
1200
|
+
request,
|
|
1201
|
+
searchParams,
|
|
1202
|
+
resolvedMode,
|
|
1203
|
+
requestHeaders,
|
|
1204
|
+
pathname: url.pathname,
|
|
1205
|
+
timestampMs: Date.now(),
|
|
1206
|
+
method: request.method.toUpperCase()
|
|
1207
|
+
};
|
|
1208
|
+
}
|
|
1209
|
+
function buildTextResponse(body, status, headers) {
|
|
1210
|
+
return new Response(body, { status, headers });
|
|
1211
|
+
}
|
|
1212
|
+
function buildHtmlResponse(html, status = 200) {
|
|
1213
|
+
return buildTextResponse(html, status, {
|
|
1214
|
+
"Content-Type": "text/html; charset=utf-8"
|
|
1215
|
+
});
|
|
1216
|
+
}
|
|
1217
|
+
function buildJsonResponse(responseData, status = 200) {
|
|
1218
|
+
return buildTextResponse(JSON.stringify(responseData), status, {
|
|
1219
|
+
"Content-Type": "application/json; charset=utf-8"
|
|
1220
|
+
});
|
|
1221
|
+
}
|
|
1222
|
+
function buildErrorResponse(errorMessage, status = 500) {
|
|
1223
|
+
return buildJsonResponse({ error: errorMessage }, status);
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1226
|
+
// src/tide/adapter.ts
|
|
1227
|
+
var STATIC_MIME_TYPES = {
|
|
1228
|
+
".js": "application/javascript; charset=utf-8",
|
|
1229
|
+
".ts": "application/javascript; charset=utf-8",
|
|
1230
|
+
".css": "text/css; charset=utf-8",
|
|
1231
|
+
".json": "application/json; charset=utf-8",
|
|
1232
|
+
".png": "image/png",
|
|
1233
|
+
".jpg": "image/jpeg",
|
|
1234
|
+
".jpeg": "image/jpeg",
|
|
1235
|
+
".webp": "image/webp",
|
|
1236
|
+
".svg": "image/svg+xml",
|
|
1237
|
+
".ico": "image/x-icon",
|
|
1238
|
+
".woff": "font/woff",
|
|
1239
|
+
".woff2": "font/woff2"
|
|
1240
|
+
};
|
|
1241
|
+
function mimeForPath(filePath) {
|
|
1242
|
+
const extensionIndex = filePath.lastIndexOf(".");
|
|
1243
|
+
if (extensionIndex < 0) {
|
|
1244
|
+
return "application/octet-stream";
|
|
1245
|
+
}
|
|
1246
|
+
const extension = filePath.slice(extensionIndex).toLowerCase();
|
|
1247
|
+
return STATIC_MIME_TYPES[extension] ?? "application/octet-stream";
|
|
1248
|
+
}
|
|
1249
|
+
function normalizeStaticPath(pathname) {
|
|
1250
|
+
const pathnameWithoutLeadingSlash = pathname.replace(/^\/+/, "");
|
|
1251
|
+
if (pathnameWithoutLeadingSlash.length > 0) {
|
|
1252
|
+
return pathnameWithoutLeadingSlash;
|
|
1253
|
+
}
|
|
1254
|
+
return "index.html";
|
|
1255
|
+
}
|
|
1256
|
+
function isApiRouteExports(value) {
|
|
1257
|
+
if (typeof value !== "object" || value === null) {
|
|
1258
|
+
return false;
|
|
1259
|
+
}
|
|
1260
|
+
return true;
|
|
1261
|
+
}
|
|
1262
|
+
function getApiRouteHandler(routeExports, method) {
|
|
1263
|
+
if (method === "GET") {
|
|
1264
|
+
return routeExports.GET;
|
|
1265
|
+
}
|
|
1266
|
+
if (method === "POST") {
|
|
1267
|
+
return routeExports.POST;
|
|
1268
|
+
}
|
|
1269
|
+
if (method === "PUT") {
|
|
1270
|
+
return routeExports.PUT;
|
|
1271
|
+
}
|
|
1272
|
+
if (method === "PATCH") {
|
|
1273
|
+
return routeExports.PATCH;
|
|
1274
|
+
}
|
|
1275
|
+
if (method === "DELETE") {
|
|
1276
|
+
return routeExports.DELETE;
|
|
1277
|
+
}
|
|
1278
|
+
if (method === "HEAD") {
|
|
1279
|
+
return routeExports.HEAD;
|
|
1280
|
+
}
|
|
1281
|
+
if (method === "OPTIONS") {
|
|
1282
|
+
return routeExports.OPTIONS;
|
|
1283
|
+
}
|
|
1284
|
+
return;
|
|
1285
|
+
}
|
|
1286
|
+
function createBunAdapter(adapterConfig, renderConfig) {
|
|
1287
|
+
const manifest = generateManifest(adapterConfig.appDir);
|
|
1288
|
+
const searchDirectories = [adapterConfig.publicDir, adapterConfig.outDir];
|
|
1289
|
+
let serverInstance;
|
|
1290
|
+
async function handle(request) {
|
|
1291
|
+
const url = new URL(request.url);
|
|
1292
|
+
const { pathname } = url;
|
|
1293
|
+
const staticPathname = normalizeStaticPath(pathname);
|
|
1294
|
+
for (const searchDirectory of searchDirectories) {
|
|
1295
|
+
const filePath = join15(searchDirectory, staticPathname);
|
|
1296
|
+
if (existsSync11(filePath) && !filePath.endsWith("/")) {
|
|
1297
|
+
return new Response(readFileSync3(filePath), {
|
|
1298
|
+
headers: {
|
|
1299
|
+
"Content-Type": mimeForPath(filePath),
|
|
1300
|
+
"Cache-Control": "public, max-age=31536000, immutable"
|
|
1301
|
+
}
|
|
1302
|
+
});
|
|
1303
|
+
}
|
|
1304
|
+
}
|
|
1305
|
+
const apiRoutes = manifest.routes.filter((route) => route.kind === "api");
|
|
1306
|
+
const apiMatch = matchRoute(pathname, apiRoutes);
|
|
1307
|
+
if (apiMatch) {
|
|
1308
|
+
const modulePath = join15(adapterConfig.appDir, apiMatch.entry.filePath);
|
|
1309
|
+
const routeModule = await import(modulePath);
|
|
1310
|
+
if (!isApiRouteExports(routeModule)) {
|
|
1311
|
+
return new Response("Invalid API route module", {
|
|
1312
|
+
status: 500
|
|
1313
|
+
});
|
|
1314
|
+
}
|
|
1315
|
+
const handler = getApiRouteHandler(routeModule, request.method.toUpperCase());
|
|
1316
|
+
if (!handler) {
|
|
1317
|
+
return new Response("Method not allowed", {
|
|
1318
|
+
status: 405
|
|
1319
|
+
});
|
|
1320
|
+
}
|
|
1321
|
+
return await handler(request);
|
|
1322
|
+
}
|
|
1323
|
+
const resolvedRouteMode = resolveRouteMode(pathname, renderConfig);
|
|
1324
|
+
const runtimeContext = createRuntimeContext(request, url, {}, resolvedRouteMode.mode);
|
|
1325
|
+
const renderResult = await render({
|
|
1326
|
+
routePath: runtimeContext.pathname,
|
|
1327
|
+
mode: runtimeContext.resolvedMode,
|
|
1328
|
+
params: runtimeContext.params,
|
|
1329
|
+
searchParams: runtimeContext.searchParams,
|
|
1330
|
+
requestHeaders: runtimeContext.requestHeaders,
|
|
1331
|
+
timestampMs: runtimeContext.timestampMs
|
|
1332
|
+
}, {
|
|
1333
|
+
appName: adapterConfig.appName,
|
|
1334
|
+
scriptPath: "/app.js",
|
|
1335
|
+
cssPath: "/globals.css",
|
|
1336
|
+
lang: "en"
|
|
1337
|
+
});
|
|
1338
|
+
if (renderResult.kind === "failure") {
|
|
1339
|
+
return buildErrorResponse(renderResult.reason, renderResult.httpStatus);
|
|
1340
|
+
}
|
|
1341
|
+
return buildHtmlResponse(renderResult.html, renderResult.httpStatus);
|
|
1342
|
+
}
|
|
1343
|
+
return {
|
|
1344
|
+
kind: "bun",
|
|
1345
|
+
handle,
|
|
1346
|
+
start: async () => {
|
|
1347
|
+
serverInstance = Bun.serve({
|
|
1348
|
+
port: adapterConfig.port,
|
|
1349
|
+
hostname: adapterConfig.host,
|
|
1350
|
+
fetch: handle
|
|
1351
|
+
});
|
|
1352
|
+
console.log(`[Rakta.js Tide] Running at http://${adapterConfig.host}:${serverInstance.port}`);
|
|
1353
|
+
},
|
|
1354
|
+
stop: () => {
|
|
1355
|
+
if (!serverInstance) {
|
|
1356
|
+
return;
|
|
1357
|
+
}
|
|
1358
|
+
serverInstance.stop();
|
|
1359
|
+
}
|
|
1360
|
+
};
|
|
1361
|
+
}
|
|
1362
|
+
|
|
1363
|
+
// src/cli/start.ts
|
|
1364
|
+
async function startCommand(cwd = process.cwd()) {
|
|
1365
|
+
const projectConfig = await loadConfig(cwd);
|
|
1366
|
+
const outputDirectory = projectConfig.build.outDir ?? "dist";
|
|
1367
|
+
console.log(`
|
|
1368
|
+
Starting Rakta.js production server...
|
|
1369
|
+
`);
|
|
1370
|
+
const serverAdapter = createBunAdapter({
|
|
1371
|
+
kind: "bun",
|
|
1372
|
+
port: projectConfig.port,
|
|
1373
|
+
host: projectConfig.server.hostname ?? "0.0.0.0",
|
|
1374
|
+
appName: projectConfig.appName,
|
|
1375
|
+
appDir: join16(cwd, projectConfig.appDir),
|
|
1376
|
+
publicDir: join16(cwd, projectConfig.publicDir),
|
|
1377
|
+
outDir: join16(cwd, outputDirectory)
|
|
1378
|
+
}, projectConfig.render);
|
|
1379
|
+
await serverAdapter.start();
|
|
1380
|
+
}
|
|
1381
|
+
|
|
1382
|
+
// src/cli/rakta.ts
|
|
1383
|
+
var RED = "\x1B[31m";
|
|
1384
|
+
var BOLD = "\x1B[1m";
|
|
1385
|
+
var DIM = "\x1B[2m";
|
|
1386
|
+
var RESET = "\x1B[0m";
|
|
1387
|
+
var cliArgs = process.argv.slice(2);
|
|
1388
|
+
var selectedCommand = cliArgs[0] ?? "help";
|
|
1389
|
+
var firstArgument = cliArgs[1];
|
|
1390
|
+
var cwd = process.cwd();
|
|
1391
|
+
var BANNER = [
|
|
1392
|
+
"",
|
|
1393
|
+
`${BOLD}${RED} \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557${RESET}`,
|
|
1394
|
+
`${BOLD}${RED} \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557 \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2554\u255D \u255A\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D${RESET}`,
|
|
1395
|
+
`${BOLD}${RED} \u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2554\u255D \u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557${RESET}`,
|
|
1396
|
+
`${BOLD}${RED} \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557 \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551 \u2588\u2588 \u2588\u2588\u2551 \u255A\u2550\u2550\u2550\u2550\u2588\u2588\u2551${RESET}`,
|
|
1397
|
+
`${BOLD}${RED} \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2557\u255A\u2588\u2588\u2588\u2588\u2588\u2554\u255D \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551${RESET}`,
|
|
1398
|
+
`${BOLD}${RED} \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D${RESET}`,
|
|
1399
|
+
""
|
|
1400
|
+
].join(`
|
|
1401
|
+
`);
|
|
1402
|
+
function printHelp() {
|
|
1403
|
+
console.log(BANNER);
|
|
1404
|
+
console.log(`${BOLD}${RED} Rakta.js CLI${RESET}`);
|
|
1405
|
+
console.log(`${DIM} Small in size. Fierce in speed. Alive in every route.${RESET}`);
|
|
1406
|
+
console.log("");
|
|
1407
|
+
console.log(`${BOLD}Usage:${RESET}`);
|
|
1408
|
+
console.log(" bun rakta <command> [options]");
|
|
1409
|
+
console.log("");
|
|
1410
|
+
console.log(`${BOLD}Commands:${RESET}`);
|
|
1411
|
+
console.log(" rakta dev");
|
|
1412
|
+
console.log(" rakta build");
|
|
1413
|
+
console.log(" rakta start");
|
|
1414
|
+
console.log(" rakta routes");
|
|
1415
|
+
console.log("");
|
|
1416
|
+
console.log(" rakta make:page <name>");
|
|
1417
|
+
console.log(" rakta make:layout <name>");
|
|
1418
|
+
console.log(" rakta make:component <name>");
|
|
1419
|
+
console.log(" rakta make:api <name>");
|
|
1420
|
+
console.log("");
|
|
1421
|
+
console.log(" rakta seo:generate");
|
|
1422
|
+
console.log(" rakta imports:generate");
|
|
1423
|
+
console.log(" rakta rpc:types");
|
|
1424
|
+
console.log("");
|
|
1425
|
+
console.log(" rakta forge:inspect");
|
|
1426
|
+
console.log(" rakta tide:render");
|
|
1427
|
+
console.log(" rakta doctor");
|
|
1428
|
+
console.log(" rakta help");
|
|
1429
|
+
console.log("");
|
|
1430
|
+
}
|
|
1431
|
+
function getRequiredArgument(commandName, argumentValue) {
|
|
1432
|
+
if (typeof argumentValue === "string" && argumentValue.trim().length > 0) {
|
|
1433
|
+
return argumentValue;
|
|
1434
|
+
}
|
|
1435
|
+
console.error(`${BOLD}${RED}Rakta.js${RESET} ${DIM}missing argument for:${RESET} ${commandName}`);
|
|
1436
|
+
console.error(`${DIM}Usage: rakta ${commandName} <name>${RESET}`);
|
|
1437
|
+
process.exit(1);
|
|
1438
|
+
}
|
|
1439
|
+
async function runForgeInspect() {
|
|
1440
|
+
const projectConfig = await loadConfig(cwd);
|
|
1441
|
+
const outputDirectory = projectConfig.build.outDir ?? "dist";
|
|
1442
|
+
const inspectReport = inspectBuild({
|
|
1443
|
+
outDir: join17(cwd, outputDirectory),
|
|
1444
|
+
renderConfig: projectConfig.render
|
|
1445
|
+
});
|
|
1446
|
+
printInspectReport(inspectReport);
|
|
1447
|
+
}
|
|
1448
|
+
async function main() {
|
|
1449
|
+
switch (selectedCommand) {
|
|
1450
|
+
case "help":
|
|
1451
|
+
case "--help":
|
|
1452
|
+
case "-h":
|
|
1453
|
+
printHelp();
|
|
1454
|
+
break;
|
|
1455
|
+
case "dev":
|
|
1456
|
+
await devCommand(cwd);
|
|
1457
|
+
break;
|
|
1458
|
+
case "build":
|
|
1459
|
+
await buildCommand(cwd);
|
|
1460
|
+
break;
|
|
1461
|
+
case "start":
|
|
1462
|
+
await startCommand(cwd);
|
|
1463
|
+
break;
|
|
1464
|
+
case "routes":
|
|
1465
|
+
await routesCommand(cwd);
|
|
1466
|
+
break;
|
|
1467
|
+
case "make:page":
|
|
1468
|
+
await makeCommand("page", getRequiredArgument("make:page", firstArgument), cwd);
|
|
1469
|
+
break;
|
|
1470
|
+
case "make:layout":
|
|
1471
|
+
await makeCommand("layout", getRequiredArgument("make:layout", firstArgument), cwd);
|
|
1472
|
+
break;
|
|
1473
|
+
case "make:component":
|
|
1474
|
+
await makeCommand("component", getRequiredArgument("make:component", firstArgument), cwd);
|
|
1475
|
+
break;
|
|
1476
|
+
case "make:api":
|
|
1477
|
+
await makeCommand("api", getRequiredArgument("make:api", firstArgument), cwd);
|
|
1478
|
+
break;
|
|
1479
|
+
case "seo:generate":
|
|
1480
|
+
await seoGenerateCommand(cwd);
|
|
1481
|
+
break;
|
|
1482
|
+
case "imports:generate":
|
|
1483
|
+
await importsGenerateCommand(cwd);
|
|
1484
|
+
break;
|
|
1485
|
+
case "rpc:types":
|
|
1486
|
+
await rpcTypesCommand(cwd);
|
|
1487
|
+
break;
|
|
1488
|
+
case "forge:inspect":
|
|
1489
|
+
await runForgeInspect();
|
|
1490
|
+
break;
|
|
1491
|
+
case "tide:render":
|
|
1492
|
+
console.log(`${BOLD}${RED}Rakta.js${RESET} ${DIM}tide:render is planned for v0.2.0.${RESET}`);
|
|
1493
|
+
break;
|
|
1494
|
+
case "doctor":
|
|
1495
|
+
await doctorCommand(cwd);
|
|
1496
|
+
break;
|
|
1497
|
+
default:
|
|
1498
|
+
console.error(`${BOLD}${RED}Rakta.js${RESET} ${DIM}unknown command:${RESET} ${selectedCommand}`);
|
|
1499
|
+
printHelp();
|
|
1500
|
+
process.exit(1);
|
|
1501
|
+
}
|
|
1502
|
+
}
|
|
1503
|
+
main().catch((caughtError) => {
|
|
1504
|
+
const errorMessage = caughtError instanceof Error ? caughtError.message : String(caughtError);
|
|
1505
|
+
console.error(`
|
|
1506
|
+
${BOLD}${RED}Rakta.js error:${RESET} ${errorMessage}
|
|
1507
|
+
`);
|
|
1508
|
+
process.exit(1);
|
|
1509
|
+
});
|
|
1510
|
+
|
|
1511
|
+
//# debugId=2D6840578E402FF964756E2164756E21
|