@stacksjs/stx 0.2.354 → 0.2.356

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 (195) hide show
  1. package/dist/chunk-0c49cy69.js +2 -0
  2. package/dist/chunk-0chkqvs1.js +2 -0
  3. package/dist/chunk-0d6qgbks.js +2 -0
  4. package/dist/{chunk-84bhnp6p.js → chunk-0dj2gne3.js} +2 -2
  5. package/dist/{chunk-57atgbyq.js → chunk-0knxnyq6.js} +1 -1
  6. package/dist/{chunk-m6dstr44.js → chunk-0smr2xjs.js} +2 -2
  7. package/dist/{chunk-e4g8wz6t.js → chunk-0t1yyn1d.js} +1 -1
  8. package/dist/{chunk-xvh1k1a4.js → chunk-19m7hed6.js} +1 -1
  9. package/dist/{chunk-vhta1be7.js → chunk-1nnza3m6.js} +1 -1
  10. package/dist/chunk-284kattb.js +2 -0
  11. package/dist/{chunk-n5657hm3.js → chunk-2gtwha4k.js} +1 -1
  12. package/dist/{chunk-f5veee11.js → chunk-2s3wvns6.js} +1 -1
  13. package/dist/chunk-2x3zy811.js +2 -0
  14. package/dist/chunk-38mpg3kn.js +2 -0
  15. package/dist/chunk-3eya6sbk.js +2 -0
  16. package/dist/chunk-3f782ywp.js +2 -0
  17. package/dist/{chunk-mmnf2pac.js → chunk-3kd0zn9c.js} +2 -2
  18. package/dist/{chunk-avye7h5s.js → chunk-3kyydhzt.js} +1 -1
  19. package/dist/{chunk-msq8xvhp.js → chunk-3ssjb8cx.js} +309 -312
  20. package/dist/{chunk-6r3aavbv.js → chunk-3y8vvkar.js} +2 -2
  21. package/dist/chunk-4ce5ma6a.js +20 -0
  22. package/dist/{chunk-8cc4vdkh.js → chunk-4gtsf1xw.js} +1 -1
  23. package/dist/chunk-4mkqzhfh.js +2 -0
  24. package/dist/{chunk-xj6bcwqa.js → chunk-5270fyk7.js} +1 -1
  25. package/dist/{chunk-a1knndrh.js → chunk-57kxjr4t.js} +10 -10
  26. package/dist/chunk-596cftgk.js +2 -0
  27. package/dist/{chunk-8bxtvsgm.js → chunk-634gaa9d.js} +1 -1
  28. package/dist/{chunk-pwh8pbks.js → chunk-6gsgayq3.js} +1 -1
  29. package/dist/{chunk-a8ghkfh6.js → chunk-6qhatdte.js} +2 -2
  30. package/dist/{chunk-6zrg2mmc.js → chunk-6yfccnr7.js} +2 -2
  31. package/dist/{chunk-z23sc9p4.js → chunk-79fj9zem.js} +2 -2
  32. package/dist/{chunk-2ver2s2y.js → chunk-7hb89n3z.js} +1 -1
  33. package/dist/{chunk-05zfez2g.js → chunk-7jcntsp4.js} +1 -1
  34. package/dist/{chunk-nf7k53ap.js → chunk-7kw3grq5.js} +1 -1
  35. package/dist/chunk-7nf8azjh.js +2 -0
  36. package/dist/{chunk-g5mhck0r.js → chunk-801bg5d8.js} +2 -2
  37. package/dist/chunk-8wgsjh65.js +2 -0
  38. package/dist/{chunk-34f8c48f.js → chunk-8xjasa91.js} +1 -1
  39. package/dist/{chunk-w5j9tbsk.js → chunk-94dz7hrj.js} +1 -1
  40. package/dist/{chunk-6h3r8v55.js → chunk-9qdn9y72.js} +1 -1
  41. package/dist/chunk-a6x0czvr.js +2 -0
  42. package/dist/{chunk-72x62kch.js → chunk-aa8015mk.js} +1 -1
  43. package/dist/{chunk-64n10kpa.js → chunk-aacfyyya.js} +1 -1
  44. package/dist/chunk-abtxpqna.js +2 -0
  45. package/dist/{chunk-c30qnb6z.js → chunk-apbr1kb1.js} +2 -2
  46. package/dist/{chunk-nm60wh6r.js → chunk-awyyskad.js} +1 -1
  47. package/dist/{chunk-ppzz953j.js → chunk-b8ywrx2w.js} +1 -1
  48. package/dist/{chunk-18018jkn.js → chunk-bcpjhcpn.js} +1 -1
  49. package/dist/{chunk-kw9cgyfc.js → chunk-c17ybabd.js} +1 -1
  50. package/dist/chunk-c4rpdtvw.js +2 -0
  51. package/dist/{chunk-zk6ghjnj.js → chunk-c79nfdyj.js} +1 -1
  52. package/dist/chunk-cgs5xpsn.js +2 -0
  53. package/dist/{chunk-avwywzsj.js → chunk-czj6nq0b.js} +2 -2
  54. package/dist/{chunk-9kmhwbwy.js → chunk-czypgdc1.js} +1 -1
  55. package/dist/{chunk-epqd3eah.js → chunk-degz74nv.js} +2 -2
  56. package/dist/chunk-dpyc7pht.js +2 -0
  57. package/dist/{chunk-hay8nwv3.js → chunk-dtdwjpvf.js} +2 -2
  58. package/dist/{chunk-dsztk2kp.js → chunk-e48050x3.js} +1 -1
  59. package/dist/{chunk-3nx7z086.js → chunk-ec8jsqgm.js} +3 -3
  60. package/dist/{chunk-86c461ca.js → chunk-eg8k598q.js} +1 -1
  61. package/dist/{chunk-bgrkxzr7.js → chunk-ej8mv2k6.js} +1 -1
  62. package/dist/{chunk-y2f41kaq.js → chunk-ejwfnttb.js} +1 -1
  63. package/dist/{chunk-ejqpq0wc.js → chunk-emjmshj8.js} +1 -1
  64. package/dist/{chunk-59906baj.js → chunk-ep034wt0.js} +1 -1
  65. package/dist/{chunk-njx93b4z.js → chunk-ez003q74.js} +1 -1
  66. package/dist/{chunk-ws0282jr.js → chunk-f11z7q5g.js} +1 -1
  67. package/dist/chunk-f3eb1f6z.js +2 -0
  68. package/dist/{chunk-1z1efvtw.js → chunk-fqq5k9ve.js} +1 -1
  69. package/dist/{chunk-c0n802j1.js → chunk-frp9fc7j.js} +1 -1
  70. package/dist/chunk-fzq2n865.js +2 -0
  71. package/dist/{chunk-g6czgcg4.js → chunk-g1vxsvqv.js} +1 -1
  72. package/dist/chunk-g3cmkyk2.js +2 -0
  73. package/dist/{chunk-7r9vgzg4.js → chunk-g7bpkpd4.js} +1 -1
  74. package/dist/{chunk-at622hg0.js → chunk-gn2qc03b.js} +1 -1
  75. package/dist/chunk-gxgrzeeh.js +2 -0
  76. package/dist/{chunk-a9w1ds4w.js → chunk-h99qxgbr.js} +1 -1
  77. package/dist/{chunk-dgv43ccd.js → chunk-hcf8399w.js} +1 -1
  78. package/dist/{chunk-hbkjzx1h.js → chunk-hh0aaxxy.js} +1 -1
  79. package/dist/{chunk-tc4pj651.js → chunk-hkrzv980.js} +2 -2
  80. package/dist/{chunk-7qp3zphs.js → chunk-hy79zwyy.js} +2 -2
  81. package/dist/chunk-j6kec9kp.js +2 -0
  82. package/dist/{chunk-e631svvs.js → chunk-j8wycqve.js} +1 -1
  83. package/dist/chunk-jmdxn2mq.js +2 -0
  84. package/dist/chunk-jwmact5f.js +2 -0
  85. package/dist/{chunk-g7msqva8.js → chunk-jy451jg5.js} +3 -3
  86. package/dist/{chunk-a959m68j.js → chunk-k33778kc.js} +1 -1
  87. package/dist/{chunk-wvc7t11n.js → chunk-kaet6r9w.js} +1 -1
  88. package/dist/chunk-kdmkk0rb.js +2 -0
  89. package/dist/{chunk-9e20mgwc.js → chunk-kjd9hbg4.js} +3 -3
  90. package/dist/{chunk-8m0g0h4x.js → chunk-krt45cqy.js} +2 -2
  91. package/dist/{chunk-2wb3pgp4.js → chunk-kyyt9xhs.js} +1 -1
  92. package/dist/{chunk-0k1mtwby.js → chunk-mdbj7sq8.js} +1 -1
  93. package/dist/{chunk-d86q27j8.js → chunk-mere396d.js} +1 -1
  94. package/dist/{chunk-1c9453w0.js → chunk-ms25gb16.js} +1 -1
  95. package/dist/{chunk-ym3r8ktq.js → chunk-n0d7nwhd.js} +1 -1
  96. package/dist/{chunk-7h9fajgs.js → chunk-n1y0hjk1.js} +2 -2
  97. package/dist/{chunk-bk1zb1xp.js → chunk-n5tpqzcb.js} +1 -1
  98. package/dist/{chunk-b5hank5e.js → chunk-nybn003d.js} +2 -2
  99. package/dist/chunk-p1426yar.js +2 -0
  100. package/dist/chunk-p78b80dp.js +2 -0
  101. package/dist/chunk-pr3hz3ec.js +2 -0
  102. package/dist/{chunk-gtqbvt3a.js → chunk-q4kgqqed.js} +2 -2
  103. package/dist/{chunk-san1eq0r.js → chunk-qbz6yn8c.js} +2 -2
  104. package/dist/{chunk-fvsefztj.js → chunk-qjvny3wf.js} +1 -1
  105. package/dist/{chunk-v0e259x2.js → chunk-qnptmtqh.js} +1 -1
  106. package/dist/{chunk-msywnv5n.js → chunk-qrvey4wk.js} +2 -2
  107. package/dist/{chunk-7we3xfde.js → chunk-ra1s4s6b.js} +1 -1
  108. package/dist/{chunk-62nz9v8t.js → chunk-rbfm5sjt.js} +2 -2
  109. package/dist/{chunk-99b5tfxc.js → chunk-rcbt11p8.js} +1 -1
  110. package/dist/{chunk-9nrgjtah.js → chunk-rtfwn4cm.js} +1 -1
  111. package/dist/chunk-rydb5n5b.js +2 -0
  112. package/dist/{chunk-6k5a03pw.js → chunk-s5dwatkj.js} +2 -2
  113. package/dist/chunk-sa6cgj9h.js +2 -0
  114. package/dist/chunk-skdfq4hq.js +2 -0
  115. package/dist/{chunk-ngjkzaj7.js → chunk-t23kccpz.js} +3 -3
  116. package/dist/{chunk-89mqnh67.js → chunk-tf16jtj4.js} +1 -1
  117. package/dist/{chunk-swvm1wgn.js → chunk-trva71am.js} +2 -2
  118. package/dist/{chunk-rwwfzkz4.js → chunk-tv04k7gq.js} +1 -1
  119. package/dist/{chunk-5wbdbcvf.js → chunk-v1qm7p7t.js} +2 -2
  120. package/dist/chunk-w1a8dwgx.js +2 -0
  121. package/dist/{chunk-y6xzr30x.js → chunk-wkyt4j9e.js} +1 -1
  122. package/dist/{chunk-p3p4d6g1.js → chunk-wz4fnsat.js} +2 -2
  123. package/dist/chunk-x131a0jq.js +2 -0
  124. package/dist/{chunk-f5cw54q3.js → chunk-x21hdhjw.js} +1 -1
  125. package/dist/chunk-x5b8az2h.js +2 -0
  126. package/dist/chunk-xk782v5z.js +2 -0
  127. package/dist/{chunk-qsbjtp60.js → chunk-y4nzmkea.js} +1 -1
  128. package/dist/{chunk-sqvqwd0p.js → chunk-y4zddt5r.js} +1 -1
  129. package/dist/{chunk-vgfd9582.js → chunk-ya5m5vbn.js} +1 -1
  130. package/dist/{chunk-1vb2rj09.js → chunk-yg0qm73a.js} +2 -2
  131. package/dist/{chunk-8y1ve8tc.js → chunk-ykevda74.js} +2 -2
  132. package/dist/chunk-ynj1y0qm.js +2 -0
  133. package/dist/{chunk-v6e35j4g.js → chunk-z50ex86m.js} +1 -1
  134. package/dist/chunk-z8gccz1q.js +2 -0
  135. package/dist/{chunk-wj21f1e0.js → chunk-zxfejz72.js} +1 -1
  136. package/dist/chunk-zxnf0c9x.js +2 -0
  137. package/dist/cli.js +14 -14
  138. package/dist/craft-entry.js +1 -1
  139. package/dist/craft.js +1 -1
  140. package/dist/database.js +1 -1
  141. package/dist/engine-bindings.d.ts +35 -0
  142. package/dist/engine-bindings.js +55 -0
  143. package/dist/pwa.js +2 -2
  144. package/dist/ssg.js +2 -2
  145. package/dist/stx-module-imports.d.ts +24 -0
  146. package/dist/stx-module-imports.js +57 -0
  147. package/dist/stx-module.d.ts +154 -0
  148. package/dist/stx-virtual-ts.d.ts +74 -1
  149. package/dist/stx-virtual-ts.js +81 -21
  150. package/dist/typecheck.d.ts +13 -0
  151. package/dist/typecheck.js +31 -25
  152. package/dist/variable-extractor.d.ts +2 -22
  153. package/dist/variable-extractor.js +3 -55
  154. package/dist/visual-testing.js +1 -1
  155. package/package.json +7 -6
  156. package/stx-module.d.ts +154 -0
  157. package/dist/chunk-04x3vny9.js +0 -19
  158. package/dist/chunk-1apnswz7.js +0 -2
  159. package/dist/chunk-1qvf8ase.js +0 -2
  160. package/dist/chunk-1vt1h19d.js +0 -2
  161. package/dist/chunk-32x78na8.js +0 -2
  162. package/dist/chunk-3z4d7kmp.js +0 -2
  163. package/dist/chunk-4b2vsawv.js +0 -2
  164. package/dist/chunk-616fn77w.js +0 -2
  165. package/dist/chunk-70zk8tj2.js +0 -2
  166. package/dist/chunk-86q2h34b.js +0 -2
  167. package/dist/chunk-88e14er7.js +0 -2
  168. package/dist/chunk-8yydz0hk.js +0 -2
  169. package/dist/chunk-a7yxvxke.js +0 -2
  170. package/dist/chunk-aanwshk6.js +0 -2
  171. package/dist/chunk-ahfj7dc7.js +0 -2
  172. package/dist/chunk-bed5f4bp.js +0 -2
  173. package/dist/chunk-dxkjvajv.js +0 -2
  174. package/dist/chunk-e7np0h2q.js +0 -2
  175. package/dist/chunk-erb294sv.js +0 -2
  176. package/dist/chunk-evfr2zgg.js +0 -2
  177. package/dist/chunk-f85f75jh.js +0 -2
  178. package/dist/chunk-gsc2vds7.js +0 -2
  179. package/dist/chunk-h5jt4mxx.js +0 -2
  180. package/dist/chunk-hke0k33t.js +0 -2
  181. package/dist/chunk-hysq9e9t.js +0 -2
  182. package/dist/chunk-kq30wqp6.js +0 -2
  183. package/dist/chunk-m2j5skez.js +0 -2
  184. package/dist/chunk-ntzzhw3w.js +0 -2
  185. package/dist/chunk-px0bs815.js +0 -2
  186. package/dist/chunk-qw92p9za.js +0 -2
  187. package/dist/chunk-rjwnw5hj.js +0 -2
  188. package/dist/chunk-tmr3xq5d.js +0 -2
  189. package/dist/chunk-tpk6wm38.js +0 -2
  190. package/dist/chunk-vw4b7g24.js +0 -2
  191. package/dist/chunk-x054qzma.js +0 -2
  192. package/dist/chunk-x2n1x19t.js +0 -2
  193. package/dist/chunk-zks9ft7n.js +0 -2
  194. package/dist/chunk-zkxnarrh.js +0 -2
  195. package/dist/chunk-zp14ksx1.js +0 -2
@@ -49,6 +49,15 @@ export interface ScriptBlock {
49
49
  code: string;
50
50
  /** 1-based line in the source file where the body's first character sits. */
51
51
  startLine: number;
52
+ /**
53
+ * 1-based column of the body's first character. Greater than 1 when the
54
+ * code starts on the line the opening tag is on (`<script client>const a`),
55
+ * which a buffer has to pad for or every diagnostic on that line lands
56
+ * left of the code it is about.
57
+ */
58
+ startColumn?: number;
59
+ /** Offset of the body's first character in the source. */
60
+ offset?: number;
52
61
  /** The opening tag's attributes, trimmed. */
53
62
  attrs: string;
54
63
  }
@@ -99,6 +108,8 @@ export interface MappedLine {
99
108
  export interface VirtualFile {
100
109
  text: string;
101
110
  componentDependencies?: Map<string, string>;
111
+ /** Whether the `reference` option's directive was written (line 1 was free). */
112
+ referenced?: boolean;
102
113
  /**
103
114
  * Virtual 1-based line → where it came from.
104
115
  *
@@ -239,7 +250,17 @@ export declare function runtimeGlobalDeclarations(): string;
239
250
  * checker report a genuine typo instead of suppressing every unresolved name.
240
251
  */
241
252
  export declare const STX_SERVER_CONTEXT: readonly ['params', 'query', 'cookies', 'host', 'ip', 'setResponseStatus', 'setResponseHeader', 'notFound', 'request', 'url', 'definePageMeta', 'defineClientPayload', 'defineStore', 'props', '$props'];
242
- export declare function serverContextDeclarations(): string;
253
+ export declare function serverContextDeclarations(typed?: ReadonlySet<string>): string;
254
+ /**
255
+ * Names a global declaration file declares as values: `declare function`,
256
+ * `declare const` / `let` / `var`, at the start of a line.
257
+ *
258
+ * Used to keep the `any` fallbacks below from colliding with a TYPED global of
259
+ * the same name in `stx.d.ts` — `definePageMeta` and `defineStore` are both
260
+ * server context and typed runtime globals, and `declare var x: any` beside
261
+ * `declare function x()` is a conflict that leaves the name typed by neither.
262
+ */
263
+ export declare function declaredGlobalNames(declarations: string): Set<string>;
243
264
  /**
244
265
  * The names a `defineClientPayload({ … })` call publishes, or `null` when the
245
266
  * page does not declare one.
@@ -421,6 +442,19 @@ export declare function expressionStatement(expression: TemplateExpression): {
421
442
  * change a line count, so every diagnostic still lands on the author's line.
422
443
  */
423
444
  export declare function absolutizeRelativeSpecifiers(code: string, originDir: string): string;
445
+ /**
446
+ * Spaces that put a block's first line at the column it occupies in the file.
447
+ *
448
+ * A body that starts on the opening tag's line (`<script client>const a = 1`)
449
+ * was written from column 1, so every diagnostic on that line pointed left of
450
+ * the code it was about by the width of the tag.
451
+ */
452
+ export declare function firstLinePadding(block: ScriptBlock): string;
453
+ /**
454
+ * Local names a block's `import` declarations bind, type-only ones included:
455
+ * `import a, { b as c, type D } from 'x'` and `import * as ns from 'x'`.
456
+ */
457
+ export declare function collectImportedNames(code: string): string[];
424
458
  export interface BuildVirtualOptions {
425
459
  /** Enables component call-site checks using the renderer's file lookup. */
426
460
  filePath?: string;
@@ -438,6 +472,28 @@ export interface BuildVirtualOptions {
438
472
  originDir?: string;
439
473
  /** Append ambient declarations at all. Default true. */
440
474
  globals?: boolean;
475
+ /**
476
+ * Where the server context, the `@stacksjs/browser` auto-imports and the
477
+ * template globals are declared. Default `'module'`.
478
+ *
479
+ * `'global'` wraps them in `declare global { … }`. Declared in the module
480
+ * they share a scope with the author's own bindings, so a block's
481
+ * `const { query } = defineProps()` is a redeclaration of the context's
482
+ * `query`; declared globally, the block's binding shadows it instead, as it
483
+ * does in `stx typecheck`, whose context is a global file. The editor, which
484
+ * has one buffer per file, needs the global form.
485
+ */
486
+ contextScope?: 'module' | 'global';
487
+ /**
488
+ * A declaration file to pull into the program with a triple-slash
489
+ * reference, written on the buffer's first line.
490
+ *
491
+ * That line is always blank when the file opens with markup or with a
492
+ * `<script>` tag on a line of its own, which is how every real file starts,
493
+ * so nothing moves. When code sits on line 1 the reference is left out and
494
+ * {@link VirtualFile.referenced} says so, so the caller can fall back.
495
+ */
496
+ reference?: string;
441
497
  /**
442
498
  * Declare the stx runtime globals as `any`. Default true.
443
499
  *
@@ -479,6 +535,23 @@ export interface ResolvedPosition {
479
535
  */
480
536
  inGuard?: boolean;
481
537
  }
538
+ /**
539
+ * Whether a diagnostic on a template-expression line is an artefact of how the
540
+ * expression was wrapped rather than a fault in it. Both checkers drop these,
541
+ * so the editor and `stx typecheck` agree:
542
+ *
543
+ * - A hit inside the `if (…) {` wrapper that markup guards put around an
544
+ * expression is about the enclosing CONDITION, which is checked on a line
545
+ * of its own. This copy would report the same fault again against innocent
546
+ * markup (stacksjs/stx#2013).
547
+ * - TS2774 ("this condition will always return true since this function is
548
+ * always defined") is wrong about a template, and only about a template.
549
+ * {@link UNWRAP_HELPER} types a signal as the intersection of the signal and
550
+ * its value, which stays callable, and the runtime unwraps a name the
551
+ * expression only reads — so `x-class="ready ? '' : 'hidden'"` does not
552
+ * always return true. In a script block it is a true positive and is kept.
553
+ */
554
+ export declare function isTemplateArtefact(position: ResolvedPosition, code: number): boolean;
482
555
  /**
483
556
  * Translate a position in a virtual buffer back to the `.stx` file.
484
557
  *
@@ -20,7 +20,7 @@ export function extractScriptBlocks(source) {
20
20
  if (typeMatch && !/^(?:module|text\/(?:javascript|typescript))$/i.test(typeMatch[1]))
21
21
  continue;
22
22
  const kind = /\bserver\b/.test(attrs) ? "server" : /\bclient\b/.test(attrs) ? "client" : "plain", startLine = lineAt(source, bodyStart);
23
- blocks.push({ kind, code: body, startLine, attrs });
23
+ blocks.push({ kind, code: body, startLine, startColumn: columnAt(source, bodyStart), offset: bodyStart, attrs });
24
24
  }
25
25
  return blocks;
26
26
  }
@@ -340,10 +340,13 @@ export const STX_SERVER_CONTEXT = [
340
340
  "props",
341
341
  "$props"
342
342
  ];
343
- export function serverContextDeclarations() {
344
- return STX_SERVER_CONTEXT.map((name) => `declare var ${name}: any`).join(`
343
+ export function serverContextDeclarations(typed = new Set) {
344
+ return STX_SERVER_CONTEXT.filter((name) => !typed.has(name)).map((name) => `declare var ${name}: any`).join(`
345
345
  `);
346
346
  }
347
+ export function declaredGlobalNames(declarations) {
348
+ return new Set([...declarations.matchAll(/^declare (?:function|const|let|var)\s+([A-Za-z_$][\w$]*)/gm)].map((match) => match[1]));
349
+ }
347
350
  export function extractClientPayloadNames(serverCode) {
348
351
  const call = /\bdefineClientPayload\s*\(\s*\{([\s\S]*?)\}\s*\)/g;
349
352
  let match, found = !1;
@@ -608,6 +611,27 @@ export function absolutizeRelativeSpecifiers(code, originDir) {
608
611
  return `${prefix}${quote}${absolute}${quote}`;
609
612
  });
610
613
  }
614
+ export function firstLinePadding(block) {
615
+ return " ".repeat(Math.max(0, (block.startColumn ?? 1) - 1));
616
+ }
617
+ export function collectImportedNames(code) {
618
+ const names = new Set, stripped = stripCommentsAndLiterals(code);
619
+ for (const match of stripped.matchAll(/\bimport\s+(?:type\s+)?([\w$*{}\s,]+?)\s+from\b/g)) {
620
+ const clause = match[1], named = /\{([^}]*)\}/.exec(clause);
621
+ for (const part of named?.[1].split(",") ?? []) {
622
+ const local = part.trim().replace(/^type\s+/, "").split(/\s+as\s+/).pop()?.trim();
623
+ if (local && /^[A-Z_$][\w$]*$/i.test(local))
624
+ names.add(local);
625
+ }
626
+ const outside = clause.replace(/\{[^}]*\}/, ""), namespace = /\*\s*as\s+([A-Z_$][\w$]*)/i.exec(outside);
627
+ if (namespace)
628
+ names.add(namespace[1]);
629
+ const defaultName = /^\s*([A-Z_$][\w$]*)/i.exec(outside);
630
+ if (defaultName && defaultName[1] !== "type")
631
+ names.add(defaultName[1]);
632
+ }
633
+ return [...names].sort();
634
+ }
611
635
  export function buildVirtualTypeScript(source, options = {}) {
612
636
  const blocks = extractScriptBlocks(source), sourceLines = source.split(`
613
637
  `), lines = Array.from({ length: sourceLines.length }, () => ""), lineMap = new Map, components = options.filePath && options.templateExpressions !== !1 ? componentContractChecks(source, maskNonTemplateRegions(source), {
@@ -617,14 +641,24 @@ export function buildVirtualTypeScript(source, options = {}) {
617
641
  readComponent: options.readComponent,
618
642
  scriptCode: (text, file) => extractScriptBlocks(text).map((block) => absolutizeRelativeSpecifiers(block.code, path.dirname(file))).join(`
619
643
  `)
620
- }) : void 0;
621
- for (const block of blocks)
622
- absolutizeRelativeSpecifiers(block.code, options.originDir ?? "").split(`
623
- `).forEach((text, i) => {
644
+ }) : void 0, occupied = new Set, blockEnds = [];
645
+ for (const block of blocks) {
646
+ const code = blankScriptDirectives(substituteInterpolationsInPlace(block.code)), bodyLines = absolutizeRelativeSpecifiers(code, options.originDir ?? "").split(`
647
+ `);
648
+ bodyLines.forEach((text, i) => {
624
649
  const index = block.startLine - 1 + i;
625
- if (index < lines.length)
626
- lines[index] = text;
650
+ if (index < lines.length) {
651
+ lines[index] = i === 0 ? firstLinePadding(block) + text : text;
652
+ occupied.add(index);
653
+ }
627
654
  });
655
+ blockEnds.push(block.startLine - 1 + bodyLines.length - 1);
656
+ }
657
+ for (const end of blockEnds) {
658
+ const at = lines[end]?.trim() === "" ? end : end + 1;
659
+ if (at < lines.length && lines[at].trim() === "" && (at === end || !occupied.has(at)))
660
+ lines[at] = ";";
661
+ }
628
662
  const append = (text, origin) => {
629
663
  for (const [index, line] of text.split(`
630
664
  `).entries()) {
@@ -635,17 +669,33 @@ export function buildVirtualTypeScript(source, options = {}) {
635
669
  };
636
670
  if (options.globals !== !1) {
637
671
  append("");
638
- if (options.runtimeGlobals !== !1)
639
- for (const decl of runtimeGlobalDeclarations().split(`
640
- `))
641
- append(decl);
642
- for (const decl of serverContextDeclarations().split(`
643
- `))
644
- append(decl);
645
- for (const name of BROWSER_CORE_IMPORTS)
646
- append(`declare var ${name}: any`);
647
- for (const name of STX_TEMPLATE_GLOBALS)
648
- append(`declare var ${name}: any`);
672
+ const bound = new Set([
673
+ ...blocks.flatMap((block) => collectBlockDeclarations(block.code)),
674
+ ...blocks.flatMap((block) => collectImportedNames(block.code))
675
+ ]), declareAny = (name) => {
676
+ if (!bound.has(name))
677
+ append(`declare var ${name}: any`);
678
+ };
679
+ if (options.runtimeGlobals !== !1) {
680
+ if (!bound.has("window"))
681
+ append("declare var window: any");
682
+ for (const name of STX_RUNTIME_GLOBALS)
683
+ declareAny(name);
684
+ }
685
+ const contextNames = [...new Set([...STX_SERVER_CONTEXT, ...BROWSER_CORE_IMPORTS, ...STX_TEMPLATE_GLOBALS])].filter((name) => options.runtimeGlobals !== !1 || !STX_RUNTIME_GLOBALS.includes(name));
686
+ if (options.contextScope === "global") {
687
+ append("declare global {");
688
+ for (const name of contextNames)
689
+ append(` var ${name}: any`);
690
+ append("}");
691
+ } else {
692
+ for (const name of STX_SERVER_CONTEXT)
693
+ declareAny(name);
694
+ for (const name of BROWSER_CORE_IMPORTS)
695
+ declareAny(name);
696
+ for (const name of STX_TEMPLATE_GLOBALS)
697
+ declareAny(name);
698
+ }
649
699
  append(`declare var ${INTERPOLATION_PLACEHOLDER}: any`);
650
700
  const declared = new Set(blocks.flatMap((block) => collectBlockDeclarations(block.code)));
651
701
  append(`type ${ELEMENT_HELPER}<T> = T extends readonly (infer E)[] ? E : T extends Iterable<infer E2> ? E2 : any`);
@@ -695,9 +745,14 @@ export function buildVirtualTypeScript(source, options = {}) {
695
745
  append("});");
696
746
  }
697
747
  }
748
+ let referenced = !1;
749
+ if (options.reference && lines[0] !== void 0 && lines[0].trim() === "") {
750
+ lines[0] = `/// <reference path=${JSON.stringify(options.reference.replace(/\\/g, "/"))} />`;
751
+ referenced = !0;
752
+ }
698
753
  append("export {}");
699
754
  return { text: lines.join(`
700
- `), lineMap, sourceLineCount: sourceLines.length, componentDependencies: components?.dependencies };
755
+ `), lineMap, sourceLineCount: sourceLines.length, componentDependencies: components?.dependencies, referenced };
701
756
  }
702
757
  export function lineStarts(text) {
703
758
  const starts = [0];
@@ -721,6 +776,11 @@ export function positionToOffset(starts, line, column) {
721
776
  const index = Math.min(Math.max(line, 1), starts.length) - 1, next = index + 1 < starts.length ? starts[index + 1] - 1 : Number.MAX_SAFE_INTEGER;
722
777
  return Math.min(starts[index] + Math.max(column, 1) - 1, next);
723
778
  }
779
+ export function isTemplateArtefact(position, code) {
780
+ if (!position.expression)
781
+ return !1;
782
+ return position.inGuard === !0 || code === 2774;
783
+ }
724
784
  export function resolvePosition(virtual, line, column) {
725
785
  const mapped = virtual.lineMap.get(line);
726
786
  if (!mapped)
@@ -34,6 +34,19 @@ export declare function sourcePathFor(virtualPath: string): string;
34
34
  * than hand tsc a path to nothing.
35
35
  */
36
36
  export declare function findRuntimeTypeDeclarations(): string | null;
37
+ /**
38
+ * Locate the package's own `stx-module.d.ts`, which declares the virtual `stx`
39
+ * module a `.stx` block imports from.
40
+ *
41
+ * It used to be generated here as `declare module 'stx' { export * from
42
+ * '@stacksjs/stx' }`, which typed `from 'stx'` as the whole package. That is
43
+ * not what the specifier binds: a client block gets only what is destructured
44
+ * off `window.stx`, a server block only what the engine passes in, and every
45
+ * other name fails at runtime (stacksjs/stx#2028). The declaration file says
46
+ * exactly that, and the editor plugin loads the same one, so the two checkers
47
+ * cannot disagree about it.
48
+ */
49
+ export declare function findStxModuleDeclarations(): string | null;
37
50
  export declare function typecheckStxFiles(files: string[], options?: TypecheckOptions): Promise<TypecheckResult>;
38
51
  /** Render diagnostics the way a CLI should: file:line:col, then the message. */
39
52
  export declare function formatTypecheckDiagnostics(diagnostics: TypecheckDiagnostic[]): string;
package/dist/typecheck.js CHANGED
@@ -14,35 +14,24 @@ import {
14
14
  scrapedBridgeNames,
15
15
  serverContextDeclarations,
16
16
  blankScriptDirectives,
17
+ declaredGlobalNames,
18
+ firstLinePadding,
19
+ isTemplateArtefact,
17
20
  substituteInterpolationsInPlace
18
21
  } from "./stx-virtual-ts.js";
22
+ import { stxImportDiagnostics } from "./stx-module-imports.js";
19
23
  export {
20
24
  extractScriptBlocks,
21
25
  extractTemplateExpressions
22
26
  } from "./stx-virtual-ts.js";
23
27
  export function buildVirtualSource(block, serverCode = "", originDir = "") {
24
28
  const leadingNewlines = block.startLine > 1 ? `
25
- `.repeat(block.startLine - 1) : "", code = blankScriptDirectives(substituteInterpolationsInPlace(block.code)), payload = block.kind === "client" || block.kind === "plain" ? clientPayloadDeclarations(serverCode, block.code) : "";
29
+ `.repeat(block.startLine - 1) : "", code = firstLinePadding(block) + blankScriptDirectives(substituteInterpolationsInPlace(block.code)), payload = block.kind === "client" || block.kind === "plain" ? clientPayloadDeclarations(serverCode, block.code) : "";
26
30
  return absolutizeRelativeSpecifiers(`${leadingNewlines + code}
27
31
  ${payload}
28
32
  export {}
29
33
  `, originDir);
30
34
  }
31
- function virtualStxModuleDeclaration(files) {
32
- const anchor = files.length > 0 ? path.dirname(path.resolve(files[0])) : process.cwd();
33
- try {
34
- Bun.resolveSync("@stacksjs/stx", anchor);
35
- } catch {
36
- return "";
37
- }
38
- return [
39
- "// Generated \u2014 `stx` is the virtual specifier client-script.ts strips.",
40
- "declare module 'stx' {",
41
- " export * from '@stacksjs/stx'",
42
- "}"
43
- ].join(`
44
- `);
45
- }
46
35
  const WINDOW_OWNED_NAMES = new Set([
47
36
  "name",
48
37
  "status",
@@ -94,9 +83,15 @@ export function sourcePathFor(virtualPath) {
94
83
  return virtualPath.replace(/\.__stx_(?:server|client|plain|template)\d+\.ts$/, "");
95
84
  }
96
85
  export function findRuntimeTypeDeclarations() {
86
+ return findPackageDeclarations("stx.d.ts");
87
+ }
88
+ export function findStxModuleDeclarations() {
89
+ return findPackageDeclarations("stx-module.d.ts");
90
+ }
91
+ function findPackageDeclarations(name) {
97
92
  for (const candidate of [
98
- path.resolve(import.meta.dir, "..", "stx.d.ts"),
99
- path.resolve(import.meta.dir, "..", "..", "stx.d.ts")
93
+ path.resolve(import.meta.dir, "..", name),
94
+ path.resolve(import.meta.dir, "..", "..", name)
100
95
  ])
101
96
  if (existsSync(candidate))
102
97
  return candidate;
@@ -190,7 +185,7 @@ function readProjectPathAliases(startDir) {
190
185
  return { paths: absolute };
191
186
  }
192
187
  export async function typecheckStxFiles(files, options = {}) {
193
- const checkClient = options.client !== !1, checkServer = options.server !== !1, checkTemplates = options.templates !== !1, componentsDir = options.componentsDir ?? (checkTemplates ? (await loadStxConfig()).componentsDir : void 0), virtualFiles = new Map, scrapedByFile = new Map, checkedFiles = [];
188
+ const checkClient = options.client !== !1, checkServer = options.server !== !1, checkTemplates = options.templates !== !1, componentsDir = options.componentsDir ?? (checkTemplates ? (await loadStxConfig()).componentsDir : void 0), virtualFiles = new Map, scrapedByFile = new Map, importDiagnostics = [], checkedFiles = [];
194
189
  let blockCount = 0, expressionCount = 0;
195
190
  const runtimeTypes = findRuntimeTypeDeclarations();
196
191
  for (const file of files) {
@@ -200,6 +195,16 @@ export async function typecheckStxFiles(files, options = {}) {
200
195
  return checkClient;
201
196
  }), serverCode = extractScriptBlocks(source).filter((block) => block.kind === "server").map((block) => block.code).join(`
202
197
  `);
198
+ for (const d of stxImportDiagnostics(source, blocks))
199
+ importDiagnostics.push({
200
+ file,
201
+ line: d.line,
202
+ column: d.column,
203
+ code: 0,
204
+ message: d.message,
205
+ category: "error",
206
+ blockKind: d.kind
207
+ });
203
208
  if (checkClient && serverCode) {
204
209
  const clientBlocks = blocks.filter((block) => block.kind !== "server"), names = [...new Set(clientBlocks.flatMap((block) => scrapedBridgeNames(serverCode, block.code)))];
205
210
  if (names.length > 0)
@@ -227,20 +232,22 @@ export async function typecheckStxFiles(files, options = {}) {
227
232
  });
228
233
  }
229
234
  if (virtualFiles.size === 0)
230
- return { diagnostics: [], checkedFiles, blockCount: 0, expressionCount: 0 };
235
+ return { diagnostics: importDiagnostics, checkedFiles, blockCount: 0, expressionCount: 0 };
231
236
  const ambient = (options.extraLibs ?? []).map((lib) => path.resolve(lib));
232
237
  if (runtimeTypes)
233
238
  ambient.push(runtimeTypes);
239
+ const stxModule = runtimeTypes ? findStxModuleDeclarations() : null;
240
+ if (stxModule)
241
+ ambient.push(stxModule);
234
242
  const globalsDts = `${stateDir()}/typecheck/__stx_globals.d.ts`, globalDecls = [
235
243
  "// Generated \u2014 the context serve.ts injects into <script server> blocks.",
236
- serverContextDeclarations(),
244
+ serverContextDeclarations(runtimeTypes ? declaredGlobalNames(readFileSync(runtimeTypes, "utf8")) : void 0),
237
245
  ...runtimeTypes ? [] : ["declare const window: any", ...STX_RUNTIME_GLOBALS.map((name) => `declare const ${name}: any`)],
238
246
  [
239
247
  "// Generated \u2014 the names stx auto-imports from @stacksjs/browser.",
240
248
  ...BROWSER_CORE_IMPORTS.map((name) => `declare const ${name}: any`)
241
249
  ].join(`
242
250
  `),
243
- virtualStxModuleDeclaration(files),
244
251
  composableGlobalDeclarations(await listComposableModules())
245
252
  ].filter(Boolean).join(`
246
253
  `), workDir = `${stateDir()}/typecheck`;
@@ -305,9 +312,7 @@ export async function typecheckStxFiles(files, options = {}) {
305
312
  const at = resolvePosition(entry.virtual, line, column);
306
313
  if (!at?.expression)
307
314
  continue;
308
- if (at.inGuard)
309
- continue;
310
- if (Number(code) === 2774)
315
+ if (isTemplateArtefact(at, Number(code)))
311
316
  continue;
312
317
  line = at.line;
313
318
  column = at.column;
@@ -345,6 +350,7 @@ export async function typecheckStxFiles(files, options = {}) {
345
350
  blockKind: scraped.kind
346
351
  });
347
352
  }
353
+ diagnostics.push(...importDiagnostics);
348
354
  const seen = new Set, unique = diagnostics.filter((d) => {
349
355
  const key = `${d.file}:${d.line}:${d.column}:${d.code}:${d.message}`;
350
356
  if (seen.has(key))
@@ -1,25 +1,5 @@
1
- /**
2
- * The runtime bindings the engine hands a `<script server>` block as function
3
- * parameters, in the order the parameters are declared.
4
- *
5
- * This drives the `new Function` parameter list rather than sitting beside it,
6
- * because the two must not drift: the value list below it is positional, so a
7
- * name added in one place and not the other silently rebinds every argument
8
- * after it.
9
- *
10
- * It is also the set a `from 'stx'` import must not redeclare. A server script
11
- * writing the documented `import { defineProps } from 'stx'` was rewritten to
12
- * `const { defineProps } = await import('stx')`, and that `const` shadows the
13
- * parameter of the same name. The package's own `defineProps` reads
14
- * `globalThis.__STX_CURRENT_PROPS__`, which nothing on the server path ever
15
- * sets — so props came back `{}` and every component rendered empty.
16
- *
17
- * It went unnoticed because `stx` does not resolve inside this repo (the
18
- * published package is `@stacksjs/stx`; the root `stx` is private), so the
19
- * generated `await import('stx')` rejected and the shadowing binding was never
20
- * created. The failure needed a real install to appear.
21
- */
22
- export declare const STX_ENGINE_BINDING_NAMES: readonly ['module', 'exports', 'require', 'props', '$props', '$bool', 'defineProps', 'withDefaults', 'defineClientPayload', 'useServerData', 'useRuntimeConfig', 'useServerRuntimeConfig', 'state', 'derived', 'effect', 'batch', 'onMount', 'onDestroy', 'definePageMeta', 'useRoute', 'useRouter', 'useHead', 'useSeoMeta', 'setResponseStatus', 'setResponseHeader', 'notFound', 'ref', 'reactive', 'computed', 'watch', 'onMounted', 'onUnmounted', 'nextTick', 'defineEmits', 'defineExpose', 'defineSlots', 'provide', 'inject', 'useColorMode', 'useDark', 'useMediaQuery', 'useScrollLock', 'usePreferredDark', 'usePreferredLight', 'usePreferredReducedMotion', 'usePreferredContrast', 'window', 'document', 'console', 'confirm', 'alert', 'fetch', 'params'];
1
+ import { STX_ENGINE_BINDING_NAMES } from './engine-bindings';
2
+ export { STX_ENGINE_BINDING_NAMES };
23
3
  /**
24
4
  * Strip TypeScript-specific syntax to make it executable as JavaScript
25
5
  *
@@ -4,61 +4,9 @@ import { useServerData, withServerData } from "./server-data.js";
4
4
  import { prepareRuntimeConfig } from "./runtime-config-loader.js";
5
5
  import { currentRuntimeConfig, resolveRuntimeConfig, useRuntimeConfig, useServerRuntimeConfig, withRuntimeConfig } from "./runtime-config-server.js";
6
6
  import { getSharedTranspiler } from "./utils.js";
7
- export const STX_ENGINE_BINDING_NAMES = [
8
- "module",
9
- "exports",
10
- "require",
11
- "props",
12
- "$props",
13
- "$bool",
14
- "defineProps",
15
- "withDefaults",
16
- "defineClientPayload",
17
- "useServerData",
18
- "useRuntimeConfig",
19
- "useServerRuntimeConfig",
20
- "state",
21
- "derived",
22
- "effect",
23
- "batch",
24
- "onMount",
25
- "onDestroy",
26
- "definePageMeta",
27
- "useRoute",
28
- "useRouter",
29
- "useHead",
30
- "useSeoMeta",
31
- "setResponseStatus",
32
- "setResponseHeader",
33
- "notFound",
34
- "ref",
35
- "reactive",
36
- "computed",
37
- "watch",
38
- "onMounted",
39
- "onUnmounted",
40
- "nextTick",
41
- "defineEmits",
42
- "defineExpose",
43
- "defineSlots",
44
- "provide",
45
- "inject",
46
- "useColorMode",
47
- "useDark",
48
- "useMediaQuery",
49
- "useScrollLock",
50
- "usePreferredDark",
51
- "usePreferredLight",
52
- "usePreferredReducedMotion",
53
- "usePreferredContrast",
54
- "window",
55
- "document",
56
- "console",
57
- "confirm",
58
- "alert",
59
- "fetch",
60
- "params"
61
- ];
7
+ import { STX_ENGINE_BINDING_NAMES } from "./engine-bindings.js";
8
+
9
+ export { STX_ENGINE_BINDING_NAMES };
62
10
  const ENGINE_BINDINGS = new Set(STX_ENGINE_BINDING_NAMES), CONTEXT_RESERVED_BINDINGS = new Set(["props", "$props", "defineProps"]), STX_RUNTIME_SPECIFIERS = new Set(["stx", "@stacksjs/stx"]);
63
11
  import { findMatchingDelimiter } from "./parser/tokenizer.js";
64
12
  import { mergeHeadConfigs, seoMetaToHeadConfig } from "./head.js";
@@ -1,5 +1,5 @@
1
1
  // @bun
2
- import"./chunk-ejqpq0wc.js";import B from"fs";import $ from"path";import k from"crypto";var V=[{name:"mobile",width:375,height:667,isMobile:!0,hasTouch:!0},{name:"tablet",width:768,height:1024,isMobile:!0,hasTouch:!0},{name:"desktop",width:1280,height:800,isMobile:!1,hasTouch:!1},{name:"wide",width:1920,height:1080,isMobile:!1,hasTouch:!1}];function J(N={}){let Y={snapshotDir:N.snapshotDir||"__snapshots__",screenshotDir:N.screenshotDir||"__screenshots__",updateSnapshots:N.updateSnapshots||process.env.UPDATE_SNAPSHOTS==="true",threshold:N.threshold??0.01,viewports:N.viewports||V,generateDiff:N.generateDiff??!0,diffDir:N.diffDir||"__diffs__",compare:N.compare||b,ci:N.ci??process.env.CI==="true",retries:N.retries??(N.ci?3:1)};return{async snapshot(M,X){let q=$.join(Y.snapshotDir,`${X}.html`),G=C(M),Q=K(G);if(await Bun.file(q).exists()){let W=await Bun.file(q).text(),Z=K(C(W));if(Q===Z)return{passed:!0,snapshotPath:q,message:"Snapshot matches"};if(Y.updateSnapshots)return await B.promises.mkdir($.dirname(q),{recursive:!0}),await Bun.write(q,G),{passed:!0,snapshotPath:q,message:"Snapshot updated"};let F=$.join(Y.diffDir,`${X}.actual.html`),U=$.join(Y.diffDir,`${X}.diff.html`);return await B.promises.mkdir(Y.diffDir,{recursive:!0}),await Bun.write(F,G),await Bun.write(U,I(W,G)),{passed:!1,snapshotPath:q,actualPath:F,diffPath:U,message:"Snapshot does not match"}}return await B.promises.mkdir($.dirname(q),{recursive:!0}),await Bun.write(q,G),{passed:!0,snapshotPath:q,message:"New snapshot created"}},async snapshotJson(M,X){let q=$.join(Y.snapshotDir,`${X}.json`),G=JSON.stringify(M,null,2);if(await Bun.file(q).exists()){let Q=await Bun.file(q).text();if(G===Q)return{passed:!0,snapshotPath:q};if(Y.updateSnapshots)return await Bun.write(q,G),{passed:!0,snapshotPath:q,message:"Snapshot updated"};return{passed:!1,snapshotPath:q,message:`JSON snapshot mismatch:
2
+ import"./chunk-emjmshj8.js";import B from"fs";import $ from"path";import k from"crypto";var V=[{name:"mobile",width:375,height:667,isMobile:!0,hasTouch:!0},{name:"tablet",width:768,height:1024,isMobile:!0,hasTouch:!0},{name:"desktop",width:1280,height:800,isMobile:!1,hasTouch:!1},{name:"wide",width:1920,height:1080,isMobile:!1,hasTouch:!1}];function J(N={}){let Y={snapshotDir:N.snapshotDir||"__snapshots__",screenshotDir:N.screenshotDir||"__screenshots__",updateSnapshots:N.updateSnapshots||process.env.UPDATE_SNAPSHOTS==="true",threshold:N.threshold??0.01,viewports:N.viewports||V,generateDiff:N.generateDiff??!0,diffDir:N.diffDir||"__diffs__",compare:N.compare||b,ci:N.ci??process.env.CI==="true",retries:N.retries??(N.ci?3:1)};return{async snapshot(M,X){let q=$.join(Y.snapshotDir,`${X}.html`),G=C(M),Q=K(G);if(await Bun.file(q).exists()){let W=await Bun.file(q).text(),Z=K(C(W));if(Q===Z)return{passed:!0,snapshotPath:q,message:"Snapshot matches"};if(Y.updateSnapshots)return await B.promises.mkdir($.dirname(q),{recursive:!0}),await Bun.write(q,G),{passed:!0,snapshotPath:q,message:"Snapshot updated"};let F=$.join(Y.diffDir,`${X}.actual.html`),U=$.join(Y.diffDir,`${X}.diff.html`);return await B.promises.mkdir(Y.diffDir,{recursive:!0}),await Bun.write(F,G),await Bun.write(U,I(W,G)),{passed:!1,snapshotPath:q,actualPath:F,diffPath:U,message:"Snapshot does not match"}}return await B.promises.mkdir($.dirname(q),{recursive:!0}),await Bun.write(q,G),{passed:!0,snapshotPath:q,message:"New snapshot created"}},async snapshotJson(M,X){let q=$.join(Y.snapshotDir,`${X}.json`),G=JSON.stringify(M,null,2);if(await Bun.file(q).exists()){let Q=await Bun.file(q).text();if(G===Q)return{passed:!0,snapshotPath:q};if(Y.updateSnapshots)return await Bun.write(q,G),{passed:!0,snapshotPath:q,message:"Snapshot updated"};return{passed:!1,snapshotPath:q,message:`JSON snapshot mismatch:
3
3
  ${z(Q,G)}`}}return await B.promises.mkdir($.dirname(q),{recursive:!0}),await Bun.write(q,G),{passed:!0,snapshotPath:q,message:"New snapshot created"}},matchInlineSnapshot(M,X){let q=C(M);if(X===void 0)return{passed:!0,expected:q};let G=C(X);return{passed:q===G,expected:q}},getConfig(){return Y}}}function D(N={}){let Y=J(N),M=Y.getConfig();return{async compareScreenshots(X,q,G="desktop"){let Q=$.join(M.screenshotDir,G,`${X}.png`),W=$.join(M.diffDir,G,`${X}.actual.png`),Z=$.join(M.diffDir,G,`${X}.diff.png`);if(await Bun.file(Q).exists()){let F=await Bun.file(Q).arrayBuffer(),U=Buffer.from(F),_=await M.compare(q,U);if(_.match||_.diffPercentage<=M.threshold)return{viewport:G,passed:!0,baselinePath:Q};if(M.updateSnapshots)return await B.promises.mkdir($.dirname(Q),{recursive:!0}),await Bun.write(Q,q),{viewport:G,passed:!0,baselinePath:Q};if(await B.promises.mkdir($.dirname(W),{recursive:!0}),await Bun.write(W,q),_.diffImage&&M.generateDiff)await Bun.write(Z,_.diffImage);return{viewport:G,passed:!1,baselinePath:Q,actualPath:W,diffPath:M.generateDiff?Z:void 0,diffPercentage:_.diffPercentage}}return await B.promises.mkdir($.dirname(Q),{recursive:!0}),await Bun.write(Q,q),{viewport:G,passed:!0,baselinePath:Q}},async testViewports(X,q,G=M.viewports){let Q=[];for(let W of G){let Z=null;for(let F=0;F<M.retries;F++)try{let U=await q(W),_=await this.compareScreenshots(X,U,W.name);Q.push(_),Z=null;break}catch(U){if(Z=U,F<M.retries-1)await new Promise((_)=>setTimeout(_,100*(F+1)))}if(Z)Q.push({viewport:W.name,passed:!1,baselinePath:"",diffPercentage:100})}return Q},getSnapshotTester(){return Y}}}function L(N={}){let M=D(N).getSnapshotTester(),X=new Map;return{addStory(q){X.set(`${q.component}/${q.name}`,q)},addStories(q){for(let G of q)this.addStory(G)},getStories(){return Array.from(X.values())},getStoriesForComponent(q){return Array.from(X.values()).filter((G)=>G.component===q)},async testStory(q){let G=X.get(q);if(!G)return{name:q,passed:!1,duration:0,snapshots:[],screenshots:[],errors:[Error(`Story not found: ${q}`)]};let Q=Date.now(),W={name:q,passed:!0,duration:0,snapshots:[],screenshots:[],errors:[]};try{let Z=await G.render(),F=await M.snapshot(Z,q.replace(/\//g,"-"));if(W.snapshots.push(F),!F.passed)W.passed=!1}catch(Z){W.passed=!1,W.errors.push(Z)}return W.duration=Date.now()-Q,W},async testAllStories(){let q=[];for(let G of X.keys()){let Q=await this.testStory(G);q.push(Q)}return q},async generateStoryIndex(){let q={};for(let G of X.values()){if(!q[G.component])q[G.component]=[];q[G.component].push({name:G.name,args:G.args})}return JSON.stringify(q,null,2)}}}async function S(N,Y={}){let M={outputDir:Y.outputDir||"visual-test-report",format:Y.format||"html",title:Y.title||"Visual Test Report",includeScreenshots:Y.includeScreenshots??!0};await B.promises.mkdir(M.outputDir,{recursive:!0});let X=N.length,q=N.filter((F)=>F.passed).length,G=X-q,Q=N.reduce((F,U)=>F+U.duration,0);if(M.format==="json"){let F={title:M.title,summary:{total:X,passed:q,failed:G,duration:Q},results:N,generatedAt:new Date().toISOString()},U=$.join(M.outputDir,"report.json");return await Bun.write(U,JSON.stringify(F,null,2)),U}if(M.format==="markdown"){let F=`# ${M.title}
4
4
 
5
5
  `;F+=`## Summary
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@stacksjs/stx",
3
3
  "type": "module",
4
- "version": "0.2.354",
4
+ "version": "0.2.356",
5
5
  "description": "A performant UI Framework. Powered by Bun.",
6
6
  "author": "Chris Breuer <chris@stacksjs.org>",
7
7
  "license": "MIT",
@@ -84,7 +84,8 @@
84
84
  "LICENSE.md",
85
85
  "README.md",
86
86
  "dist",
87
- "stx.d.ts"
87
+ "stx.d.ts",
88
+ "stx-module.d.ts"
88
89
  ],
89
90
  "scripts": {
90
91
  "build": "bun run build:css && bun --bun build.ts",
@@ -117,12 +118,12 @@
117
118
  "dependencies": {
118
119
  "@stacksjs/ts-css": "^0.3.2",
119
120
  "@stacksjs/clapp": "^0.2.16",
120
- "@stacksjs/desktop": "0.2.354",
121
- "@stacksjs/sanitizer": "0.2.354",
121
+ "@stacksjs/desktop": "0.2.356",
122
+ "@stacksjs/sanitizer": "0.2.356",
122
123
  "@stacksjs/ts-i18n": "^0.1.11",
123
- "bun-plugin-stx": "0.2.354",
124
+ "bun-plugin-stx": "0.2.356",
124
125
  "bunfig": "^0.15.19",
125
- "stx-router": "0.2.354",
126
+ "stx-router": "0.2.356",
126
127
  "ts-broadcasting": "^0.0.10",
127
128
  "ts-images": "^0.2.22",
128
129
  "ts-syntax-highlighter": "^0.2.1"