char/avril
(wip) why'd you have to go and make things so complicaaaaated?
git clone https://git.t4t.associates/char/avril
bce30ee
main
JSR packages
avril supports native jsr: imports, without npm compatibility tarballs or
node_modules:
import { assertEquals } from "jsr:@std/assert@^1" ;
Packages belong in the deps of avril.conf:
deps: [
'jsr:@std/assert@^1.0.19',
{from: 'jsr:@std/path@^1.1.6', name: 'path'},
],
A dependency binds its package name, or the name given, together with its
subpaths: here @std/assert and @std/assert/equals select the package's . and
./equals exports, and path/posix selects @std/path's ./posix. The same
package can be listed twice with different ranges if one entry is renamed.
imports can still map an alias to a single export, or a slash-terminated
alias to the exports beneath a prefix:
imports: {
posix: 'jsr:@std/path@^1/posix',
'std-path/': 'jsr:@std/path@^1/',
},
Only declared public exports can be selected through jsr:. Published modules
resolve their relative imports within their own package and version and their
external dependencies through explicit jsr: references. They never inherit the
application's aliases. Arbitrary HTTP imports, CommonJS, JSX, declaration-file
execution, and runtime JSON imports are not supported. Npm packages use npm:
specifiers instead; see Npm packages.
TypeScript is transpiled, not type-checked; erased type-only dependencies do not
need to exist at runtime. JSR does not guarantee runtime portability: packages
that require Deno.*, Node APIs, or JSON imports will not work. Pure ECMAScript
and supported Web API packages, such as @std/assert, are suitable candidates.
Preparation and identity
Before execution, the static graph is acquired and TypeScript is transformed. V8 linking reads prepared source and never performs network I/O. Computed dynamic imports asynchronously acquire the requested static closure through the host event loop, then link and evaluate it. Concurrent acquisition shares downloads, and modules share the same V8 identity regardless of their import spelling.
Local identity is a canonical file path. Registry identity is the canonical,
versioned registry source URL, not the cache path. import.meta.url and mapped
TypeScript stacks expose that URL. Non-canonical percent-encoded aliases are
rejected. Relative registry imports cannot cross package or version boundaries.
Registry graph metadata is an optional, bounded prefetch hint; source parsing
remains authoritative.
Lockfile and cache
The root package's avril.lock records version selections for the execution graph
and SHA-256 integrity for immutable version metadata. Manifests in that metadata
cover each source's SHA-256 and size. Requests with different constraints may use
different versions; a locked constraint keeps its selection until explicitly
updated. New selections skip yanked versions. Existing pins can keep yanked
versions, and immutable integrity pins are never silently replaced by an update.
Selections and integrity are saved after successful preparation, including for
computed dynamic imports. The lock is fJSON, the same format as
avril.conf, and merges concurrent changes: an
update is recomputed if another process replaced the file while it was being
prepared, though a write landing in the instant before the rename can still be
lost. A conflicting selection used by this preparation fails rather than
overwriting another process's selection. Atomic writes preserve existing modes.
avril.conf edits follow the same scheme. Commit avril.lock. Unchanged locks
are not rewritten, so locked runs work in read-only project directories without
requiring --frozen. Root selection uses
trusted host-side discovery, independently of guest execution grants.
Cached artifacts are immutable and verified on use. Writes are atomic, and
per-artifact file locks coordinate processes when an artifact must be acquired
or refreshed; verified cached hits are read without taking those locks. The
default cache is $AVRIL_CACHE_DIR, $XDG_CACHE_HOME/avril, or
$HOME/.cache/avril. Use
--cache-dir=PATH to override it. A corrupt locked artifact fails with an integrity
error; remove that artifact or use an empty cache to reacquire it online.
--offline never contacts the registry and fails on cache misses. --frozen
requires an existing lockfile, selections, and metadata integrity; it permits
acquiring missing locked artifacts unless combined with --offline.
--lock=PATH overrides the default root lock. update --offline reselects from
cached indexes only and does not promise the registry's latest versions.
Permissions
Package acquisition is host tooling authority, separate from guest net, read,
and write. It accesses only the configured registry, cache, and lockfile; it
never follows HTTP redirects. Acquiring a package does not grant guest network
access or access to the cache filesystem.
Remote code still needs execution authority. Default execution is unrestricted;
with -0, explicitly grant URLs, for example:
avril run-0 --allow-js-exec=. --allow-js-exec=https://jsr.io/@std/ main.ts
A slash-terminated URL grant covers descendants; otherwise it grants one exact
file. Denials cover the named path and descendants. URL paths are normalized
before checking, so percent-encoding cannot bypass a denial. Network permission
alone never grants remote execution. Prefer grants for individual packages and
their transitive dependencies; scope and whole-registry grants let guest code
request arbitrary package names within that grant, including names that can
convey data to registry request logs even when guest net is denied.
pledge({jsExec: false}) revokes both local and remote execution, and mixed
URL/path allow lists narrow both domains.
Metadata acquisition checks whether some code in the requested package/version could be authorized; selecting an export and acquiring its source also check the exact module. Access is rechecked after asynchronous work and before cached modules are linked or evaluated. Revoking an already-evaluated dependency does not reexecute it when its consumer's existing namespace is reimported.
Commands
avril add jsr:@std/assert@^1 adds the package to deps in the nearest trusted
avril.conf, or replaces the entry already binding @std/assert. A subpath target
such as jsr:@std/path@^1/posix adds its whole package. avril add assert jsr:@std/assert@^1 binds the package as assert instead, while an alias for a
subpath or slash prefix, such as avril add posix jsr:@std/path@^1/posix, goes in
imports; slash-prefix aliases require slash-terminated targets. --config=PATH
chooses a config explicitly. It edits source spans, preserving existing comments
and other entries. It prints the resulting binding and config on stdout, preceded
on stderr by whether it was added, updated, or unchanged.
A target without a version, such as jsr:@std/assert or jsr:@std/path/posix,
gets a caret range of the package's newest non-yanked stable release, as in
jsr:@std/assert@^1.0.13; --pre lets that release be a prerelease. After editing,
add caches what the new entry can import, like avril cache, and records it in
the config's avril.lock: every export of a dependency or slash prefix, or an
alias's single export. --offline, --jsr-registry, and --cache-dir apply as for other commands.
avril lsp provides live type-resolution support; see
editor integration.
avril run main.tsavril cache main.tsavril info main.tsavril update main.tsavril vendor--include=jsr:@my/lazy@^1 main.tsavril run--offline --frozen --vendor=vendor main.ts
cache, info, update, and vendor prepare without executing application code.
info emits the acquired graph as JSON. update explicitly reselects encountered
constraints. update --prune additionally drops unused specifier and metadata
pins; include all known dynamic entry points when pruning, and avoid concurrent
lock-changing commands during this explicit cleanup. Ordinary runs never prune.
vendor writes verified metadata and source into readable
vendor/@scope/package/version/... paths. --out=PATH changes its output directory;
--vendor=PATH supplies an existing vendor tree for reads.
These commands prepare static imports only. Computed dependencies cannot be
inferred ahead of time; repeat --include=SPECIFIER to prepare known dynamic
entry points too. For a private computed file, use for example
--include-from=jsr:@my/pkg@^1 --include=./lazy.ts; the referrer must be a source
module, and normal package confinement applies. --include-from=. resets the
referrer to the entry. A vendored run still verifies metadata against the root
lock
and sources against their manifests. Previously unused dynamic files require
additional cached/vendored artifacts.
The registry defaults to https://jsr.io/. --jsr-registry=URL selects another base;
it must use HTTPS, except that HTTP is allowed for loopback fixture registries.
Registry identity is part of the lock and cache keys, and changing registries
requires a separate lockfile.
1# JSR packages 2 3avril supports native `jsr:` imports, without npm compatibility tarballs or 4`node_modules`: 5 6``` ts 7import { assertEquals } from "jsr:@std/assert@^1"; 8``` 9 10Packages belong in the `deps` of `avril.conf`: 11 12``` text 13deps: [ 14'jsr:@std/assert@^1.0.19', 15{from: 'jsr:@std/path@^1.1.6', name: 'path'}, 16], 17``` 18 19A dependency binds its package name, or the `name` given, together with its 20subpaths: here `@std/assert` and `@std/assert/equals` select the package's `.` and 21`./equals` exports, and `path/posix` selects `@std/path`'s `./posix`. The same 22package can be listed twice with different ranges if one entry is renamed. 23`imports` can still map an alias to a single export, or a slash-terminated 24alias to the exports beneath a prefix: 25 26``` text 27imports: { 28posix: 'jsr:@std/path@^1/posix', 29'std-path/': 'jsr:@std/path@^1/', 30}, 31``` 32Only declared public exports can be selected through `jsr:`. Published modules 33resolve their relative imports within their own package and version and their 34external dependencies through explicit `jsr:` references. They never inherit the 35application's aliases. Arbitrary HTTP imports, CommonJS, JSX, declaration-file 36execution, and runtime JSON imports are not supported. Npm packages use `npm:` 37specifiers instead; see [Npm packages](npm.md). 38TypeScript is transpiled, not type-checked; erased type-only dependencies do not 39need to exist at runtime. JSR does not guarantee runtime portability: packages 40that require `Deno.*`, Node APIs, or JSON imports will not work. Pure ECMAScript 41and supported Web API packages, such as `@std/assert`, are suitable candidates. 42 43## Preparation and identity 44 45Before execution, the static graph is acquired and TypeScript is transformed. 46V8 linking reads prepared source and never performs network I/O. Computed dynamic 47imports asynchronously acquire the requested static closure through the host 48event loop, then link and evaluate it. Concurrent acquisition shares downloads, 49and modules share the same V8 identity regardless of their import spelling. 50 51Local identity is a canonical file path. Registry identity is the canonical, 52versioned registry source URL, not the cache path. `import.meta.url` and mapped 53TypeScript stacks expose that URL. Non-canonical percent-encoded aliases are 54rejected. Relative registry imports cannot cross package or version boundaries. 55Registry graph metadata is an optional, bounded prefetch hint; source parsing 56remains authoritative. 57 58## Lockfile and cache 59 60The root package's `avril.lock` records version selections for the execution graph 61and SHA-256 integrity for immutable version metadata. Manifests in that metadata 62cover each source's SHA-256 and size. Requests with different constraints may use 63different versions; a locked constraint keeps its selection until explicitly 64updated. New selections skip yanked versions. Existing pins can keep yanked 65versions, and immutable integrity pins are never silently replaced by an update. 66 67Selections and integrity are saved after successful preparation, including for 68computed dynamic imports. The lock is [fJSON](fjson.md), the same format as 69`avril.conf`, and merges concurrent changes: an 70update is recomputed if another process replaced the file while it was being 71prepared, though a write landing in the instant before the rename can still be 72lost. A conflicting selection used by this preparation fails rather than 73overwriting another process's selection. Atomic writes preserve existing modes. 74`avril.conf` edits follow the same scheme. Commit `avril.lock`. Unchanged locks 75are not rewritten, so locked runs work in read-only project directories without 76requiring `--frozen`. Root selection uses 77trusted host-side discovery, independently of guest execution grants. 78 79Cached artifacts are immutable and verified on use. Writes are atomic, and 80per-artifact file locks coordinate processes when an artifact must be acquired 81or refreshed; verified cached hits are read without taking those locks. The 82default cache is `$AVRIL_CACHE_DIR`, `$XDG_CACHE_HOME/avril`, or 83`$HOME/.cache/avril`. Use 84`--cache-dir=PATH` to override it. A corrupt locked artifact fails with an integrity 85error; remove that artifact or use an empty cache to reacquire it online. 86 87`--offline` never contacts the registry and fails on cache misses. `--frozen` 88requires an existing lockfile, selections, and metadata integrity; it permits 89acquiring missing locked artifacts unless combined with `--offline`. 90`--lock=PATH` overrides the default root lock. `update --offline` reselects from 91cached indexes only and does not promise the registry's latest versions. 92 93## Permissions 94 95Package acquisition is host tooling authority, separate from guest `net`, `read`, 96and `write`. It accesses only the configured registry, cache, and lockfile; it 97never follows HTTP redirects. Acquiring a package does not grant guest network 98access or access to the cache filesystem. 99 100Remote code still needs execution authority. Default execution is unrestricted; 101with `-0`, explicitly grant URLs, for example: 102 103``` sh 104avril run -0 --allow-js-exec=. --allow-js-exec=https://jsr.io/@std/ main.ts 105``` 106 107A slash-terminated URL grant covers descendants; otherwise it grants one exact 108file. Denials cover the named path and descendants. URL paths are normalized 109before checking, so percent-encoding cannot bypass a denial. Network permission 110alone never grants remote execution. Prefer grants for individual packages and 111their transitive dependencies; scope and whole-registry grants let guest code 112request arbitrary package names within that grant, including names that can 113convey data to registry request logs even when guest `net` is denied. 114`pledge({jsExec: false})` revokes both local and remote execution, and mixed 115URL/path allow lists narrow both domains. 116 117Metadata acquisition checks whether some code in the requested package/version 118could be authorized; selecting an export and acquiring its source also check the 119exact module. Access is rechecked after asynchronous work and before cached 120modules are linked or evaluated. Revoking an already-evaluated dependency does 121not reexecute it when its consumer's existing namespace is reimported. 122 123## Commands 124 125`avril add jsr:@std/assert@^1` adds the package to `deps` in the nearest trusted 126`avril.conf`, or replaces the entry already binding `@std/assert`. A subpath target 127such as `jsr:@std/path@^1/posix` adds its whole package. `avril add assert 128jsr:@std/assert@^1` binds the package as `assert` instead, while an alias for a 129subpath or slash prefix, such as `avril add posix jsr:@std/path@^1/posix`, goes in 130`imports`; slash-prefix aliases require slash-terminated targets. `--config=PATH` 131chooses a config explicitly. It edits source spans, preserving existing comments 132and other entries. It prints the resulting binding and config on stdout, preceded 133on stderr by whether it was added, updated, or unchanged. 134 135A target without a version, such as `jsr:@std/assert` or `jsr:@std/path/posix`, 136gets a caret range of the package's newest non-yanked stable release, as in 137`jsr:@std/assert@^1.0.13`; `--pre` lets that release be a prerelease. After editing, 138`add` caches what the new entry can import, like `avril cache`, and records it in 139the config's `avril.lock`: every export of a dependency or slash prefix, or an 140alias's single export. `--offline`, `--jsr-registry`, and `--cache-dir` apply as for other commands. 141 142`avril lsp` provides live type-resolution support; see 143[editor integration](editor.md). 144 145``` sh 146avril run main.ts 147avril cache main.ts 148avril info main.ts 149avril update main.ts 150avril vendor --include=jsr:@my/lazy@^1 main.ts 151avril run --offline --frozen --vendor=vendor main.ts 152``` 153 154`cache`, `info`, `update`, and `vendor` prepare without executing application code. 155`info` emits the acquired graph as JSON. `update` explicitly reselects encountered 156constraints. `update --prune` additionally drops unused specifier and metadata 157pins; include all known dynamic entry points when pruning, and avoid concurrent 158lock-changing commands during this explicit cleanup. Ordinary runs never prune. 159`vendor` writes verified metadata and source into readable 160`vendor/@scope/package/version/...` paths. `--out=PATH` changes its output directory; 161`--vendor=PATH` supplies an existing vendor tree for reads. 162 163These commands prepare static imports only. Computed dependencies cannot be 164inferred ahead of time; repeat `--include=SPECIFIER` to prepare known dynamic 165entry points too. For a private computed file, use for example 166`--include-from=jsr:@my/pkg@^1 --include=./lazy.ts`; the referrer must be a source 167module, and normal package confinement applies. `--include-from=.` resets the 168referrer to the entry. A vendored run still verifies metadata against the root 169lock 170and sources against their manifests. Previously unused dynamic files require 171additional cached/vendored artifacts. 172 173The registry defaults to `https://jsr.io/`. `--jsr-registry=URL` selects another base; 174it must use HTTPS, except that HTTP is allowed for loopback fixture registries. 175Registry identity is part of the lock and cache keys, and changing registries 176requires a separate lockfile.