swift-rust 0.2.1 → 1.0.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/bin/build.mjs +85 -31
- package/package.json +6 -6
package/bin/build.mjs
CHANGED
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
mkdirSync,
|
|
6
6
|
readdirSync,
|
|
7
7
|
writeFileSync,
|
|
8
|
+
readFileSync,
|
|
8
9
|
rmSync,
|
|
9
10
|
cpSync,
|
|
10
11
|
openSync,
|
|
@@ -27,9 +28,9 @@ let PORT = PORT_START;
|
|
|
27
28
|
const ROUTE_TIMEOUT_MS = 30_000;
|
|
28
29
|
const HEALTH_TIMEOUT_MS = 30_000;
|
|
29
30
|
|
|
30
|
-
const PAGE_EXTENSIONS = new Set(["page.tsx", "page.ts", "page.jsx", "page.js"]);
|
|
31
31
|
const CATCH_PARAM = /^\[\.{3}([^\]]+)\]$/;
|
|
32
32
|
const NAMED_PARAM = /^\[([^\]]+)\]$/;
|
|
33
|
+
const PAGE_EXTENSIONS = ["page.tsx", "page.ts", "page.jsx", "page.js"];
|
|
33
34
|
const NOT_FOUND_FILES = ["not-found.tsx", "not-found.ts", "not-found.jsx", "not-found.js"];
|
|
34
35
|
|
|
35
36
|
const c = {
|
|
@@ -41,6 +42,16 @@ const useColor = process.stdout.isTTY !== false && !process.env.NO_COLOR;
|
|
|
41
42
|
const paint = (color, s) => (useColor ? `${c[color]}${s}${c.reset}` : s);
|
|
42
43
|
const fmtMs = (ms) => (ms < 1000 ? `${Math.round(ms)}ms` : `${(ms / 1000).toFixed(2)}s`);
|
|
43
44
|
|
|
45
|
+
let activeLogFile = null;
|
|
46
|
+
|
|
47
|
+
function findPageFile(dir) {
|
|
48
|
+
for (const ext of PAGE_EXTENSIONS) {
|
|
49
|
+
const p = join(dir, ext);
|
|
50
|
+
if (existsSync(p)) return p;
|
|
51
|
+
}
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
|
|
44
55
|
function discoverPages(dir, base = "") {
|
|
45
56
|
const pages = [];
|
|
46
57
|
if (!existsSync(dir)) return pages;
|
|
@@ -48,15 +59,24 @@ function discoverPages(dir, base = "") {
|
|
|
48
59
|
if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
|
|
49
60
|
const full = join(dir, entry.name);
|
|
50
61
|
if (entry.isDirectory()) {
|
|
51
|
-
const
|
|
52
|
-
if (
|
|
53
|
-
|
|
62
|
+
const catchAll = entry.name.match(CATCH_PARAM);
|
|
63
|
+
if (catchAll) {
|
|
64
|
+
const pageFile = findPageFile(full);
|
|
65
|
+
if (pageFile) {
|
|
66
|
+
pages.push({ type: "catchall", dir: full, file: pageFile, base, paramName: catchAll[1] });
|
|
67
|
+
}
|
|
54
68
|
continue;
|
|
55
69
|
}
|
|
56
70
|
const named = entry.name.match(NAMED_PARAM);
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
71
|
+
if (named) {
|
|
72
|
+
const pageFile = findPageFile(full);
|
|
73
|
+
if (pageFile) {
|
|
74
|
+
pages.push({ type: "dynamic", dir: full, file: pageFile, base, paramName: named[1] });
|
|
75
|
+
}
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
pages.push(...discoverPages(full, base + "/" + entry.name));
|
|
79
|
+
} else if (PAGE_EXTENSIONS.includes(entry.name)) {
|
|
60
80
|
pages.push({ type: "static", file: full, route: base || "/" });
|
|
61
81
|
}
|
|
62
82
|
}
|
|
@@ -71,9 +91,9 @@ function findNotFoundFile(dir) {
|
|
|
71
91
|
return null;
|
|
72
92
|
}
|
|
73
93
|
|
|
74
|
-
async function
|
|
75
|
-
for (const ext of
|
|
76
|
-
const p = join(page.dir,
|
|
94
|
+
async function enumerateParams(page) {
|
|
95
|
+
for (const ext of PAGE_EXTENSIONS) {
|
|
96
|
+
const p = join(page.dir, ext);
|
|
77
97
|
if (!existsSync(p)) continue;
|
|
78
98
|
try {
|
|
79
99
|
const mod = await import(`${p}?t=${Date.now()}`);
|
|
@@ -85,13 +105,12 @@ async function enumerateCatchAll(page) {
|
|
|
85
105
|
return [];
|
|
86
106
|
}
|
|
87
107
|
|
|
88
|
-
function
|
|
108
|
+
function routesFromParams(base, paramName, params) {
|
|
89
109
|
return params
|
|
90
110
|
.map((p) => {
|
|
91
|
-
const v = p[
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
return catchall.base + "/" + arr.join("/");
|
|
111
|
+
const v = p[paramName];
|
|
112
|
+
if (v == null) return null;
|
|
113
|
+
return base + "/" + (Array.isArray(v) ? v.join("/") : String(v));
|
|
95
114
|
})
|
|
96
115
|
.filter(Boolean);
|
|
97
116
|
}
|
|
@@ -130,7 +149,7 @@ function startDevServer(port, logFile) {
|
|
|
130
149
|
const runtime = findBun();
|
|
131
150
|
const stdio = logFile
|
|
132
151
|
? ["ignore", openSync(logFile, "w"), "inherit"]
|
|
133
|
-
: ["ignore", "
|
|
152
|
+
: ["ignore", "inherit", "inherit"];
|
|
134
153
|
const proc = spawn(runtime, [devServer, "--port", String(port), "--hostname", HOST], {
|
|
135
154
|
stdio,
|
|
136
155
|
env: { ...process.env, NO_COLOR: "1", SWIFT_RUST_BUILD: "1" },
|
|
@@ -167,6 +186,16 @@ async function fetchRoute(pathname, timeoutMs = ROUTE_TIMEOUT_MS) {
|
|
|
167
186
|
}
|
|
168
187
|
}
|
|
169
188
|
|
|
189
|
+
function tailDevLog(lines = 40) {
|
|
190
|
+
if (!activeLogFile || !existsSync(activeLogFile)) return [];
|
|
191
|
+
try {
|
|
192
|
+
const all = readFileSync(activeLogFile, "utf8").split("\n");
|
|
193
|
+
return all.slice(-lines);
|
|
194
|
+
} catch {
|
|
195
|
+
return [];
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
170
199
|
function stripHmrScript(html) {
|
|
171
200
|
return html.replace(/\s*<script src="\/_swift-rust\/hmr-client\.js"[^>]*>\s*<\/script>/g, "");
|
|
172
201
|
}
|
|
@@ -178,27 +207,28 @@ function writeStaticFile(outDir, pathname, html) {
|
|
|
178
207
|
writeFileSync(outPath, html);
|
|
179
208
|
}
|
|
180
209
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
210
|
+
// Writes a literal file (e.g. 404.html) at static/<name>, NOT static/<name>/index.html.
|
|
211
|
+
function writeRawFile(outDir, name, contents) {
|
|
212
|
+
const outPath = join(outDir, name);
|
|
213
|
+
mkdirSync(dirname(outPath), { recursive: true });
|
|
214
|
+
writeFileSync(outPath, contents);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function writeConfigJson(outDir, _hasPublic) {
|
|
218
|
+
// Build Output API v3 config. Only schema-valid fields here — unknown
|
|
219
|
+
// top-level fields or route properties are rejected at "Deploying outputs".
|
|
185
220
|
const config = {
|
|
186
221
|
version: 3,
|
|
187
|
-
framework: { slug: "swift-rust", name: "Swift Rust" },
|
|
188
222
|
routes: [
|
|
189
223
|
{ src: "/_swift-rust/static/(.*)", headers: { "Cache-Control": "public, max-age=31536000, immutable" } },
|
|
190
224
|
{ src: "/fonts/(.*)", headers: { "Cache-Control": "public, max-age=31536000, immutable" } },
|
|
191
|
-
...(hasPublic
|
|
192
|
-
? [{ src: "/(.*)", headers: { "Cache-Control": "public, max-age=31536000, immutable" }, "isr-per-page": false }]
|
|
193
|
-
: []),
|
|
194
225
|
{ handle: "filesystem" },
|
|
195
226
|
{ src: "^(.*)$", status: 404, dest: "/404.html" },
|
|
196
227
|
],
|
|
197
228
|
overrides: {
|
|
198
|
-
"404": { path: "404", contentType: "text/html; charset=utf-8" },
|
|
229
|
+
"404.html": { path: "404", contentType: "text/html; charset=utf-8" },
|
|
199
230
|
},
|
|
200
231
|
};
|
|
201
|
-
if (!hasPublic) config.routes = config.routes.filter((r) => !r["isr-per-page"]);
|
|
202
232
|
writeFileSync(join(outDir, "config.json"), `${JSON.stringify(config, null, 2)}\n`);
|
|
203
233
|
}
|
|
204
234
|
|
|
@@ -220,13 +250,25 @@ async function main() {
|
|
|
220
250
|
const pages = discoverPages(APP_DIR);
|
|
221
251
|
const staticRoutes = pages.filter((p) => p.type === "static").map((p) => p.route);
|
|
222
252
|
const catchalls = pages.filter((p) => p.type === "catchall");
|
|
253
|
+
const dynamics = pages.filter((p) => p.type === "dynamic");
|
|
223
254
|
process.stdout.write(` ${paint("dim", "•")} static routes: ${paint("bold", String(staticRoutes.length))}\n`);
|
|
255
|
+
|
|
224
256
|
for (const ca of catchalls) {
|
|
225
|
-
const params = await
|
|
226
|
-
const routes =
|
|
257
|
+
const params = await enumerateParams(ca);
|
|
258
|
+
const routes = routesFromParams(ca.base, ca.paramName, params);
|
|
227
259
|
for (const r of routes) staticRoutes.push(r);
|
|
228
260
|
process.stdout.write(` ${paint("dim", "•")} catch-all ${paint("cyan", ca.base + "/[...]")}: ${paint("bold", String(routes.length))}\n`);
|
|
229
261
|
}
|
|
262
|
+
for (const dyn of dynamics) {
|
|
263
|
+
const params = await enumerateParams(dyn);
|
|
264
|
+
if (params.length === 0) {
|
|
265
|
+
process.stdout.write(` ${paint("dim", "•")} dynamic ${paint("cyan", dyn.base + "/[" + dyn.paramName + "]")}: ${paint("yellow", "needs serverless function (skipped for v0.1.0)")}\n`);
|
|
266
|
+
continue;
|
|
267
|
+
}
|
|
268
|
+
const routes = routesFromParams(dyn.base, dyn.paramName, params);
|
|
269
|
+
for (const r of routes) staticRoutes.push(r);
|
|
270
|
+
process.stdout.write(` ${paint("dim", "•")} dynamic ${paint("cyan", dyn.base + "/[" + dyn.paramName + "]")}: ${paint("bold", String(routes.length))}\n`);
|
|
271
|
+
}
|
|
230
272
|
|
|
231
273
|
const allRoutes = [...new Set(staticRoutes)].sort();
|
|
232
274
|
process.stdout.write(` ${paint("dim", "•")} total: ${paint("bold", String(allRoutes.length))}\n\n`);
|
|
@@ -234,7 +276,9 @@ async function main() {
|
|
|
234
276
|
process.stdout.write(` ${paint("dim", "starting dev server on " + HOST + ":" + PORT_START + "…")}\n`);
|
|
235
277
|
PORT = await findFreePort(PORT_START);
|
|
236
278
|
process.stdout.write(` ${paint("dim", "using port " + PORT + "\n")}\n`);
|
|
237
|
-
|
|
279
|
+
activeLogFile = process.env.SWIFT_RUST_BUILD_LOG || "/tmp/swift-rust-build-dev.log";
|
|
280
|
+
try { unlinkSync(activeLogFile); } catch {}
|
|
281
|
+
const proc = startDevServer(PORT, activeLogFile);
|
|
238
282
|
let okCount = 0;
|
|
239
283
|
let skipCount = 0;
|
|
240
284
|
let failCount = 0;
|
|
@@ -255,7 +299,9 @@ async function main() {
|
|
|
255
299
|
process.stdout.write(` ${paint("dim", "○")} ${route} ${paint("dim", "(404, skipped)")}\n`);
|
|
256
300
|
} else {
|
|
257
301
|
failCount++;
|
|
302
|
+
const snippet = body.replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim().slice(0, 160);
|
|
258
303
|
process.stdout.write(` ${paint("yellow", "!")} ${route} ${paint("dim", `(${status})`)}\n`);
|
|
304
|
+
if (snippet) process.stdout.write(` ${paint("dim", snippet)}\n`);
|
|
259
305
|
}
|
|
260
306
|
} catch (e) {
|
|
261
307
|
failCount++;
|
|
@@ -263,13 +309,21 @@ async function main() {
|
|
|
263
309
|
}
|
|
264
310
|
}
|
|
265
311
|
|
|
312
|
+
if (failCount > 0) {
|
|
313
|
+
const tail = tailDevLog(30);
|
|
314
|
+
if (tail.length) {
|
|
315
|
+
process.stdout.write(`\n ${paint("dim", "dev server tail:")}\n`);
|
|
316
|
+
for (const line of tail) process.stdout.write(` ${paint("dim", line)}\n`);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
266
320
|
const notFoundFile = findNotFoundFile(APP_DIR);
|
|
267
321
|
if (notFoundFile) {
|
|
268
322
|
try {
|
|
269
323
|
const { status, body } = await fetchRoute("/_not_found_");
|
|
270
324
|
if (status === 200 || status === 404) {
|
|
271
325
|
const html = stripHmrScript(body).replace(/<title>[^<]*<\/title>/, "<title>404 · Swift Rust</title>");
|
|
272
|
-
|
|
326
|
+
writeRawFile(STATIC_DIR, "404.html", html);
|
|
273
327
|
process.stdout.write(`\n ${paint("green", "✓")} 404.html\n`);
|
|
274
328
|
}
|
|
275
329
|
} catch (e) {
|
|
@@ -277,7 +331,7 @@ async function main() {
|
|
|
277
331
|
}
|
|
278
332
|
}
|
|
279
333
|
if (!existsSync(join(STATIC_DIR, "404.html"))) {
|
|
280
|
-
|
|
334
|
+
writeRawFile(STATIC_DIR, "404.html", `<!DOCTYPE html>
|
|
281
335
|
<html lang="en"><head><meta charset="utf-8" /><title>404 · Swift Rust</title></head>
|
|
282
336
|
<body><main style="font-family:system-ui;padding:4rem;text-align:center">
|
|
283
337
|
<h1>404</h1><p>This page could not be found.</p><a href="/">← Back home</a>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "swift-rust",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"description": "The full-stack React framework powered with Rust + Bun. TSX-first, Rust rendering, 10x faster than Next.js, single binary deploy.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://swift-rust.dev",
|
|
@@ -87,11 +87,11 @@
|
|
|
87
87
|
},
|
|
88
88
|
"dependencies": {
|
|
89
89
|
"@dotenvx/dotenvx": "^1.0.0",
|
|
90
|
-
"@swift-rust/env": "
|
|
91
|
-
"@swift-rust/font": "
|
|
92
|
-
"@swift-rust/image": "
|
|
93
|
-
"@swift-rust/pdf": "
|
|
94
|
-
"@swift-rust/video": "
|
|
90
|
+
"@swift-rust/env": "workspace:*",
|
|
91
|
+
"@swift-rust/font": "workspace:*",
|
|
92
|
+
"@swift-rust/image": "workspace:*",
|
|
93
|
+
"@swift-rust/pdf": "workspace:*",
|
|
94
|
+
"@swift-rust/video": "workspace:*",
|
|
95
95
|
"react": "^19.0.0",
|
|
96
96
|
"react-dom": "^19.0.0"
|
|
97
97
|
},
|