volaro 0.1.0-alpha.6 → 0.1.0-alpha.7
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.
- package/README.md +1 -1
- package/bin/vl.js +5 -0
- package/compiler/SOURCE_INFO.json +2 -2
- package/compiler/SOURCE_REV +1 -1
- package/compiler/vlbuild/vlbuild/static_assets.py +58 -11
- package/language/crib.md +1 -1
- package/language/spec.md +3 -2
- package/language/supported.md +2 -2
- package/package.json +1 -1
- package/scripts/selftest.mjs +10 -0
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Volaro
|
|
2
2
|
|
|
3
|
-
**Limited alpha** (this is `0.1.0-alpha.
|
|
3
|
+
**Limited alpha** (this is `0.1.0-alpha.7`), published under both the
|
|
4
4
|
`latest` and `alpha` dist-tags: `npm install volaro`.
|
|
5
5
|
|
|
6
6
|
Volaro is an experimental application language intended for AI authoring and
|
package/bin/vl.js
CHANGED
|
@@ -69,6 +69,11 @@ function pyInvocation(pp, moduleName, moduleArgs) {
|
|
|
69
69
|
...process.env,
|
|
70
70
|
PYTHONPATH: [pp, VALIDATOR_PP, process.env.PYTHONPATH].filter(Boolean).join(delimiter),
|
|
71
71
|
PYTHONDONTWRITEBYTECODE: "1",
|
|
72
|
+
// Diagnostics and build summaries contain non-ASCII (box drawing, arrows).
|
|
73
|
+
// Without this, Windows encodes a redirected stdout with the legacy code
|
|
74
|
+
// page and the compiler dies with UnicodeEncodeError; the capturing path
|
|
75
|
+
// below also decodes the child's output as UTF-8.
|
|
76
|
+
PYTHONIOENCODING: "utf-8",
|
|
72
77
|
};
|
|
73
78
|
return { cmd: py.cmd, argv: ["-B", "-m", moduleName, ...moduleArgs], env };
|
|
74
79
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
|
-
"revision": "
|
|
2
|
+
"revision": "3469656bab89d0e379c1e31cb19013a21fdef556",
|
|
3
3
|
"source": "git",
|
|
4
4
|
"dirty": false,
|
|
5
|
-
"content_sha256": "
|
|
5
|
+
"content_sha256": "656e9ef8c494c763da68868cf0f80ce509bf51bfd9e71b79ae153407eeb16e83"
|
|
6
6
|
}
|
package/compiler/SOURCE_REV
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
3469656bab89d0e379c1e31cb19013a21fdef556
|
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
from __future__ import annotations
|
|
3
3
|
|
|
4
4
|
import json
|
|
5
|
+
import os
|
|
5
6
|
import shutil
|
|
7
|
+
import stat
|
|
6
8
|
from pathlib import Path
|
|
7
9
|
from urllib.parse import unquote, urlsplit
|
|
8
10
|
|
|
@@ -10,6 +12,57 @@ from vlcheck import ast_nodes as A
|
|
|
10
12
|
from .emit import BuildError, _walk
|
|
11
13
|
|
|
12
14
|
|
|
15
|
+
# A Windows reparse tag with this bit set names another file or directory:
|
|
16
|
+
# symbolic links, directory junctions (mount points) and similar redirects.
|
|
17
|
+
# Cloud-file placeholders (OneDrive) do not set it, so they still count as files.
|
|
18
|
+
_NAME_SURROGATE = 0x20000000
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _is_link(path: Path) -> bool:
|
|
22
|
+
"""True for a symbolic link or a Windows junction; neither is followed.
|
|
23
|
+
|
|
24
|
+
`Path.is_symlink()` is False for an NTFS junction, and `Path.is_junction()`
|
|
25
|
+
only exists from Python 3.12, so older Pythons read the reparse tag.
|
|
26
|
+
"""
|
|
27
|
+
try:
|
|
28
|
+
st = os.lstat(path)
|
|
29
|
+
except OSError:
|
|
30
|
+
return False
|
|
31
|
+
if stat.S_ISLNK(st.st_mode):
|
|
32
|
+
return True
|
|
33
|
+
is_junction = getattr(path, "is_junction", None)
|
|
34
|
+
if is_junction is not None and is_junction():
|
|
35
|
+
return True
|
|
36
|
+
return bool(getattr(st, "st_reparse_tag", 0) & _NAME_SURROGATE)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _asset_files(root: Path) -> list[Path]:
|
|
40
|
+
"""Every regular file under root, refusing links and never descending into one."""
|
|
41
|
+
files = []
|
|
42
|
+
pending = [root]
|
|
43
|
+
while pending:
|
|
44
|
+
folder = pending.pop()
|
|
45
|
+
for path in sorted(folder.iterdir()):
|
|
46
|
+
if _is_link(path):
|
|
47
|
+
raise BuildError(
|
|
48
|
+
f"E-ASSET-LINK: symbolic links and junctions are not supported in assets/: {path}")
|
|
49
|
+
if path.is_dir():
|
|
50
|
+
pending.append(path)
|
|
51
|
+
elif path.is_file():
|
|
52
|
+
files.append(path)
|
|
53
|
+
else:
|
|
54
|
+
raise BuildError(f"E-ASSET-PATH: assets/ supports regular files only: {path}")
|
|
55
|
+
# Defence in depth: whatever the checks above missed, every file must
|
|
56
|
+
# still resolve to somewhere inside the resolved assets/ root.
|
|
57
|
+
real_root = root.resolve()
|
|
58
|
+
for path in files:
|
|
59
|
+
real = path.resolve()
|
|
60
|
+
if real != real_root and real_root not in real.parents:
|
|
61
|
+
raise BuildError(
|
|
62
|
+
f"E-ASSET-LINK: {path} resolves outside assets/; symbolic links and junctions are not supported")
|
|
63
|
+
return sorted(files)
|
|
64
|
+
|
|
65
|
+
|
|
13
66
|
def prepare_assets(entry: Path, bundle: Path, emitter) -> tuple[Path, list[Path]]:
|
|
14
67
|
root = entry.parent / "assets"
|
|
15
68
|
dest = bundle / "assets"
|
|
@@ -17,23 +70,17 @@ def prepare_assets(entry: Path, bundle: Path, emitter) -> tuple[Path, list[Path]
|
|
|
17
70
|
if root.exists() and (dest.resolve() == root.resolve() or root.resolve() in dest.resolve().parents
|
|
18
71
|
or dest.resolve() in root.resolve().parents):
|
|
19
72
|
raise BuildError("E-ASSET-OUTPUT: output assets/ overlaps source assets/; choose another -o directory")
|
|
20
|
-
if dest
|
|
21
|
-
raise BuildError("E-ASSET-OUTPUT: output assets/ must not be a symbolic link")
|
|
73
|
+
if _is_link(dest):
|
|
74
|
+
raise BuildError("E-ASSET-OUTPUT: output assets/ must not be a symbolic link or junction")
|
|
22
75
|
if dest.exists() and not dest.is_dir():
|
|
23
76
|
raise BuildError("E-ASSET-OUTPUT: output assets/ must be a directory")
|
|
24
77
|
files = []
|
|
25
|
-
if root
|
|
26
|
-
raise BuildError("E-ASSET-LINK: assets/ must not be a symbolic link")
|
|
78
|
+
if _is_link(root):
|
|
79
|
+
raise BuildError("E-ASSET-LINK: assets/ must not be a symbolic link or junction")
|
|
27
80
|
if root.exists():
|
|
28
81
|
if not root.is_dir():
|
|
29
82
|
raise BuildError("E-ASSET-PATH: assets must be a directory beside the entry file")
|
|
30
|
-
|
|
31
|
-
if path.is_symlink():
|
|
32
|
-
raise BuildError(f"E-ASSET-LINK: symbolic links are not supported in assets/: {path}")
|
|
33
|
-
if path.is_file():
|
|
34
|
-
files.append(path)
|
|
35
|
-
elif not path.is_dir():
|
|
36
|
-
raise BuildError(f"E-ASSET-PATH: assets/ supports regular files only: {path}")
|
|
83
|
+
files = _asset_files(root)
|
|
37
84
|
# Check literal img references into this convention directory. Other URLs
|
|
38
85
|
# and dynamic strings retain their existing meaning; no guessing/rewrite.
|
|
39
86
|
for view in emitter.views:
|
package/language/crib.md
CHANGED
|
@@ -748,7 +748,7 @@ No `alt` is `E-IMG-ALT`. Put local files in `assets/` beside the entry file:
|
|
|
748
748
|
`img src:"assets/logo.svg" alt:"Logo"`. Build and dev copy the directory into
|
|
749
749
|
the served bundle; dev rebuilds on asset additions, edits and deletions.
|
|
750
750
|
A missing literal `assets/...` image is `E-ASSET-MISSING` at build time.
|
|
751
|
-
The output `assets/` directory is compiler-owned; source symlinks are rejected.
|
|
751
|
+
The output `assets/` directory is compiler-owned; source symlinks and junctions are rejected.
|
|
752
752
|
Other relative paths are not copied automatically; `data:` and external URLs work.
|
|
753
753
|
|
|
754
754
|
Every build that renders a page needs a module-level `page title:"…"` (becomes
|
package/language/spec.md
CHANGED
|
@@ -2536,5 +2536,6 @@ hex digits representing a Unicode scalar value; surrogates are invalid.
|
|
|
2536
2536
|
The prototype copies the entry file's sibling `assets/` directory into the
|
|
2537
2537
|
browser bundle at `assets/` for both build and dev. Literal image references
|
|
2538
2538
|
inside this directory must exist at build time; dynamic URLs are not checked.
|
|
2539
|
-
The directory contains public files only, cannot contain symbolic links
|
|
2540
|
-
is copied without transformations. Output `assets/` is
|
|
2539
|
+
The directory contains public files only, cannot contain symbolic links or
|
|
2540
|
+
Windows junctions, and is copied without transformations. Output `assets/` is
|
|
2541
|
+
compiler-owned.
|
package/language/supported.md
CHANGED
|
@@ -482,8 +482,8 @@ Dev uses the same build path and watches asset additions, edits and deletions.
|
|
|
482
482
|
Refresh the browser after a rebuild; this does not add automatic browser reload.
|
|
483
483
|
|
|
484
484
|
Literal image URLs into `assets/` are checked before output is written.
|
|
485
|
-
Missing files fail with `E-ASSET-MISSING`; traversal paths
|
|
486
|
-
are rejected. Query strings and URL-encoded filenames are supported. Dynamic
|
|
485
|
+
Missing files fail with `E-ASSET-MISSING`; traversal paths, symbolic links and
|
|
486
|
+
Windows junctions are rejected (`E-ASSET-LINK`). Query strings and URL-encoded filenames are supported. Dynamic
|
|
487
487
|
URLs are not existence-checked, but all files in the directory are copied.
|
|
488
488
|
All regular files in `assets/`, including dotfiles, are public: put only intended
|
|
489
489
|
public resources there. Output `assets/` is compiler-owned and replaced on each
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "volaro",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.7",
|
|
4
4
|
"description": "Volaro \u2014 an application language written to be authored by an AI agent and read by a person. Ships the language reference and a working compiler for a supported subset (volaro check / build / dev). CLI: `volaro` (alias `vl`).",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
package/scripts/selftest.mjs
CHANGED
|
@@ -48,6 +48,16 @@ try {
|
|
|
48
48
|
const badChk = spawnSync(node, [vl, "check", bad], { encoding: "utf8" });
|
|
49
49
|
check("vl check exits non-zero on broken source", badChk.status !== 0);
|
|
50
50
|
|
|
51
|
+
// A diagnostic's source frame uses box-drawing characters. Windows gives a
|
|
52
|
+
// redirected Python stdout a legacy code page (cp1252), which cannot
|
|
53
|
+
// encode them; PYTHONIOENCODING=ascii reproduces that on every OS.
|
|
54
|
+
const narrow = spawnSync(node, [vl, "check", bad],
|
|
55
|
+
{ encoding: "utf8", env: { ...process.env, PYTHONIOENCODING: "ascii" } });
|
|
56
|
+
const narrowOut = (narrow.stdout || "") + (narrow.stderr || "");
|
|
57
|
+
check("vl check prints its diagnostic through a non-UTF-8 stdout",
|
|
58
|
+
!narrowOut.includes("UnicodeEncodeError") && narrowOut.includes("error[E-") && narrowOut.includes("┌─"),
|
|
59
|
+
narrowOut.trim().split("\n").pop());
|
|
60
|
+
|
|
51
61
|
// `volaro routes <project>`: page-server route manifest inspection
|
|
52
62
|
const posts = join(dir, "app", "posts");
|
|
53
63
|
mkdirSync(posts, { recursive: true });
|