@vesk/vesk-cli 0.2.37 → 0.2.39

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/cli.js +498 -135
  2. package/package.json +5 -5
package/dist/cli.js CHANGED
@@ -379,6 +379,14 @@ var init_ir = __esm({
379
379
  ComponentIR = class {
380
380
  name;
381
381
  paramNames;
382
+ /**
383
+ * The declared first parameter when it is a plain identifier
384
+ * (`component Card(p)`), i.e. the whole props object bound to another name.
385
+ * `null` for a destructuring pattern (`component Card({ title })`) or when
386
+ * there is no parameter. `paramNames` alone cannot express this: `p` and
387
+ * `{ p }` both flatten to `['p']` but need opposite bindings.
388
+ */
389
+ propsAlias;
382
390
  propsType;
383
391
  isClient;
384
392
  isAsync;
@@ -391,6 +399,7 @@ var init_ir = __esm({
391
399
  constructor(name, paramNames, body, opts = {}) {
392
400
  this.name = name;
393
401
  this.paramNames = paramNames;
402
+ this.propsAlias = opts.propsAlias ?? null;
394
403
  this.body = body;
395
404
  this.isClient = opts.isClient ?? false;
396
405
  this.isAsync = opts.isAsync ?? false;
@@ -1540,6 +1549,26 @@ function setActiveComponent(value) {
1540
1549
  function createContext(value) {
1541
1550
  return new Context(value);
1542
1551
  }
1552
+ function createLocals() {
1553
+ const store = {};
1554
+ return {
1555
+ set(key, value) {
1556
+ store[key] = value;
1557
+ },
1558
+ get(key) {
1559
+ return store[key];
1560
+ },
1561
+ has(key) {
1562
+ return Object.prototype.hasOwnProperty.call(store, key);
1563
+ },
1564
+ delete(key) {
1565
+ delete store[key];
1566
+ },
1567
+ all() {
1568
+ return { ...store };
1569
+ }
1570
+ };
1571
+ }
1543
1572
  var ctx, Context;
1544
1573
  var init_context = __esm({
1545
1574
  "../runtime/src/context.ts"() {
@@ -1547,9 +1576,17 @@ var init_context = __esm({
1547
1576
  ctx = { current: null };
1548
1577
  Context = class {
1549
1578
  _v;
1579
+ /** Identity of this context instance — a Map key in the provider chain. */
1580
+ id;
1550
1581
  constructor(value) {
1551
1582
  this._v = value;
1583
+ this.id = Symbol("vesk-context");
1552
1584
  }
1585
+ /**
1586
+ * Reads the nearest value set by an ancestor component, else the default.
1587
+ * Typed as `T`, so a context created with a type carries that type all the
1588
+ * way to every reader.
1589
+ */
1553
1590
  get() {
1554
1591
  let current2 = getActiveComponent();
1555
1592
  while (current2) {
@@ -1558,6 +1595,7 @@ var init_context = __esm({
1558
1595
  }
1559
1596
  return this._v;
1560
1597
  }
1598
+ /** Sets the value for this component and its descendants. Typed as `T`. */
1561
1599
  set(value) {
1562
1600
  const component = getActiveComponent();
1563
1601
  if (component === null) throw new Error("No active component found, cannot set context");
@@ -1999,6 +2037,32 @@ function revalidate(handle2) {
1999
2037
  handle2.controller?.abort();
2000
2038
  startRequest(handle2, true);
2001
2039
  }
2040
+ function mutate(key, data2) {
2041
+ if (isServer()) return;
2042
+ const registry = getRegistry().get(key);
2043
+ const prev = getInflight().get(key);
2044
+ if (data2 !== void 0) {
2045
+ getClientCache().set(key, { data: data2, fetchedAt: Date.now() });
2046
+ if (prev) {
2047
+ getInflight().delete(key);
2048
+ if (registry) registry.forEach((h) => h.controller?.abort());
2049
+ }
2050
+ if (registry) {
2051
+ registry.forEach((h) => {
2052
+ if (h.into) setInto(h.into, data2);
2053
+ set(h.state, { loading: false, error: null, data: data2 });
2054
+ });
2055
+ }
2056
+ return;
2057
+ }
2058
+ if (registry && registry.size > 0) {
2059
+ if (prev) {
2060
+ getInflight().delete(key);
2061
+ registry.forEach((h) => h.controller?.abort());
2062
+ }
2063
+ registry.forEach((h) => startRequest(h, true));
2064
+ }
2065
+ }
2002
2066
  function createResourceAccessor(handle2) {
2003
2067
  const state = handle2.state;
2004
2068
  const accessor = {};
@@ -5342,11 +5406,18 @@ function createRouter(routes, options = {}) {
5342
5406
  if (decision === false) return;
5343
5407
  if (typeof decision === "string" && decision !== url.pathname && (self._guardDepth ?? 0) < 5) {
5344
5408
  self._guardDepth = (self._guardDepth ?? 0) + 1;
5345
- try {
5346
- self.navigate(decision, { replace: true });
5347
- } finally {
5348
- self._guardDepth = 0;
5409
+ const hop = self.navigate(decision, { replace: true });
5410
+ if (hop && typeof hop.then === "function") {
5411
+ return hop.then(
5412
+ () => {
5413
+ self._guardDepth = 0;
5414
+ },
5415
+ () => {
5416
+ self._guardDepth = 0;
5417
+ }
5418
+ );
5349
5419
  }
5420
+ self._guardDepth = 0;
5350
5421
  return;
5351
5422
  }
5352
5423
  proceedNav();
@@ -5767,11 +5838,18 @@ function createFileRouter(routeTree, options = {}) {
5767
5838
  if (decision === false) return;
5768
5839
  if (typeof decision === "string" && decision !== url.pathname && (router._guardDepth || 0) < 5) {
5769
5840
  router._guardDepth = (router._guardDepth || 0) + 1;
5770
- try {
5771
- router.navigate(decision, { replace: true });
5772
- } finally {
5773
- router._guardDepth = 0;
5841
+ const hop = router.navigate(decision, { replace: true });
5842
+ if (hop && typeof hop.then === "function") {
5843
+ return hop.then(
5844
+ () => {
5845
+ router._guardDepth = 0;
5846
+ },
5847
+ () => {
5848
+ router._guardDepth = 0;
5849
+ }
5850
+ );
5774
5851
  }
5852
+ router._guardDepth = 0;
5775
5853
  return;
5776
5854
  }
5777
5855
  afterGuards();
@@ -17589,7 +17667,7 @@ function skipComment(text, i) {
17589
17667
  }
17590
17668
  return i;
17591
17669
  }
17592
- function blankLeadingLineComments(text) {
17670
+ function blankComments(text) {
17593
17671
  const chars = text.split("");
17594
17672
  const n = text.length;
17595
17673
  let lineStart = true;
@@ -17615,7 +17693,11 @@ function blankLeadingLineComments(text) {
17615
17693
  i = end;
17616
17694
  continue;
17617
17695
  }
17618
- if (ch === "/" && text[i + 1] === "/" && lineStart) {
17696
+ if (ch === "/" && text[i + 1] === "/") {
17697
+ if (!lineStart) {
17698
+ i += 2;
17699
+ continue;
17700
+ }
17619
17701
  let j = i;
17620
17702
  while (j < n && text[j] !== "\n") {
17621
17703
  chars[j] = " ";
@@ -17624,6 +17706,30 @@ function blankLeadingLineComments(text) {
17624
17706
  i = j;
17625
17707
  continue;
17626
17708
  }
17709
+ if (ch === "/" && text[i + 1] === "*") {
17710
+ if (!lineStart) {
17711
+ i += 2;
17712
+ continue;
17713
+ }
17714
+ let j = i + 2;
17715
+ let end = -1;
17716
+ while (j < n) {
17717
+ if (text[j] === "*" && text[j + 1] === "/") {
17718
+ end = j + 2;
17719
+ break;
17720
+ }
17721
+ j++;
17722
+ }
17723
+ if (end === -1) {
17724
+ lineStart = false;
17725
+ i += 2;
17726
+ continue;
17727
+ }
17728
+ for (let k = i; k < end; k++) if (text[k] !== "\n") chars[k] = " ";
17729
+ lineStart = false;
17730
+ i = end;
17731
+ continue;
17732
+ }
17627
17733
  lineStart = false;
17628
17734
  i++;
17629
17735
  }
@@ -18027,7 +18133,7 @@ function createBaseParser() {
18027
18133
  }
18028
18134
  function parse4(source, options = {}) {
18029
18135
  const ParserClass = createBaseParser();
18030
- const { code, annotations } = preprocessForClauses(blankLeadingLineComments(source));
18136
+ const { code, annotations } = preprocessForClauses(blankComments(source));
18031
18137
  try {
18032
18138
  const ast = ParserClass.parse(code, {
18033
18139
  ecmaVersion: "latest",
@@ -20615,6 +20721,8 @@ function generateIR(ast, source, filename) {
20615
20721
  }
20616
20722
  const name = inner.id.name;
20617
20723
  const paramNames = getParamNames(inner.params, source);
20724
+ const firstParam = inner.params?.[0];
20725
+ const propsAlias = firstParam && firstParam.type === "Identifier" ? firstParam.name : null;
20618
20726
  const propsType = getPropsType(inner.params, source);
20619
20727
  const bodyStmts = inner.body.body;
20620
20728
  const isClientComp = !!inner.client;
@@ -20624,7 +20732,7 @@ function generateIR(ast, source, filename) {
20624
20732
  const raw2 = processStatementModeBody(source, bodyStmts, file);
20625
20733
  const { body, css } = extractStyle(raw2);
20626
20734
  validateBlocks(name, isClientComp, body, file, source);
20627
- const comp = new ComponentIR(name, paramNames, body, { mode: "statement", exported, defaultExport, isClient: inner.client, isAsync: inner.async, ssrAwait: componentUsesFetch(body), propsType });
20735
+ const comp = new ComponentIR(name, paramNames, body, { mode: "statement", exported, defaultExport, isClient: inner.client, isAsync: inner.async, ssrAwait: componentUsesFetch(body), propsType, propsAlias });
20628
20736
  comp.style = css;
20629
20737
  components.push(comp);
20630
20738
  __slotProps = prevSlotProps;
@@ -21621,11 +21729,14 @@ function tryParseImport(code) {
21621
21729
  return null;
21622
21730
  }
21623
21731
  }
21624
- function buildParamInit(paramNames) {
21732
+ function buildParamInit(paramNames, propsAlias = null) {
21733
+ if (propsAlias) {
21734
+ return propsAlias === "props" ? "" : `const ${propsAlias} = props;`;
21735
+ }
21736
+ if (paramNames.length === 0) return "";
21625
21737
  if (paramNames.length === 1 && paramNames[0] === "props") {
21626
21738
  return "";
21627
21739
  }
21628
- if (paramNames.length === 0) return "";
21629
21740
  return `const { ${paramNames.join(", ")} } = props;`;
21630
21741
  }
21631
21742
  function resolveComponentName(source) {
@@ -22043,6 +22154,12 @@ function webhook(options) {
22043
22154
  });
22044
22155
  }
22045
22156
  async function webhookHandler(request) {
22157
+ if (request.method !== "POST") {
22158
+ return new Response(JSON.stringify({ error: "Method not allowed" }), {
22159
+ status: 405,
22160
+ headers: { "Content-Type": "application/json", Allow: "POST" }
22161
+ });
22162
+ }
22046
22163
  const signature = request.headers.get(headerName);
22047
22164
  if (!signature) {
22048
22165
  return new Response(JSON.stringify({ error: "Missing signature header" }), {
@@ -22211,7 +22328,7 @@ async function readSignedCookie2(name, cookieString, host) {
22211
22328
  if (impl.readSignedCookie) return impl.readSignedCookie(name, cookieString, host);
22212
22329
  throw new Error("readSignedCookie: @vesk/compiler not available");
22213
22330
  }
22214
- var ServerRequest, ServerResponse, VeskRequest, _VeskResponse, VeskResponse, _signImpl;
22331
+ var ServerRequest, ServerResponse, DEFAULT_MAX_BODY_BYTES2, PayloadTooLargeError, VeskRequest, _VeskResponse, VeskResponse, _signImpl;
22215
22332
  var init_request = __esm({
22216
22333
  "../runtime/src/request.ts"() {
22217
22334
  "use strict";
@@ -22279,6 +22396,14 @@ var init_request = __esm({
22279
22396
  });
22280
22397
  }
22281
22398
  };
22399
+ DEFAULT_MAX_BODY_BYTES2 = 1024 * 1024;
22400
+ PayloadTooLargeError = class extends Error {
22401
+ status = 413;
22402
+ constructor(message = `Request body exceeds limit (${DEFAULT_MAX_BODY_BYTES2} bytes)`) {
22403
+ super(message);
22404
+ this.name = "PayloadTooLargeError";
22405
+ }
22406
+ };
22282
22407
  VeskRequest = class _VeskRequest extends ServerRequest {
22283
22408
  _query;
22284
22409
  _ip;
@@ -22487,14 +22612,35 @@ var init_request = __esm({
22487
22612
  this._flushSecurityHeaders();
22488
22613
  return this;
22489
22614
  }
22615
+ /**
22616
+ * Rejects a body that declares itself over the cap before any bytes are
22617
+ * read. The dev server additionally caps the Node stream itself; this is
22618
+ * the portable guard for platform/serverless builds, so a route that does
22619
+ * `await req.json()` on an oversized payload fails with 413 instead of an
22620
+ * opaque 500.
22621
+ */
22622
+ _assertBodyWithinLimit() {
22623
+ const limit = DEFAULT_MAX_BODY_BYTES2;
22624
+ const declared = Number(this.headers?.get("content-length") || "0");
22625
+ if (Number.isFinite(declared) && declared > limit) {
22626
+ throw new PayloadTooLargeError(`Request body exceeds limit (${limit} bytes)`);
22627
+ }
22628
+ }
22490
22629
  async text() {
22491
22630
  this._flushSecurityHeaders();
22631
+ this._assertBodyWithinLimit();
22492
22632
  return super.text();
22493
22633
  }
22494
22634
  async json() {
22495
22635
  this._flushSecurityHeaders();
22636
+ this._assertBodyWithinLimit();
22496
22637
  return super.json();
22497
22638
  }
22639
+ async formData() {
22640
+ this._flushSecurityHeaders();
22641
+ this._assertBodyWithinLimit();
22642
+ return super.formData();
22643
+ }
22498
22644
  setCookie(name, value, opts = {}) {
22499
22645
  const parts = [`${name}=${encodeURIComponent(value)}`];
22500
22646
  if (opts.httpOnly !== false) parts.push("HttpOnly");
@@ -25684,11 +25830,13 @@ __export(index_server_exports, {
25684
25830
  ArticleSchema: () => ArticleSchema,
25685
25831
  BreadcrumbListSchema: () => BreadcrumbListSchema,
25686
25832
  Context: () => Context,
25833
+ DEFAULT_MAX_BODY_BYTES: () => DEFAULT_MAX_BODY_BYTES2,
25687
25834
  Experiment: () => Experiment,
25688
25835
  FAQPageSchema: () => FAQPageSchema,
25689
25836
  Field: () => Field,
25690
25837
  For: () => For,
25691
25838
  Form: () => Form,
25839
+ HttpError: () => HttpError,
25692
25840
  Image: () => Image,
25693
25841
  JsonLd: () => JsonLd,
25694
25842
  Link: () => Link,
@@ -25700,6 +25848,7 @@ __export(index_server_exports, {
25700
25848
  NotFoundError: () => NotFoundError,
25701
25849
  OrganizationSchema: () => OrganizationSchema,
25702
25850
  Outlet: () => Outlet,
25851
+ PayloadTooLargeError: () => PayloadTooLargeError,
25703
25852
  Portal: () => Portal,
25704
25853
  ProductSchema: () => ProductSchema,
25705
25854
  Redirect: () => Redirect,
@@ -25707,6 +25856,7 @@ __export(index_server_exports, {
25707
25856
  ServerResponse: () => ServerResponse,
25708
25857
  Show: () => Show,
25709
25858
  Switch: () => Switch,
25859
+ TimeoutError: () => TimeoutError,
25710
25860
  VeskRequest: () => VeskRequest,
25711
25861
  VeskResponse: () => VeskResponse,
25712
25862
  VideoSchema: () => VideoSchema,
@@ -25727,6 +25877,7 @@ __export(index_server_exports, {
25727
25877
  cors: () => cors,
25728
25878
  createContext: () => createContext,
25729
25879
  createFileRouter: () => createFileRouter,
25880
+ createLocals: () => createLocals,
25730
25881
  createResource: () => createResource,
25731
25882
  createRouter: () => createRouter,
25732
25883
  create_component_ctx: () => create_component_ctx,
@@ -25758,6 +25909,7 @@ __export(index_server_exports, {
25758
25909
  locals: () => locals,
25759
25910
  maxLength: () => maxLength,
25760
25911
  minLength: () => minLength,
25912
+ mutate: () => mutate,
25761
25913
  notFound: () => notFound,
25762
25914
  on_destroy: () => on_destroy,
25763
25915
  pageIsr: () => pageIsr,
@@ -29883,7 +30035,7 @@ function generateComponent(comp, importedNames = /* @__PURE__ */ new Set(), hydr
29883
30035
  ctx2.push(indent2(`const $root = document.createDocumentFragment();`));
29884
30036
  }
29885
30037
  ctx2.push(indent2(`let __pendingChild = null;`));
29886
- const paramInit = buildParamInit2(comp.paramNames);
30038
+ const paramInit = buildParamInit2(comp.paramNames, comp.propsAlias);
29887
30039
  if (paramInit) ctx2.push(indent2(paramInit));
29888
30040
  for (const node of comp.body) {
29889
30041
  if (node instanceof TrackDecl) {
@@ -29932,7 +30084,8 @@ function generateComponent(comp, importedNames = /* @__PURE__ */ new Set(), hydr
29932
30084
  ctx2.push(`}`);
29933
30085
  return ctx2.getCode();
29934
30086
  }
29935
- function buildParamInit2(paramNames) {
30087
+ function buildParamInit2(paramNames, propsAlias = null) {
30088
+ if (propsAlias) return propsAlias === "props" ? "" : `const ${propsAlias} = props;`;
29936
30089
  if (paramNames.length === 1 && paramNames[0] === "props") return "";
29937
30090
  if (paramNames.length === 0) return "";
29938
30091
  return `const { ${paramNames.join(", ")} } = props;`;
@@ -30817,7 +30970,7 @@ function buildComponentEntries(irRoot) {
30817
30970
  const entries = [];
30818
30971
  for (const comp of irRoot.components) {
30819
30972
  const bodyCode = generateFunctionBody(comp, importedNames);
30820
- const paramInit = buildParamInit(comp.paramNames);
30973
+ const paramInit = buildParamInit(comp.paramNames, comp.propsAlias);
30821
30974
  const diag = process.env.VESK_SSR_LOG ? `console.error('[SSR-CALL]', ${JSON.stringify(comp.name)}, props ? JSON.stringify(props) : String(props));
30822
30975
  ` : "";
30823
30976
  const code = `${scopeDecl}${paramInit}${diag}
@@ -36881,6 +37034,54 @@ function isTrackDeclStatement(stmt) {
36881
37034
  (d) => d && d.id && d.id.type === "ArrayPattern" && d.id.lazy === true && d.init
36882
37035
  );
36883
37036
  }
37037
+ function stripCallTypeArgs(text) {
37038
+ let i = 0;
37039
+ while (i < text.length && /\s/.test(text[i])) i++;
37040
+ const nameStart = i;
37041
+ while (i < text.length && /[A-Za-z0-9_$]/.test(text[i])) i++;
37042
+ if (i === nameStart) return text;
37043
+ while (i < text.length && (text[i] === "." || text[i] === "?" || text[i] === "[")) {
37044
+ const ch = text[i];
37045
+ if (ch === "[") {
37046
+ const close = text.indexOf("]", i);
37047
+ if (close === -1) return text;
37048
+ i = close + 1;
37049
+ } else {
37050
+ i++;
37051
+ while (i < text.length && /[A-Za-z0-9_$]/.test(text[i])) i++;
37052
+ }
37053
+ }
37054
+ if (text[i] !== "<") return text;
37055
+ let depth = 0;
37056
+ let quote = "";
37057
+ for (let j = i; j < text.length; j++) {
37058
+ const c = text[j];
37059
+ if (quote) {
37060
+ if (c === "\\") {
37061
+ j++;
37062
+ continue;
37063
+ }
37064
+ if (c === quote) quote = "";
37065
+ continue;
37066
+ }
37067
+ if (c === '"' || c === "'" || c === "`") {
37068
+ quote = c;
37069
+ continue;
37070
+ }
37071
+ if (c === "<") depth++;
37072
+ else if (c === ">") {
37073
+ depth--;
37074
+ if (depth === 0) {
37075
+ const after = text.slice(j + 1);
37076
+ if (/^\s*\(/.test(after)) return text.slice(0, i) + after;
37077
+ return text;
37078
+ }
37079
+ } else if (c === ";" || c === "\n") {
37080
+ return text;
37081
+ }
37082
+ }
37083
+ return text;
37084
+ }
36884
37085
  function emitTrackDeclStatement(g2, source, stmt, indent4, isLast, opts) {
36885
37086
  const fullText = stripTrailingSemicolons(source.slice(stmt.start, stmt.end).trimEnd());
36886
37087
  const text = fullText + (!fullText.endsWith("}") && !isLast ? ";" : "");
@@ -36918,8 +37119,15 @@ function emitTrackDeclStatement(g2, source, stmt, indent4, isLast, opts) {
36918
37119
  if (opts.typedCells) {
36919
37120
  const cellName = g2.cellCount === 0 ? "__cell" : `__cell${g2.cellCount}`;
36920
37121
  g2.cellCount++;
36921
- g2.add(`const ${cellName} = `);
36922
- g2.add(initText, decl.init.start, decl.init.end);
37122
+ if (annotation) {
37123
+ g2.add(`const ${cellName}: Tracked<`);
37124
+ g2.add(annotation, annStart, annEnd);
37125
+ g2.add("> = ");
37126
+ } else {
37127
+ g2.add(`const ${cellName} = `);
37128
+ }
37129
+ const cellInit = annotation ? stripCallTypeArgs(initText) : initText;
37130
+ g2.add(cellInit, decl.init.start, decl.init.end);
36923
37131
  g2.add(";");
36924
37132
  g2.add(" let ");
36925
37133
  g2.add(first, firstRange[0], firstRange[1], REACTIVE_DATA);
@@ -36941,16 +37149,18 @@ function emitTrackDeclStatement(g2, source, stmt, indent4, isLast, opts) {
36941
37149
  g2.add(": ");
36942
37150
  g2.add(annotation, annStart, annEnd);
36943
37151
  g2.add(" = (");
36944
- g2.add(initText, decl.init.start, decl.init.end);
37152
+ g2.add(stripCallTypeArgs(initText), decl.init.start, decl.init.end);
36945
37153
  g2.add(" as unknown as ");
36946
37154
  g2.add(annotation, annStart, annEnd);
36947
37155
  g2.add(");");
36948
37156
  } else {
36949
- g2.add("let ");
36950
- g2.add(first, firstRange[0], firstRange[1], REACTIVE_DATA);
36951
- g2.add(": any = ");
37157
+ const cellName = g2.cellCount === 0 ? "__cell" : `__cell${g2.cellCount}`;
37158
+ g2.cellCount++;
37159
+ g2.add(`const ${cellName} = `);
36952
37160
  g2.add(initText, decl.init.start, decl.init.end);
36953
- g2.add(";");
37161
+ g2.add("; let ");
37162
+ g2.add(first, firstRange[0], firstRange[1], REACTIVE_DATA);
37163
+ g2.add(` = ${cellName}.get();`);
36954
37164
  }
36955
37165
  for (let n = 1; n < names.length; n++) {
36956
37166
  g2.add(" let ");
@@ -37212,10 +37422,18 @@ function emitIf(g2, source, stmt, indent4, opts) {
37212
37422
  emitBlock(g2, source, stmt.alternate, indent4, opts);
37213
37423
  }
37214
37424
  }
37425
+ function emitLoopVariable(g2, source, left) {
37426
+ if (left && left.type === "Identifier") {
37427
+ g2.addRaw("const ");
37428
+ g2.add(source.slice(left.start, left.end), left.start, left.end);
37429
+ return;
37430
+ }
37431
+ g2.add(source.slice(left.start, left.end), left.start, left.end);
37432
+ }
37215
37433
  function emitForOf(g2, source, stmt, indent4, opts) {
37216
37434
  g2.add(indent4);
37217
37435
  g2.addRaw("for (");
37218
- g2.add(source.slice(stmt.left.start, stmt.left.end), stmt.left.start, stmt.left.end);
37436
+ emitLoopVariable(g2, source, stmt.left);
37219
37437
  g2.addRaw(" of ");
37220
37438
  g2.add(source.slice(stmt.right.start, stmt.right.end), stmt.right.start, stmt.right.end);
37221
37439
  g2.addRaw(") ");
@@ -37224,7 +37442,7 @@ function emitForOf(g2, source, stmt, indent4, opts) {
37224
37442
  function emitForIn(g2, source, stmt, indent4, opts) {
37225
37443
  g2.add(indent4);
37226
37444
  g2.addRaw("for (");
37227
- g2.add(source.slice(stmt.left.start, stmt.left.end), stmt.left.start, stmt.left.end);
37445
+ emitLoopVariable(g2, source, stmt.left);
37228
37446
  g2.addRaw(" in ");
37229
37447
  g2.add(source.slice(stmt.right.start, stmt.right.end), stmt.right.start, stmt.right.end);
37230
37448
  g2.addRaw(") ");
@@ -37692,6 +37910,9 @@ function compileVskCodegen(source, opts = {}) {
37692
37910
  g2.prepend("declare const Head: (props: { children?: Component }) => Component;\n");
37693
37911
  code = g2.code;
37694
37912
  }
37913
+ if (!isModuleAst(ast)) {
37914
+ code += "\nexport {};\n";
37915
+ }
37695
37916
  return { code, mappings: g2.mappings, styleRegions: g2.styleRegions, errors: [] };
37696
37917
  }
37697
37918
  function vskToTsx(source) {
@@ -37959,7 +38180,8 @@ function createTypecheckHost(projectRoot, appDir, vskFiles) {
37959
38180
  resolveModuleNameLiterals(moduleLiterals, containingFile, redirectedReference, options) {
37960
38181
  return moduleLiterals.map(({ text }) => {
37961
38182
  if (text.endsWith(".vsk")) {
37962
- const abs = normalize(join25(dirname27(containingFile), text));
38183
+ const aliased = text.startsWith(".") ? null : resolveAliasModule(text, dirname27(containingFile));
38184
+ const abs = normalize(aliased ?? join25(dirname27(containingFile), text));
37963
38185
  return {
37964
38186
  resolvedModule: {
37965
38187
  resolvedFileName: abs + ".d.ts",
@@ -38042,6 +38264,8 @@ function typecheckProject(projectRoot, opts = {}) {
38042
38264
  esModuleInterop: true,
38043
38265
  allowSyntheticDefaultImports: true,
38044
38266
  allowImportingTsExtensions: true,
38267
+ // Required so the virtual `.vsk` files are actually pulled into the
38268
+ // program (without it tsc silently excludes them and reports nothing).
38045
38269
  allowNonTsExtensions: true,
38046
38270
  resolveJsonModule: true,
38047
38271
  lib: ["lib.es2022.d.ts", "lib.dom.d.ts", "lib.dom.iterable.d.ts"],
@@ -38089,6 +38313,7 @@ var init_typecheck = __esm({
38089
38313
  "use strict";
38090
38314
  init_parser();
38091
38315
  init_vsk_tsx();
38316
+ init_module_imports();
38092
38317
  SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".vesk", ".git", "dist", "tarballs", ".next"]);
38093
38318
  SKIP_FILES = /* @__PURE__ */ new Set(["__vesk_ambient.d.ts", "__vesk_runtime_override.d.ts"]);
38094
38319
  AMBIENT = `
@@ -38463,6 +38688,16 @@ declare namespace JSX {
38463
38688
  }
38464
38689
  declare type Component = string | number | boolean | null | undefined | Component[];
38465
38690
  declare const Head: (props: { children?: Component }) => Component;
38691
+ // Server-side .vsk code reads process.env (NODE_ENV, feature flags, secrets).
38692
+ // @types/node is not installed for browser targets, so declare the slice the
38693
+ // framework documents rather than failing every project that branches on env.
38694
+ declare const process: {
38695
+ env: Record<string, string | undefined>;
38696
+ argv: string[];
38697
+ platform: string;
38698
+ cwd(): string;
38699
+ exit(code?: number): never;
38700
+ };
38466
38701
  interface Tracked<T> {
38467
38702
  get(): T;
38468
38703
  set(value: T): void;
@@ -38480,7 +38715,26 @@ declare function peek<T>(fn: () => T): T;
38480
38715
  declare function tick(): Promise<void>;
38481
38716
  declare function flushSync(fn: () => void): void;
38482
38717
  declare function on_destroy(fn: () => void): void;
38483
- declare function createContext<T>(defaultValue?: T): { id: symbol; defaultValue: T | undefined };
38718
+ /**
38719
+ * A context created by createContext. The value type flows from the default
38720
+ * (or an explicit type argument) through set() and every get().
38721
+ */
38722
+ interface VeskContext<T> {
38723
+ readonly id: symbol;
38724
+ get(): T;
38725
+ set(value: T): void;
38726
+ }
38727
+ declare function createContext<const T>(defaultValue: T): VeskContext<T>;
38728
+ // Typed request-scoped store (middleware locals, event payloads). Declare the
38729
+ // shape once and every set/get is checked against it.
38730
+ interface VeskLocals<T extends object> {
38731
+ set<K extends keyof T>(key: K, value: T[K]): void;
38732
+ get<K extends keyof T>(key: K): T[K];
38733
+ has<K extends keyof T>(key: K): boolean;
38734
+ delete<K extends keyof T>(key: K): void;
38735
+ all(): T;
38736
+ }
38737
+ declare function createLocals<T extends object = Record<string, unknown>>(): VeskLocals<T>;
38484
38738
  // useFetch returns a THENABLE resource, not the data itself. In async
38485
38739
  // components you must \`await\` it before reading/iterating the payload;
38486
38740
  // passing \`into: <tracked cell>\` writes the payload into the cell and makes
@@ -38599,6 +38853,8 @@ declare module '@vesk/runtime' {
38599
38853
  export function track<T>(initialValue: T): Tracked<T>;
38600
38854
  export function track<T>(fn: () => T): Derived<T>;
38601
38855
  export function derived<T>(fn: () => T): Derived<T>;
38856
+ export function createContext<const T>(defaultValue: T): VeskContext<T>;
38857
+ export function createLocals<T extends object = Record<string, unknown>>(): VeskLocals<T>;
38602
38858
  }
38603
38859
  `;
38604
38860
  }
@@ -38659,10 +38915,14 @@ SECURITY_PRESETS["default"] = { ...SECURITY_PRESETS["strict"] };
38659
38915
  function preset(name, overrides = {}) {
38660
38916
  const presets = {
38661
38917
  production: { ...SECURITY_PRESETS["strict"] },
38918
+ // `strict` is the real name of the production preset; both resolve to it.
38919
+ strict: { ...SECURITY_PRESETS["strict"] },
38662
38920
  development: {
38663
38921
  ...SECURITY_PRESETS["strict"],
38664
38922
  contentSecurityPolicy: false
38665
- }
38923
+ },
38924
+ // autoEscape only, SAMEORIGIN framing, no CSRF/HSTS/CSP.
38925
+ minimal: { ...SECURITY_PRESETS["minimal"] }
38666
38926
  };
38667
38927
  if (!presets[name]) {
38668
38928
  throw VeskError.configError(`Unknown security preset: "${name}".`, Object.keys(presets));
@@ -38680,8 +38940,13 @@ function definePlugin(plugin) {
38680
38940
  }
38681
38941
  function defineConfig(config) {
38682
38942
  if (config.security === false || config.security === "off") {
38683
- config.security = {};
38684
- } else if (typeof config.security === "string") {
38943
+ config.security = { autoEscape: true };
38944
+ if (config.routeDataCache === void 0) config.routeDataCache = 0;
38945
+ const mdOff = normalizeMdConfig(config.md);
38946
+ if (mdOff !== void 0) config.md = mdOff;
38947
+ return config;
38948
+ }
38949
+ if (typeof config.security === "string") {
38685
38950
  const p = SECURITY_PRESETS[config.security];
38686
38951
  if (!p) throw VeskError.configError(`Unknown security preset string: "${config.security}".`, Object.keys(SECURITY_PRESETS));
38687
38952
  config.security = { ...p };
@@ -38800,7 +39065,7 @@ init_parser();
38800
39065
  init_ir_generator();
38801
39066
  init_scan();
38802
39067
  init_tokens();
38803
- var DEFAULT_MAX_BODY_BYTES2 = 1024 * 1024;
39068
+ var DEFAULT_MAX_BODY_BYTES3 = 1024 * 1024;
38804
39069
  function isAlnum2(code) {
38805
39070
  return code >= 48 && code <= 57 || code >= 65 && code <= 90 || code >= 97 && code <= 122;
38806
39071
  }
@@ -39285,6 +39550,8 @@ async function bundleRuntime(appDir, outDir) {
39285
39550
  "",
39286
39551
  "// Re-export runtime classes used by API routes",
39287
39552
  "export const VeskRequest = __veskRuntime.VeskRequest;",
39553
+ "export const PayloadTooLargeError = __veskRuntime.PayloadTooLargeError;",
39554
+ "export const DEFAULT_MAX_BODY_BYTES = __veskRuntime.DEFAULT_MAX_BODY_BYTES;",
39288
39555
  "export const VeskResponse = __veskRuntime.VeskResponse;",
39289
39556
  "",
39290
39557
  "// Server actions",
@@ -39294,6 +39561,39 @@ async function bundleRuntime(appDir, outDir) {
39294
39561
  "export const validateActionInput = __veskRuntime.validateActionInput;",
39295
39562
  "export const issuesToFieldMap = __veskRuntime.issuesToFieldMap;",
39296
39563
  "",
39564
+ "// API-route surface. `@vesk/runtime/server` imports in app/api/**/route.ts",
39565
+ "// are rewritten to this bundle, so anything a route is documented to",
39566
+ "// import must be re-exported here \u2014 otherwise it resolves in dev (which",
39567
+ "// imports the real barrel) and fails only in a production build.",
39568
+ "export const redirect = __veskRuntime.redirect;",
39569
+ "export const permanentRedirect = __veskRuntime.permanentRedirect;",
39570
+ "export const notFound = __veskRuntime.notFound;",
39571
+ "export const NotFoundError = __veskRuntime.NotFoundError;",
39572
+ "export const useParams = __veskRuntime.useParams;",
39573
+ "export const useBody = __veskRuntime.useBody;",
39574
+ "export const useRequest = __veskRuntime.useRequest;",
39575
+ "export const withValidation = __veskRuntime.withValidation;",
39576
+ "export const cors = __veskRuntime.cors;",
39577
+ "export const webhook = __veskRuntime.webhook;",
39578
+ "export const signCookie = __veskRuntime.signCookie;",
39579
+ "export const unsignCookie = __veskRuntime.unsignCookie;",
39580
+ "export const setSignedCookie = __veskRuntime.setSignedCookie;",
39581
+ "export const readSignedCookie = __veskRuntime.readSignedCookie;",
39582
+ "export const HttpError = __veskRuntime.HttpError;",
39583
+ "export const TimeoutError = __veskRuntime.TimeoutError;",
39584
+ "export const mutate = __veskRuntime.mutate;",
39585
+ "export const runHooks = __veskRuntime.runHooks;",
39586
+ "",
39587
+ "// ISR (server-only; revalidate*/clear* are no-ops outside a request).",
39588
+ "export const isr = __veskRuntime.isr;",
39589
+ "export const pageIsr = __veskRuntime.pageIsr;",
39590
+ "export const componentIsr = __veskRuntime.componentIsr;",
39591
+ "export const revalidatePath = __veskRuntime.revalidatePath;",
39592
+ "export const revalidateTag = __veskRuntime.revalidateTag;",
39593
+ "export const revalidateComponent = __veskRuntime.revalidateComponent;",
39594
+ "export const clearIsrCache = __veskRuntime.clearIsrCache;",
39595
+ "export const isrConfigToRevalidate = __veskRuntime.isrConfigToRevalidate;",
39596
+ "",
39297
39597
  "// Server-wide context store (server events)",
39298
39598
  "export const serverLocals = __veskRuntime.serverLocals;",
39299
39599
  "export const getServerContext = __veskRuntime.getServerContext;",
@@ -39963,19 +40263,122 @@ function generateApiFunction(apiNode, _apiDir, outDir, options) {
39963
40263
  }
39964
40264
  const paramsCode = extracts.length > 0 ? ` const params = { ${extracts.join(", ")} };
39965
40265
  ` : " const params = {};\n";
39966
- const handlerMethods = ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"].filter((m) => routeSrc.includes(`async function ${m}(`) || routeSrc.includes(`async function ${m} (`) || routeSrc.includes(`export async function ${m}`)).map((m) => `${m}`).join(", ");
40266
+ const sharedBody = [
40267
+ " const url = new URL(request.url);",
40268
+ " const urlParts = url.pathname.replace(/^\\/api\\/?/, '/').split('/').filter(Boolean);",
40269
+ " const method = request.method || 'GET';",
40270
+ `${paramsCode}`,
40271
+ " Object.defineProperty(request, 'query', {",
40272
+ " get: () => Object.fromEntries(url.searchParams.entries()),",
40273
+ " enumerable: true,",
40274
+ " });",
40275
+ " const __rootLocals = (globalThis.__vesk_request && globalThis.__vesk_request.locals) || {};"
40276
+ ];
40277
+ const dispatchBody = [
40278
+ " try {",
40279
+ " const __mod = {",
40280
+ ...["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"].map(
40281
+ (m) => ` ${m}: typeof ${m} === 'function' ? ${m} : undefined,`
40282
+ ),
40283
+ " };",
40284
+ " const __allowed = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS'].filter((m) => __mod[m]);",
40285
+ " const handler = __mod[method];",
40286
+ " if (!handler) {",
40287
+ " if (method === 'OPTIONS') {",
40288
+ " return new Response(null, { status: 204, headers: { Allow: __allowed.join(', ') } });",
40289
+ " }",
40290
+ " return new Response(JSON.stringify({ error: `Method ${method} not allowed` }), {",
40291
+ " status: 405,",
40292
+ " headers: { 'Content-Type': 'application/json', Allow: __allowed.join(', ') },",
40293
+ " });",
40294
+ " }",
40295
+ " const __routeConfig = (typeof config !== 'undefined' && config) || {};",
40296
+ " // Default CSRF defense for mutating API calls: same-origin check unless",
40297
+ " // the route opts out via `config.csrf = false`.",
40298
+ " if (__routeConfig.csrf !== false && method !== 'GET' && method !== 'HEAD' && method !== 'OPTIONS') {",
40299
+ " try {",
40300
+ " assertSameOrigin({",
40301
+ " method,",
40302
+ " headers: Object.fromEntries([...request.headers.entries()].map(([k, v]) => [k, Array.isArray(v) ? v.join(', ') : v])),",
40303
+ " });",
40304
+ " } catch {",
40305
+ " return new Response(JSON.stringify({ error: 'Cross-origin request blocked' }), {",
40306
+ " status: 403, headers: { 'Content-Type': 'application/json' },",
40307
+ " });",
40308
+ " }",
40309
+ " }",
40310
+ " // Body cap. The handler receives the raw platform Request, so a route that",
40311
+ " // calls `req.json()` directly would bypass a VeskRequest-level guard \u2014",
40312
+ " // enforce it here, before any handler runs, on every target (dev, node,",
40313
+ " // serverless, edge).",
40314
+ " const __declared = Number(request.headers.get('content-length') || '0');",
40315
+ " if (Number.isFinite(__declared) && __declared > DEFAULT_MAX_BODY_BYTES) {",
40316
+ " return new Response(JSON.stringify({ error: `Request body exceeds limit (${DEFAULT_MAX_BODY_BYTES} bytes)` }), {",
40317
+ " status: 413, headers: { 'Content-Type': 'application/json' },",
40318
+ " });",
40319
+ " }",
40320
+ " let __signal = request.signal;",
40321
+ " let __timeout;",
40322
+ " if (__routeConfig.maxDuration) {",
40323
+ " const __controller = new AbortController();",
40324
+ " __timeout = setTimeout(",
40325
+ " () => __controller.abort(new Error(`Request timed out after ${__routeConfig.maxDuration}s`)),",
40326
+ " Number(__routeConfig.maxDuration) * 1000",
40327
+ " );",
40328
+ " __signal = __controller.signal;",
40329
+ " Object.defineProperty(request, 'signal', { value: __signal, writable: false });",
40330
+ " }",
40331
+ " // Route hooks may be a single function or an array of them.",
40332
+ " const __before = typeof beforeRequest === 'undefined' ? [] : (Array.isArray(beforeRequest) ? beforeRequest : [beforeRequest]);",
40333
+ " for (const __hook of __before) {",
40334
+ " const __hookResult = await __hook(request, { params: Promise.resolve(params), locals: ctx.locals });",
40335
+ " if (__hookResult instanceof Response) return __hookResult;",
40336
+ " }",
40337
+ " let __global = await runHooks('beforeRequest', request, { params, locals: ctx.locals });",
40338
+ " if (__global instanceof Response) return __global;",
40339
+ " let __response;",
40340
+ " try {",
40341
+ " __response = await handler(request, { params: Promise.resolve(params) });",
40342
+ " } catch (__e) {",
40343
+ " const __handled = await runHooks('onError', __e, request);",
40344
+ " if (__handled instanceof Response) return __handled;",
40345
+ " throw __e;",
40346
+ " } finally {",
40347
+ " if (__timeout) clearTimeout(__timeout);",
40348
+ " }",
40349
+ " const __after = typeof afterRequest === 'undefined' ? [] : (Array.isArray(afterRequest) ? afterRequest : [afterRequest]);",
40350
+ " for (const __hook of __after) {",
40351
+ " const __hookResult = await __hook(request, __response);",
40352
+ " if (__hookResult instanceof Response) __response = __hookResult;",
40353
+ " }",
40354
+ " __global = await runHooks('afterRequest', request, __response);",
40355
+ " if (__global instanceof Response) __response = __global;",
40356
+ " if (__response && typeof __response.build === 'function') __response.build();",
40357
+ " if (__response instanceof Response) return __response;",
40358
+ " return new Response(JSON.stringify(__response), {",
40359
+ " status: 200,",
40360
+ " headers: { 'Content-Type': 'application/json' },",
40361
+ " });",
40362
+ " } catch (e) {",
40363
+ " const err = /** @type {Error & Record<string, unknown>} */(e);",
40364
+ " if (err.name === 'Redirect') {",
40365
+ " return new Response(null, { status: Number(err.status) || 302, headers: { Location: String(err.url || '/') } });",
40366
+ " }",
40367
+ " if (err.name === 'NotFoundError') {",
40368
+ " return new Response(JSON.stringify({ error: 'Not Found' }), { status: 404, headers: { 'Content-Type': 'application/json' } });",
40369
+ " }",
40370
+ " if (err.name === 'PayloadTooLargeError') {",
40371
+ " return new Response(JSON.stringify({ error: 'Request body too large' }), { status: 413, headers: { 'Content-Type': 'application/json' } });",
40372
+ " }",
40373
+ " return new Response(JSON.stringify({ error: process.env.NODE_ENV === 'production' ? 'Internal Server Error' : err.message }), { status: 500, headers: { 'Content-Type': 'application/json' } });",
40374
+ " } finally {",
40375
+ " globalThis.__vesk_request = prev;",
40376
+ " }"
40377
+ ];
39967
40378
  let handleBody;
39968
40379
  if (middlewareCode) {
39969
40380
  handleBody = [
39970
- " const url = new URL(request.url);",
39971
- " const urlParts = url.pathname.replace(/^\\/api\\/?/, '/').split('/').filter(Boolean);",
39972
- " const method = request.method || 'GET';",
39973
- `${paramsCode}`,
39974
- " Object.defineProperty(request, 'query', {",
39975
- " get: () => Object.fromEntries(url.searchParams.entries()),",
39976
- " enumerable: true,",
39977
- " });",
39978
- " const __rootLocals = (globalThis.__vesk_request && globalThis.__vesk_request.locals) || {};",
40381
+ ...sharedBody,
39979
40382
  " // \u2500\u2500 Middleware context \u2500\u2500",
39980
40383
  " const __ctx = {",
39981
40384
  " request,",
@@ -40002,47 +40405,11 @@ function generateApiFunction(apiNode, _apiDir, outDir, options) {
40002
40405
  " });",
40003
40406
  " const prev = globalThis.__vesk_request;",
40004
40407
  " globalThis.__vesk_request = ctx;",
40005
- " try {",
40006
- ` const handler = { ${handlerMethods} }[method];`,
40007
- " if (!handler) {",
40008
- " return new Response(JSON.stringify({ error: 'Method not allowed' }), {",
40009
- " status: 405,",
40010
- " headers: { 'Content-Type': 'application/json' },",
40011
- " });",
40012
- " }",
40013
- " const response = await handler(request, { params: Promise.resolve(params) });",
40014
- " if (response instanceof Response) {",
40015
- " if (typeof response.build === 'function') response.build();",
40016
- " return response;",
40017
- " }",
40018
- " return new Response(JSON.stringify(response), {",
40019
- " status: 200,",
40020
- " headers: { 'Content-Type': 'application/json' },",
40021
- " });",
40022
- " } catch (e) {",
40023
- " const err = /** @type {Error & Record<string, unknown>} */(e);",
40024
- " if (err.name === 'Redirect') {",
40025
- ' return new Response(null, { status: Number(err.status) || 302, headers: { Location: String(err.url || "") } });',
40026
- " }",
40027
- " if (err.name === 'NotFoundError') {",
40028
- " return new Response(JSON.stringify({ error: 'Not Found' }), { status: 404, headers: { 'Content-Type': 'application/json' } });",
40029
- " }",
40030
- " return new Response(JSON.stringify({ error: err.message }), { status: 500, headers: { 'Content-Type': 'application/json' } });",
40031
- " } finally {",
40032
- " globalThis.__vesk_request = prev;",
40033
- " }"
40408
+ ...dispatchBody
40034
40409
  ].join("\n");
40035
40410
  } else {
40036
40411
  handleBody = [
40037
- " const url = new URL(request.url);",
40038
- " const urlParts = url.pathname.replace(/^\\/api\\/?/, '/').split('/').filter(Boolean);",
40039
- " const method = request.method || 'GET';",
40040
- `${paramsCode}`,
40041
- " Object.defineProperty(request, 'query', {",
40042
- " get: () => Object.fromEntries(url.searchParams.entries()),",
40043
- " enumerable: true,",
40044
- " });",
40045
- " const __rootLocals = (globalThis.__vesk_request && globalThis.__vesk_request.locals) || {};",
40412
+ ...sharedBody,
40046
40413
  " const ctx = {",
40047
40414
  " headers: Object.fromEntries(request.headers.entries()),",
40048
40415
  " url: request.url,",
@@ -40056,35 +40423,7 @@ function generateApiFunction(apiNode, _apiDir, outDir, options) {
40056
40423
  " });",
40057
40424
  " const prev = globalThis.__vesk_request;",
40058
40425
  " globalThis.__vesk_request = ctx;",
40059
- " try {",
40060
- ` const handler = { ${handlerMethods} }[method];`,
40061
- " if (!handler) {",
40062
- " return new Response(JSON.stringify({ error: 'Method not allowed' }), {",
40063
- " status: 405,",
40064
- " headers: { 'Content-Type': 'application/json' },",
40065
- " });",
40066
- " }",
40067
- " const response = await handler(request, { params: Promise.resolve(params) });",
40068
- " if (response instanceof Response) {",
40069
- " if (typeof response.build === 'function') response.build();",
40070
- " return response;",
40071
- " }",
40072
- " return new Response(JSON.stringify(response), {",
40073
- " status: 200,",
40074
- " headers: { 'Content-Type': 'application/json' },",
40075
- " });",
40076
- " } catch (e) {",
40077
- " const err = /** @type {Error & Record<string, unknown>} */(e);",
40078
- " if (err.name === 'Redirect') {",
40079
- ' return new Response(null, { status: Number(err.status) || 302, headers: { Location: String(err.url || "") } });',
40080
- " }",
40081
- " if (err.name === 'NotFoundError') {",
40082
- " return new Response(JSON.stringify({ error: 'Not Found' }), { status: 404, headers: { 'Content-Type': 'application/json' } });",
40083
- " }",
40084
- " return new Response(JSON.stringify({ error: err.message }), { status: 500, headers: { 'Content-Type': 'application/json' } });",
40085
- " } finally {",
40086
- " globalThis.__vesk_request = prev;",
40087
- " }"
40426
+ ...dispatchBody
40088
40427
  ].join("\n");
40089
40428
  }
40090
40429
  const funcCode = [
@@ -40093,7 +40432,7 @@ function generateApiFunction(apiNode, _apiDir, outDir, options) {
40093
40432
  routeSrc.trim(),
40094
40433
  "",
40095
40434
  "// \u2500\u2500 Request handler wrapper \u2500\u2500",
40096
- "import { parseCookies } from '../runtime.js';",
40435
+ "import { parseCookies, assertSameOrigin, runHooks, DEFAULT_MAX_BODY_BYTES } from '../runtime.js';",
40097
40436
  middlewareCode ? "import { parseCookies as __parseCookies } from '../runtime.js';" : "",
40098
40437
  "",
40099
40438
  middlewareCode || "",
@@ -44283,7 +44622,7 @@ function generateComponent2(comp, importedNames = /* @__PURE__ */ new Set(), hyd
44283
44622
  ctx2.push(indent3(`const $root = document.createDocumentFragment();`));
44284
44623
  }
44285
44624
  ctx2.push(indent3(`let __pendingChild = null;`));
44286
- const paramInit = buildParamInit3(comp.paramNames);
44625
+ const paramInit = buildParamInit3(comp.paramNames, comp.propsAlias);
44287
44626
  if (paramInit)
44288
44627
  ctx2.push(indent3(paramInit));
44289
44628
  for (const node of comp.body) {
@@ -44332,7 +44671,9 @@ function generateComponent2(comp, importedNames = /* @__PURE__ */ new Set(), hyd
44332
44671
  ctx2.push(`}`);
44333
44672
  return ctx2.getCode();
44334
44673
  }
44335
- function buildParamInit3(paramNames) {
44674
+ function buildParamInit3(paramNames, propsAlias = null) {
44675
+ if (propsAlias)
44676
+ return propsAlias === "props" ? "" : `const ${propsAlias} = props;`;
44336
44677
  if (paramNames.length === 1 && paramNames[0] === "props")
44337
44678
  return "";
44338
44679
  if (paramNames.length === 0)
@@ -45421,8 +45762,12 @@ async function executeApiRoute(filePath, method, request, params = {}, locals2 =
45421
45762
  signal = controller.signal;
45422
45763
  Object.defineProperty(request, "signal", { value: signal, writable: false });
45423
45764
  }
45424
- const beforeHooks = mod.beforeRequest || [];
45425
- for (const hook of beforeHooks) {
45765
+ const toHookList = (v) => {
45766
+ if (v == null)
45767
+ return [];
45768
+ return Array.isArray(v) ? v : [v];
45769
+ };
45770
+ for (const hook of toHookList(mod.beforeRequest)) {
45426
45771
  const hookResult = await hook(request, { params, locals: locals2 });
45427
45772
  if (hookResult instanceof Response)
45428
45773
  return hookResult;
@@ -45440,8 +45785,7 @@ async function executeApiRoute(filePath, method, request, params = {}, locals2 =
45440
45785
  return errorResult;
45441
45786
  throw e;
45442
45787
  }
45443
- const afterHooks = mod.afterRequest || [];
45444
- for (const hook of afterHooks) {
45788
+ for (const hook of toHookList(mod.afterRequest)) {
45445
45789
  const hookResult = await hook(request, response);
45446
45790
  if (hookResult instanceof Response)
45447
45791
  response = hookResult;
@@ -52837,7 +53181,7 @@ async function handleActionRequest(req, res, ctx2) {
52837
53181
  let tooLarge = false;
52838
53182
  for await (const chunk of req) {
52839
53183
  bodyTotal += chunk.byteLength;
52840
- if (bodyTotal > (ctx2.maxBodyBytes ?? DEFAULT_MAX_BODY_BYTES2)) {
53184
+ if (bodyTotal > (ctx2.maxBodyBytes ?? DEFAULT_MAX_BODY_BYTES3)) {
52841
53185
  tooLarge = true;
52842
53186
  break;
52843
53187
  }
@@ -52845,7 +53189,7 @@ async function handleActionRequest(req, res, ctx2) {
52845
53189
  }
52846
53190
  if (tooLarge) {
52847
53191
  res.writeHead(413, { "Content-Type": "application/json" });
52848
- res.end(JSON.stringify({ ok: false, error: `Request body exceeds limit (${ctx2.maxBodyBytes ?? DEFAULT_MAX_BODY_BYTES2} bytes)` }));
53192
+ res.end(JSON.stringify({ ok: false, error: `Request body exceeds limit (${ctx2.maxBodyBytes ?? DEFAULT_MAX_BODY_BYTES3} bytes)` }));
52849
53193
  return true;
52850
53194
  }
52851
53195
  const bodyBuffer = Buffer.concat(chunks);
@@ -53311,8 +53655,8 @@ async function startDevServer(port, projectDir, config, host) {
53311
53655
  if (!process.env.NODE_ENV) process.env.NODE_ENV = "development";
53312
53656
  const secCfg = config.security || {};
53313
53657
  const maxBodyBytes = (typeof secCfg.maxBodyBytes === "number" ? secCfg.maxBodyBytes : void 0) || DEFAULT_MAX_BODY_BYTES;
53314
- const appDirPath = join24(projectDir, "app");
53315
- const publicDir = join24(projectDir, "public");
53658
+ const appDirPath = resolve38(projectDir, typeof config.appDir === "string" ? config.appDir : "./app");
53659
+ const publicDir = resolve38(projectDir, typeof config.publicDir === "string" ? config.publicDir : "./public");
53316
53660
  installMdReadHook2([publicDir]);
53317
53661
  try {
53318
53662
  ensurePackagesBuilt();
@@ -54693,11 +55037,13 @@ __export(index_server_exports2, {
54693
55037
  ArticleSchema: () => ArticleSchema,
54694
55038
  BreadcrumbListSchema: () => BreadcrumbListSchema,
54695
55039
  Context: () => Context,
55040
+ DEFAULT_MAX_BODY_BYTES: () => DEFAULT_MAX_BODY_BYTES2,
54696
55041
  Experiment: () => Experiment,
54697
55042
  FAQPageSchema: () => FAQPageSchema,
54698
55043
  Field: () => Field,
54699
55044
  For: () => For,
54700
55045
  Form: () => Form,
55046
+ HttpError: () => HttpError,
54701
55047
  Image: () => Image,
54702
55048
  JsonLd: () => JsonLd,
54703
55049
  Link: () => Link,
@@ -54709,6 +55055,7 @@ __export(index_server_exports2, {
54709
55055
  NotFoundError: () => NotFoundError,
54710
55056
  OrganizationSchema: () => OrganizationSchema,
54711
55057
  Outlet: () => Outlet,
55058
+ PayloadTooLargeError: () => PayloadTooLargeError,
54712
55059
  Portal: () => Portal,
54713
55060
  ProductSchema: () => ProductSchema,
54714
55061
  Redirect: () => Redirect,
@@ -54716,6 +55063,7 @@ __export(index_server_exports2, {
54716
55063
  ServerResponse: () => ServerResponse,
54717
55064
  Show: () => Show,
54718
55065
  Switch: () => Switch,
55066
+ TimeoutError: () => TimeoutError,
54719
55067
  VeskRequest: () => VeskRequest,
54720
55068
  VeskResponse: () => VeskResponse,
54721
55069
  VideoSchema: () => VideoSchema,
@@ -54736,6 +55084,7 @@ __export(index_server_exports2, {
54736
55084
  cors: () => cors,
54737
55085
  createContext: () => createContext,
54738
55086
  createFileRouter: () => createFileRouter,
55087
+ createLocals: () => createLocals,
54739
55088
  createResource: () => createResource,
54740
55089
  createRouter: () => createRouter,
54741
55090
  create_component_ctx: () => create_component_ctx,
@@ -54767,6 +55116,7 @@ __export(index_server_exports2, {
54767
55116
  locals: () => locals,
54768
55117
  maxLength: () => maxLength,
54769
55118
  minLength: () => minLength,
55119
+ mutate: () => mutate,
54770
55120
  notFound: () => notFound,
54771
55121
  on_destroy: () => on_destroy,
54772
55122
  pageIsr: () => pageIsr,
@@ -54923,12 +55273,20 @@ async function loadConfig(projectDir) {
54923
55273
  }
54924
55274
  return config;
54925
55275
  }
55276
+ function resolveProjectDirs(projectDir, config) {
55277
+ const cfg = config || {};
55278
+ return {
55279
+ appDir: resolve40(projectDir, typeof cfg.appDir === "string" ? cfg.appDir : "./app"),
55280
+ publicDir: resolve40(projectDir, typeof cfg.publicDir === "string" ? cfg.publicDir : "./public"),
55281
+ outDir: resolve40(projectDir, typeof cfg.outDir === "string" ? cfg.outDir : ".vesk")
55282
+ };
55283
+ }
54926
55284
  if (cmd === "build") {
54927
55285
  const projectDir = process.cwd();
54928
- const appDirPath = join26(projectDir, "app");
54929
- const publicDir = join26(projectDir, "public");
55286
+ const rawConfig = await loadConfig(projectDir) || {};
55287
+ const { appDir: appDirPath, publicDir, outDir } = resolveProjectDirs(projectDir, rawConfig);
54930
55288
  if (!existsSync43(appDirPath)) {
54931
- console.error(`vesk build: no app/ directory found in ${projectDir}`);
55289
+ console.error(`vesk build: no app directory found in ${projectDir} (config appDir: ${rawConfig.appDir ?? "./app"})`);
54932
55290
  process.exit(1);
54933
55291
  }
54934
55292
  const restArgs = process.argv.slice(3);
@@ -54938,13 +55296,14 @@ if (cmd === "build") {
54938
55296
  const platform = platformIdx !== -1 ? restArgs[platformIdx + 1] : void 0;
54939
55297
  const targetIdx = restArgs.indexOf("--target");
54940
55298
  const target = targetIdx !== -1 && restArgs[targetIdx + 1] === "edge" ? "edge" : "node";
54941
- const config = await loadConfig(projectDir);
54942
- const plugins = config?.plugins || [];
54943
- const mdCfg = config?.md;
54944
- const opts = { publicDir, plugins, seo, strictSeo: strict, codeSplit: !restArgs.includes("--skip-split"), target };
55299
+ const config = rawConfig;
55300
+ const plugins = config.plugins || [];
55301
+ const mdCfg = config.md;
55302
+ const opts = { outDir, publicDir, plugins, seo, strictSeo: strict, codeSplit: !restArgs.includes("--skip-split"), target };
54945
55303
  if (mdCfg) opts.md = mdCfg;
54946
55304
  if (config.routeDataCache !== void 0) opts.routeDataCache = config.routeDataCache;
54947
55305
  if (platform) opts.platform = platform;
55306
+ if (config.ssg !== void 0 && config.ssg !== false) opts.ssg = true;
54948
55307
  try {
54949
55308
  await build2(appDirPath, opts);
54950
55309
  const { drainMdHtmlWarnings: drainMdHtmlWarnings3 } = await Promise.resolve().then(() => (init_md2(), md_exports2));
@@ -54997,9 +55356,9 @@ ${code}`;
54997
55356
  }
54998
55357
  if (cmd === "seo") {
54999
55358
  const projectDir = process.cwd();
55000
- const appDirPath = join26(projectDir, "app");
55359
+ const { appDir: appDirPath } = resolveProjectDirs(projectDir, await loadConfig(projectDir) || {});
55001
55360
  if (!existsSync43(appDirPath)) {
55002
- console.error(`vesk seo: no app/ directory found in ${projectDir}`);
55361
+ console.error(`vesk seo: no app directory found in ${projectDir}`);
55003
55362
  process.exit(1);
55004
55363
  }
55005
55364
  const strict = args.includes("--strict");
@@ -55012,9 +55371,9 @@ if (cmd === "seo") {
55012
55371
  }
55013
55372
  if (cmd === "typecheck") {
55014
55373
  const projectDir = process.cwd();
55015
- const appDirPath = join26(projectDir, "app");
55374
+ const { appDir: appDirPath } = resolveProjectDirs(projectDir, await loadConfig(projectDir) || {});
55016
55375
  if (!existsSync43(appDirPath)) {
55017
- console.error(`vesk typecheck: no app/ directory found in ${projectDir}`);
55376
+ console.error(`vesk typecheck: no app directory found in ${projectDir}`);
55018
55377
  process.exit(1);
55019
55378
  }
55020
55379
  const { typecheckProject: typecheckProject2, formatTypecheckErrors: formatTypecheckErrors2, formatTypecheckWarnings: formatTypecheckWarnings2 } = await Promise.resolve().then(() => (init_typecheck(), typecheck_exports));
@@ -55037,16 +55396,20 @@ if (cmd === "typecheck") {
55037
55396
  }
55038
55397
  if (cmd === "start") {
55039
55398
  const projectDir = process.cwd();
55040
- const outDir = join26(projectDir, ".vesk");
55399
+ const { outDir } = resolveProjectDirs(projectDir, await loadConfig(projectDir) || {});
55041
55400
  const port = parsePortArg(args);
55042
55401
  const host = parseHostArg(args);
55402
+ if (!existsSync43(outDir)) {
55403
+ console.error(`vesk start: no build found at ${outDir} \u2014 run "vesk build" first.`);
55404
+ process.exit(1);
55405
+ }
55043
55406
  startProdServer(outDir, { port, host });
55044
55407
  await new Promise(() => {
55045
55408
  });
55046
55409
  }
55047
55410
  if (cmd === "init") {
55048
55411
  const projectDir = process.cwd();
55049
- const appDirPath = join26(projectDir, "app");
55412
+ const { appDir: appDirPath } = resolveProjectDirs(projectDir, await loadConfig(projectDir) || {});
55050
55413
  const target = join26(appDirPath, "global.css");
55051
55414
  if (existsSync43(target)) {
55052
55415
  console.error(`vesk init: ${target} already exists \u2014 skipping`);
@@ -55066,7 +55429,7 @@ if (cmd === "init") {
55066
55429
  }
55067
55430
  if (cmd === "dev") {
55068
55431
  const projectDir = process.cwd();
55069
- const appDirPath = join26(projectDir, "app");
55432
+ const { appDir: appDirPath, publicDir } = resolveProjectDirs(projectDir, await loadConfig(projectDir) || {});
55070
55433
  const port = parsePortArg(args);
55071
55434
  const host = parseHostArg(args);
55072
55435
  if (!existsSync43(appDirPath)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vesk/vesk-cli",
3
- "version": "0.2.37",
3
+ "version": "0.2.39",
4
4
  "type": "module",
5
5
  "description": "Vesk — compiler-first framework for the post-VDOM web. vesk dev, vesk build.",
6
6
  "bin": {
@@ -8,10 +8,10 @@
8
8
  },
9
9
  "main": "./dist/cli.js",
10
10
  "dependencies": {
11
- "@vesk/adapter": "^0.2.37",
12
- "@vesk/compiler": "^0.2.37",
13
- "@vesk/plugin-tailwind": "^0.2.37",
14
- "@vesk/runtime": "^0.2.37",
11
+ "@vesk/adapter": "^0.2.39",
12
+ "@vesk/compiler": "^0.2.39",
13
+ "@vesk/plugin-tailwind": "^0.2.39",
14
+ "@vesk/runtime": "^0.2.39",
15
15
  "esrap": "^2.3.0",
16
16
  "typescript": "^5.8.0",
17
17
  "ws": "^8.18.0",