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.
Files changed (2) hide show
  1. package/bin/build.mjs +85 -31
  2. 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 match = entry.name.match(CATCH_PARAM);
52
- if (match) {
53
- pages.push({ type: "catchall", dir: full, base, paramName: match[1] });
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
- const segment = named ? `[${named[1]}]` : entry.name;
58
- pages.push(...discoverPages(full, base + "/" + segment));
59
- } else if (PAGE_EXTENSIONS.has(entry.name)) {
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 enumerateCatchAll(page) {
75
- for (const ext of ["tsx", "ts", "jsx", "js"]) {
76
- const p = join(page.dir, `page.${ext}`);
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 routesFromCatchAllParams(catchall, params) {
108
+ function routesFromParams(base, paramName, params) {
89
109
  return params
90
110
  .map((p) => {
91
- const v = p[catchall.paramName];
92
- const arr = Array.isArray(v) ? v : v != null ? [String(v)] : [];
93
- if (arr.length === 0) return null;
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", "ignore", "inherit"];
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
- function writeConfigJson(outDir, hasPublic) {
182
- const headers = [
183
- { key: "Cache-Control", value: "public, max-age=0, must-revalidate" },
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 enumerateCatchAll(ca);
226
- const routes = routesFromCatchAllParams(ca, params);
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
- const proc = startDevServer(PORT, null);
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
- writeStaticFile(STATIC_DIR, "/404.html", html);
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
- writeStaticFile(STATIC_DIR, "/404.html", `<!DOCTYPE html>
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.2.1",
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": "0.2.0",
91
- "@swift-rust/font": "0.2.0",
92
- "@swift-rust/image": "1.0.0",
93
- "@swift-rust/pdf": "0.2.0",
94
- "@swift-rust/video": "0.2.0",
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
  },