char/avril

(wip) why'd you have to go and make things so complicaaaaated?

git clone https://git.t4t.associates/char/avril

Charlotte Somchore: use lowercase avril branding consistentlyb862289

main
4.8 KiB108 linesraw

Environment variables

Import env from avril:std:

import { env, pledge } from "avril:std";

const token = env.get("API_TOKEN");
env.delete("API_TOKEN");
pledge({ env: false });
// Pass the captured token explicitly to code that needs it.

env starts with a snapshot of the inherited process environment. Its mutations are local to avril: they do not change the host process environment or native libraries' configuration.

  • env.get(name) returns a string, or undefined if the variable is absent.
  • env.has(name) tests presence; an empty string is still a present value.
  • env.set(name, value) creates or replaces a variable.
  • env.delete(name) removes a variable; removing an absent variable is a no-op.
  • env.toObject() returns an independent, mutable, null-prototype object containing only permitted variables. Changing it does not change the environment store.

All four named operations require permission for that name, even when the variable is absent. Permission failures use errors.PermissionDenied from avril:std.

Names must be nonempty and contain neither = nor NUL. Values may be empty but cannot contain NUL. Invalid names and values use errors.InvalidInput. Names and values are converted to well-formed strings; symbols are rejected. Host variables with non-UTF-8 names or values are omitted rather than lossily decoded. Names are case-sensitive on Unix and ASCII case-insensitive on Windows.

Permissions

Environment access is allowed by default. -0 disables it along with the other ambient capabilities. Grant individual names with repeatable flags:

avril run -0 --allow-env=API_TOKEN --allow-env=MODE main.ts
avril run --deny-env=API_TOKEN main.ts

Names are exact, not patterns or comma-separated lists. Explicit denials win over grants, regardless of flag order. Granting environment access does not grant filesystem, network, or module execution access.

pledge narrows environment authority just like other capabilities:

pledge({ env: { allow: ["MODE"], deny: ["API_TOKEN"] } });
pledge({ env: false });

Omitted categories are unchanged. Successive pledges intersect allowed names and accumulate denials; they cannot restore authority. An invalid pledge does not partially change permissions. Retained methods recheck current permissions, but previously captured strings and snapshots remain usable.

Static imports execute before the importing module's body. Use startup permissions if dependencies must never see a secret, or capture and delete secrets before loading dependencies with dynamic imports.

Environment files

avril run accepts repeatable --env-file=PATH or --env-file PATH flags. Paths are relative to the current working directory, not the entry module's directory. Use the equals form (or a ./ prefix) for filenames beginning with -.

avril run -0 --allow-env=API_TOKEN --env-file=.env --env-file=.development.env main.ts

Inherited environment values take precedence, including empty strings. Later files override earlier files, and the last assignment to a name within a file wins. There is no implicit .env discovery. Missing, unreadable, non-UTF-8, or NUL-containing files stop startup before JavaScript runs; diagnostics do not echo file contents.

Naming an env file authorizes the CLI to load it, even with -0 or read denials, but grants no environment or filesystem authority to JavaScript. Under -0, each variable still requires --allow-env=NAME, whether its value comes from a file or the inherited environment. Explicit --deny-env rules still win, and pledge can subsequently remove authority. Loading values and granting access are separate operations.

The dotenv syntax follows Node 24's conventions, with more predictable handling of indented comments, empty names, and whitespace-only empty values:

  • Assignments use NAME=value; surrounding spaces and tabs are trimmed.
  • Blank lines, indented comments, lines without an assignment, and assignments with empty names are ignored.
  • # begins a comment outside a quoted value.
  • Values enclosed in single quotes, double quotes, or backticks retain their interior whitespace and may span multiple lines.
  • Double-quoted values expand literal \n sequences into newlines. Other escapes and variable references such as $NAME or ${NAME} remain literal.
  • An export prefix is ignored. Empty values are empty strings, including exported assignments and values followed only by whitespace.
  • CRLF is supported, and an initial UTF-8 BOM is ignored.
  • Text after a closing quote on the same line is ignored. If no matching closing quote exists anywhere later in the file, the value is kept literally through the end of its first line.

File values enter only the runtime-local env store. Unlike Node's flag, they do not configure native libraries or CLI settings such as AVRIL_CACHE_DIR, and NODE_OPTIONS has no special meaning.

1# Environment variables
2
3Import `env` from `avril:std`:
4
5```ts
6import { env, pledge } from "avril:std";
7
8const token = env.get("API_TOKEN");
9env.delete("API_TOKEN");
10pledge({ env: false });
11// Pass the captured token explicitly to code that needs it.
12```
13
14`env` starts with a snapshot of the inherited process environment. Its mutations
15are local to avril: they do not change the host process environment or native
16libraries' configuration.
17
18- `env.get(name)` returns a string, or `undefined` if the variable is absent.
19- `env.has(name)` tests presence; an empty string is still a present value.
20- `env.set(name, value)` creates or replaces a variable.
21- `env.delete(name)` removes a variable; removing an absent variable is a no-op.
22- `env.toObject()` returns an independent, mutable, null-prototype object containing
23  only permitted variables. Changing it does not change the environment store.
24
25All four named operations require permission for that name, even when the
26variable is absent. Permission failures use `errors.PermissionDenied` from
27`avril:std`.
28
29Names must be nonempty and contain neither `=` nor NUL. Values may be empty but
30cannot contain NUL. Invalid names and values use `errors.InvalidInput`. Names
31and values are converted to well-formed strings; symbols are rejected. Host
32variables with non-UTF-8 names or values are omitted rather than lossily decoded.
33Names are case-sensitive on Unix and ASCII case-insensitive on Windows.
34
35## Permissions
36
37Environment access is allowed by default. `-0` disables it along with the other
38ambient capabilities. Grant individual names with repeatable flags:
39
40```sh
41avril run -0 --allow-env=API_TOKEN --allow-env=MODE main.ts
42avril run --deny-env=API_TOKEN main.ts
43```
44
45Names are exact, not patterns or comma-separated lists. Explicit denials win over
46grants, regardless of flag order. Granting environment access does not grant
47filesystem, network, or module execution access.
48
49`pledge` narrows environment authority just like other capabilities:
50
51```ts
52pledge({ env: { allow: ["MODE"], deny: ["API_TOKEN"] } });
53pledge({ env: false });
54```
55
56Omitted categories are unchanged. Successive pledges intersect allowed names and
57accumulate denials; they cannot restore authority. An invalid pledge does not
58partially change permissions. Retained methods recheck current permissions, but
59previously captured strings and snapshots remain usable.
60
61Static imports execute before the importing module's body. Use startup permissions
62if dependencies must never see a secret, or capture and delete secrets before
63loading dependencies with dynamic imports.
64
65## Environment files
66
67`avril run` accepts repeatable `--env-file=PATH` or `--env-file PATH` flags. Paths
68are relative to the current working directory, not the entry module's directory.
69Use the equals form (or a `./` prefix) for filenames beginning with `-`.
70
71```sh
72avril run -0 --allow-env=API_TOKEN --env-file=.env --env-file=.development.env main.ts
73```
74
75Inherited environment values take precedence, including empty strings. Later
76files override earlier files, and the last assignment to a name within a file
77wins. There is no implicit `.env` discovery. Missing, unreadable, non-UTF-8, or
78NUL-containing files stop startup before JavaScript runs; diagnostics do not echo
79file contents.
80
81Naming an env file authorizes the CLI to load it, even with `-0` or read denials,
82but grants **no environment or filesystem authority** to JavaScript. Under `-0`,
83each variable still requires `--allow-env=NAME`, whether its value comes from a
84file or the inherited environment. Explicit `--deny-env` rules still win, and
85`pledge` can subsequently remove authority. Loading values and granting access
86are separate operations.
87
88The dotenv syntax follows Node 24's conventions, with more predictable handling
89of indented comments, empty names, and whitespace-only empty values:
90
91- Assignments use `NAME=value`; surrounding spaces and tabs are trimmed.
92- Blank lines, indented comments, lines without an assignment, and assignments
93  with empty names are ignored.
94- `#` begins a comment outside a quoted value.
95- Values enclosed in single quotes, double quotes, or backticks retain their
96  interior whitespace and may span multiple lines.
97- Double-quoted values expand literal `\n` sequences into newlines. Other escapes
98  and variable references such as `$NAME` or `${NAME}` remain literal.
99- An `export ` prefix is ignored. Empty values are empty strings, including
100  exported assignments and values followed only by whitespace.
101- CRLF is supported, and an initial UTF-8 BOM is ignored.
102- Text after a closing quote on the same line is ignored. If no matching closing
103  quote exists anywhere later in the file, the value is kept literally through
104  the end of its first line.
105
106File values enter only the runtime-local env store. Unlike Node's flag, they do
107not configure native libraries or CLI settings such as `AVRIL_CACHE_DIR`, and
108`NODE_OPTIONS` has no special meaning.