volaro 0.0.2 → 0.1.0-alpha.1

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 (34) hide show
  1. package/README.md +88 -22
  2. package/bin/vl.js +437 -28
  3. package/compiler/SOURCE_REV +1 -0
  4. package/compiler/validator/vlcheck/__init__.py +10 -0
  5. package/compiler/validator/vlcheck/__main__.py +128 -0
  6. package/compiler/validator/vlcheck/ast_nodes.py +397 -0
  7. package/compiler/validator/vlcheck/checks.py +1075 -0
  8. package/compiler/validator/vlcheck/diagnostics.py +88 -0
  9. package/compiler/validator/vlcheck/lexer.py +311 -0
  10. package/compiler/validator/vlcheck/parser.py +1432 -0
  11. package/compiler/validator/vlcheck/project_config.py +97 -0
  12. package/compiler/validator/vlcheck/resolve.py +848 -0
  13. package/compiler/validator/vlcheck/test_ids.py +98 -0
  14. package/compiler/vlbuild/styling/README.md +39 -0
  15. package/compiler/vlbuild/styling/build-css.mjs +103 -0
  16. package/compiler/vlbuild/styling/package-lock.json +1254 -0
  17. package/compiler/vlbuild/styling/package.json +15 -0
  18. package/compiler/vlbuild/styling/test-build-css.mjs +69 -0
  19. package/compiler/vlbuild/vlbuild/__init__.py +16 -0
  20. package/compiler/vlbuild/vlbuild/__main__.py +243 -0
  21. package/compiler/vlbuild/vlbuild/assets/vlrt.css +165 -0
  22. package/compiler/vlbuild/vlbuild/assets/vlrt.js +1151 -0
  23. package/compiler/vlbuild/vlbuild/emit.py +1839 -0
  24. package/compiler/vlbuild/vlbuild/server_emit.py +1549 -0
  25. package/compiler/vlbuild/vlbuild/style_config.py +347 -0
  26. package/compiler/vlbuild/vlbuild/styling.py +39 -0
  27. package/examples/station.vl +2 -2
  28. package/language/crib.md +51 -4
  29. package/language/spec.md +20 -1
  30. package/language/supported.md +117 -0
  31. package/lib/env.js +107 -0
  32. package/package.json +19 -2
  33. package/scripts/selftest.mjs +54 -0
  34. package/scripts/sync-compiler.sh +52 -0
package/README.md CHANGED
@@ -1,45 +1,111 @@
1
1
  # Volaro
2
2
 
3
- **Pre-release language reference CLI compiler not included.**
3
+ **Limited alpha (`0.1.0-alpha.1`). Not yet published to npm.** Install from a
4
+ local `npm pack` tarball to try it.
4
5
 
5
6
  Volaro is an experimental application language intended for AI authoring and
6
- human review. This package contains its specification, authoring crib and
7
- examples. It does not include the repository's prototype compiler.
7
+ human review. This package ships the language reference **and a working
8
+ compiler for a supported subset**: `volaro check`, `volaro build`,
9
+ `volaro dev`.
8
10
 
9
- ## Use the reference
11
+ The command is **`volaro`** (canonical). **`vl` is a compatibility alias** for
12
+ the same binary. Run it via your project's `npm run …` scripts or
13
+ `npx --no-install volaro …` inside the project — a bare `npx volaro` / `npx vl`
14
+ from elsewhere may resolve an unrelated package.
15
+
16
+ ## What you need
17
+
18
+ - **Python 3.10+** — runs the compiler, which is bundled inside this package
19
+ (`compiler/`). `volaro` finds `python3` on your PATH; set `VOLARO_PYTHON` to
20
+ choose a different interpreter. A missing or too-old Python is reported with
21
+ the fix, not a stack trace.
22
+ - **Node.js** — runs the `volaro` command and the dev server. The minimum
23
+ depends on what you build:
24
+
25
+ | App shape | Node | Verified in CI |
26
+ |---|---|---|
27
+ | Minimal single-page (view only): `check` / `build` / static `dev` | **≥ 18** | 18, 20, 22, 24 (packaging matrix) |
28
+ | Full-stack: a `service` backed by a `model` → generated `server.js` (`node:sqlite`) | **≥ 22.5** | 22, 24 (packaging matrix, full-stack `dev`) |
29
+ | Password-auth: the generated server's built-in Argon2 | **≥ 24.7** | 24 only (`./verify.sh` browser journey) |
30
+
31
+ A green packaging matrix establishes the minimal-app row across 18–24; it
32
+ does **not** by itself establish the full-stack or auth rows on 18/20.
33
+
34
+ ### Operating systems
35
+
36
+ | OS | Status |
37
+ |---|---|
38
+ | **Linux** | **Verified** — CI (`verify` + `packaging` matrix) and an isolated-install test |
39
+ | **macOS** | **Unverified** — never run. No known blocker, but no evidence. |
40
+ | **Windows** | **Unverified** — the `npm.cmd` / `PYTHONPATH` handling is present but untested; the packaging test scripts are `bash`. |
41
+
42
+ Do not treat this as cross-platform support. Only Linux has been tested.
43
+
44
+ No global install, no repository checkout, and no network access are needed
45
+ once the package is installed.
46
+
47
+ ## Commands
10
48
 
11
49
  ```bash
12
- npx volaro crib
13
- npx volaro spec
14
- npx volaro example sensors
15
- npx volaro example station
50
+ volaro check <path>... # validate .vl source (full resolver), exit non-zero on error
51
+ volaro build app.vl # transpile to ./build (index.html + app.js + runtime)
52
+ volaro build app.vl -o dist --release
53
+ volaro dev # build ./app.vl, serve http://127.0.0.1:5173, rebuild on change
54
+ volaro crib # the authoring reference (write from this)
55
+ volaro supported # what THIS version accepts (the shipped-feature guide)
56
+ volaro spec # the full language design (a superset of this build)
57
+ volaro example sensors # a worked example (also: station)
58
+ volaro version | volaro help
16
59
  ```
17
60
 
18
- The CLI command is `vl`. The crib is approximately 2.8k o200k_base tokens.
61
+ `volaro dev` serves a static bundle for a single-page app. If the app compiles
62
+ to a full-stack server (`server.js`), it runs that instead and needs
63
+ Node 22.5+ for `node:sqlite`.
64
+
65
+ ## Scaffold a project
66
+
67
+ ```bash
68
+ npm create volaro my-app # the separate create-volaro package
69
+ ```
70
+
71
+ It writes one single-page starter, a `volaro.json`, and `package.json` scripts
72
+ (`dev` / `build` / `check`) that call `volaro`.
19
73
 
20
74
  ## Scope and evidence
21
75
 
22
- The project-authored twelve-feature corpus records roughly 3.00× source density
23
- against selected baselines. This is not a general productivity result.
76
+ The project-authored twelve-feature corpus records roughly 3.00× source
77
+ density against selected baselines not a general productivity result.
24
78
  Security and accessibility checks cover a tested prototype subset, not a
25
- universal guarantee. Independent human-readability validation remains outstanding.
79
+ universal guarantee. Independent human-readability validation is outstanding.
26
80
 
27
- ## Not included
81
+ **MVP scope is single-page.** Routing, nested layouts and multi-page starters
82
+ are later work; `volaro build` / `volaro dev` target one entry file.
28
83
 
29
- `vl new`, `vl check`, `vl build` and `vl dev` are not provided by this package.
30
- The repository has a working compiler for a supported subset, but distributing
31
- that toolchain is separate work. Styling and project creation are not supplied
32
- by this reference CLI.
84
+ ## Not included
33
85
 
34
- `npm create volaro` currently runs a separate placeholder package: it prints
35
- a status message and does not create an application.
86
+ Migrations, deployable production output, a specified stdlib surface, and
87
+ escape hatches to npm packages (email, payments, storage) are not here. Styled
88
+ builds (`theme` / `recipe`) additionally need a one-time
89
+ `npm ci` inside `compiler/vlbuild/styling`; the single-page starter does not
90
+ use them.
36
91
 
37
- The legacy `volara` npm package is unchanged by this release.
92
+ **Form controls:** the element set is `text` inputs plus `button` / `link`
93
+ there is no `checkbox`, `radio`, `select`, `textarea`, or `disabled` attribute.
94
+ Model a toggle as a `button` reading a `state` bool. `variant:` is a literal
95
+ style name, not a computed expression. `if` used as an expression is binary
96
+ (`if c a else b`); `match` and block `if` are statements only. A module-level
97
+ `fn` is not available to a view. **`volaro supported` and `volaro crib` are
98
+ authoritative** for what this build accepts — `volaro spec` describes the
99
+ wider language design, not this subset.
38
100
 
39
101
  ## Local testing
40
102
 
41
- Run `node bin/vl.js crib` from this package directory, or use `npm pack` and
42
- install the tarball into a temporary project. No global installation is required.
103
+ ```bash
104
+ npm pack # runs prepack -> vendors compiler/ -> volaro-*.tgz
105
+ npm test # scripts/selftest.mjs: check + build in a temp dir
106
+ ```
107
+
108
+ The legacy `volara` npm package is unrelated to this release.
43
109
 
44
110
  ## License
45
111
 
package/bin/vl.js CHANGED
@@ -1,34 +1,443 @@
1
1
  #!/usr/bin/env node
2
- import { readFileSync } from "node:fs";
2
+ // The `volaro` command (alias: `vl`): a thin Node wrapper around the Volaro compiler, which
3
+ // ships as Python sources under ../compiler and is invoked as a subprocess.
4
+ //
5
+ // volaro check <path>... validate .vl source (lexer / parser / resolver)
6
+ // volaro build <file.vl> transpile to a runnable bundle
7
+ // volaro dev [file.vl] build, serve on localhost, rebuild on change
8
+ // volaro crib | volaro spec print the language reference
9
+ // volaro example <name> print a worked example
10
+ // volaro version | volaro help
11
+ //
12
+ // Python is located and version-checked up front with an actionable message
13
+ // (see ../lib/env.js). Nothing here depends on the Volaro repository layout.
14
+
15
+ import { readFileSync, existsSync, statSync, readdirSync } from "node:fs";
3
16
  import { fileURLToPath } from "node:url";
4
- import { dirname, join } from "node:path";
17
+ import { dirname, join, resolve as resolvePath, extname, relative, sep, delimiter } from "node:path";
18
+ import { spawn, spawnSync } from "node:child_process";
19
+ import { createServer } from "node:http";
20
+ import { resolvePython, nodeCanRunServer, VolaroEnvError, MIN_NODE_FOR_SERVER } from "../lib/env.js";
5
21
 
6
22
  const root = join(dirname(fileURLToPath(import.meta.url)), "..");
7
23
  const read = (p) => readFileSync(join(root, p), "utf8");
8
- const { version } = JSON.parse(read("package.json"));
9
- const cmd = process.argv[2];
10
-
11
- if (cmd === "crib") process.stdout.write(read("language/crib.md"));
12
- else if (cmd === "spec") process.stdout.write(read("language/spec.md"));
13
- else if (cmd === "example") {
14
- const n = process.argv[3];
15
- if (n === "sensors" || n === "station") process.stdout.write(read(`examples/${n}.vl`));
16
- else console.log("usage: vl example sensors|station");
17
- } else {
18
- console.log(`Volaro ${version} pre-release.
19
-
20
- An application language written to be authored by an AI agent and read by a
21
- person: views, HTTP endpoints, outbound API clients and user accounts.
22
-
23
- vl crib the one-page language reference (~1,700 tokens)
24
- vl spec the full language specification
25
- vl example sensors a worked example — models, unions, a fallible fn
26
- vl example station a worked example — a guarded service and a view
27
-
28
- TO AN AI AGENT reading this: run \`vl crib\` and load its output. That is the
29
- complete authoring reference — it is designed to be read once and written from.
30
-
31
- The toolchain (vl new / check / build / dev) is not published yet, so Volaro
32
- written today cannot yet be compiled from this package.
33
- `);
24
+ const pkg = JSON.parse(read("package.json"));
25
+
26
+ // The canonical command is `volaro`; `vl` is a compatibility alias. Messages
27
+ // use whichever name this process was invoked as, defaulting to `volaro`.
28
+ const CLI = (() => {
29
+ const b = (process.argv[1] || "").split(/[\\/]/).pop() || "";
30
+ return b === "vl" || b === "vl.js" ? "vl" : "volaro";
31
+ })();
32
+
33
+ const COMPILER = join(root, "compiler");
34
+ const VALIDATOR_PP = join(COMPILER, "validator");
35
+ const VLBUILD_PP = join(COMPILER, "vlbuild");
36
+
37
+ function fail(msg, code = 1) {
38
+ process.stderr.write((msg.endsWith("\n") ? msg : msg + "\n"));
39
+ process.exit(code);
40
+ }
41
+
42
+ function haveCompiler() {
43
+ return existsSync(join(VALIDATOR_PP, "vlcheck", "__main__.py")) &&
44
+ existsSync(join(VLBUILD_PP, "vlbuild", "__main__.py"));
45
+ }
46
+
47
+ function python() {
48
+ if (!haveCompiler()) {
49
+ fail(
50
+ `${CLI}: the bundled compiler is missing from this package.\n` +
51
+ "If you are working inside the Volaro repo, run:\n" +
52
+ " bash packaging/volaro/scripts/sync-compiler.sh\n" +
53
+ "An installed copy from `npm pack` / a published release always includes it.",
54
+ );
55
+ }
56
+ try {
57
+ return resolvePython();
58
+ } catch (err) {
59
+ if (err instanceof VolaroEnvError) fail(`${CLI}: ` + err.message);
60
+ throw err;
61
+ }
62
+ }
63
+
64
+ // Shared Python-invocation setup for both the streaming and capturing paths.
65
+ function pyInvocation(pp, moduleName, moduleArgs) {
66
+ const py = python();
67
+ const env = {
68
+ ...process.env,
69
+ PYTHONPATH: [pp, VALIDATOR_PP, process.env.PYTHONPATH].filter(Boolean).join(delimiter),
70
+ PYTHONDONTWRITEBYTECODE: "1",
71
+ };
72
+ return { cmd: py.cmd, argv: ["-B", "-m", moduleName, ...moduleArgs], env };
73
+ }
74
+
75
+ // Run a Python module, streaming its output, and exit with its status.
76
+ function runPython(pp, moduleName, moduleArgs) {
77
+ const { cmd, argv, env } = pyInvocation(pp, moduleName, moduleArgs);
78
+ const child = spawn(cmd, argv, { stdio: "inherit", env });
79
+ child.on("error", (e) => fail(`${CLI}: failed to start Python (${cmd}): ${e.message}`));
80
+ child.on("exit", (code, signal) => process.exit(signal ? 1 : code ?? 0));
81
+ }
82
+
83
+ function pyCapture(pp, moduleName, moduleArgs) {
84
+ const { cmd, argv, env } = pyInvocation(pp, moduleName, moduleArgs);
85
+ return spawnSync(cmd, argv, { encoding: "utf8", env });
86
+ }
87
+
88
+ // ---- subcommands -----------------------------------------------------------
89
+
90
+ function cmdCheck(args) {
91
+ if (args.length === 0) fail("usage: volaro check <file-or-dir>... [--release] [--config PATH]");
92
+ // Always resolve: a bare structural pass is weaker than the real check.
93
+ runPython(VALIDATOR_PP, "vlcheck", ["--resolve", ...args]);
94
+ }
95
+
96
+ function parseBuildArgs(args) {
97
+ const out = { source: null, o: null, data: null, release: false, config: null };
98
+ for (let i = 0; i < args.length; i++) {
99
+ const a = args[i];
100
+ if (a === "-o" || a === "--out") out.o = args[++i];
101
+ else if (a === "--data") out.data = args[++i];
102
+ else if (a === "--config") out.config = args[++i];
103
+ else if (a === "--release") out.release = true;
104
+ else if (a.startsWith("-")) fail(`volaro build: unknown option ${a}`);
105
+ else if (!out.source) out.source = a;
106
+ else fail(`volaro build: unexpected argument ${a}`);
107
+ }
108
+ return out;
109
+ }
110
+
111
+ function resolveEntry(explicit) {
112
+ if (explicit) {
113
+ if (!existsSync(explicit)) fail(`${CLI}: no such file: ${explicit}`);
114
+ return resolvePath(explicit);
115
+ }
116
+ for (const c of ["app.vl", join("src", "app.vl")]) {
117
+ if (existsSync(c)) return resolvePath(c);
118
+ }
119
+ fail(
120
+ "volaro: no entry file given and no app.vl found in this directory.\n" +
121
+ "Pass one explicitly: volaro build path/to/app.vl",
122
+ );
123
+ }
124
+
125
+ function cmdBuild(args) {
126
+ const b = parseBuildArgs(args);
127
+ const source = resolveEntry(b.source);
128
+ const outDir = b.o ? resolvePath(b.o) : resolvePath("build");
129
+ const pyArgs = [source, "-o", outDir];
130
+ if (b.data) pyArgs.push("--data", resolvePath(b.data));
131
+ if (b.config) pyArgs.push("--config", resolvePath(b.config));
132
+ if (b.release) pyArgs.push("--release");
133
+ runPython(VLBUILD_PP, "vlbuild", pyArgs);
134
+ }
135
+
136
+ // Synchronous single build for `volaro dev`; returns { ok, server } or exits on env error.
137
+ function buildOnce(source, outDir, extra = []) {
138
+ const r = pyCapture(VLBUILD_PP, "vlbuild", [source, "-o", outDir, ...extra]);
139
+ if (r.error) fail(`${CLI}: failed to start Python: ${r.error.message}`);
140
+ if (r.stdout) process.stdout.write(r.stdout);
141
+ if (r.stderr) process.stderr.write(r.stderr);
142
+ return { ok: r.status === 0, server: existsSync(join(outDir, "server.js")) };
143
+ }
144
+
145
+ const MIME = {
146
+ ".html": "text/html; charset=utf-8",
147
+ ".js": "text/javascript; charset=utf-8",
148
+ ".css": "text/css; charset=utf-8",
149
+ ".json": "application/json; charset=utf-8",
150
+ ".svg": "image/svg+xml",
151
+ ".map": "application/json",
152
+ };
153
+
154
+ function staticServer(dir, port) {
155
+ const base = resolvePath(dir);
156
+ const srv = createServer((req, res) => {
157
+ let rel = decodeURIComponent((req.url || "/").split("?")[0]);
158
+ if (rel.endsWith("/")) rel += "index.html";
159
+ const target = resolvePath(join(base, rel));
160
+ // Contain to `base`: an exact match, or a path that starts with `base` +
161
+ // separator. A bare `startsWith(base)` would also accept a sibling like
162
+ // `<base>-notes/…`.
163
+ if (target !== base && !target.startsWith(base + sep)) {
164
+ res.writeHead(403).end("forbidden");
165
+ return;
166
+ }
167
+ if (!existsSync(target) || statSync(target).isDirectory()) {
168
+ res.writeHead(404, { "content-type": "text/html; charset=utf-8" });
169
+ res.end("<!doctype html><meta charset=utf-8><title>404</title><p>Not found. The dev server serves the built bundle in <code>" + dir + "</code>.");
170
+ return;
171
+ }
172
+ res.writeHead(200, { "content-type": MIME[extname(target)] || "application/octet-stream", "cache-control": "no-store" });
173
+ res.end(readFileSync(target));
174
+ });
175
+ srv.on("error", (e) => {
176
+ if (e.code === "EADDRINUSE") fail(`volaro dev: port ${port} is already in use. Pass --port <n>.`);
177
+ fail(`volaro dev: ${e.message}`);
178
+ });
179
+ srv.listen(port, "127.0.0.1");
180
+ return srv;
181
+ }
182
+
183
+ // Snapshot every .vl file under dir (bounded depth) as path -> "mtime:size".
184
+ // Polling beats fs.watch here: it is identical across platforms and editors
185
+ // (rename-replace, truncate-write and append all show up), which fs.watch is
186
+ // not, and the cost is trivial for a project-sized tree.
187
+ function scanVl(dir, depth = 6, acc = new Map()) {
188
+ let entries;
189
+ try {
190
+ entries = readdirSync(dir, { withFileTypes: true });
191
+ } catch {
192
+ return acc;
193
+ }
194
+ for (const e of entries) {
195
+ if (e.name === "node_modules" || e.name === ".git" || e.name.startsWith(".")) continue;
196
+ const p = join(dir, e.name);
197
+ if (e.isDirectory()) {
198
+ if (depth > 0) scanVl(p, depth - 1, acc);
199
+ } else if (extname(e.name) === ".vl") {
200
+ try {
201
+ const s = statSync(p);
202
+ acc.set(p, `${s.mtimeMs}:${s.size}`);
203
+ } catch {}
204
+ }
205
+ }
206
+ return acc;
207
+ }
208
+
209
+ function snapEqual(a, b) {
210
+ if (a.size !== b.size) return false;
211
+ for (const [k, v] of a) if (b.get(k) !== v) return false;
212
+ return true;
213
+ }
214
+
215
+ // Poll watchDir for .vl changes and call onChange (debounced by the interval).
216
+ // Returns a close() that stops polling.
217
+ function watchVl(watchDir, onChange) {
218
+ let prev = scanVl(watchDir);
219
+ const iv = setInterval(() => {
220
+ const now = scanVl(watchDir);
221
+ if (!snapEqual(prev, now)) {
222
+ prev = now;
223
+ onChange();
224
+ }
225
+ }, 250);
226
+ iv.unref();
227
+ return () => clearInterval(iv);
228
+ }
229
+
230
+ function cmdDev(args) {
231
+ let source = null,
232
+ port = 5173,
233
+ outDir = null;
234
+ for (let i = 0; i < args.length; i++) {
235
+ const a = args[i];
236
+ if (a === "--port" || a === "-p") port = Number(args[++i]);
237
+ else if (a === "-o" || a === "--out") outDir = args[++i];
238
+ else if (a.startsWith("-")) fail(`volaro dev: unknown option ${a}`);
239
+ else if (!source) source = a;
240
+ }
241
+ if (!Number.isInteger(port) || port < 1 || port > 65535) fail("volaro dev: --port must be 1..65535");
242
+ const entry = resolveEntry(source);
243
+ const out = outDir ? resolvePath(outDir) : resolvePath("build");
244
+ const watchDir = dirname(entry);
245
+
246
+ process.stdout.write(`volaro dev: building ${relative(process.cwd(), entry) || entry}\n`);
247
+ const first = buildOnce(entry, out);
248
+ if (!first.ok) fail("volaro dev: initial build failed. Fix the errors above and re-run.");
249
+
250
+ let shuttingDown = false;
251
+ let stopWatch = () => {};
252
+
253
+ // ---- full-stack: run server.js, restart it on a successful rebuild ----
254
+ if (first.server) {
255
+ if (!nodeCanRunServer()) {
256
+ fail(
257
+ `volaro dev: this app builds a full-stack server (server.js), which needs Node ` +
258
+ `${MIN_NODE_FOR_SERVER.join(".")}+ for node:sqlite. You have ${process.versions.node}.`,
259
+ );
260
+ }
261
+ const serverPath = join(out, "server.js");
262
+ let child = null;
263
+ let restarting = false;
264
+
265
+ const startServer = () => {
266
+ child = spawn(process.execPath, [serverPath], {
267
+ stdio: "inherit",
268
+ env: { ...process.env, PORT: String(port) },
269
+ });
270
+ child.on("exit", (code, signal) => {
271
+ if (shuttingDown || restarting) return;
272
+ // the server died on its own (e.g. a port clash it reported to stderr)
273
+ stopWatch();
274
+ process.exit(signal ? 1 : code ?? 0);
275
+ });
276
+ };
277
+
278
+ const restartServer = () =>
279
+ new Promise((done) => {
280
+ if (!child) return done();
281
+ restarting = true;
282
+ child.once("exit", () => {
283
+ restarting = false;
284
+ done();
285
+ });
286
+ child.kill("SIGTERM");
287
+ });
288
+
289
+ process.stdout.write(`volaro dev: full-stack app — starting ${relative(process.cwd(), serverPath) || serverPath}\n`);
290
+ startServer();
291
+
292
+ stopWatch = watchVl(watchDir, async () => {
293
+ process.stdout.write("volaro dev: change detected — rebuilding\n");
294
+ const r = buildOnce(entry, out);
295
+ if (!r.ok) {
296
+ process.stdout.write("volaro dev: build failed — server left running on the last good build\n");
297
+ return;
298
+ }
299
+ await restartServer();
300
+ startServer();
301
+ process.stdout.write("volaro dev: server restarted\n");
302
+ });
303
+
304
+ const bye = () => {
305
+ if (shuttingDown) return;
306
+ shuttingDown = true;
307
+ stopWatch();
308
+ if (child) child.kill("SIGTERM");
309
+ setTimeout(() => {
310
+ if (child) child.kill("SIGKILL");
311
+ process.exit(0);
312
+ }, 2000).unref();
313
+ if (child) child.once("exit", () => process.exit(0));
314
+ else process.exit(0);
315
+ };
316
+ process.on("SIGINT", bye);
317
+ process.on("SIGTERM", bye);
318
+ return;
319
+ }
320
+
321
+ // ---- static single-page: serve the bundle, rebuild on change ----
322
+ const srv = staticServer(out, port);
323
+ process.stdout.write(`volaro dev: serving http://127.0.0.1:${port} (Ctrl+C to stop)\n`);
324
+
325
+ stopWatch = watchVl(watchDir, () => {
326
+ process.stdout.write("volaro dev: change detected — rebuilding\n");
327
+ const r = buildOnce(entry, out);
328
+ process.stdout.write(r.ok ? "volaro dev: rebuilt\n" : "volaro dev: build failed — keeping last good bundle\n");
329
+ });
330
+
331
+ const bye = () => {
332
+ if (shuttingDown) return;
333
+ shuttingDown = true;
334
+ stopWatch();
335
+ srv.close(() => process.exit(0));
336
+ setTimeout(() => process.exit(0), 1000).unref();
337
+ };
338
+ process.on("SIGINT", bye);
339
+ process.on("SIGTERM", bye);
340
+ }
341
+
342
+ function cmdReference(which) {
343
+ if (which === "spec") {
344
+ // `volaro-language-spec.md` is the whole-language DESIGN — a superset of
345
+ // what this build accepts. `supported.md` is the version-specific
346
+ // shipped-feature guide; `crib.md` is the authoring reference.
347
+ process.stderr.write(
348
+ `note: \`${CLI} spec\` is the full language design — a SUPERSET of this build.\n` +
349
+ ` \`${CLI} supported\` is what version ${pkg.version} actually accepts;\n` +
350
+ ` \`${CLI} crib\` is the authoring reference.\n\n`,
351
+ );
352
+ }
353
+ const file = which === "supported" ? "supported" : which;
354
+ process.stdout.write(read(`language/${file}.md`));
355
+ }
356
+
357
+ function cmdExample(name) {
358
+ if (name === "sensors" || name === "station") process.stdout.write(read(`examples/${name}.vl`));
359
+ else fail("usage: volaro example sensors|station");
360
+ }
361
+
362
+ const HELP = `Volaro ${pkg.version} — an application language for AI authoring and human review.
363
+
364
+ USAGE
365
+ volaro <command> [args] (installed as both \`volaro\` and \`vl\`;
366
+ \`volaro\` is the canonical name)
367
+
368
+ COMMANDS
369
+ check <path>... Validate .vl source. Runs the full resolver, not just
370
+ a structural pass. Exit non-zero on any error.
371
+ build <file.vl> Transpile to a runnable bundle (default: ./build).
372
+ -o <dir> output directory
373
+ --data <file> data.json to bundle
374
+ --release fail on unresolved placeholders (e.g. img alt_todo:)
375
+ dev [file.vl] Build, serve on http://127.0.0.1:5173, and rebuild
376
+ when a .vl file changes. Defaults to ./app.vl. A
377
+ full-stack build runs server.js instead (Node 22.5+)
378
+ and restarts it after each successful rebuild.
379
+ --port <n> dev server port (passed to server.js as $PORT)
380
+ crib The authoring reference (write from this).
381
+ supported What version ${pkg.version} actually accepts.
382
+ spec The full language design (a SUPERSET of this build).
383
+ example <name> Print a worked example: sensors | station.
384
+ version Print the version.
385
+ help This message.
386
+
387
+ The compiler is Python and ships inside this package. Set VOLARO_PYTHON to
388
+ choose the interpreter if \`python3\` is not the one you want.
389
+
390
+ SHIPPED SUBSET: single page (no router); text \`input\` / \`button\` / \`link\`
391
+ only (no checkbox/select/textarea/disabled — a toggle is a \`button\` + \`state\`,
392
+ not a semantic checkbox); \`if\` as an expression is binary; \`match\` is a
393
+ statement; a module-level \`fn\` cannot be called from a view. \`volaro crib\` +
394
+ \`volaro supported\` are authoritative for this build; \`volaro spec\` is the wider
395
+ design, not what it accepts.
396
+
397
+ Run commands via your project's \`npm run …\` scripts or \`npx --no-install
398
+ volaro …\` inside the project. A bare \`npx volaro …\` from elsewhere may fetch
399
+ the wrong thing.
400
+
401
+ TO AN AI AGENT: run \`volaro crib\` and load its output — that is the complete
402
+ authoring reference. Then write source, run \`volaro check\`, and repair from the
403
+ diagnostics until it is clean.`;
404
+
405
+ // ---- dispatch ------------------------------------------------------------
406
+
407
+ const [cmd, ...rest] = process.argv.slice(2);
408
+ switch (cmd) {
409
+ case "check":
410
+ cmdCheck(rest);
411
+ break;
412
+ case "build":
413
+ cmdBuild(rest);
414
+ break;
415
+ case "dev":
416
+ cmdDev(rest);
417
+ break;
418
+ case "crib":
419
+ cmdReference("crib");
420
+ break;
421
+ case "spec":
422
+ cmdReference("spec");
423
+ break;
424
+ case "supported":
425
+ cmdReference("supported");
426
+ break;
427
+ case "example":
428
+ cmdExample(rest[0]);
429
+ break;
430
+ case "version":
431
+ case "--version":
432
+ case "-v":
433
+ process.stdout.write(pkg.version + "\n");
434
+ break;
435
+ case undefined:
436
+ case "help":
437
+ case "--help":
438
+ case "-h":
439
+ process.stdout.write(HELP + "\n");
440
+ break;
441
+ default:
442
+ fail(`${CLI}: unknown command "${cmd}". Run \`${CLI} help\`.`, 2);
34
443
  }
@@ -0,0 +1 @@
1
+ e1d25fea4407ecd621dc6a0982a2023dfb5c5ba1
@@ -0,0 +1,10 @@
1
+ """vlcheck - a lightweight validator for the Volaro language (Stage 1 prototype).
2
+
3
+ Not a compiler. Lexes and tolerantly parses .vl source, then runs a handful of
4
+ structural and heuristic-semantic checks whose purpose is to measure whether a
5
+ model produces valid, secure Volaro more reliably than the mainstream stack.
6
+
7
+ See ../../PROJECT.md for the full context and check list.
8
+ """
9
+
10
+ __version__ = "0.2.0"