bun-plugin-stx 0.2.168 → 0.2.170

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/dist/serve.js +570 -567
  2. package/package.json +1 -1
package/dist/serve.js CHANGED
@@ -8721,6 +8721,7 @@ var defaultGeneratedDir4 = /* @__PURE__ */ resolve12(process20.cwd(), "src/gener
8721
8721
  import { BUILD_ID_HEADER, extractPageResponseStatus, findContainerRegion, getBuildId, mergeCrosswindConfig, stateDir, stateDirName } from "@stacksjs/stx";
8722
8722
  import { buildCodeFrame, locateFailureLine } from "@stacksjs/stx/build-message";
8723
8723
  import { clearBundleFailures, getBundleFailures } from "@stacksjs/stx/client-script-bundler";
8724
+ import { compressResponse } from "@stacksjs/stx";
8724
8725
  var defaultStxModule = import("@stacksjs/stx");
8725
8726
  var REACTIVE_TEMPLATE_DIRECTIVE_RE = /@for|:for|@if|:if|@else|:else|x-for|x-if|x-else/;
8726
8727
  function extractServerScriptsFromTemplate(content) {
@@ -10136,426 +10137,427 @@ ${html}`;
10136
10137
  reusePort: options.reusePort ?? false,
10137
10138
  idleTimeout: 0,
10138
10139
  async fetch(req, server) {
10139
- const url = new URL(req.url);
10140
- let path = url.pathname;
10141
- const proxyRules = stxConfig.server?.proxy;
10142
- if (proxyRules) {
10143
- for (const [prefix, rule] of Object.entries(proxyRules)) {
10144
- if (path !== prefix && !path.startsWith(prefix))
10145
- continue;
10146
- const ruleCfg = typeof rule === "string" ? { target: rule } : rule;
10147
- const outPath = ruleCfg.rewrite ? ruleCfg.rewrite(path) : path;
10148
- const target = new URL(ruleCfg.target);
10149
- const outUrl = new URL(outPath + url.search, target);
10150
- const headers = new Headers(req.headers);
10151
- if (ruleCfg.changeOrigin !== false)
10152
- headers.set("host", target.host);
10153
- try {
10154
- return await fetch(outUrl, {
10155
- method: req.method,
10156
- headers,
10157
- body: req.body,
10158
- redirect: "manual",
10159
- ...req.body ? { duplex: "half" } : {}
10160
- });
10161
- } catch (err) {
10162
- return new Response(`[stx dev proxy] ${prefix} \u2192 ${ruleCfg.target} failed: ${err instanceof Error ? err.message : String(err)}`, { status: 502 });
10140
+ return compressResponse(req, await (async () => {
10141
+ const url = new URL(req.url);
10142
+ let path = url.pathname;
10143
+ const proxyRules = stxConfig.server?.proxy;
10144
+ if (proxyRules) {
10145
+ for (const [prefix, rule] of Object.entries(proxyRules)) {
10146
+ if (path !== prefix && !path.startsWith(prefix))
10147
+ continue;
10148
+ const ruleCfg = typeof rule === "string" ? { target: rule } : rule;
10149
+ const outPath = ruleCfg.rewrite ? ruleCfg.rewrite(path) : path;
10150
+ const target = new URL(ruleCfg.target);
10151
+ const outUrl = new URL(outPath + url.search, target);
10152
+ const headers = new Headers(req.headers);
10153
+ if (ruleCfg.changeOrigin !== false)
10154
+ headers.set("host", target.host);
10155
+ try {
10156
+ return await fetch(outUrl, {
10157
+ method: req.method,
10158
+ headers,
10159
+ body: req.body,
10160
+ redirect: "manual",
10161
+ ...req.body ? { duplex: "half" } : {}
10162
+ });
10163
+ } catch (err) {
10164
+ return new Response(`[stx dev proxy] ${prefix} \u2192 ${ruleCfg.target} failed: ${err instanceof Error ? err.message : String(err)}`, { status: 502 });
10165
+ }
10163
10166
  }
10164
10167
  }
10165
- }
10166
- let i18nLocale = null;
10167
- if (i18nConfig && !url.pathname.startsWith("/api/")) {
10168
- const stripped = localeFromPath(url.pathname);
10169
- i18nLocale = stripped.locale;
10170
- if (stripped.path !== url.pathname) {
10171
- const rewritten = new URL(req.url);
10172
- rewritten.pathname = stripped.path;
10173
- const headers = new Headers(req.headers);
10174
- headers.set("x-stx-locale", stripped.locale);
10175
- req = new Request(rewritten, { headers, method: req.method, body: req.body, redirect: req.redirect, duplex: "half" });
10176
- path = stripped.path;
10177
- } else if (i18nLocale) {
10178
- const headers = new Headers(req.headers);
10179
- headers.set("x-stx-locale", i18nLocale);
10180
- req = new Request(req, { headers });
10181
- }
10182
- }
10183
- let mintedCsrfToken = null;
10184
- const _i18nResp = await (async () => {
10185
- activeServeLocale = i18nLocale;
10186
- activeServeSearch = url.search;
10187
- activeServeHost = req.headers.get("host") || "";
10188
- activeServeCookieHeader = req.headers.get("cookie") || "";
10189
- activeServeCookies = parseCookies(req);
10190
- activeServeIp = server.requestIP(req)?.address || (req.headers.get("x-forwarded-for") || "").split(",")[0].trim();
10191
- mintedCsrfToken = csrfTokenToMint(req, activeServeCookies);
10192
- if (mintedCsrfToken)
10193
- activeServeCookies[CSRF_COOKIE] = mintedCsrfToken;
10194
- const reqCtx = {
10195
- url: req.url,
10196
- path,
10197
- search: activeServeSearch,
10198
- host: activeServeHost,
10199
- cookieHeader: activeServeCookieHeader,
10200
- cookies: activeServeCookies,
10201
- ip: activeServeIp,
10202
- locale: activeServeLocale,
10203
- params: {},
10204
- method: (req.method || "GET").toUpperCase(),
10205
- request: req
10206
- };
10207
- try {
10208
- if (req.method === "OPTIONS") {
10209
- return new Response(null, { headers: corsHeaders });
10168
+ let i18nLocale = null;
10169
+ if (i18nConfig && !url.pathname.startsWith("/api/")) {
10170
+ const stripped = localeFromPath(url.pathname);
10171
+ i18nLocale = stripped.locale;
10172
+ if (stripped.path !== url.pathname) {
10173
+ const rewritten = new URL(req.url);
10174
+ rewritten.pathname = stripped.path;
10175
+ const headers = new Headers(req.headers);
10176
+ headers.set("x-stx-locale", stripped.locale);
10177
+ req = new Request(rewritten, { headers, method: req.method, body: req.body, redirect: req.redirect, duplex: "half" });
10178
+ path = stripped.path;
10179
+ } else if (i18nLocale) {
10180
+ const headers = new Headers(req.headers);
10181
+ headers.set("x-stx-locale", i18nLocale);
10182
+ req = new Request(req, { headers });
10210
10183
  }
10211
- if (path === "/_stx/hmr") {
10212
- let controller;
10213
- let keepalive;
10214
- const stream = new ReadableStream({
10215
- start(c) {
10216
- controller = c;
10217
- hmrClients.add(c);
10218
- c.enqueue(hmrEncoder.encode(`data: {"type":"connected"}
10184
+ }
10185
+ let mintedCsrfToken = null;
10186
+ const _i18nResp = await (async () => {
10187
+ activeServeLocale = i18nLocale;
10188
+ activeServeSearch = url.search;
10189
+ activeServeHost = req.headers.get("host") || "";
10190
+ activeServeCookieHeader = req.headers.get("cookie") || "";
10191
+ activeServeCookies = parseCookies(req);
10192
+ activeServeIp = server.requestIP(req)?.address || (req.headers.get("x-forwarded-for") || "").split(",")[0].trim();
10193
+ mintedCsrfToken = csrfTokenToMint(req, activeServeCookies);
10194
+ if (mintedCsrfToken)
10195
+ activeServeCookies[CSRF_COOKIE] = mintedCsrfToken;
10196
+ const reqCtx = {
10197
+ url: req.url,
10198
+ path,
10199
+ search: activeServeSearch,
10200
+ host: activeServeHost,
10201
+ cookieHeader: activeServeCookieHeader,
10202
+ cookies: activeServeCookies,
10203
+ ip: activeServeIp,
10204
+ locale: activeServeLocale,
10205
+ params: {},
10206
+ method: (req.method || "GET").toUpperCase(),
10207
+ request: req
10208
+ };
10209
+ try {
10210
+ if (req.method === "OPTIONS") {
10211
+ return new Response(null, { headers: corsHeaders });
10212
+ }
10213
+ if (path === "/_stx/hmr") {
10214
+ let controller;
10215
+ let keepalive;
10216
+ const stream = new ReadableStream({
10217
+ start(c) {
10218
+ controller = c;
10219
+ hmrClients.add(c);
10220
+ c.enqueue(hmrEncoder.encode(`data: {"type":"connected"}
10219
10221
 
10220
10222
  `));
10221
- if (currentBuildErrors.length > 0) {
10222
- c.enqueue(hmrEncoder.encode(`data: ${JSON.stringify({ type: "build-error", errors: currentBuildErrors })}
10223
+ if (currentBuildErrors.length > 0) {
10224
+ c.enqueue(hmrEncoder.encode(`data: ${JSON.stringify({ type: "build-error", errors: currentBuildErrors })}
10223
10225
 
10224
10226
  `));
10225
- }
10226
- keepalive = setInterval(() => {
10227
- try {
10228
- c.enqueue(hmrEncoder.encode(`: keepalive
10227
+ }
10228
+ keepalive = setInterval(() => {
10229
+ try {
10230
+ c.enqueue(hmrEncoder.encode(`: keepalive
10229
10231
 
10230
10232
  `));
10231
- } catch {}
10232
- }, 5000);
10233
- },
10234
- cancel() {
10235
- if (keepalive)
10236
- clearInterval(keepalive);
10237
- if (controller)
10238
- hmrClients.delete(controller);
10239
- }
10240
- });
10241
- return new Response(stream, {
10242
- headers: {
10243
- "Content-Type": "text/event-stream",
10244
- "Cache-Control": "no-cache, no-transform",
10245
- Connection: "keep-alive",
10246
- "X-Accel-Buffering": "no"
10247
- }
10248
- });
10249
- }
10250
- if (path === "/_stx/runtime.js" || path === "/_stx/router.js") {
10251
- const stx = await stxModule;
10252
- const content2 = path === "/_stx/runtime.js" ? await stx.getCachedSignalsRuntime(stxConfig.debug === true) : await stx.getCachedRouterScript();
10253
- const etag = `"${Bun.hash(content2).toString(16)}"`;
10254
- const headers = {
10255
- "Content-Type": "application/javascript; charset=utf-8",
10256
- "Cache-Control": "public, max-age=0, must-revalidate",
10257
- ETag: etag,
10258
- ...corsHeaders
10259
- };
10260
- if (req.headers.get("if-none-match") === etag)
10261
- return new Response(null, { status: 304, headers });
10262
- if (req.method === "HEAD")
10263
- return new Response(null, { headers });
10264
- return new Response(content2, { headers });
10265
- }
10266
- if (path === "/_stx/stores.js") {
10267
- const stx = await stxModule;
10268
- const code = typeof stx.getStoreScript === "function" ? await stx.getStoreScript() : null;
10269
- return new Response(code ?? "", {
10270
- status: code === null ? 404 : 200,
10271
- headers: {
10233
+ } catch {}
10234
+ }, 5000);
10235
+ },
10236
+ cancel() {
10237
+ if (keepalive)
10238
+ clearInterval(keepalive);
10239
+ if (controller)
10240
+ hmrClients.delete(controller);
10241
+ }
10242
+ });
10243
+ return new Response(stream, {
10244
+ headers: {
10245
+ "Content-Type": "text/event-stream",
10246
+ "Cache-Control": "no-cache, no-transform",
10247
+ Connection: "keep-alive",
10248
+ "X-Accel-Buffering": "no"
10249
+ }
10250
+ });
10251
+ }
10252
+ if (path === "/_stx/runtime.js" || path === "/_stx/router.js") {
10253
+ const stx = await stxModule;
10254
+ const content2 = path === "/_stx/runtime.js" ? await stx.getCachedSignalsRuntime(stxConfig.debug === true) : await stx.getCachedRouterScript();
10255
+ const etag = `"${Bun.hash(content2).toString(16)}"`;
10256
+ const headers = {
10272
10257
  "Content-Type": "application/javascript; charset=utf-8",
10273
- "Cache-Control": "no-store",
10258
+ "Cache-Control": "public, max-age=0, must-revalidate",
10259
+ ETag: etag,
10274
10260
  ...corsHeaders
10275
- }
10276
- });
10277
- }
10278
- const crosswindAsset = path.match(/^\/_stx\/crosswind\.([a-f0-9]{16})\.css$/);
10279
- if (crosswindAsset) {
10280
- const stx = await stxModule;
10281
- const content2 = stx.getCrosswindServeAsset(crosswindAsset[1]);
10282
- if (content2 === undefined)
10283
- return new Response("Crosswind asset not found", { status: 404 });
10284
- const headers = {
10285
- "Content-Type": "text/css; charset=utf-8",
10286
- "Cache-Control": "public, max-age=31536000, immutable",
10287
- ...corsHeaders
10288
- };
10289
- if (req.method === "HEAD")
10290
- return new Response(null, { headers });
10291
- return new Response(content2, { headers });
10292
- }
10293
- if (options.onRequest) {
10294
- const hookResult = await options.onRequest(req);
10295
- if (hookResult instanceof Response)
10296
- return hookResult;
10297
- if (hookResult && typeof hookResult === "object")
10298
- Object.assign(reqCtx, hookResult);
10299
- }
10300
- const route = resolveRouteMiddleware(path);
10301
- const extraNames = extraProtectedPrefixes.some((p) => path === p || path.startsWith(p.endsWith("/") ? p : `${p}/`)) ? ["auth"] : [];
10302
- const requested = [...globalMiddlewareNames, ...route.names, ...extraNames];
10303
- if (requested.length > 0) {
10304
- const expanded = expandMiddlewareNames(requested);
10305
- const cookies = parseCookies(req);
10306
- const ctx = {
10307
- path,
10308
- url,
10309
- params: route.params,
10310
- cookies,
10311
- redirect: (to, status = 302) => {
10312
- const sep = to.includes("?") ? "&" : "?";
10313
- const next = encodeURIComponent(path + (url.search || ""));
10314
- return new Response(null, {
10315
- status,
10316
- headers: { Location: `${to}${sep}next=${next}` }
10317
- });
10318
- }
10319
- };
10320
- for (const entry of expanded) {
10321
- const colon = entry.indexOf(":");
10322
- const name = colon === -1 ? entry : entry.slice(0, colon);
10323
- const args = colon === -1 ? [] : entry.slice(colon + 1).split(",");
10324
- const handler = middlewareRegistry[name];
10325
- if (!handler) {
10326
- console.warn(`[stx serve] unknown middleware "${name}" on ${path}`);
10327
- continue;
10328
- }
10329
- const result = await handler(req, ctx, ...args);
10330
- if (result instanceof Response)
10331
- return result;
10261
+ };
10262
+ if (req.headers.get("if-none-match") === etag)
10263
+ return new Response(null, { status: 304, headers });
10264
+ if (req.method === "HEAD")
10265
+ return new Response(null, { headers });
10266
+ return new Response(content2, { headers });
10332
10267
  }
10333
- }
10334
- if (options.routes) {
10335
- const routeHandler = options.routes[path];
10336
- if (routeHandler) {
10337
- return routeHandler(req);
10268
+ if (path === "/_stx/stores.js") {
10269
+ const stx = await stxModule;
10270
+ const code = typeof stx.getStoreScript === "function" ? await stx.getStoreScript() : null;
10271
+ return new Response(code ?? "", {
10272
+ status: code === null ? 404 : 200,
10273
+ headers: {
10274
+ "Content-Type": "application/javascript; charset=utf-8",
10275
+ "Cache-Control": "no-store",
10276
+ ...corsHeaders
10277
+ }
10278
+ });
10338
10279
  }
10339
- }
10340
- if (path.startsWith("/_stx/component/")) {
10341
- const componentName = decodeURIComponent(path.slice("/_stx/component/".length));
10342
- if (componentName) {
10343
- try {
10344
- const { processDirectives, defaultConfig: defaultConfig4 } = await stxModule;
10345
- const componentTemplate = `<${componentName} />`;
10346
- const componentOpts = {
10347
- ...defaultConfig4,
10348
- ...componentsDir && { componentsDir },
10349
- ...layoutsDir && { layoutsDir },
10350
- ...partialsDir && { partialsDir },
10351
- autoShell: false
10352
- };
10353
- const html = await processDirectives(componentTemplate, {}, `${componentsDir}/${componentName}.stx`, componentOpts, new Set);
10354
- return new Response(html, {
10355
- headers: { "Content-Type": "text/html; charset=utf-8", ...corsHeaders }
10356
- });
10357
- } catch (e) {
10358
- return new Response(`<div class="stx-async-error">${e.message}</div>`, {
10359
- status: 500,
10360
- headers: { "Content-Type": "text/html; charset=utf-8", ...corsHeaders }
10361
- });
10280
+ const crosswindAsset = path.match(/^\/_stx\/crosswind\.([a-f0-9]{16})\.css$/);
10281
+ if (crosswindAsset) {
10282
+ const stx = await stxModule;
10283
+ const content2 = stx.getCrosswindServeAsset(crosswindAsset[1]);
10284
+ if (content2 === undefined)
10285
+ return new Response("Crosswind asset not found", { status: 404 });
10286
+ const headers = {
10287
+ "Content-Type": "text/css; charset=utf-8",
10288
+ "Cache-Control": "public, max-age=31536000, immutable",
10289
+ ...corsHeaders
10290
+ };
10291
+ if (req.method === "HEAD")
10292
+ return new Response(null, { headers });
10293
+ return new Response(content2, { headers });
10294
+ }
10295
+ if (options.onRequest) {
10296
+ const hookResult = await options.onRequest(req);
10297
+ if (hookResult instanceof Response)
10298
+ return hookResult;
10299
+ if (hookResult && typeof hookResult === "object")
10300
+ Object.assign(reqCtx, hookResult);
10301
+ }
10302
+ const route = resolveRouteMiddleware(path);
10303
+ const extraNames = extraProtectedPrefixes.some((p) => path === p || path.startsWith(p.endsWith("/") ? p : `${p}/`)) ? ["auth"] : [];
10304
+ const requested = [...globalMiddlewareNames, ...route.names, ...extraNames];
10305
+ if (requested.length > 0) {
10306
+ const expanded = expandMiddlewareNames(requested);
10307
+ const cookies = parseCookies(req);
10308
+ const ctx = {
10309
+ path,
10310
+ url,
10311
+ params: route.params,
10312
+ cookies,
10313
+ redirect: (to, status = 302) => {
10314
+ const sep = to.includes("?") ? "&" : "?";
10315
+ const next = encodeURIComponent(path + (url.search || ""));
10316
+ return new Response(null, {
10317
+ status,
10318
+ headers: { Location: `${to}${sep}next=${next}` }
10319
+ });
10320
+ }
10321
+ };
10322
+ for (const entry of expanded) {
10323
+ const colon = entry.indexOf(":");
10324
+ const name = colon === -1 ? entry : entry.slice(0, colon);
10325
+ const args = colon === -1 ? [] : entry.slice(colon + 1).split(",");
10326
+ const handler = middlewareRegistry[name];
10327
+ if (!handler) {
10328
+ console.warn(`[stx serve] unknown middleware "${name}" on ${path}`);
10329
+ continue;
10330
+ }
10331
+ const result = await handler(req, ctx, ...args);
10332
+ if (result instanceof Response)
10333
+ return result;
10362
10334
  }
10363
10335
  }
10364
- }
10365
- if (path.startsWith("/api/data/") && req.method === "POST") {
10366
- try {
10367
- const tableName = path.replace("/api/data/", "").split("/")[0];
10368
- if (!tableName) {
10369
- return new Response(JSON.stringify({ error: "Table name required" }), {
10370
- status: 400,
10371
- headers: { "Content-Type": "application/json", ...corsHeaders }
10372
- });
10336
+ if (options.routes) {
10337
+ const routeHandler = options.routes[path];
10338
+ if (routeHandler) {
10339
+ return routeHandler(req);
10373
10340
  }
10374
- if (!/^[a-zA-Z_]\w*$/.test(tableName)) {
10375
- return new Response(JSON.stringify({ error: "Invalid table name" }), {
10376
- status: 400,
10377
- headers: { "Content-Type": "application/json", ...corsHeaders }
10378
- });
10341
+ }
10342
+ if (path.startsWith("/_stx/component/")) {
10343
+ const componentName = decodeURIComponent(path.slice("/_stx/component/".length));
10344
+ if (componentName) {
10345
+ try {
10346
+ const { processDirectives, defaultConfig: defaultConfig4 } = await stxModule;
10347
+ const componentTemplate = `<${componentName} />`;
10348
+ const componentOpts = {
10349
+ ...defaultConfig4,
10350
+ ...componentsDir && { componentsDir },
10351
+ ...layoutsDir && { layoutsDir },
10352
+ ...partialsDir && { partialsDir },
10353
+ autoShell: false
10354
+ };
10355
+ const html = await processDirectives(componentTemplate, {}, `${componentsDir}/${componentName}.stx`, componentOpts, new Set);
10356
+ return new Response(html, {
10357
+ headers: { "Content-Type": "text/html; charset=utf-8", ...corsHeaders }
10358
+ });
10359
+ } catch (e) {
10360
+ return new Response(`<div class="stx-async-error">${e.message}</div>`, {
10361
+ status: 500,
10362
+ headers: { "Content-Type": "text/html; charset=utf-8", ...corsHeaders }
10363
+ });
10364
+ }
10379
10365
  }
10380
- const body = await req.json();
10381
- const { Database } = await import("bun:sqlite");
10382
- const dbPath = nodePath.resolve(process19.cwd(), "database/stacks.sqlite");
10383
- const db = new Database(dbPath);
10366
+ }
10367
+ if (path.startsWith("/api/data/") && req.method === "POST") {
10384
10368
  try {
10385
- const tableInfo = db.query(`PRAGMA table_info("${tableName}")`).all();
10386
- const validColumns = tableInfo.map((c) => c.name).filter((n) => n !== "id" && n !== "created_at" && n !== "updated_at");
10387
- const columns = [];
10388
- const placeholders = [];
10389
- const values = [];
10390
- for (const col of validColumns) {
10391
- if (body[col] !== undefined && body[col] !== "") {
10392
- columns.push(col);
10393
- placeholders.push("?");
10394
- values.push(body[col]);
10395
- }
10396
- }
10397
- const now = new Date().toISOString();
10398
- if (tableInfo.some((c) => c.name === "created_at")) {
10399
- columns.push("created_at");
10400
- placeholders.push("?");
10401
- values.push(now);
10402
- }
10403
- if (tableInfo.some((c) => c.name === "updated_at")) {
10404
- columns.push("updated_at");
10405
- placeholders.push("?");
10406
- values.push(now);
10369
+ const tableName = path.replace("/api/data/", "").split("/")[0];
10370
+ if (!tableName) {
10371
+ return new Response(JSON.stringify({ error: "Table name required" }), {
10372
+ status: 400,
10373
+ headers: { "Content-Type": "application/json", ...corsHeaders }
10374
+ });
10407
10375
  }
10408
- if (columns.length === 0) {
10409
- return new Response(JSON.stringify({ error: "No valid fields provided" }), {
10376
+ if (!/^[a-zA-Z_]\w*$/.test(tableName)) {
10377
+ return new Response(JSON.stringify({ error: "Invalid table name" }), {
10410
10378
  status: 400,
10411
10379
  headers: { "Content-Type": "application/json", ...corsHeaders }
10412
10380
  });
10413
10381
  }
10414
- const query = `INSERT INTO "${tableName}" (${columns.join(", ")}) VALUES (${placeholders.join(", ")})`;
10415
- const stmt = db.prepare(query);
10416
- const result = stmt.run(...values);
10417
- return new Response(JSON.stringify({
10418
- success: true,
10419
- id: result.lastInsertRowid,
10420
- message: "Record created successfully"
10421
- }), {
10422
- status: 201,
10423
- headers: { "Content-Type": "application/json", ...corsHeaders }
10424
- });
10425
- } catch (error) {
10426
- console.error("API Error:", error);
10427
- return new Response(JSON.stringify({
10428
- error: error.message || "Failed to create record"
10429
- }), {
10430
- status: 500,
10431
- headers: { "Content-Type": "application/json", ...corsHeaders }
10432
- });
10433
- } finally {
10434
- db.close();
10435
- }
10436
- } catch {}
10437
- }
10438
- if (path === "/index")
10439
- path = "/";
10440
- if (path === "/") {
10441
- const indexContent = await getRoute("/", reqCtx);
10442
- if (!indexContent) {
10443
- const homeContent = await getRoute("/home", reqCtx);
10444
- if (homeContent) {
10445
- return new Response(null, {
10446
- status: 302,
10447
- headers: {
10448
- Location: "/home",
10449
- ...corsHeaders
10382
+ const body = await req.json();
10383
+ const { Database } = await import("bun:sqlite");
10384
+ const dbPath = nodePath.resolve(process19.cwd(), "database/stacks.sqlite");
10385
+ const db = new Database(dbPath);
10386
+ try {
10387
+ const tableInfo = db.query(`PRAGMA table_info("${tableName}")`).all();
10388
+ const validColumns = tableInfo.map((c) => c.name).filter((n) => n !== "id" && n !== "created_at" && n !== "updated_at");
10389
+ const columns = [];
10390
+ const placeholders = [];
10391
+ const values = [];
10392
+ for (const col of validColumns) {
10393
+ if (body[col] !== undefined && body[col] !== "") {
10394
+ columns.push(col);
10395
+ placeholders.push("?");
10396
+ values.push(body[col]);
10397
+ }
10450
10398
  }
10451
- });
10399
+ const now = new Date().toISOString();
10400
+ if (tableInfo.some((c) => c.name === "created_at")) {
10401
+ columns.push("created_at");
10402
+ placeholders.push("?");
10403
+ values.push(now);
10404
+ }
10405
+ if (tableInfo.some((c) => c.name === "updated_at")) {
10406
+ columns.push("updated_at");
10407
+ placeholders.push("?");
10408
+ values.push(now);
10409
+ }
10410
+ if (columns.length === 0) {
10411
+ return new Response(JSON.stringify({ error: "No valid fields provided" }), {
10412
+ status: 400,
10413
+ headers: { "Content-Type": "application/json", ...corsHeaders }
10414
+ });
10415
+ }
10416
+ const query = `INSERT INTO "${tableName}" (${columns.join(", ")}) VALUES (${placeholders.join(", ")})`;
10417
+ const stmt = db.prepare(query);
10418
+ const result = stmt.run(...values);
10419
+ return new Response(JSON.stringify({
10420
+ success: true,
10421
+ id: result.lastInsertRowid,
10422
+ message: "Record created successfully"
10423
+ }), {
10424
+ status: 201,
10425
+ headers: { "Content-Type": "application/json", ...corsHeaders }
10426
+ });
10427
+ } catch (error) {
10428
+ console.error("API Error:", error);
10429
+ return new Response(JSON.stringify({
10430
+ error: error.message || "Failed to create record"
10431
+ }), {
10432
+ status: 500,
10433
+ headers: { "Content-Type": "application/json", ...corsHeaders }
10434
+ });
10435
+ } finally {
10436
+ db.close();
10437
+ }
10438
+ } catch {}
10439
+ }
10440
+ if (path === "/index")
10441
+ path = "/";
10442
+ if (path === "/") {
10443
+ const indexContent = await getRoute("/", reqCtx);
10444
+ if (!indexContent) {
10445
+ const homeContent = await getRoute("/home", reqCtx);
10446
+ if (homeContent) {
10447
+ return new Response(null, {
10448
+ status: 302,
10449
+ headers: {
10450
+ Location: "/home",
10451
+ ...corsHeaders
10452
+ }
10453
+ });
10454
+ }
10452
10455
  }
10453
10456
  }
10454
- }
10455
- const content = await getRoute(path, reqCtx);
10456
- if (reqCtx.actionRedirect) {
10457
- return new Response(null, {
10458
- status: 303,
10459
- headers: { ...corsHeaders, Location: reqCtx.actionRedirect }
10460
- });
10461
- }
10462
- if (content) {
10463
- const responseStatus = reqCtx.responseStatus ?? 200;
10464
- const isSpaNav = req.headers.get("X-STX-Router") === "true";
10465
- if (isSpaNav) {
10466
- const layoutMatch = content.match(/<!-- stx-layout: ([^ ]+) -->/);
10467
- const pageLayout = layoutMatch ? layoutMatch[1] : "default";
10468
- const groupMetaMatch = content.match(/<meta\b[^>]*name=["']stx-layout-group["'][^>]*content=["']([^"']+)["'][^>]*>/i);
10469
- const pageLayoutGroup = i18nConfig && i18nLocale ? `i18n:${i18nLocale}` : groupMetaMatch?.[1] || deriveLayoutGroup(pageLayout);
10470
- let fragment = content;
10471
- let containerAttrs = "";
10472
- let mainContentStart = -1;
10473
- let mainContentEnd = -1;
10474
- const headStyles = [];
10475
- const headMatch = content.match(/<head\b[^>]*>([\s\S]*?)<\/head>/i);
10476
- if (headMatch) {
10477
- const headContent = headMatch[1];
10478
- let styleMatch;
10479
- const styleRe = /<style\b[^>]*>[\s\S]*?<\/style>/gi;
10480
- while ((styleMatch = styleRe.exec(headContent)) !== null) {
10481
- if (styleMatch[0].includes("data-crosswind")) {
10482
- const cssContent = styleMatch[0].replace(/<style[^>]*>/, "").replace(/<\/style>/, "");
10483
- const hiddenRule = cssContent.indexOf("[hidden]");
10484
- if (hiddenRule !== -1) {
10485
- const afterPreflight = cssContent.indexOf("}", hiddenRule) + 1;
10486
- const utilities = cssContent.slice(afterPreflight).trim();
10487
- if (utilities) {
10488
- headStyles.push(`<style data-crosswind="fragment">${utilities}</style>`);
10457
+ const content = await getRoute(path, reqCtx);
10458
+ if (reqCtx.actionRedirect) {
10459
+ return new Response(null, {
10460
+ status: 303,
10461
+ headers: { ...corsHeaders, Location: reqCtx.actionRedirect }
10462
+ });
10463
+ }
10464
+ if (content) {
10465
+ const responseStatus = reqCtx.responseStatus ?? 200;
10466
+ const isSpaNav = req.headers.get("X-STX-Router") === "true";
10467
+ if (isSpaNav) {
10468
+ const layoutMatch = content.match(/<!-- stx-layout: ([^ ]+) -->/);
10469
+ const pageLayout = layoutMatch ? layoutMatch[1] : "default";
10470
+ const groupMetaMatch = content.match(/<meta\b[^>]*name=["']stx-layout-group["'][^>]*content=["']([^"']+)["'][^>]*>/i);
10471
+ const pageLayoutGroup = i18nConfig && i18nLocale ? `i18n:${i18nLocale}` : groupMetaMatch?.[1] || deriveLayoutGroup(pageLayout);
10472
+ let fragment = content;
10473
+ let containerAttrs = "";
10474
+ let mainContentStart = -1;
10475
+ let mainContentEnd = -1;
10476
+ const headStyles = [];
10477
+ const headMatch = content.match(/<head\b[^>]*>([\s\S]*?)<\/head>/i);
10478
+ if (headMatch) {
10479
+ const headContent = headMatch[1];
10480
+ let styleMatch;
10481
+ const styleRe = /<style\b[^>]*>[\s\S]*?<\/style>/gi;
10482
+ while ((styleMatch = styleRe.exec(headContent)) !== null) {
10483
+ if (styleMatch[0].includes("data-crosswind")) {
10484
+ const cssContent = styleMatch[0].replace(/<style[^>]*>/, "").replace(/<\/style>/, "");
10485
+ const hiddenRule = cssContent.indexOf("[hidden]");
10486
+ if (hiddenRule !== -1) {
10487
+ const afterPreflight = cssContent.indexOf("}", hiddenRule) + 1;
10488
+ const utilities = cssContent.slice(afterPreflight).trim();
10489
+ if (utilities) {
10490
+ headStyles.push(`<style data-crosswind="fragment">${utilities}</style>`);
10491
+ }
10489
10492
  }
10493
+ continue;
10490
10494
  }
10491
- continue;
10495
+ headStyles.push(styleMatch[0]);
10496
+ }
10497
+ const crosswindLinkRe = /<link\b[^>]*\bdata-crosswind=(?:"generated"|'generated')[^>]*>/gi;
10498
+ let crosswindLinkMatch;
10499
+ while ((crosswindLinkMatch = crosswindLinkRe.exec(headContent)) !== null) {
10500
+ headStyles.push(crosswindLinkMatch[0]);
10492
10501
  }
10493
- headStyles.push(styleMatch[0]);
10494
10502
  }
10495
- const crosswindLinkRe = /<link\b[^>]*\bdata-crosswind=(?:"generated"|'generated')[^>]*>/gi;
10496
- let crosswindLinkMatch;
10497
- while ((crosswindLinkMatch = crosswindLinkRe.exec(headContent)) !== null) {
10498
- headStyles.push(crosswindLinkMatch[0]);
10503
+ const containerSelector = stxConfig?.router?.container || "main";
10504
+ const containerRegion = findContainerRegion(fragment, containerSelector);
10505
+ if (!containerRegion) {
10506
+ console.warn(`[stx] No SPA swap container matching "${containerSelector}" in ${new URL(req.url).pathname}. SPA navigation is disabled for links on this page; the full document will load instead. Add a matching element, or point router.container at one this page has.`);
10499
10507
  }
10500
- }
10501
- const containerSelector = stxConfig?.router?.container || "main";
10502
- const containerRegion = findContainerRegion(fragment, containerSelector);
10503
- if (!containerRegion) {
10504
- console.warn(`[stx] No SPA swap container matching "${containerSelector}" in ${new URL(req.url).pathname}. SPA navigation is disabled for links on this page; the full document will load instead. Add a matching element, or point router.container at one this page has.`);
10505
- }
10506
- const mainOpenMatch = containerRegion ? { 0: containerRegion.openTag, index: containerRegion.openIndex } : null;
10507
- const mainCloseIdx = containerRegion ? containerRegion.end : -1;
10508
- if (mainOpenMatch && mainCloseIdx !== -1) {
10509
- const bodyMatch = content.match(/<body\b[^>]*>/i);
10510
- if (bodyMatch) {
10511
- const bodyStart = bodyMatch.index + bodyMatch[0].length;
10512
- const mainIdx = mainOpenMatch.index;
10513
- const beforeMain = content.slice(bodyStart, mainIdx);
10514
- let bodyStyleMatch;
10515
- const bodyStyleRe = /<style\b[^>]*>[\s\S]*?<\/style>/gi;
10516
- while ((bodyStyleMatch = bodyStyleRe.exec(beforeMain)) !== null) {
10517
- headStyles.push(bodyStyleMatch[0]);
10508
+ const mainOpenMatch = containerRegion ? { 0: containerRegion.openTag, index: containerRegion.openIndex } : null;
10509
+ const mainCloseIdx = containerRegion ? containerRegion.end : -1;
10510
+ if (mainOpenMatch && mainCloseIdx !== -1) {
10511
+ const bodyMatch = content.match(/<body\b[^>]*>/i);
10512
+ if (bodyMatch) {
10513
+ const bodyStart = bodyMatch.index + bodyMatch[0].length;
10514
+ const mainIdx = mainOpenMatch.index;
10515
+ const beforeMain = content.slice(bodyStart, mainIdx);
10516
+ let bodyStyleMatch;
10517
+ const bodyStyleRe = /<style\b[^>]*>[\s\S]*?<\/style>/gi;
10518
+ while ((bodyStyleMatch = bodyStyleRe.exec(beforeMain)) !== null) {
10519
+ headStyles.push(bodyStyleMatch[0]);
10520
+ }
10518
10521
  }
10522
+ containerAttrs = mainOpenMatch[0].replace(new RegExp(`^<${containerRegion.tagName}\\b`, "i"), "").replace(/\/?>$/, "").trim();
10523
+ const mainStart = mainOpenMatch.index + mainOpenMatch[0].length;
10524
+ mainContentStart = mainStart;
10525
+ mainContentEnd = mainCloseIdx;
10526
+ fragment = fragment.slice(mainStart, mainCloseIdx).trim();
10519
10527
  }
10520
- containerAttrs = mainOpenMatch[0].replace(new RegExp(`^<${containerRegion.tagName}\\b`, "i"), "").replace(/\/?>$/, "").trim();
10521
- const mainStart = mainOpenMatch.index + mainOpenMatch[0].length;
10522
- mainContentStart = mainStart;
10523
- mainContentEnd = mainCloseIdx;
10524
- fragment = fragment.slice(mainStart, mainCloseIdx).trim();
10525
- }
10526
- const pageSetupScripts = [];
10527
- const pageSetupScriptOffsets = new Set;
10528
- const appendOutsideMainScript = (match) => {
10529
- const offset = match.index;
10530
- const insideMain = mainContentStart !== -1 && offset >= mainContentStart && offset < mainContentEnd;
10531
- if (insideMain || pageSetupScriptOffsets.has(offset))
10532
- return;
10533
- pageSetupScriptOffsets.add(offset);
10534
- pageSetupScripts.push(match[0]);
10535
- };
10536
- const routeParamsRe = /<script\b[^>]*data-stx-route-params[^>]*>[\s\S]*?<\/script>/gi;
10537
- let setupMatch;
10538
- while ((setupMatch = routeParamsRe.exec(content)) !== null) {
10539
- appendOutsideMainScript(setupMatch);
10540
- }
10541
- const allScriptRe = /<script\b[^>]*data-stx-scoped[^>]*>[\s\S]*?<\/script>/gi;
10542
- while ((setupMatch = allScriptRe.exec(content)) !== null) {
10543
- const scriptContent = setupMatch[0];
10544
- if (scriptContent.includes("__stx_early_mounts"))
10545
- continue;
10546
- if (scriptContent.includes("data-stx-reactive") && scriptContent.includes("window.__stx_reactive"))
10547
- continue;
10548
- appendOutsideMainScript(setupMatch);
10549
- }
10550
- const bridgeInitRe = /<script\b[^>]*data-stx-reactive[^>]*>(?![\s\S]*window\.__stx_reactive)[\s\S]*?<\/script>/gi;
10551
- while ((setupMatch = bridgeInitRe.exec(content)) !== null) {
10552
- appendOutsideMainScript(setupMatch);
10553
- }
10554
- fragment = fragment.replace(/<script data-stx-scoped>\s*;?\(function\(\)\s*\{[\s\S]*?<\/script>/g, "");
10555
- const clearStale = "<script data-stx-page>if(window.stx)window.stx._latestSetup=null;</script>";
10556
- const componentFactoryScripts = pageSetupScripts.filter((script) => script.includes("data-stx-component-factories"));
10557
- const trailingPageScripts = pageSetupScripts.filter((script) => !script.includes("data-stx-component-factories"));
10558
- fragment = `${headStyles.join(`
10528
+ const pageSetupScripts = [];
10529
+ const pageSetupScriptOffsets = new Set;
10530
+ const appendOutsideMainScript = (match) => {
10531
+ const offset = match.index;
10532
+ const insideMain = mainContentStart !== -1 && offset >= mainContentStart && offset < mainContentEnd;
10533
+ if (insideMain || pageSetupScriptOffsets.has(offset))
10534
+ return;
10535
+ pageSetupScriptOffsets.add(offset);
10536
+ pageSetupScripts.push(match[0]);
10537
+ };
10538
+ const routeParamsRe = /<script\b[^>]*data-stx-route-params[^>]*>[\s\S]*?<\/script>/gi;
10539
+ let setupMatch;
10540
+ while ((setupMatch = routeParamsRe.exec(content)) !== null) {
10541
+ appendOutsideMainScript(setupMatch);
10542
+ }
10543
+ const allScriptRe = /<script\b[^>]*data-stx-scoped[^>]*>[\s\S]*?<\/script>/gi;
10544
+ while ((setupMatch = allScriptRe.exec(content)) !== null) {
10545
+ const scriptContent = setupMatch[0];
10546
+ if (scriptContent.includes("__stx_early_mounts"))
10547
+ continue;
10548
+ if (scriptContent.includes("data-stx-reactive") && scriptContent.includes("window.__stx_reactive"))
10549
+ continue;
10550
+ appendOutsideMainScript(setupMatch);
10551
+ }
10552
+ const bridgeInitRe = /<script\b[^>]*data-stx-reactive[^>]*>(?![\s\S]*window\.__stx_reactive)[\s\S]*?<\/script>/gi;
10553
+ while ((setupMatch = bridgeInitRe.exec(content)) !== null) {
10554
+ appendOutsideMainScript(setupMatch);
10555
+ }
10556
+ fragment = fragment.replace(/<script data-stx-scoped>\s*;?\(function\(\)\s*\{[\s\S]*?<\/script>/g, "");
10557
+ const clearStale = "<script data-stx-page>if(window.stx)window.stx._latestSetup=null;</script>";
10558
+ const componentFactoryScripts = pageSetupScripts.filter((script) => script.includes("data-stx-component-factories"));
10559
+ const trailingPageScripts = pageSetupScripts.filter((script) => !script.includes("data-stx-component-factories"));
10560
+ fragment = `${headStyles.join(`
10559
10561
  `)}
10560
10562
  ${componentFactoryScripts.join(`
10561
10563
  `)}
@@ -10563,215 +10565,216 @@ ${fragment}
10563
10565
  ${clearStale}
10564
10566
  ${trailingPageScripts.join(`
10565
10567
  `)}`;
10566
- const titleMatch = content.match(/<title[^>]*>([\s\S]*?)<\/title>/i);
10567
- const pageTitle = titleMatch ? titleMatch[1].trim() : "";
10568
- const shipsRuntime = content.includes("data-stx-runtime");
10569
- return new Response(fragment, {
10570
- status: isRenderFailure(content) ? 500 : responseStatus,
10568
+ const titleMatch = content.match(/<title[^>]*>([\s\S]*?)<\/title>/i);
10569
+ const pageTitle = titleMatch ? titleMatch[1].trim() : "";
10570
+ const shipsRuntime = content.includes("data-stx-runtime");
10571
+ return new Response(fragment, {
10572
+ status: isRenderFailure(content) ? 500 : responseStatus,
10573
+ headers: {
10574
+ "Content-Type": "text/html; charset=utf-8",
10575
+ "X-STX-Fragment": "true",
10576
+ "X-STX-Layout": pageLayout,
10577
+ "X-STX-Layout-Group": pageLayoutGroup,
10578
+ "X-STX-Runtime": shipsRuntime ? "true" : "false",
10579
+ ...pageTitle && { "X-STX-Title": encodeURIComponent(pageTitle) },
10580
+ [BUILD_ID_HEADER]: getBuildId(),
10581
+ ...containerAttrs && { "X-STX-Container-Attrs": encodeURIComponent(containerAttrs) },
10582
+ "Cache-Control": "no-store",
10583
+ ...corsHeaders
10584
+ }
10585
+ });
10586
+ }
10587
+ let cleaned = content;
10588
+ let runtimeCount = 0;
10589
+ cleaned = content.replace(/<script data-stx-scoped>\(function\(\)\{'use strict';var cloakStyle[\s\S]*?<\/script>/g, (match) => {
10590
+ runtimeCount++;
10591
+ return runtimeCount === 1 ? match : "";
10592
+ });
10593
+ const pageRenderFailed = isRenderFailure(cleaned);
10594
+ if (pageRenderFailed) {
10595
+ console.error(`[stx] render failed for ${new URL(req.url).pathname} \u2014 serving 500`);
10596
+ for (const custom500 of ["/500", "/errors/500"]) {
10597
+ try {
10598
+ const errorPage = usableErrorPage(await getRoute(custom500, reqCtx));
10599
+ if (errorPage) {
10600
+ const isProd2 = isProductionServe();
10601
+ return new Response(isProd2 ? errorPage : injectHmrClient(errorPage), {
10602
+ status: 500,
10603
+ headers: {
10604
+ "Content-Type": "text/html; charset=utf-8",
10605
+ "Cache-Control": "no-store",
10606
+ ...corsHeaders
10607
+ }
10608
+ });
10609
+ }
10610
+ } catch {}
10611
+ }
10612
+ }
10613
+ return new Response(injectHmrClient(cleaned), {
10614
+ status: pageRenderFailed ? 500 : responseStatus,
10571
10615
  headers: {
10572
10616
  "Content-Type": "text/html; charset=utf-8",
10573
- "X-STX-Fragment": "true",
10574
- "X-STX-Layout": pageLayout,
10575
- "X-STX-Layout-Group": pageLayoutGroup,
10576
- "X-STX-Runtime": shipsRuntime ? "true" : "false",
10577
- ...pageTitle && { "X-STX-Title": encodeURIComponent(pageTitle) },
10578
- [BUILD_ID_HEADER]: getBuildId(),
10579
- ...containerAttrs && { "X-STX-Container-Attrs": encodeURIComponent(containerAttrs) },
10580
10617
  "Cache-Control": "no-store",
10581
10618
  ...corsHeaders
10582
10619
  }
10583
10620
  });
10584
10621
  }
10585
- let cleaned = content;
10586
- let runtimeCount = 0;
10587
- cleaned = content.replace(/<script data-stx-scoped>\(function\(\)\{'use strict';var cloakStyle[\s\S]*?<\/script>/g, (match) => {
10588
- runtimeCount++;
10589
- return runtimeCount === 1 ? match : "";
10590
- });
10591
- const pageRenderFailed = isRenderFailure(cleaned);
10592
- if (pageRenderFailed) {
10593
- console.error(`[stx] render failed for ${new URL(req.url).pathname} \u2014 serving 500`);
10594
- for (const custom500 of ["/500", "/errors/500"]) {
10595
- try {
10596
- const errorPage = usableErrorPage(await getRoute(custom500, reqCtx));
10597
- if (errorPage) {
10598
- const isProd2 = isProductionServe();
10599
- return new Response(isProd2 ? errorPage : injectHmrClient(errorPage), {
10600
- status: 500,
10601
- headers: {
10602
- "Content-Type": "text/html; charset=utf-8",
10603
- "Cache-Control": "no-store",
10604
- ...corsHeaders
10605
- }
10606
- });
10607
- }
10608
- } catch {}
10609
- }
10610
- }
10611
- return new Response(injectHmrClient(cleaned), {
10612
- status: pageRenderFailed ? 500 : responseStatus,
10613
- headers: {
10614
- "Content-Type": "text/html; charset=utf-8",
10615
- "Cache-Control": "no-store",
10616
- ...corsHeaders
10617
- }
10618
- });
10619
- }
10620
- const contentWithExt = await getRoute(`${path}.html`, reqCtx);
10621
- if (contentWithExt) {
10622
- return new Response(injectHmrClient(contentWithExt), {
10623
- status: reqCtx.responseStatus ?? 200,
10624
- headers: {
10625
- "Content-Type": "text/html; charset=utf-8",
10626
- "Cache-Control": "no-store",
10627
- ...corsHeaders
10628
- }
10629
- });
10630
- }
10631
- if (path.startsWith("/chunk-") || path.endsWith(".js") || path.endsWith(".css")) {
10632
- try {
10633
- const buildFile = Bun.file(stateDir(process19.cwd(), path));
10634
- if (await buildFile.exists()) {
10635
- const ext = path.split(".").pop()?.toLowerCase();
10636
- const contentType = ext === "css" ? "text/css" : ext === "js" ? "application/javascript" : "text/plain";
10637
- return new Response(buildFile, {
10638
- headers: {
10639
- "Content-Type": contentType,
10640
- "Cache-Control": "no-store"
10641
- }
10642
- });
10643
- }
10644
- } catch {}
10645
- }
10646
- if (path.startsWith("/assets/") || path.startsWith("/resources/assets/")) {
10647
- await ensureAssets();
10648
- let assetPathname;
10649
- try {
10650
- assetPathname = decodeURIComponent(path);
10651
- } catch {
10652
- return new Response("Invalid asset path", { status: 400 });
10622
+ const contentWithExt = await getRoute(`${path}.html`, reqCtx);
10623
+ if (contentWithExt) {
10624
+ return new Response(injectHmrClient(contentWithExt), {
10625
+ status: reqCtx.responseStatus ?? 200,
10626
+ headers: {
10627
+ "Content-Type": "text/html; charset=utf-8",
10628
+ "Cache-Control": "no-store",
10629
+ ...corsHeaders
10630
+ }
10631
+ });
10653
10632
  }
10654
- if (!isSafeAssetPath(assetPathname))
10655
- return new Response("Invalid asset path", { status: 400 });
10656
- for (const assetPath of assetRequestPaths(assetPathname)) {
10633
+ if (path.startsWith("/chunk-") || path.endsWith(".js") || path.endsWith(".css")) {
10657
10634
  try {
10658
- const filePath = nodePath.resolve(process19.cwd(), `.${assetPath}`);
10659
- const file = Bun.file(filePath);
10660
- if (await file.exists()) {
10661
- const ext = getAssetExtension(assetPath);
10662
- if (isBundledAssetExtension(ext))
10663
- return bundleBrowserAsset(filePath);
10664
- return new Response(file, {
10635
+ const buildFile = Bun.file(stateDir(process19.cwd(), path));
10636
+ if (await buildFile.exists()) {
10637
+ const ext = path.split(".").pop()?.toLowerCase();
10638
+ const contentType = ext === "css" ? "text/css" : ext === "js" ? "application/javascript" : "text/plain";
10639
+ return new Response(buildFile, {
10665
10640
  headers: {
10666
- "Content-Type": staticContentTypes[ext || ""] || "application/octet-stream",
10641
+ "Content-Type": contentType,
10667
10642
  "Cache-Control": "no-store"
10668
10643
  }
10669
10644
  });
10670
10645
  }
10646
+ } catch {}
10647
+ }
10648
+ if (path.startsWith("/assets/") || path.startsWith("/resources/assets/")) {
10649
+ await ensureAssets();
10650
+ let assetPathname;
10651
+ try {
10652
+ assetPathname = decodeURIComponent(path);
10671
10653
  } catch {
10672
- continue;
10654
+ return new Response("Invalid asset path", { status: 400 });
10673
10655
  }
10674
- }
10675
- }
10676
- if ((req.method === "GET" || req.method === "HEAD") && path !== "/") {
10677
- const publicRoot = nodePath.resolve(process19.cwd(), publicDir);
10678
- let safePathname;
10679
- try {
10680
- safePathname = decodeURIComponent(path);
10681
- } catch {
10682
- safePathname = path;
10683
- }
10684
- if (!safePathname.includes("\x00")) {
10685
- const resolvedPath = nodePath.resolve(publicRoot, `.${safePathname}`);
10686
- const isInsidePublicRoot = resolvedPath === publicRoot || resolvedPath.startsWith(`${publicRoot}${nodePath.sep}`);
10687
- if (isInsidePublicRoot) {
10656
+ if (!isSafeAssetPath(assetPathname))
10657
+ return new Response("Invalid asset path", { status: 400 });
10658
+ for (const assetPath of assetRequestPaths(assetPathname)) {
10688
10659
  try {
10689
- const file = Bun.file(resolvedPath);
10660
+ const filePath = nodePath.resolve(process19.cwd(), `.${assetPath}`);
10661
+ const file = Bun.file(filePath);
10690
10662
  if (await file.exists()) {
10691
- const stat4 = await file.stat().catch(() => null);
10692
- if (stat4 && !stat4.isDirectory()) {
10693
- const ext = resolvedPath.split(".").pop()?.toLowerCase();
10694
- return new Response(file, {
10695
- headers: {
10696
- "Content-Type": staticContentTypes[ext || ""] || "application/octet-stream",
10697
- "Cache-Control": staticCacheControl(resolvedPath)
10698
- }
10699
- });
10700
- }
10663
+ const ext = getAssetExtension(assetPath);
10664
+ if (isBundledAssetExtension(ext))
10665
+ return bundleBrowserAsset(filePath);
10666
+ return new Response(file, {
10667
+ headers: {
10668
+ "Content-Type": staticContentTypes[ext || ""] || "application/octet-stream",
10669
+ "Cache-Control": "no-store"
10670
+ }
10671
+ });
10701
10672
  }
10702
- } catch {}
10673
+ } catch {
10674
+ continue;
10675
+ }
10703
10676
  }
10704
10677
  }
10705
- }
10706
- if (path === "/favicon.ico") {
10707
- return new Response(null, { status: 204 });
10708
- }
10709
- const isProd = isProductionServe();
10710
- for (const custom404 of ["/404", "/errors/404"]) {
10711
- try {
10712
- const customContent = await getRoute(custom404, reqCtx);
10713
- if (customContent) {
10714
- return new Response(isProd ? customContent : injectHmrClient(customContent), {
10715
- status: 404,
10716
- headers: { "Content-Type": "text/html; charset=utf-8" }
10717
- });
10678
+ if ((req.method === "GET" || req.method === "HEAD") && path !== "/") {
10679
+ const publicRoot = nodePath.resolve(process19.cwd(), publicDir);
10680
+ let safePathname;
10681
+ try {
10682
+ safePathname = decodeURIComponent(path);
10683
+ } catch {
10684
+ safePathname = path;
10718
10685
  }
10719
- } catch {}
10720
- }
10721
- let availableRoutes;
10722
- if (!isProd) {
10723
- const files = await discoverFiles();
10724
- availableRoutes = [];
10725
- for (const filePath of files) {
10726
- let normalizedPath = filePath.replace(/^\.\//, "").replace(/\\/g, "/");
10727
- for (const pattern of patterns) {
10728
- const normalizedPattern = pattern.replace(/\\/g, "/").replace(/\/$/, "");
10729
- if (normalizedPath.startsWith(`${normalizedPattern}/`)) {
10730
- normalizedPath = normalizedPath.slice(normalizedPattern.length + 1);
10731
- break;
10686
+ if (!safePathname.includes("\x00")) {
10687
+ const resolvedPath = nodePath.resolve(publicRoot, `.${safePathname}`);
10688
+ const isInsidePublicRoot = resolvedPath === publicRoot || resolvedPath.startsWith(`${publicRoot}${nodePath.sep}`);
10689
+ if (isInsidePublicRoot) {
10690
+ try {
10691
+ const file = Bun.file(resolvedPath);
10692
+ if (await file.exists()) {
10693
+ const stat4 = await file.stat().catch(() => null);
10694
+ if (stat4 && !stat4.isDirectory()) {
10695
+ const ext = resolvedPath.split(".").pop()?.toLowerCase();
10696
+ return new Response(file, {
10697
+ headers: {
10698
+ "Content-Type": staticContentTypes[ext || ""] || "application/octet-stream",
10699
+ "Cache-Control": staticCacheControl(resolvedPath)
10700
+ }
10701
+ });
10702
+ }
10703
+ }
10704
+ } catch {}
10732
10705
  }
10733
10706
  }
10734
- let route2 = normalizedPath.replace(/\.(stx|md|html)$/, "");
10735
- if (route2.startsWith("pages/")) {
10736
- route2 = route2.slice(6);
10707
+ }
10708
+ if (path === "/favicon.ico") {
10709
+ return new Response(null, { status: 204 });
10710
+ }
10711
+ const isProd = isProductionServe();
10712
+ for (const custom404 of ["/404", "/errors/404"]) {
10713
+ try {
10714
+ const customContent = await getRoute(custom404, reqCtx);
10715
+ if (customContent) {
10716
+ return new Response(isProd ? customContent : injectHmrClient(customContent), {
10717
+ status: 404,
10718
+ headers: { "Content-Type": "text/html; charset=utf-8" }
10719
+ });
10720
+ }
10721
+ } catch {}
10722
+ }
10723
+ let availableRoutes;
10724
+ if (!isProd) {
10725
+ const files = await discoverFiles();
10726
+ availableRoutes = [];
10727
+ for (const filePath of files) {
10728
+ let normalizedPath = filePath.replace(/^\.\//, "").replace(/\\/g, "/");
10729
+ for (const pattern of patterns) {
10730
+ const normalizedPattern = pattern.replace(/\\/g, "/").replace(/\/$/, "");
10731
+ if (normalizedPath.startsWith(`${normalizedPattern}/`)) {
10732
+ normalizedPath = normalizedPath.slice(normalizedPattern.length + 1);
10733
+ break;
10734
+ }
10735
+ }
10736
+ let route2 = normalizedPath.replace(/\.(stx|md|html)$/, "");
10737
+ if (route2.startsWith("pages/")) {
10738
+ route2 = route2.slice(6);
10739
+ }
10740
+ availableRoutes.push(route2);
10737
10741
  }
10738
- availableRoutes.push(route2);
10739
10742
  }
10743
+ const notFoundHtml = render404Page({ path, routes: availableRoutes, isProduction: isProd });
10744
+ return new Response(isProd ? notFoundHtml : injectHmrClient(notFoundHtml), {
10745
+ status: 404,
10746
+ headers: { "Content-Type": "text/html; charset=utf-8" }
10747
+ });
10748
+ } finally {
10749
+ activeServeLocale = null;
10750
+ activeServeSearch = "";
10751
+ activeServeHost = "";
10752
+ activeServeCookieHeader = "";
10753
+ activeServeCookies = {};
10754
+ activeServeIp = "";
10755
+ }
10756
+ })();
10757
+ let _finalResp = await applyI18nToResponse(_i18nResp, i18nLocale ?? (i18nConfig?.defaultLocale ?? "en"), path);
10758
+ if (mintedCsrfToken) {
10759
+ try {
10760
+ _finalResp.headers.append("Set-Cookie", csrfCookieHeader(mintedCsrfToken, req.url.startsWith("https://")));
10761
+ } catch {
10762
+ const headers = new Headers(_finalResp.headers);
10763
+ headers.append("Set-Cookie", csrfCookieHeader(mintedCsrfToken, req.url.startsWith("https://")));
10764
+ _finalResp = new Response(_finalResp.body, {
10765
+ status: _finalResp.status,
10766
+ statusText: _finalResp.statusText,
10767
+ headers
10768
+ });
10740
10769
  }
10741
- const notFoundHtml = render404Page({ path, routes: availableRoutes, isProduction: isProd });
10742
- return new Response(isProd ? notFoundHtml : injectHmrClient(notFoundHtml), {
10743
- status: 404,
10744
- headers: { "Content-Type": "text/html; charset=utf-8" }
10745
- });
10746
- } finally {
10747
- activeServeLocale = null;
10748
- activeServeSearch = "";
10749
- activeServeHost = "";
10750
- activeServeCookieHeader = "";
10751
- activeServeCookies = {};
10752
- activeServeIp = "";
10753
10770
  }
10754
- })();
10755
- let _finalResp = await applyI18nToResponse(_i18nResp, i18nLocale ?? (i18nConfig?.defaultLocale ?? "en"), path);
10756
- if (mintedCsrfToken) {
10757
- try {
10758
- _finalResp.headers.append("Set-Cookie", csrfCookieHeader(mintedCsrfToken, req.url.startsWith("https://")));
10759
- } catch {
10760
- const headers = new Headers(_finalResp.headers);
10761
- headers.append("Set-Cookie", csrfCookieHeader(mintedCsrfToken, req.url.startsWith("https://")));
10762
- _finalResp = new Response(_finalResp.body, {
10763
- status: _finalResp.status,
10764
- statusText: _finalResp.statusText,
10765
- headers
10766
- });
10771
+ if (options.onResponse) {
10772
+ const hooked = await options.onResponse(req, _finalResp);
10773
+ if (hooked instanceof Response)
10774
+ return hooked;
10767
10775
  }
10768
- }
10769
- if (options.onResponse) {
10770
- const hooked = await options.onResponse(req, _finalResp);
10771
- if (hooked instanceof Response)
10772
- return hooked;
10773
- }
10774
- return _finalResp;
10776
+ return _finalResp;
10777
+ })());
10775
10778
  }
10776
10779
  });
10777
10780
  break;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "bun-plugin-stx",
3
3
  "type": "module",
4
- "version": "0.2.168",
4
+ "version": "0.2.170",
5
5
  "description": "A Bun plugin that allows for using Laravel Blade-like syntax.",
6
6
  "author": "Chris Breuer <chris@stacksjs.org>",
7
7
  "license": "MIT",