@nexus_js/compiler 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +17 -0
  3. package/dist/codegen.d.ts +4 -0
  4. package/dist/codegen.d.ts.map +1 -0
  5. package/dist/codegen.js +308 -0
  6. package/dist/codegen.js.map +1 -0
  7. package/dist/css-scope.d.ts +76 -0
  8. package/dist/css-scope.d.ts.map +1 -0
  9. package/dist/css-scope.js +327 -0
  10. package/dist/css-scope.js.map +1 -0
  11. package/dist/guard.d.ts +59 -0
  12. package/dist/guard.d.ts.map +1 -0
  13. package/dist/guard.js +212 -0
  14. package/dist/guard.js.map +1 -0
  15. package/dist/index.d.ts +8 -0
  16. package/dist/index.d.ts.map +1 -0
  17. package/dist/index.js +19 -0
  18. package/dist/index.js.map +1 -0
  19. package/dist/island-ssr-stubs.d.ts +25 -0
  20. package/dist/island-ssr-stubs.d.ts.map +1 -0
  21. package/dist/island-ssr-stubs.js +107 -0
  22. package/dist/island-ssr-stubs.js.map +1 -0
  23. package/dist/island-wrap.d.ts +19 -0
  24. package/dist/island-wrap.d.ts.map +1 -0
  25. package/dist/island-wrap.js +108 -0
  26. package/dist/island-wrap.js.map +1 -0
  27. package/dist/parser.d.ts +28 -0
  28. package/dist/parser.d.ts.map +1 -0
  29. package/dist/parser.js +128 -0
  30. package/dist/parser.js.map +1 -0
  31. package/dist/preload-scanner.d.ts +60 -0
  32. package/dist/preload-scanner.d.ts.map +1 -0
  33. package/dist/preload-scanner.js +156 -0
  34. package/dist/preload-scanner.js.map +1 -0
  35. package/dist/server-actions-extract.d.ts +9 -0
  36. package/dist/server-actions-extract.d.ts.map +1 -0
  37. package/dist/server-actions-extract.js +89 -0
  38. package/dist/server-actions-extract.js.map +1 -0
  39. package/dist/types.d.ts +98 -0
  40. package/dist/types.d.ts.map +1 -0
  41. package/dist/types.js +2 -0
  42. package/dist/types.js.map +1 -0
  43. package/package.json +56 -0
@@ -0,0 +1,327 @@
1
+ /**
2
+ * Nexus CSS Scoping — Compile-time class hash injection with @layer.
3
+ *
4
+ * Strategy: Pure AOT, zero runtime overhead.
5
+ *
6
+ * Specificity fix (the insight from the user):
7
+ * Plain [data-nx="hash"] .card has higher specificity than .card,
8
+ * which breaks overrides from parent components or third-party libraries.
9
+ *
10
+ * Solution: Wrap ALL generated scoped styles inside @layer nexus.scoped.
11
+ * CSS Cascade Layers (Level 5 spec, baseline 2022) have LOWER specificity
12
+ * than unlayered styles by design, regardless of selector weight.
13
+ *
14
+ * Layer precedence (highest wins, last wins within same layer):
15
+ * unlayered styles > @layer nexus.global > @layer nexus.scoped
16
+ *
17
+ * This means:
18
+ * - Component styles (.card) are isolated by hash ✓
19
+ * - Parent overrides work without !important ✓
20
+ * - Third-party libraries can override without !important ✓
21
+ * - :global(selector) still works as an escape hatch ✓
22
+ *
23
+ * How it works:
24
+ * 1. Compute a stable 6-char hash from the component filepath (FNV-1a).
25
+ * 2. Rewrite every CSS selector to be scoped with [data-nx="<hash>"].
26
+ * 3. Wrap the entire output in @layer nexus.scoped { ... }.
27
+ * 4. Inject data-nx="<hash>" onto every root element in the template.
28
+ *
29
+ * Example — input:
30
+ * .card { color: red }
31
+ * .card:hover h2 { font-size: 2rem }
32
+ * @media (max-width: 768px) { .card { display: none } }
33
+ *
34
+ * Output:
35
+ * @layer nexus.scoped {
36
+ * [data-nx="a3f9c1"] .card { color: red }
37
+ * [data-nx="a3f9c1"] .card:hover h2 { font-size: 2rem }
38
+ * @media (max-width: 768px) { [data-nx="a3f9c1"] .card { display: none } }
39
+ * }
40
+ *
41
+ * Template rewrite:
42
+ * <div class="card"> → <div class="card" data-nx="a3f9c1">
43
+ *
44
+ * Layer declaration (injected once in root layout):
45
+ * @layer nexus.scoped, nexus.global;
46
+ */
47
+ /** Murmurhash-inspired: fast, stable 32-bit hash → 6 hex chars */
48
+ export function componentHash(filepath) {
49
+ let h = 0x811c9dc5;
50
+ for (let i = 0; i < filepath.length; i++) {
51
+ h ^= filepath.charCodeAt(i);
52
+ h = Math.imul(h, 0x01000193);
53
+ }
54
+ return (h >>> 0).toString(16).padStart(8, '0').slice(0, 6);
55
+ }
56
+ /**
57
+ * Transforms raw CSS into scoped CSS using the component hash.
58
+ * Handles: selectors, @media, @keyframes (not scoped), @layer, :global() escape hatch.
59
+ */
60
+ /** Layer declaration to emit once in the root <head> */
61
+ export const NEXUS_LAYER_DECLARATION = '@layer nexus.scoped, nexus.global;';
62
+ export function scopeCSS(rawCSS, filepath) {
63
+ const hash = componentHash(filepath);
64
+ const attr = `[data-nx="${hash}"]`;
65
+ const classes = new Set();
66
+ // Extract class names for template injection tracking
67
+ const classRe = /\.(-?[a-zA-Z_][a-zA-Z0-9_-]*)/g;
68
+ let m;
69
+ while ((m = classRe.exec(rawCSS)) !== null) {
70
+ if (m[1])
71
+ classes.add(m[1]);
72
+ }
73
+ const scoped = transformCSS(rawCSS, attr);
74
+ // Wrap in @layer nexus.scoped — fixes specificity wars.
75
+ // Cascade layers have lower priority than unlayered styles, so parent
76
+ // components and global CSS can always override without !important.
77
+ const layered = `@layer nexus.scoped {\n${scoped}\n}`;
78
+ return { css: layered, hash, classes };
79
+ }
80
+ /**
81
+ * Adds scope attribute to every HTML root element in a template string.
82
+ * Skips elements that are: slot, nexus-island, html, head, body.
83
+ * Handles :global(selector) — removes scoping for that selector.
84
+ */
85
+ /**
86
+ * `.nx` pages often use `<template>...</template>` as the root. In the live DOM,
87
+ * `<template>` contents are inert (not rendered). SSR must unwrap the outer
88
+ * wrapper so the shell is visible; nested `<template>` inside the tree is rare
89
+ * and still wrapped until unwrapped by the same pass on inner routes only when
90
+ * they are the file root.
91
+ */
92
+ export function unwrapOuterTemplateElement(html) {
93
+ const t = html.trimStart();
94
+ if (!/^<template\b/i.test(t))
95
+ return html;
96
+ const lower = t.toLowerCase();
97
+ let depth = 0;
98
+ let i = 0;
99
+ let contentStart = -1;
100
+ while (i < t.length) {
101
+ const open = lower.indexOf('<template', i);
102
+ const close = lower.indexOf('</template>', i);
103
+ if (open !== -1 && (close === -1 || open < close)) {
104
+ if (depth === 0) {
105
+ const gt = t.indexOf('>', open);
106
+ if (gt === -1)
107
+ return html;
108
+ contentStart = gt + 1;
109
+ }
110
+ depth++;
111
+ i = open + '<template'.length;
112
+ continue;
113
+ }
114
+ if (close !== -1) {
115
+ depth--;
116
+ if (depth === 0 && contentStart !== -1) {
117
+ return t.slice(contentStart, close).trim();
118
+ }
119
+ i = close + '</template>'.length;
120
+ continue;
121
+ }
122
+ break;
123
+ }
124
+ return html;
125
+ }
126
+ export function scopeTemplate(html, hash) {
127
+ const skip = new Set(['html', 'head', 'body', 'meta', 'link', 'script', 'style', 'nexus-island', 'slot']);
128
+ let out = '';
129
+ let i = 0;
130
+ while (i < html.length) {
131
+ const lt = html.indexOf('<', i);
132
+ if (lt === -1) {
133
+ out += html.slice(i);
134
+ break;
135
+ }
136
+ out += html.slice(i, lt);
137
+ const afterLt = html.slice(lt + 1);
138
+ if (afterLt[0] === '/' || afterLt[0] === '!') {
139
+ const gt = html.indexOf('>', lt);
140
+ if (gt === -1) {
141
+ out += html.slice(lt);
142
+ break;
143
+ }
144
+ out += html.slice(lt, gt + 1);
145
+ i = gt + 1;
146
+ continue;
147
+ }
148
+ const tagM = /^([a-zA-Z][\w-]*)/.exec(afterLt);
149
+ if (!tagM) {
150
+ out += '<';
151
+ i = lt + 1;
152
+ continue;
153
+ }
154
+ const tag = tagM[1] ?? '';
155
+ if (!tag) {
156
+ out += '<';
157
+ i = lt + 1;
158
+ continue;
159
+ }
160
+ const lower = tag.toLowerCase();
161
+ let j = lt + 1 + tagM[0].length;
162
+ let brace = 0;
163
+ let quote = null;
164
+ let closed = false;
165
+ while (j < html.length) {
166
+ const c = html[j];
167
+ if (quote !== null) {
168
+ if (c === '\\' && j + 1 < html.length) {
169
+ j += 2;
170
+ continue;
171
+ }
172
+ if (c === quote)
173
+ quote = null;
174
+ j++;
175
+ continue;
176
+ }
177
+ if (c === '"' || c === "'") {
178
+ quote = c;
179
+ j++;
180
+ continue;
181
+ }
182
+ if (c === '{')
183
+ brace++;
184
+ else if (c === '}')
185
+ brace = Math.max(0, brace - 1);
186
+ else if (c === '/' && html[j + 1] === '>' && brace === 0) {
187
+ const full = html.slice(lt, j + 2);
188
+ if (skip.has(lower) || full.includes('data-nx=')) {
189
+ out += full;
190
+ }
191
+ else {
192
+ const attrPart = html.slice(lt + 1 + tagM[0].length, j);
193
+ out += `<${tag}${attrPart} data-nx="${hash}" />`;
194
+ }
195
+ j += 2;
196
+ closed = true;
197
+ break;
198
+ }
199
+ else if (c === '>' && brace === 0) {
200
+ const full = html.slice(lt, j + 1);
201
+ if (skip.has(lower) || full.includes('data-nx=')) {
202
+ out += full;
203
+ }
204
+ else {
205
+ const attrPart = html.slice(lt + 1 + tagM[0].length, j);
206
+ out += `<${tag}${attrPart} data-nx="${hash}">`;
207
+ }
208
+ j++;
209
+ closed = true;
210
+ break;
211
+ }
212
+ j++;
213
+ }
214
+ if (!closed) {
215
+ out += html.slice(lt);
216
+ break;
217
+ }
218
+ i = j;
219
+ }
220
+ return out;
221
+ }
222
+ // ─────────────────────────────────────────────────────────────────────────────
223
+ // Internal CSS transformer
224
+ // ─────────────────────────────────────────────────────────────────────────────
225
+ function transformCSS(css, attr) {
226
+ // Remove /* comments */
227
+ const stripped = css.replace(/\/\*[\s\S]*?\*\//g, '');
228
+ // Tokenize into rules (naive but effective for our subset)
229
+ return processBlock(stripped, attr);
230
+ }
231
+ function processBlock(block, attr) {
232
+ const result = [];
233
+ let i = 0;
234
+ const len = block.length;
235
+ while (i < len) {
236
+ // Skip whitespace
237
+ const wsStart = i;
238
+ while (i < len && /\s/.test(block[i] ?? ''))
239
+ i++;
240
+ // @ rules
241
+ if (block[i] === '@') {
242
+ const atEnd = block.indexOf('{', i);
243
+ if (atEnd === -1) {
244
+ i = len;
245
+ continue;
246
+ }
247
+ const atRule = block.slice(i, atEnd).trim();
248
+ i = atEnd + 1;
249
+ // Find matching closing brace
250
+ let depth = 1;
251
+ const bodyStart = i;
252
+ while (i < len && depth > 0) {
253
+ if (block[i] === '{')
254
+ depth++;
255
+ else if (block[i] === '}')
256
+ depth--;
257
+ i++;
258
+ }
259
+ const body = block.slice(bodyStart, i - 1);
260
+ // @keyframes — don't scope
261
+ if (/^@keyframes/i.test(atRule)) {
262
+ result.push(`${atRule} {${body}}`);
263
+ }
264
+ // @layer, @supports, @media — recurse into body
265
+ else if (/^@(media|supports|layer|container)/i.test(atRule)) {
266
+ result.push(`${atRule} {${processBlock(body, attr)}}`);
267
+ }
268
+ // Other @ rules — pass through
269
+ else {
270
+ result.push(`${atRule} {${body}}`);
271
+ }
272
+ continue;
273
+ }
274
+ // Regular rule: find selector + body
275
+ const ruleStart = i;
276
+ const braceOpen = block.indexOf('{', i);
277
+ if (braceOpen === -1)
278
+ break;
279
+ const selector = block.slice(i, braceOpen).trim();
280
+ i = braceOpen + 1;
281
+ let depth = 1;
282
+ const bodyStart = i;
283
+ while (i < len && depth > 0) {
284
+ if (block[i] === '{')
285
+ depth++;
286
+ else if (block[i] === '}')
287
+ depth--;
288
+ i++;
289
+ }
290
+ const body = block.slice(bodyStart, i - 1);
291
+ if (!selector)
292
+ continue;
293
+ const scopedSelector = scopeSelector(selector, attr);
294
+ result.push(`${scopedSelector} {${body}}`);
295
+ }
296
+ return result.join('\n');
297
+ }
298
+ /**
299
+ * Scopes a CSS selector string (may be comma-separated).
300
+ * Handles :global(selector) escape hatch — removes scope for that part.
301
+ */
302
+ function scopeSelector(selector, attr) {
303
+ return selector
304
+ .split(',')
305
+ .map((s) => scopeSingleSelector(s.trim(), attr))
306
+ .join(', ');
307
+ }
308
+ function scopeSingleSelector(sel, attr) {
309
+ // :global(...) escape hatch — strip :global() wrapper, don't scope
310
+ if (/^:global\(/.test(sel)) {
311
+ return sel.replace(/:global\(([^)]+)\)/g, '$1');
312
+ }
313
+ // Handle :global inside a selector
314
+ if (sel.includes(':global(')) {
315
+ return sel.replace(/:global\(([^)]+)\)/g, '$1');
316
+ }
317
+ // Skip already-scoped or bare combinators
318
+ if (sel.startsWith(attr))
319
+ return sel;
320
+ // For :root, :host — insert attr before
321
+ if (/^:(root|host)/.test(sel)) {
322
+ return `${attr}${sel}`;
323
+ }
324
+ // Default: prepend the scope attribute
325
+ return `${attr} ${sel}`;
326
+ }
327
+ //# sourceMappingURL=css-scope.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"css-scope.js","sourceRoot":"","sources":["../src/css-scope.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6CG;AAEH,kEAAkE;AAClE,MAAM,UAAU,aAAa,CAAC,QAAgB;IAC5C,IAAI,CAAC,GAAG,UAAU,CAAC;IACnB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,QAAQ,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACzC,CAAC,IAAI,QAAQ,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;QAC5B,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC;IAC/B,CAAC;IACD,OAAO,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;AAC7D,CAAC;AASD;;;GAGG;AACH,wDAAwD;AACxD,MAAM,CAAC,MAAM,uBAAuB,GAAG,oCAAoC,CAAC;AAE5E,MAAM,UAAU,QAAQ,CAAC,MAAc,EAAE,QAAgB;IACvD,MAAM,IAAI,GAAG,aAAa,CAAC,QAAQ,CAAC,CAAC;IACrC,MAAM,IAAI,GAAG,aAAa,IAAI,IAAI,CAAC;IACnC,MAAM,OAAO,GAAG,IAAI,GAAG,EAAU,CAAC;IAElC,sDAAsD;IACtD,MAAM,OAAO,GAAG,gCAAgC,CAAC;IACjD,IAAI,CAAyB,CAAC;IAC9B,OAAO,CAAC,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;QAC3C,IAAI,CAAC,CAAC,CAAC,CAAC;YAAE,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC9B,CAAC;IAED,MAAM,MAAM,GAAG,YAAY,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IAE1C,wDAAwD;IACxD,sEAAsE;IACtE,oEAAoE;IACpE,MAAM,OAAO,GAAG,0BAA0B,MAAM,KAAK,CAAC;IAEtD,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;AACzC,CAAC;AAED;;;;GAIG;AACH;;;;;;GAMG;AACH,MAAM,UAAU,0BAA0B,CAAC,IAAY;IACrD,MAAM,CAAC,GAAG,IAAI,CAAC,SAAS,EAAE,CAAC;IAC3B,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC,CAAC;QAAE,OAAO,IAAI,CAAC;IAE1C,MAAM,KAAK,GAAG,CAAC,CAAC,WAAW,EAAE,CAAC;IAC9B,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,IAAI,CAAC,GAAG,CAAC,CAAC;IACV,IAAI,YAAY,GAAG,CAAC,CAAC,CAAC;IAEtB,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC;QACpB,MAAM,IAAI,GAAG,KAAK,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC;QAC3C,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,aAAa,EAAE,CAAC,CAAC,CAAC;QAE9C,IAAI,IAAI,KAAK,CAAC,CAAC,IAAI,CAAC,KAAK,KAAK,CAAC,CAAC,IAAI,IAAI,GAAG,KAAK,CAAC,EAAE,CAAC;YAClD,IAAI,KAAK,KAAK,CAAC,EAAE,CAAC;gBAChB,MAAM,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;gBAChC,IAAI,EAAE,KAAK,CAAC,CAAC;oBAAE,OAAO,IAAI,CAAC;gBAC3B,YAAY,GAAG,EAAE,GAAG,CAAC,CAAC;YACxB,CAAC;YACD,KAAK,EAAE,CAAC;YACR,CAAC,GAAG,IAAI,GAAG,WAAW,CAAC,MAAM,CAAC;YAC9B,SAAS;QACX,CAAC;QAED,IAAI,KAAK,KAAK,CAAC,CAAC,EAAE,CAAC;YACjB,KAAK,EAAE,CAAC;YACR,IAAI,KAAK,KAAK,CAAC,IAAI,YAAY,KAAK,CAAC,CAAC,EAAE,CAAC;gBACvC,OAAO,CAAC,CAAC,KAAK,CAAC,YAAY,EAAE,KAAK,CAAC,CAAC,IAAI,EAAE,CAAC;YAC7C,CAAC;YACD,CAAC,GAAG,KAAK,GAAG,aAAa,CAAC,MAAM,CAAC;YACjC,SAAS;QACX,CAAC;QAED,MAAM;IACR,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,IAAY,EAAE,IAAY;IACtD,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,CAAC,CAAC,CAAC;IAC1G,IAAI,GAAG,GAAG,EAAE,CAAC;IACb,IAAI,CAAC,GAAG,CAAC,CAAC;IACV,OAAO,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;QACvB,MAAM,EAAE,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;QAChC,IAAI,EAAE,KAAK,CAAC,CAAC,EAAE,CAAC;YACd,GAAG,IAAI,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;YACrB,MAAM;QACR,CAAC;QACD,GAAG,IAAI,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QACzB,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;QACnC,IAAI,OAAO,CAAC,CAAC,CAAC,KAAK,GAAG,IAAI,OAAO,CAAC,CAAC,CAAC,KAAK,GAAG,EAAE,CAAC;YAC7C,MAAM,EAAE,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;YACjC,IAAI,EAAE,KAAK,CAAC,CAAC,EAAE,CAAC;gBACd,GAAG,IAAI,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;gBACtB,MAAM;YACR,CAAC;YACD,GAAG,IAAI,IAAI,CAAC,KAAK,CAAC,EAAE,EAAE,EAAE,GAAG,CAAC,CAAC,CAAC;YAC9B,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;YACX,SAAS;QACX,CAAC;QACD,MAAM,IAAI,GAAG,mBAAmB,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAC/C,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,GAAG,IAAI,GAAG,CAAC;YACX,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;YACX,SAAS;QACX,CAAC;QACD,MAAM,GAAG,GAAG,IAAI,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QAC1B,IAAI,CAAC,GAAG,EAAE,CAAC;YACT,GAAG,IAAI,GAAG,CAAC;YACX,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;YACX,SAAS;QACX,CAAC;QACD,MAAM,KAAK,GAAG,GAAG,CAAC,WAAW,EAAE,CAAC;QAChC,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;QAChC,IAAI,KAAK,GAAG,CAAC,CAAC;QACd,IAAI,KAAK,GAAkB,IAAI,CAAC;QAChC,IAAI,MAAM,GAAG,KAAK,CAAC;QACnB,OAAO,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;YACvB,MAAM,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;YAClB,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;gBACnB,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;oBACtC,CAAC,IAAI,CAAC,CAAC;oBACP,SAAS;gBACX,CAAC;gBACD,IAAI,CAAC,KAAK,KAAK;oBAAE,KAAK,GAAG,IAAI,CAAC;gBAC9B,CAAC,EAAE,CAAC;gBACJ,SAAS;YACX,CAAC;YACD,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,GAAG,EAAE,CAAC;gBAC3B,KAAK,GAAG,CAAC,CAAC;gBACV,CAAC,EAAE,CAAC;gBACJ,SAAS;YACX,CAAC;YACD,IAAI,CAAC,KAAK,GAAG;gBAAE,KAAK,EAAE,CAAC;iBAClB,IAAI,CAAC,KAAK,GAAG;gBAAE,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,GAAG,CAAC,CAAC,CAAC;iBAC9C,IAAI,CAAC,KAAK,GAAG,IAAI,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,GAAG,IAAI,KAAK,KAAK,CAAC,EAAE,CAAC;gBACzD,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;gBACnC,IAAI,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC;oBACjD,GAAG,IAAI,IAAI,CAAC;gBACd,CAAC;qBAAM,CAAC;oBACN,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,GAAG,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;oBACxD,GAAG,IAAI,IAAI,GAAG,GAAG,QAAQ,aAAa,IAAI,MAAM,CAAC;gBACnD,CAAC;gBACD,CAAC,IAAI,CAAC,CAAC;gBACP,MAAM,GAAG,IAAI,CAAC;gBACd,MAAM;YACR,CAAC;iBAAM,IAAI,CAAC,KAAK,GAAG,IAAI,KAAK,KAAK,CAAC,EAAE,CAAC;gBACpC,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;gBACnC,IAAI,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC;oBACjD,GAAG,IAAI,IAAI,CAAC;gBACd,CAAC;qBAAM,CAAC;oBACN,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,GAAG,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;oBACxD,GAAG,IAAI,IAAI,GAAG,GAAG,QAAQ,aAAa,IAAI,IAAI,CAAC;gBACjD,CAAC;gBACD,CAAC,EAAE,CAAC;gBACJ,MAAM,GAAG,IAAI,CAAC;gBACd,MAAM;YACR,CAAC;YACD,CAAC,EAAE,CAAC;QACN,CAAC;QACD,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,GAAG,IAAI,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;YACtB,MAAM;QACR,CAAC;QACD,CAAC,GAAG,CAAC,CAAC;IACR,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,gFAAgF;AAChF,2BAA2B;AAC3B,gFAAgF;AAEhF,SAAS,YAAY,CAAC,GAAW,EAAE,IAAY;IAC7C,wBAAwB;IACxB,MAAM,QAAQ,GAAG,GAAG,CAAC,OAAO,CAAC,mBAAmB,EAAE,EAAE,CAAC,CAAC;IAEtD,2DAA2D;IAC3D,OAAO,YAAY,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;AACtC,CAAC;AAED,SAAS,YAAY,CAAC,KAAa,EAAE,IAAY;IAC/C,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,IAAI,CAAC,GAAG,CAAC,CAAC;IACV,MAAM,GAAG,GAAG,KAAK,CAAC,MAAM,CAAC;IAEzB,OAAO,CAAC,GAAG,GAAG,EAAE,CAAC;QACf,kBAAkB;QAClB,MAAM,OAAO,GAAG,CAAC,CAAC;QAClB,OAAO,CAAC,GAAG,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;YAAE,CAAC,EAAE,CAAC;QAEjD,UAAU;QACV,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,GAAG,EAAE,CAAC;YACrB,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;YACpC,IAAI,KAAK,KAAK,CAAC,CAAC,EAAE,CAAC;gBAAC,CAAC,GAAG,GAAG,CAAC;gBAAC,SAAS;YAAC,CAAC;YAExC,MAAM,MAAM,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,IAAI,EAAE,CAAC;YAC5C,CAAC,GAAG,KAAK,GAAG,CAAC,CAAC;YAEd,8BAA8B;YAC9B,IAAI,KAAK,GAAG,CAAC,CAAC;YACd,MAAM,SAAS,GAAG,CAAC,CAAC;YACpB,OAAO,CAAC,GAAG,GAAG,IAAI,KAAK,GAAG,CAAC,EAAE,CAAC;gBAC5B,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,GAAG;oBAAE,KAAK,EAAE,CAAC;qBACzB,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,GAAG;oBAAE,KAAK,EAAE,CAAC;gBACnC,CAAC,EAAE,CAAC;YACN,CAAC;YACD,MAAM,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,SAAS,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;YAE3C,2BAA2B;YAC3B,IAAI,cAAc,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;gBAChC,MAAM,CAAC,IAAI,CAAC,GAAG,MAAM,KAAK,IAAI,GAAG,CAAC,CAAC;YACrC,CAAC;YACD,gDAAgD;iBAC3C,IAAI,qCAAqC,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;gBAC5D,MAAM,CAAC,IAAI,CAAC,GAAG,MAAM,KAAK,YAAY,CAAC,IAAI,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC;YACzD,CAAC;YACD,+BAA+B;iBAC1B,CAAC;gBACJ,MAAM,CAAC,IAAI,CAAC,GAAG,MAAM,KAAK,IAAI,GAAG,CAAC,CAAC;YACrC,CAAC;YACD,SAAS;QACX,CAAC;QAED,qCAAqC;QACrC,MAAM,SAAS,GAAG,CAAC,CAAC;QACpB,MAAM,SAAS,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;QACxC,IAAI,SAAS,KAAK,CAAC,CAAC;YAAE,MAAM;QAE5B,MAAM,QAAQ,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC,IAAI,EAAE,CAAC;QAClD,CAAC,GAAG,SAAS,GAAG,CAAC,CAAC;QAElB,IAAI,KAAK,GAAG,CAAC,CAAC;QACd,MAAM,SAAS,GAAG,CAAC,CAAC;QACpB,OAAO,CAAC,GAAG,GAAG,IAAI,KAAK,GAAG,CAAC,EAAE,CAAC;YAC5B,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,GAAG;gBAAE,KAAK,EAAE,CAAC;iBACzB,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,GAAG;gBAAE,KAAK,EAAE,CAAC;YACnC,CAAC,EAAE,CAAC;QACN,CAAC;QACD,MAAM,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,SAAS,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;QAE3C,IAAI,CAAC,QAAQ;YAAE,SAAS;QAExB,MAAM,cAAc,GAAG,aAAa,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;QACrD,MAAM,CAAC,IAAI,CAAC,GAAG,cAAc,KAAK,IAAI,GAAG,CAAC,CAAC;IAC7C,CAAC;IAED,OAAO,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC3B,CAAC;AAED;;;GAGG;AACH,SAAS,aAAa,CAAC,QAAgB,EAAE,IAAY;IACnD,OAAO,QAAQ;SACZ,KAAK,CAAC,GAAG,CAAC;SACV,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,mBAAmB,CAAC,CAAC,CAAC,IAAI,EAAE,EAAE,IAAI,CAAC,CAAC;SAC/C,IAAI,CAAC,IAAI,CAAC,CAAC;AAChB,CAAC;AAED,SAAS,mBAAmB,CAAC,GAAW,EAAE,IAAY;IACpD,mEAAmE;IACnE,IAAI,YAAY,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;QAC3B,OAAO,GAAG,CAAC,OAAO,CAAC,qBAAqB,EAAE,IAAI,CAAC,CAAC;IAClD,CAAC;IAED,mCAAmC;IACnC,IAAI,GAAG,CAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC;QAC7B,OAAO,GAAG,CAAC,OAAO,CAAC,qBAAqB,EAAE,IAAI,CAAC,CAAC;IAClD,CAAC;IAED,0CAA0C;IAC1C,IAAI,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,GAAG,CAAC;IAErC,wCAAwC;IACxC,IAAI,eAAe,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;QAC9B,OAAO,GAAG,IAAI,GAAG,GAAG,EAAE,CAAC;IACzB,CAAC;IAED,uCAAuC;IACvC,OAAO,GAAG,IAAI,IAAI,GAAG,EAAE,CAAC;AAC1B,CAAC"}
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Nexus Guard — Compiler-level security leak detector.
3
+ *
4
+ * Analyzes .nx files to ensure server-only values (environment variables,
5
+ * DB connection strings, API secrets, private keys) never reach the client
6
+ * bundle. The guard runs at build time AND in dev mode on every file change.
7
+ *
8
+ * Architecture:
9
+ * .nx file
10
+ * ├── Server block (between --- delimiters) — safe to use secrets
11
+ * └── Template section
12
+ * ├── Static HTML — never executed by client
13
+ * └── <script> blocks — compiled into client bundle ← DANGER ZONE
14
+ *
15
+ * The guard checks if variables holding secret values are referenced inside
16
+ * <script> blocks that will be included in the client bundle.
17
+ */
18
+ export type LeakType = 'env-secret' | 'env-var' | 'db-connection' | 'api-key' | 'private-key';
19
+ export type Severity = 'error' | 'warning';
20
+ export interface SecurityLeak {
21
+ type: LeakType;
22
+ variable: string;
23
+ line: number;
24
+ column: number;
25
+ severity: Severity;
26
+ message: string;
27
+ /** Actionable fix suggestion */
28
+ hint: string;
29
+ }
30
+ export interface GuardResult {
31
+ filepath: string;
32
+ passed: boolean;
33
+ leaks: SecurityLeak[];
34
+ /** Lines scanned */
35
+ scanned: number;
36
+ /** ms taken to run the guard */
37
+ duration: number;
38
+ }
39
+ /**
40
+ * Analyzes a .nx file for security leaks.
41
+ *
42
+ * @param source - Raw .nx file content
43
+ * @param filepath - Path to the file (for error messages)
44
+ * @returns GuardResult with all detected leaks
45
+ *
46
+ * @example
47
+ * import { guard } from '@nexus_js/compiler/guard';
48
+ * const result = guard(source, 'src/routes/+page.nx');
49
+ * if (!result.passed) {
50
+ * for (const leak of result.leaks.filter(l => l.severity === 'error')) {
51
+ * console.error(`[Guard] ${leak.message} (line ${leak.line})`);
52
+ * }
53
+ * process.exit(1);
54
+ * }
55
+ */
56
+ export declare function guard(source: string, filepath: string): GuardResult;
57
+ /** Format a GuardResult for terminal output with ANSI colors. */
58
+ export declare function formatGuardResult(result: GuardResult): string;
59
+ //# sourceMappingURL=guard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"guard.d.ts","sourceRoot":"","sources":["../src/guard.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,MAAM,MAAM,QAAQ,GAChB,YAAY,GACZ,SAAS,GACT,eAAe,GACf,SAAS,GACT,aAAa,CAAC;AAElB,MAAM,MAAM,QAAQ,GAAG,OAAO,GAAG,SAAS,CAAC;AAE3C,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAM,QAAQ,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAM,MAAM,CAAC;IACjB,MAAM,EAAI,MAAM,CAAC;IACjB,QAAQ,EAAE,QAAQ,CAAC;IACnB,OAAO,EAAG,MAAM,CAAC;IACjB,gCAAgC;IAChC,IAAI,EAAM,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAG,MAAM,CAAC;IAClB,MAAM,EAAK,OAAO,CAAC;IACnB,KAAK,EAAM,YAAY,EAAE,CAAC;IAC1B,oBAAoB;IACpB,OAAO,EAAI,MAAM,CAAC;IAClB,gCAAgC;IAChC,QAAQ,EAAG,MAAM,CAAC;CACnB;AA0GD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,KAAK,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,WAAW,CAiFnE;AAED,iEAAiE;AACjE,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,WAAW,GAAG,MAAM,CA0B7D"}
package/dist/guard.js ADDED
@@ -0,0 +1,212 @@
1
+ /**
2
+ * Nexus Guard — Compiler-level security leak detector.
3
+ *
4
+ * Analyzes .nx files to ensure server-only values (environment variables,
5
+ * DB connection strings, API secrets, private keys) never reach the client
6
+ * bundle. The guard runs at build time AND in dev mode on every file change.
7
+ *
8
+ * Architecture:
9
+ * .nx file
10
+ * ├── Server block (between --- delimiters) — safe to use secrets
11
+ * └── Template section
12
+ * ├── Static HTML — never executed by client
13
+ * └── <script> blocks — compiled into client bundle ← DANGER ZONE
14
+ *
15
+ * The guard checks if variables holding secret values are referenced inside
16
+ * <script> blocks that will be included in the client bundle.
17
+ */
18
+ const PATTERNS = [
19
+ {
20
+ // PEM private keys
21
+ regex: /-----BEGIN\s+[\w\s]+PRIVATE KEY-----/gi,
22
+ type: 'private-key',
23
+ severity: 'error',
24
+ hint: 'Private keys must never appear in source files. Use a secrets manager or environment variable.',
25
+ },
26
+ {
27
+ // Stripe, Clerk, Supabase secret keys
28
+ regex: /\b(sk_live_|sk_test_|supabase_secret_|clerk_secret_|AKID)[A-Za-z0-9_-]{10,}/g,
29
+ type: 'api-key',
30
+ severity: 'error',
31
+ hint: 'Hard-coded API keys are a critical security risk. Store in process.env and reference server-side only.',
32
+ },
33
+ {
34
+ // Database connection strings
35
+ regex: /(['"`])(postgresql|mysql|mongodb|redis|sqlite):\/\/[^'"`\s]+\1/gi,
36
+ type: 'db-connection',
37
+ severity: 'error',
38
+ hint: 'Database URLs contain credentials. Use process.env.DATABASE_URL in the server block only.',
39
+ },
40
+ {
41
+ // High-risk env vars (secrets by name)
42
+ regex: /process\.env\.(\w*(?:PASSWORD|SECRET|PRIVATE|KEY|TOKEN|CERT|SEED|SALT|CREDENTIALS)\w*)/gi,
43
+ type: 'env-secret',
44
+ severity: 'error',
45
+ hint: 'This env var appears to contain a secret. Access it only in the server frontmatter (--- block).',
46
+ },
47
+ {
48
+ // Any other process.env.* reference in client code
49
+ regex: /process\.env\.([A-Z_][A-Z0-9_]+)/g,
50
+ type: 'env-var',
51
+ severity: 'warning',
52
+ hint: 'process.env is not available in the browser. Move this to the server frontmatter or use $env() from @nexus_js/runtime.',
53
+ },
54
+ ];
55
+ // ── Parser helpers ─────────────────────────────────────────────────────────────
56
+ /** Extract the server frontmatter block (between --- delimiters). */
57
+ function extractServerBlock(source) {
58
+ const match = /^---\r?\n([\s\S]*?)\r?\n---/.exec(source);
59
+ return match?.[1] ?? '';
60
+ }
61
+ /** Extract all client-facing <script> blocks from the template section. */
62
+ function extractClientScripts(source) {
63
+ // Remove frontmatter first
64
+ const template = source.replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n?/, '');
65
+ const frontmatterLines = source.length - template.length > 0
66
+ ? source.slice(0, source.indexOf(template)).split('\n').length
67
+ : 0;
68
+ const results = [];
69
+ const scriptRe = /<script(?:\s[^>]*)?>(?!\s*\/\/)?([\s\S]*?)<\/script>/gi;
70
+ let match;
71
+ while ((match = scriptRe.exec(template)) !== null) {
72
+ const before = template.slice(0, match.index);
73
+ const startLine = frontmatterLines + before.split('\n').length;
74
+ results.push({ content: match[1] ?? '', startLine });
75
+ }
76
+ return results;
77
+ }
78
+ /** Scan a code block for a pattern, returning all match locations. */
79
+ function scanBlock(content, startLine, pattern) {
80
+ const results = [];
81
+ const lines = content.split('\n');
82
+ const regex = new RegExp(pattern.regex.source, pattern.regex.flags.includes('g') ? pattern.regex.flags : pattern.regex.flags + 'g');
83
+ for (let i = 0; i < lines.length; i++) {
84
+ regex.lastIndex = 0;
85
+ let m;
86
+ while ((m = regex.exec(lines[i] ?? '')) !== null) {
87
+ results.push({
88
+ variable: m[1] != null ? `${m[0].split('(')[0]}.${m[1]}` : m[0].slice(0, 60),
89
+ line: startLine + i,
90
+ column: m.index,
91
+ });
92
+ }
93
+ }
94
+ return results;
95
+ }
96
+ // ── Public API ─────────────────────────────────────────────────────────────────
97
+ /**
98
+ * Analyzes a .nx file for security leaks.
99
+ *
100
+ * @param source - Raw .nx file content
101
+ * @param filepath - Path to the file (for error messages)
102
+ * @returns GuardResult with all detected leaks
103
+ *
104
+ * @example
105
+ * import { guard } from '@nexus_js/compiler/guard';
106
+ * const result = guard(source, 'src/routes/+page.nx');
107
+ * if (!result.passed) {
108
+ * for (const leak of result.leaks.filter(l => l.severity === 'error')) {
109
+ * console.error(`[Guard] ${leak.message} (line ${leak.line})`);
110
+ * }
111
+ * process.exit(1);
112
+ * }
113
+ */
114
+ export function guard(source, filepath) {
115
+ const t0 = Date.now();
116
+ const leaks = [];
117
+ const serverBlock = extractServerBlock(source);
118
+ const clientScripts = extractClientScripts(source);
119
+ // ── Step 1: Find secret variable names defined in the server block ─────────
120
+ const serverSecrets = new Map();
121
+ for (const pattern of PATTERNS) {
122
+ const regex = new RegExp(pattern.regex.source, 'gi');
123
+ let m;
124
+ while ((m = regex.exec(serverBlock)) !== null) {
125
+ // Look backwards for a variable assignment: const apiKey = process.env.API_KEY
126
+ const beforeMatch = serverBlock.slice(0, m.index);
127
+ const assignMatch = /(?:const|let|var)\s+(\w+)\s*=\s*$/.exec(beforeMatch.trimEnd());
128
+ if (assignMatch?.[1]) {
129
+ serverSecrets.set(assignMatch[1], { type: pattern.type, severity: pattern.severity });
130
+ }
131
+ }
132
+ }
133
+ // ── Step 2: Scan client scripts for secret variable references ─────────────
134
+ for (const { content, startLine } of clientScripts) {
135
+ // Check if any server-assigned secret variable is used here
136
+ for (const [varName, info] of serverSecrets) {
137
+ const varRe = new RegExp(`\\b${varName}\\b`, 'g');
138
+ const lines = content.split('\n');
139
+ for (let i = 0; i < lines.length; i++) {
140
+ const col = (lines[i] ?? '').search(varRe);
141
+ if (col !== -1) {
142
+ leaks.push({
143
+ type: info.type,
144
+ variable: varName,
145
+ line: startLine + i,
146
+ column: col,
147
+ severity: 'error',
148
+ message: `Secret "${varName}" defined in server block may leak to client bundle`,
149
+ hint: `Pass only sanitized values to island props. Never forward server secrets as component attributes.`,
150
+ });
151
+ }
152
+ }
153
+ }
154
+ // Check for direct secret patterns in client code
155
+ for (const pattern of PATTERNS) {
156
+ const found = scanBlock(content, startLine, pattern);
157
+ for (const f of found) {
158
+ // Skip if already caught by step 1
159
+ if (!leaks.some((l) => l.line === f.line && l.variable === f.variable)) {
160
+ leaks.push({
161
+ type: pattern.type,
162
+ variable: f.variable,
163
+ line: f.line,
164
+ column: f.column,
165
+ severity: pattern.severity,
166
+ message: `${pattern.type === 'env-var' ? 'process.env' : 'Secret pattern'} "${f.variable}" in client-facing script`,
167
+ hint: pattern.hint,
168
+ });
169
+ }
170
+ }
171
+ }
172
+ }
173
+ // Deduplicate by line+variable
174
+ const seen = new Set();
175
+ const unique = leaks.filter((l) => {
176
+ const key = `${l.line}:${l.variable}`;
177
+ if (seen.has(key))
178
+ return false;
179
+ seen.add(key);
180
+ return true;
181
+ });
182
+ return {
183
+ filepath,
184
+ passed: unique.filter((l) => l.severity === 'error').length === 0,
185
+ leaks: unique,
186
+ scanned: source.split('\n').length,
187
+ duration: Date.now() - t0,
188
+ };
189
+ }
190
+ /** Format a GuardResult for terminal output with ANSI colors. */
191
+ export function formatGuardResult(result) {
192
+ const c = {
193
+ reset: '\x1b[0m', bold: '\x1b[1m', dim: '\x1b[2m',
194
+ red: '\x1b[31m', green: '\x1b[32m', yellow: '\x1b[33m',
195
+ cyan: '\x1b[36m', gray: '\x1b[90m',
196
+ };
197
+ if (result.passed && result.leaks.length === 0) {
198
+ return ` ${c.green}🛡️ Guard${c.reset} ${result.filepath} ${c.dim}${result.scanned} lines — no leaks found${c.reset}`;
199
+ }
200
+ const lines = [
201
+ ` ${c.red}🛡️ Guard${c.reset} ${result.filepath} ${c.dim}${result.scanned} lines${c.reset}`,
202
+ ];
203
+ for (const leak of result.leaks) {
204
+ const sCol = leak.severity === 'error' ? c.red : c.yellow;
205
+ lines.push(`\n ${sCol}${leak.severity.toUpperCase().padEnd(7)}${c.reset}` +
206
+ ` line ${leak.line} ${c.bold}"${leak.variable}"${c.reset}` +
207
+ `\n ${c.dim}${leak.message}${c.reset}` +
208
+ `\n ${c.cyan}Hint: ${leak.hint}${c.reset}`);
209
+ }
210
+ return lines.join('\n');
211
+ }
212
+ //# sourceMappingURL=guard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"guard.js","sourceRoot":"","sources":["../src/guard.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ export { parse } from './parser.js';
2
+ export { generate } from './codegen.js';
3
+ export { extractServerActionsFromSource } from './server-actions-extract.js';
4
+ export type { ParsedComponent, CompileOptions, CompileResult, NexusBlock, IslandDirective, IslandHydration, ServerAction, IslandManifest, IslandEntry, RouteManifest, RouteEntry, CompileWarning, } from './types.js';
5
+ import type { CompileOptions, CompileResult } from './types.js';
6
+ /** High-level API: compile a .nx source string end-to-end */
7
+ export declare function compile(source: string, filepath: string, opts?: Partial<CompileOptions>): CompileResult;
8
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AACpC,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AACxC,OAAO,EAAE,8BAA8B,EAAE,MAAM,6BAA6B,CAAC;AAC7E,YAAY,EACV,eAAe,EACf,cAAc,EACd,aAAa,EACb,UAAU,EACV,eAAe,EACf,eAAe,EACf,YAAY,EACZ,cAAc,EACd,WAAW,EACX,aAAa,EACb,UAAU,EACV,cAAc,GACf,MAAM,YAAY,CAAC;AAIpB,OAAO,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEhE,6DAA6D;AAC7D,wBAAgB,OAAO,CACrB,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,IAAI,GAAE,OAAO,CAAC,cAAc,CAAM,GACjC,aAAa,CAYf"}
package/dist/index.js ADDED
@@ -0,0 +1,19 @@
1
+ export { parse } from './parser.js';
2
+ export { generate } from './codegen.js';
3
+ export { extractServerActionsFromSource } from './server-actions-extract.js';
4
+ import { parse } from './parser.js';
5
+ import { generate } from './codegen.js';
6
+ /** High-level API: compile a .nx source string end-to-end */
7
+ export function compile(source, filepath, opts = {}) {
8
+ const options = {
9
+ mode: opts.mode ?? 'server',
10
+ dev: opts.dev ?? false,
11
+ ssr: opts.ssr ?? true,
12
+ emitIslandManifest: opts.emitIslandManifest ?? true,
13
+ target: opts.target ?? 'node',
14
+ ...(opts.appRoot !== undefined ? { appRoot: opts.appRoot } : {}),
15
+ };
16
+ const parsed = parse(source, filepath);
17
+ return generate(parsed, options);
18
+ }
19
+ //# sourceMappingURL=index.js.map