novac 2.2.0 → 2.2.2

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 (122) hide show
  1. package/LICENSE +0 -0
  2. package/README.md +0 -0
  3. package/bin/novac +6 -3
  4. package/bin/nvc +0 -0
  5. package/bin/nvml +0 -0
  6. package/demo.nv +0 -0
  7. package/demo_builtins.nv +0 -0
  8. package/demo_http.nv +0 -0
  9. package/examples/bf.nv +5 -13
  10. package/examples/math.nv +2 -2
  11. package/kits/kitffmpeg/kitdef.js +1174 -0
  12. package/kits/libos/kitdef.js +3135 -0
  13. package/kits/libtasker/kitdef.js +125 -0
  14. package/package.json +1 -1
  15. package/scripts/update-bin.js +0 -0
  16. package/src/core/executor.js +7 -4
  17. package/src/core/lexer.js +2 -2
  18. package/src/index.js +0 -0
  19. package/novac/LICENSE +0 -21
  20. package/novac/README.md +0 -1823
  21. package/novac/bin/novac +0 -950
  22. package/novac/bin/nvc +0 -522
  23. package/novac/bin/nvml +0 -542
  24. package/novac/demo.nv +0 -245
  25. package/novac/demo_builtins.nv +0 -209
  26. package/novac/demo_http.nv +0 -62
  27. package/novac/examples/bf.nv +0 -69
  28. package/novac/examples/math.nv +0 -21
  29. package/novac/kits/kitai/kitdef.js +0 -2185
  30. package/novac/kits/kitansi/kitdef.js +0 -1402
  31. package/novac/kits/kitformat/kitdef.js +0 -1485
  32. package/novac/kits/kitgps/kitdef.js +0 -1862
  33. package/novac/kits/kitlibfs/kitdef.js +0 -231
  34. package/novac/kits/kitlibproc/kitdef.js +0 -78
  35. package/novac/kits/kitmatrix/ex.js +0 -19
  36. package/novac/kits/kitmatrix/kitdef.js +0 -960
  37. package/novac/kits/kitmpatch/kitdef.js +0 -906
  38. package/novac/kits/kitnovacweb/README.md +0 -1572
  39. package/novac/kits/kitnovacweb/demo.nv +0 -12
  40. package/novac/kits/kitnovacweb/demo.nvml +0 -71
  41. package/novac/kits/kitnovacweb/index.nova +0 -12
  42. package/novac/kits/kitnovacweb/kitdef.js +0 -692
  43. package/novac/kits/kitnovacweb/nova.kit.json +0 -8
  44. package/novac/kits/kitnovacweb/nvml/executor.js +0 -739
  45. package/novac/kits/kitnovacweb/nvml/index.js +0 -67
  46. package/novac/kits/kitnovacweb/nvml/lexer.js +0 -263
  47. package/novac/kits/kitnovacweb/nvml/parser.js +0 -508
  48. package/novac/kits/kitnovacweb/nvml/renderer.js +0 -924
  49. package/novac/kits/kitparse/kitdef.js +0 -1688
  50. package/novac/kits/kitregex++/kitdef.js +0 -1353
  51. package/novac/kits/kitrequire/kitdef.js +0 -1599
  52. package/novac/kits/kitx11/kitdef.js +0 -1
  53. package/novac/kits/kitx11/kitx11.js +0 -2472
  54. package/novac/kits/kitx11/kitx11_conn.js +0 -948
  55. package/novac/kits/kitx11/kitx11_worker.js +0 -121
  56. package/novac/kits/libtea/tf.js +0 -2691
  57. package/novac/kits/libterm/ex.js +0 -285
  58. package/novac/kits/libterm/kitdef.js +0 -1927
  59. package/novac/node_modules/chalk/license +0 -9
  60. package/novac/node_modules/chalk/package.json +0 -83
  61. package/novac/node_modules/chalk/readme.md +0 -297
  62. package/novac/node_modules/chalk/source/index.d.ts +0 -325
  63. package/novac/node_modules/chalk/source/index.js +0 -225
  64. package/novac/node_modules/chalk/source/utilities.js +0 -33
  65. package/novac/node_modules/chalk/source/vendor/ansi-styles/index.d.ts +0 -236
  66. package/novac/node_modules/chalk/source/vendor/ansi-styles/index.js +0 -223
  67. package/novac/node_modules/chalk/source/vendor/supports-color/browser.d.ts +0 -1
  68. package/novac/node_modules/chalk/source/vendor/supports-color/browser.js +0 -34
  69. package/novac/node_modules/chalk/source/vendor/supports-color/index.d.ts +0 -55
  70. package/novac/node_modules/chalk/source/vendor/supports-color/index.js +0 -190
  71. package/novac/node_modules/commander/LICENSE +0 -22
  72. package/novac/node_modules/commander/Readme.md +0 -1176
  73. package/novac/node_modules/commander/esm.mjs +0 -16
  74. package/novac/node_modules/commander/index.js +0 -24
  75. package/novac/node_modules/commander/lib/argument.js +0 -150
  76. package/novac/node_modules/commander/lib/command.js +0 -2777
  77. package/novac/node_modules/commander/lib/error.js +0 -39
  78. package/novac/node_modules/commander/lib/help.js +0 -747
  79. package/novac/node_modules/commander/lib/option.js +0 -380
  80. package/novac/node_modules/commander/lib/suggestSimilar.js +0 -101
  81. package/novac/node_modules/commander/package-support.json +0 -19
  82. package/novac/node_modules/commander/package.json +0 -82
  83. package/novac/node_modules/commander/typings/esm.d.mts +0 -3
  84. package/novac/node_modules/commander/typings/index.d.ts +0 -1113
  85. package/novac/node_modules/node-addon-api/LICENSE.md +0 -9
  86. package/novac/node_modules/node-addon-api/README.md +0 -95
  87. package/novac/node_modules/node-addon-api/common.gypi +0 -21
  88. package/novac/node_modules/node-addon-api/except.gypi +0 -25
  89. package/novac/node_modules/node-addon-api/index.js +0 -14
  90. package/novac/node_modules/node-addon-api/napi-inl.deprecated.h +0 -186
  91. package/novac/node_modules/node-addon-api/napi-inl.h +0 -7165
  92. package/novac/node_modules/node-addon-api/napi.h +0 -3364
  93. package/novac/node_modules/node-addon-api/node_addon_api.gyp +0 -42
  94. package/novac/node_modules/node-addon-api/node_api.gyp +0 -9
  95. package/novac/node_modules/node-addon-api/noexcept.gypi +0 -26
  96. package/novac/node_modules/node-addon-api/nothing.c +0 -0
  97. package/novac/node_modules/node-addon-api/package-support.json +0 -21
  98. package/novac/node_modules/node-addon-api/package.json +0 -480
  99. package/novac/node_modules/node-addon-api/tools/README.md +0 -73
  100. package/novac/node_modules/node-addon-api/tools/check-napi.js +0 -99
  101. package/novac/node_modules/node-addon-api/tools/clang-format.js +0 -71
  102. package/novac/node_modules/node-addon-api/tools/conversion.js +0 -301
  103. package/novac/node_modules/serialize-javascript/LICENSE +0 -27
  104. package/novac/node_modules/serialize-javascript/README.md +0 -149
  105. package/novac/node_modules/serialize-javascript/index.js +0 -297
  106. package/novac/node_modules/serialize-javascript/package.json +0 -33
  107. package/novac/package.json +0 -27
  108. package/novac/scripts/update-bin.js +0 -24
  109. package/novac/src/core/bstd.js +0 -1035
  110. package/novac/src/core/config.js +0 -155
  111. package/novac/src/core/describe.js +0 -187
  112. package/novac/src/core/emitter.js +0 -499
  113. package/novac/src/core/error.js +0 -86
  114. package/novac/src/core/executor.js +0 -5606
  115. package/novac/src/core/formatter.js +0 -686
  116. package/novac/src/core/lexer.js +0 -1026
  117. package/novac/src/core/nova_builtins.js +0 -717
  118. package/novac/src/core/nova_thread_worker.js +0 -166
  119. package/novac/src/core/parser.js +0 -2181
  120. package/novac/src/core/types.js +0 -112
  121. package/novac/src/index.js +0 -28
  122. package/novac/src/runtime/stdlib.js +0 -244
package/novac/bin/nvml DELETED
@@ -1,542 +0,0 @@
1
- #!/usr/bin/env node
2
- 'use strict';
3
-
4
- /**
5
- * nvml — NVML CLI
6
- * Part of the novac toolchain.
7
- *
8
- * Usage:
9
- * nvml <port> <file.nvml> [route] Serve file.nvml on localhost:<port>
10
- * nvml compile <file.nvml> Compile to HTML (stdout)
11
- * nvml ast <file.nvml> Print AST as JSON
12
- * nvml tokens <file.nvml> Print token stream as JSON
13
- * nvml check <file.nvml> Check syntax
14
- * nvml --help Show help
15
- * nvml --version Show version
16
- */
17
-
18
- const fs = require('fs');
19
- const path = require('path');
20
- const http = require('http');
21
- const os = require('os');
22
- const url = require('url');
23
-
24
- const chalk = (() => {
25
- try { return require('chalk').default ?? require('chalk'); } catch { return null; }
26
- })();
27
-
28
- const VERSION = '1.0.0';
29
-
30
- // ── colour helpers (graceful fallback if chalk absent) ────────
31
- const c = {
32
- green: s => chalk ? chalk.green(s) : s,
33
- red: s => chalk ? chalk.red(s) : s,
34
- cyan: s => chalk ? chalk.cyan(s) : s,
35
- bold: s => chalk ? chalk.bold(s) : s,
36
- dim: s => chalk ? chalk.dim(s) : s,
37
- yellow: s => chalk ? chalk.yellow(s) : s,
38
- };
39
-
40
- function die(msg) {
41
- process.stderr.write(c.red('[nvml] ' + msg) + '\n');
42
- process.exit(1);
43
- }
44
-
45
- // ── locate kitnovacweb/nvml/index.js ─────────────────────────
46
- function resolveNvmlKit() {
47
- const candidates = [
48
- path.join(__dirname, '..', 'kits', 'kitnovacweb', 'nvml', 'index.js'),
49
- path.join(process.cwd(), 'nova_modules', 'kitnovacweb', 'nvml', 'index.js'),
50
- path.join(os.homedir(), '.novac', 'nova_modules', 'kitnovacweb', 'nvml', 'index.js'),
51
- path.join(__dirname, '..', 'node_modules', 'kitnovacweb', 'nvml', 'index.js'),
52
- ];
53
- for (const p of candidates) {
54
- if (fs.existsSync(p)) return p;
55
- }
56
- return null;
57
- }
58
-
59
- function loadKit() {
60
- const kitPath = resolveNvmlKit();
61
- if (!kitPath) {
62
- die(
63
- 'kitnovacweb not found.\n\n' +
64
- ' Install it with:\n' +
65
- ' novac etc kit kitnovacweb\n\n' +
66
- ' Or put it in nova_modules/kitnovacweb/'
67
- );
68
- }
69
- return require(kitPath);
70
- }
71
-
72
- function loadLexer() {
73
- const kitPath = resolveNvmlKit();
74
- if (!kitPath) die('kitnovacweb not found.');
75
- return require(path.join(path.dirname(kitPath), 'lexer.js'));
76
- }
77
-
78
- // ── Nova runtime loader ───────────────────────────────────────
79
- // Finds src/index.js and returns the `run` function, or null if not found.
80
- function resolveNovaRun() {
81
- const searchPaths = [
82
- path.join(__dirname, '..', 'src', 'index.js'),
83
- path.join(process.cwd(), 'src', 'index.js'),
84
- path.join(os.homedir(), '.novac', 'node_modules', 'novac', 'src', 'index.js'),
85
- path.resolve(process.cwd(), 'node_modules', 'novac', 'src', 'index.js'),
86
- ];
87
- for (const p of searchPaths) {
88
- if (fs.existsSync(p)) {
89
- try {
90
- const m = require(p);
91
- return m.run || m.default?.run || null;
92
- } catch (e) {
93
- process.stderr.write(`[nvml] Warning: failed to load novac runtime from ${p}: ${e.message}\n`);
94
- return null;
95
- }
96
- }
97
- }
98
- return null;
99
- }
100
-
101
- // ── document proxy for server-side Nova scripts ───────────────
102
- function findElementById(elements, id) {
103
- for (const el of elements) {
104
- if (el.props && el.props.id === id) return el;
105
- if (el.children) {
106
- const found = findElementById(el.children, id);
107
- if (found) return found;
108
- }
109
- }
110
- return null;
111
- }
112
-
113
- function makeDocumentProxy(doc) {
114
- return {
115
- setConfig: (k, v) => { doc.config[k] = v; },
116
- getConfig: (k) => doc.config[k],
117
- setTitle: (t) => { doc.config.title = t; },
118
- setMeta: (k, v) => { doc.config[k] = v; },
119
- set: (id, content) => {
120
- const el = findElementById(doc.visual, id);
121
- if (el) el.textValue = String(content);
122
- },
123
- get: (id) => {
124
- const el = findElementById(doc.visual, id);
125
- return el ? (el.textValue || null) : null;
126
- },
127
- setProp: (id, key, value) => {
128
- const el = findElementById(doc.visual, id);
129
- if (el) el.props[String(key)] = String(value);
130
- },
131
- getProp: (id, key) => {
132
- const el = findElementById(doc.visual, id);
133
- return el ? (el.props[String(key)] || null) : null;
134
- },
135
- addClass: (id, cls) => {
136
- const el = findElementById(doc.visual, id);
137
- if (!el) return;
138
- const existing = el.props.class || '';
139
- el.props.class = (existing ? existing + ' ' : '') + cls;
140
- },
141
- setClass: (id, cls) => {
142
- const el = findElementById(doc.visual, id);
143
- if (el) el.props.class = String(cls);
144
- },
145
- addElementStyle: (id, css) => {
146
- const el = findElementById(doc.visual, id);
147
- if (el) el.ss = (el.ss ? el.ss + '\n' : '') + css;
148
- },
149
- addStyle: (css) => { doc.globalStyles += '\n' + css; },
150
- hide: (id) => {
151
- const el = findElementById(doc.visual, id);
152
- if (el) el.ss = (el.ss ? el.ss + '\n' : '') + 'display: none;';
153
- },
154
- show: (id, display) => {
155
- const el = findElementById(doc.visual, id);
156
- if (el) el.ss = (el.ss ? el.ss + '\n' : '') + `display: ${display || 'block'};`;
157
- },
158
- alert: (_msg) => {},
159
- config: doc.config,
160
- _doc: doc,
161
- };
162
- }
163
-
164
- // ── error page (served when compile fails during --serve) ─────
165
- function errorPage(file, err) {
166
- const esc = s => String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
167
- return `<!DOCTYPE html>
168
- <html><head><meta charset="UTF-8"><title>NVML Error</title>
169
- <style>
170
- body{font-family:monospace;background:#1a1a1a;color:#eee;padding:2rem;margin:0}
171
- h1{color:#f87171;font-size:1.1rem;margin:0 0 .75rem}
172
- .file{color:#94a3b8;font-size:.8rem;margin-bottom:1.5rem}
173
- pre{background:#111;border:1px solid #333;padding:1rem;border-radius:6px;
174
- white-space:pre-wrap;word-break:break-word;font-size:.85rem;line-height:1.6}
175
- .msg{color:#fca5a5}
176
- .stack{color:#475569;font-size:.75rem;margin-top:.75rem}
177
- </style></head><body>
178
- <h1>NVML Error</h1>
179
- <div class="file">${esc(file)}</div>
180
- <pre class="msg">${esc(err.message)}</pre>
181
- <pre class="stack">${esc(err.stack || '')}</pre>
182
- </body></html>`;
183
- }
184
-
185
- // ── serve ─────────────────────────────────────────────────────
186
- function cmdServe(port, nvmlFile, route) {
187
- const nvml = loadKit();
188
- const absFile = path.resolve(process.cwd(), nvmlFile);
189
-
190
- if (!fs.existsSync(absFile)) die(`File not found: ${absFile}`);
191
- if (!route.startsWith('/')) route = '/' + route;
192
-
193
- // Load the Nova runtime once — used for every server-side script block
194
- const novaRun = resolveNovaRun();
195
- if (!novaRun) {
196
- process.stderr.write(
197
- c.yellow('[nvml] Warning: novac runtime not found — server-side Nova scripts will not run.\n') +
198
- c.dim(' Searched: src/index.js relative to bin/ and cwd\n\n')
199
- );
200
- }
201
-
202
- // Build a novaRunner for a given NvmlDocument + request context
203
- function makeNovaRunner(reqCtx) {
204
- if (!novaRun) return null;
205
- return (code, doc) => {
206
- const scope = {
207
- document: makeDocumentProxy(doc),
208
- request: reqCtx || {},
209
- };
210
- novaRun(code, scope);
211
- };
212
- }
213
-
214
- // Build a mutation-capturing proxy for /_nvml/run
215
- const _sseClients = new Set(); // Server-Sent Events connections
216
-
217
- function makeMutationProxy(mutations, live) {
218
- return {
219
- // config
220
- setConfig: (k, v) => mutations.push({ type: 'setConfig', key: String(k), value: v }),
221
- setTitle: (t) => mutations.push({ type: 'setConfig', key: 'title', value: t }),
222
- setMeta: (k, v) => mutations.push({ type: 'setConfig', key: String(k), value: v }),
223
- // text / html
224
- set: (id, val) => mutations.push({ type: 'setText', id: String(id), value: String(val) }),
225
- setHTML: (id, val) => mutations.push({ type: 'setHTML', id: String(id), value: String(val) }),
226
- // props / attrs
227
- setProp: (id, k, v) => mutations.push({ type: 'setProp', id: String(id), key: String(k), value: String(v) }),
228
- setAttr: (id, k, v) => mutations.push({ type: 'setAttr', id: String(id), key: String(k), value: String(v) }),
229
- removeAttr: (id, k) => mutations.push({ type: 'removeAttr', id: String(id), key: String(k) }),
230
- // classes
231
- addClass: (id, cls) => mutations.push({ type: 'addClass', id: String(id), value: String(cls) }),
232
- removeClass: (id, cls) => mutations.push({ type: 'removeClass', id: String(id), value: String(cls) }),
233
- toggleClass: (id, cls, f) => mutations.push({ type: 'toggleClass', id: String(id), value: String(cls), force: f }),
234
- setClass: (id, cls) => mutations.push({ type: 'setClass', id: String(id), value: String(cls) }),
235
- // style
236
- addStyle: (css) => mutations.push({ type: 'addStyle', value: String(css) }),
237
- addElementStyle:(id, css) => mutations.push({ type: 'addStyle', id: String(id), value: String(css) }),
238
- setStyle: (id, k, v) => mutations.push({ type: 'setStyle', id: String(id), key: String(k), value: String(v) }),
239
- setCSSVar: (name, val) => mutations.push({ type: 'setCSSVar', name: String(name), value: String(val) }),
240
- // visibility
241
- hide: (id, t) => mutations.push({ type: 'hide', id: String(id), transition: t || null }),
242
- show: (id, d, t) => mutations.push({ type: 'show', id: String(id), value: d || 'block', transition: t || null }),
243
- // DOM manipulation
244
- remove: (id, t) => mutations.push({ type: 'remove', id: String(id), transition: t || null }),
245
- appendChild: (id, html) => mutations.push({ type: 'appendChild', id: String(id), html: String(html) }),
246
- insertBefore: (id, html) => mutations.push({ type: 'insertBefore', id: String(id), html: String(html) }),
247
- insertAfter: (id, html) => mutations.push({ type: 'insertAfter', id: String(id), html: String(html) }),
248
- // focus / scroll
249
- focus: (id) => mutations.push({ type: 'focus', id: String(id) }),
250
- blur: (id) => mutations.push({ type: 'blur', id: String(id) }),
251
- scroll: (id, beh) => mutations.push({ type: 'scroll', id: String(id), behavior: beh || 'smooth' }),
252
- // signals
253
- setSignal: (name, val) => mutations.push({ type: 'setSignal', name: String(name), value: val }),
254
- // list patch
255
- patchList: (id, items, tpl, key) => mutations.push({ type: 'patchList', id: String(id), items, template: String(tpl), key: key || null }),
256
- // navigation
257
- navigate: (path) => mutations.push({ type: 'navigate', path: String(path) }),
258
- reload: () => mutations.push({ type: 'reload' }),
259
- redirect: (url) => mutations.push({ type: 'redirect', url: String(url) }),
260
- // feedback
261
- alert: (msg) => mutations.push({ type: 'alert', value: String(msg) }),
262
- toast: (msg, d, t) => mutations.push({ type: 'toast', value: String(msg), duration: d || 3000, type: t || 'info' }),
263
- console: (msg, lvl) => mutations.push({ type: 'console', value: String(msg), level: lvl || 'log' }),
264
- // reads from live snapshot
265
- get: (id) => (live.elements && live.elements[id]) ? live.elements[id].text : null,
266
- getValue: (id) => (live.elements && live.elements[id]) ? live.elements[id].value : null,
267
- getClass: (id) => (live.elements && live.elements[id]) ? live.elements[id].class : null,
268
- getConfig: (k) => live[k] ?? null,
269
- getSignal: (n) => (live.state && live.state[n]) ?? null,
270
- config: live,
271
- // SSE push (server → all connected clients)
272
- push: (name, val) => {
273
- const msg = `event: signal\ndata: ${JSON.stringify({ name, value: val })}\n\n`;
274
- _sseClients.forEach(c => { try { c.write(msg); } catch(_) {} });
275
- },
276
- pushMutations: (muts) => {
277
- const msg = `event: mutations\ndata: ${JSON.stringify(muts)}\n\n`;
278
- _sseClients.forEach(c => { try { c.write(msg); } catch(_) {} });
279
- },
280
- };
281
- }
282
-
283
- const server = http.createServer((req, res) => {
284
- const parsed = url.parse(req.url || '/');
285
- const urlPath = parsed.pathname || '/';
286
-
287
- // CORS
288
- res.setHeader('Access-Control-Allow-Origin', '*');
289
- res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
290
- res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
291
- if (req.method === 'OPTIONS') { res.writeHead(204); res.end(); return; }
292
-
293
- // ── /_nvml/sse — Server-Sent Events for server-push signal updates ──
294
- if (urlPath === '/_nvml/sse' && req.method === 'GET') {
295
- res.writeHead(200, {
296
- 'Content-Type': 'text/event-stream',
297
- 'Cache-Control': 'no-cache',
298
- 'Connection': 'keep-alive',
299
- 'X-Accel-Buffering': 'no',
300
- });
301
- res.write('retry: 3000\n\n'); // tell client to reconnect after 3s if disconnected
302
- _sseClients.add(res);
303
- req.on('close', () => _sseClients.delete(res));
304
- return;
305
- }
306
-
307
- // ── /_nvml/run — triggered server-side Nova scripts ──────
308
- if (urlPath === '/_nvml/run' && req.method === 'POST') {
309
- let rawBody = '';
310
- req.on('data', chunk => { rawBody += chunk; });
311
- req.on('end', () => {
312
- let code, live;
313
- try {
314
- const body = JSON.parse(rawBody);
315
- code = String(body.code || '');
316
- live = body.live || {};
317
- } catch (e) {
318
- return sendJSON(res, 400, { error: 'Invalid JSON body' });
319
- }
320
-
321
- if (!code.trim()) return sendJSON(res, 200, { mutations: [] });
322
-
323
- if (!novaRun) {
324
- return sendJSON(res, 503, { error: 'Nova runtime not available' });
325
- }
326
-
327
- const mutations = [];
328
- const docProxy = makeMutationProxy(mutations, live);
329
- try {
330
- novaRun(code, { document: docProxy, request: live });
331
- return sendJSON(res, 200, { mutations });
332
- } catch (e) {
333
- process.stderr.write(c.red(`[nvml] /_nvml/run error: ${e.message}`) + '\n');
334
- return sendJSON(res, 500, { error: e.message, mutations });
335
- }
336
- });
337
- return;
338
- }
339
-
340
- // ── main route ───────────────────────────────────────────
341
- if (urlPath !== route) {
342
- res.writeHead(404, { 'Content-Type': 'text/plain' });
343
- res.end(`404 — this server only handles: ${route}`);
344
- return;
345
- }
346
-
347
- let source;
348
- try { source = fs.readFileSync(absFile, 'utf8'); }
349
- catch (e) { res.writeHead(500); res.end(e.message); return; }
350
-
351
- // Build request context
352
- const reqContext = {
353
- method: req.method,
354
- url: req.url,
355
- path: urlPath,
356
- query: Object.fromEntries(new URLSearchParams(parsed.query || '')),
357
- headers: req.headers,
358
- params: {},
359
- body: null,
360
- };
361
-
362
- const compileAndSend = () => {
363
- let html;
364
- try {
365
- html = nvml.compile(source, {
366
- novaRunner: makeNovaRunner(reqContext),
367
- novaEmitter: null,
368
- });
369
- } catch (e) {
370
- process.stderr.write(c.red(`[nvml] compile error: ${e.message}`) + '\n');
371
- const errHtml = errorPage(nvmlFile, e);
372
- res.writeHead(500, {
373
- 'Content-Type': 'text/html; charset=UTF-8',
374
- 'Content-Length': Buffer.byteLength(errHtml),
375
- });
376
- res.end(errHtml);
377
- return;
378
- }
379
-
380
- res.writeHead(200, {
381
- 'Content-Type': 'text/html; charset=UTF-8',
382
- 'Content-Length': Buffer.byteLength(html),
383
- 'Cache-Control': 'no-store',
384
- 'X-Powered-By': `nvml/${VERSION}`,
385
- });
386
- res.end(html);
387
- process.stdout.write(c.dim(` ${req.method} ${urlPath} → 200`) + '\n');
388
- };
389
-
390
- // Collect body for POST/PUT/PATCH before compiling
391
- if (['POST', 'PUT', 'PATCH'].includes(req.method)) {
392
- let rawBody = '';
393
- req.on('data', chunk => { rawBody += chunk; });
394
- req.on('end', () => {
395
- try { reqContext.body = JSON.parse(rawBody); } catch { reqContext.body = rawBody; }
396
- compileAndSend();
397
- });
398
- } else {
399
- compileAndSend();
400
- }
401
- });
402
-
403
- server.on('error', e => {
404
- if (e.code === 'EADDRINUSE') die(`Port ${port} is already in use.`);
405
- die(`Server error: ${e.message}`);
406
- });
407
-
408
- server.listen(port, '127.0.0.1', () => {
409
- process.stdout.write(
410
- c.green('\n nvml server running') + '\n' +
411
- c.cyan(` http://localhost:${port}${route}`) + '\n\n' +
412
- c.dim(` file : ${absFile}`) + '\n' +
413
- c.dim(` route : ${route}`) + '\n' +
414
- c.dim(` reload : automatic (re-reads file on every request)`) + '\n\n' +
415
- ` Press ${c.bold('Ctrl+C')} to stop.\n\n`
416
- );
417
- });
418
-
419
- process.on('SIGINT', () => { process.stdout.write('\n'); process.exit(0); });
420
- process.on('SIGTERM', () => process.exit(0));
421
- }
422
-
423
- // ── helpers ───────────────────────────────────────────────────
424
- function sendJSON(res, code, obj) {
425
- const body = JSON.stringify(obj);
426
- res.writeHead(code, {
427
- 'Content-Type': 'application/json; charset=UTF-8',
428
- 'Content-Length': Buffer.byteLength(body),
429
- 'Access-Control-Allow-Origin': '*',
430
- });
431
- res.end(body);
432
- }
433
-
434
- // ── compile ───────────────────────────────────────────────────
435
- function cmdCompile(nvmlFile) {
436
- const nvml = loadKit();
437
- const absFile = path.resolve(process.cwd(), nvmlFile);
438
- if (!fs.existsSync(absFile)) die(`File not found: ${absFile}`);
439
- const novaRun = resolveNovaRun();
440
- const novaRunner = novaRun
441
- ? (code, doc) => novaRun(code, { document: makeDocumentProxy(doc), request: {} })
442
- : null;
443
- try {
444
- process.stdout.write(nvml.compile(fs.readFileSync(absFile, 'utf8'), { novaRunner, novaEmitter: null }));
445
- } catch (e) {
446
- die(`Compile error: ${e.message}`);
447
- }
448
- }
449
-
450
- // ── ast ───────────────────────────────────────────────────────
451
- function cmdAst(nvmlFile) {
452
- const nvml = loadKit();
453
- const absFile = path.resolve(process.cwd(), nvmlFile);
454
- if (!fs.existsSync(absFile)) die(`File not found: ${absFile}`);
455
- try {
456
- process.stdout.write(JSON.stringify(nvml.parse(fs.readFileSync(absFile, 'utf8')), null, 2) + '\n');
457
- } catch (e) {
458
- die(`Parse error: ${e.message}`);
459
- }
460
- }
461
-
462
- // ── tokens ────────────────────────────────────────────────────
463
- function cmdTokens(nvmlFile) {
464
- const { Lexer } = loadLexer();
465
- const absFile = path.resolve(process.cwd(), nvmlFile);
466
- if (!fs.existsSync(absFile)) die(`File not found: ${absFile}`);
467
- try {
468
- process.stdout.write(JSON.stringify(new Lexer(fs.readFileSync(absFile, 'utf8')).tokenize(), null, 2) + '\n');
469
- } catch (e) {
470
- die(`Lex error: ${e.message}`);
471
- }
472
- }
473
-
474
- // ── check ─────────────────────────────────────────────────────
475
- function cmdCheck(nvmlFile) {
476
- const nvml = loadKit();
477
- const absFile = path.resolve(process.cwd(), nvmlFile);
478
- if (!fs.existsSync(absFile)) die(`File not found: ${absFile}`);
479
- try {
480
- nvml.parse(fs.readFileSync(absFile, 'utf8'));
481
- process.stdout.write(c.green(` Syntax OK`) + c.dim(` — ${nvmlFile}`) + '\n');
482
- } catch (e) {
483
- die(`Syntax error: ${e.message}`);
484
- }
485
- }
486
-
487
- // ── help ──────────────────────────────────────────────────────
488
- function printHelp() {
489
- process.stdout.write(`
490
- ${c.bold('nvml')} v${VERSION} — NVML CLI ${c.dim('(part of novac)')}
491
-
492
- ${c.bold('Usage:')}
493
- nvml ${c.cyan('<port>')} ${c.cyan('<file.nvml>')} Serve file on localhost:<port> (route: /)
494
- nvml ${c.cyan('<port>')} ${c.cyan('<file.nvml>')} ${c.cyan('<route>')} Serve on a specific route
495
- nvml compile ${c.cyan('<file.nvml>')} Compile to HTML and print to stdout
496
- nvml ast ${c.cyan('<file.nvml>')} Print parsed AST as JSON
497
- nvml tokens ${c.cyan('<file.nvml>')} Print token stream as JSON
498
- nvml check ${c.cyan('<file.nvml>')} Validate NVML syntax
499
- nvml --help Show this help
500
- nvml --version Show version
501
-
502
- ${c.bold('Examples:')}
503
- nvml 3000 index.nvml
504
- nvml 8080 about.nvml /about
505
- nvml compile index.nvml > index.html
506
- nvml check index.nvml
507
- nvml ast index.nvml | head -30
508
-
509
- ${c.bold('Notes:')}
510
- The server re-reads the NVML file on every request — no restart needed.
511
- CORS headers are set so nvml-browser can connect without issues.
512
-
513
- `);
514
- }
515
-
516
- // ── entry point ───────────────────────────────────────────────
517
- const args = process.argv.slice(2);
518
-
519
- if (args.length === 0 || args[0] === '--help' || args[0] === '-h') {
520
- printHelp(); process.exit(0);
521
- }
522
-
523
- if (args[0] === '--version' || args[0] === '-v') {
524
- process.stdout.write(`nvml v${VERSION}\n`); process.exit(0);
525
- }
526
-
527
- // subcommands
528
- switch (args[0]) {
529
- case 'compile': if (!args[1]) die('Usage: nvml compile <file.nvml>'); cmdCompile(args[1]); break;
530
- case 'ast': if (!args[1]) die('Usage: nvml ast <file.nvml>'); cmdAst(args[1]); break;
531
- case 'tokens': if (!args[1]) die('Usage: nvml tokens <file.nvml>'); cmdTokens(args[1]); break;
532
- case 'check': if (!args[1]) die('Usage: nvml check <file.nvml>'); cmdCheck(args[1]); break;
533
- default: {
534
- // nvml <port> <file> [route]
535
- const port = parseInt(args[0], 10);
536
- const file = args[1];
537
- const route = args[2] || '/';
538
- if (!port || port < 1 || port > 65535) die(`Invalid port: "${args[0]}"\nUsage: nvml <port> <file.nvml>`);
539
- if (!file) die('Usage: nvml <port> <file.nvml> [route]');
540
- cmdServe(port, file, route);
541
- }
542
- }