@sibylline-labs/eris 0.0.0-stage → 0.3.0

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 (69) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +2 -0
  3. package/README.md +146 -2
  4. package/bin/eris.ts +42 -0
  5. package/docs/ARCHITECTURE.md +188 -0
  6. package/docs/COMPLETION.md +106 -0
  7. package/docs/ERRORS.md +632 -0
  8. package/docs/OUTPUT-CONTRACT.md +219 -0
  9. package/docs/README.md +30 -0
  10. package/docs/UPDATE.md +159 -0
  11. package/docs/YAK.md +395 -0
  12. package/docs/dev/ADDING-A-COMMAND.md +79 -0
  13. package/docs/dev/DEVELOPMENT.md +88 -0
  14. package/docs/dev/LAYOUT.md +74 -0
  15. package/docs/dev/PLUGINS.md +382 -0
  16. package/docs/dev/RELEASING.md +157 -0
  17. package/docs/dev/TESTING.md +84 -0
  18. package/docs/dev/surfaces/secrets.md +95 -0
  19. package/package.json +55 -4
  20. package/packages/yak-contract/package.json +28 -0
  21. package/packages/yak-contract/schema/yak-v2.schema.json +416 -0
  22. package/packages/yak-contract/src/index.ts +1 -0
  23. package/packages/yak-contract/src/v2.ts +375 -0
  24. package/plugins/index.ts +12 -0
  25. package/plugins/yak/handlers.ts +128 -0
  26. package/plugins/yak/index.ts +51 -0
  27. package/plugins/yak/login.ts +300 -0
  28. package/plugins/yak/yak-context.ts +280 -0
  29. package/plugins/yak/yak-identity.ts +399 -0
  30. package/plugins/yak/yak-redact.ts +686 -0
  31. package/plugins/yak/yak-setup.ts +133 -0
  32. package/plugins/yak/yak-submit.ts +1166 -0
  33. package/plugins/yak/yak.ts +485 -0
  34. package/src/build.ts +248 -0
  35. package/src/cli/args.ts +434 -0
  36. package/src/cli/commands/completion/bash.ts +24 -0
  37. package/src/cli/commands/completion/domain.ts +203 -0
  38. package/src/cli/commands/completion/emit.ts +246 -0
  39. package/src/cli/commands/completion/shell.ts +24 -0
  40. package/src/cli/commands/completion/zsh.ts +24 -0
  41. package/src/cli/commands/completion.ts +48 -0
  42. package/src/cli/commands/update/github.ts +328 -0
  43. package/src/cli/commands/update/opt-in.ts +32 -0
  44. package/src/cli/commands/update/refusal.ts +21 -0
  45. package/src/cli/commands/update/release.ts +351 -0
  46. package/src/cli/commands/update/replace.ts +143 -0
  47. package/src/cli/commands/update/semver.ts +48 -0
  48. package/src/cli/commands/update.ts +47 -0
  49. package/src/cli/commands/usage.ts +68 -0
  50. package/src/cli/commands/version.ts +29 -0
  51. package/src/cli/compose.ts +273 -0
  52. package/src/cli/output.ts +81 -0
  53. package/src/cli/plugin.ts +85 -0
  54. package/src/cli/registry.ts +180 -0
  55. package/src/cli/router.ts +31 -0
  56. package/src/cli/ts-verb.ts +126 -0
  57. package/src/main.ts +223 -0
  58. package/src/runtime/env.ts +16 -0
  59. package/src/runtime/eris-assets.d.ts +5 -0
  60. package/src/runtime/home.ts +306 -0
  61. package/src/runtime/logging.ts +43 -0
  62. package/src/runtime/process.ts +388 -0
  63. package/src/runtime/settings.ts +191 -0
  64. package/src/sdk.ts +62 -0
  65. package/testing/build-child.ts +55 -0
  66. package/testing/fake-runner.ts +23 -0
  67. package/testing/index.ts +16 -0
  68. package/ts/lib/eris-fail.ts +180 -0
  69. package/ts/lib/pool.ts +63 -0
@@ -0,0 +1,219 @@
1
+ # The eris output contract
2
+
3
+ What eris writes, where it writes it, and which parts of that a program may
4
+ depend on. This is a **contract**, not a description: the shapes below are the
5
+ ones consumers parse, and they are pinned black-box by
6
+ `tests/output-contract.test.ts`.
7
+
8
+ ---
9
+
10
+ ## 1. The stream split
11
+
12
+ **stdout is the payload. stderr is everything else. They never merge.**
13
+
14
+ | Stream | Carries | Who reads it |
15
+ |---|---|---|
16
+ | **stdout** | the command's output, and nothing else: the rendered view, the JSON document, the emitted completion script | a pipe, a `$( )`, a program |
17
+ | **stderr** | every diagnostic: `INFO` / `WARN` lines, success (`INFO ✓ …`) included (`src/runtime/logging.ts`), progress lines, interactive prompts, deprecation warnings, and the whole failure block (§4) | a human, or a log |
18
+
19
+ Consequences a caller may rely on:
20
+
21
+ - A command that produces no payload writes **nothing at all** to stdout. A
22
+ failure writes nothing to stdout — the failure is entirely on stderr plus the
23
+ exit code.
24
+ - Progress and success chatter never contaminates a captured value. `vid="$(eris
25
+ … )"` gets the value, never the "✓ …" line that accompanied it.
26
+ - Nothing on stdout is ever *only* human-readable in a way stderr already said.
27
+ If both a table and a note exist, the table is the payload and the note is a
28
+ diagnostic.
29
+
30
+ **One scoped exception:** `eris yak issue|suggest|fatal` under `--output
31
+ json` writes nothing to stderr at all, on any outcome — a stricter rule than
32
+ "stderr carries every diagnostic" above, and deliberately scoped to that one
33
+ surface rather than generalized. See [§3's yak row](#3-machine-surfaces) and
34
+ [YAK.md](YAK.md#--output-json) for what moved onto the stdout record instead
35
+ so nothing is lost.
36
+
37
+ ### Exit codes
38
+
39
+ | Code | Meaning |
40
+ |---|---|
41
+ | `0` | success |
42
+ | `1` | hard failure (an unslugged `ERROR` line, or the §4 failure block) |
43
+ | `2` | **idempotent no-op** — nothing was written and this is not an error, where a command documents one. The payload is emitted either way |
44
+ | `10` | **an update is available**, from `eris update --check` only. Not a failure: the answer a script asked for |
45
+
46
+ `2` and `10` never mean failure. A consumer that treats "non-zero" as "broken" will
47
+ report a successful no-op, or an available update, as an incident.
48
+
49
+ ### ANSI
50
+
51
+ ANSI escape sequences may be written to stdout **only** when all three hold:
52
+
53
+ 1. stdout is a TTY, and
54
+ 2. `NO_COLOR` is unset or empty ([no-color.org](https://no-color.org)), and
55
+ 3. the selected output format is not `json`.
56
+
57
+ No surface in this build writes colour, so a piped run is escape-free.
58
+
59
+ stderr is not styled at all. The diagnostic helpers (`src/runtime/logging.ts`)
60
+ write plain `LEVEL message` text unconditionally, so the failure block in §4 has
61
+ one shape whatever the terminal and whatever is installed.
62
+
63
+ ---
64
+
65
+ ## 2. The flag
66
+
67
+ ```
68
+ eris [--output text|json] <command> [args…]
69
+ ```
70
+
71
+ `--output` is **global**: it is parsed once before dispatch and stripped from
72
+ the argv (`src/cli/args.ts`), for every command line: every verb runs behind it
73
+ in-process. It may appear anywhere in the command line. `--`
74
+ terminates global parsing and is itself consumed, so a positional that begins
75
+ with `--output` is still reachable.
76
+
77
+ - `text` (default) — the human rendering.
78
+ - `json` — the machine rendering.
79
+
80
+ There is deliberately **no `ERIS_OUTPUT` environment variable**. An exported
81
+ format would silently change what every script in a shell gets, including
82
+ scripts written before the variable existed; the flag is the only selector.
83
+
84
+ ### `--output json` never silently degrades
85
+
86
+ A command with no JSON rendering **refuses** the flag
87
+ ([`output-json-unsupported`](ERRORS.md#output-json-unsupported)) rather than
88
+ printing its human rendering. Falling back would hand a parser text and
89
+ surface as a JSON syntax error somewhere downstream, in someone else's stack
90
+ trace. The refusal names the surfaces that do accept it.
91
+
92
+ An unrecognised value is refused the same way
93
+ ([`output-format-unknown`](ERRORS.md#output-format-unknown)).
94
+
95
+ ---
96
+
97
+ ## 3. Machine surfaces
98
+
99
+ The surfaces a program may parse. "Stable" means: changing the shape is a
100
+ breaking change, requires a deprecation path, and must update this table.
101
+
102
+ A build's plugins can add surfaces (`json: true` on a plugin's sub row,
103
+ [dev/PLUGINS.md](dev/PLUGINS.md#--output-json)). The allow-list is derived from
104
+ core and plugin rows together and fails closed: a plugin command without a json
105
+ row is refused like any other, and the refusal's fix text names each plugin
106
+ surface. A plugin's surfaces are documented with that plugin, not here.
107
+
108
+ | Surface | stdout | `--output json` | Stability |
109
+ |---|---|---|---|
110
+ | `eris completion bash\|zsh` | a valid shell script | refused | stable |
111
+ | `eris --version` | text: `eris <version> (<owner/repo>)`, then a line of the build's own if it adds one. JSON: one bare record `{version, release_repo, build_id, extra}` (`build_id` and `extra` are `null` when absent) | accepted | stable ([UPDATE.md](UPDATE.md)) |
112
+ | `eris update` | text: with `--check`, `update available: <current> -> <latest>` or `up to date: <current>`; otherwise nothing (progress and the result are `INFO` lines on stderr). JSON: one bare record `{current, latest, update_available, updated}`. Exit `0` / `10` / `1` for `--check` | accepted | stable ([UPDATE.md](UPDATE.md)) |
113
+ | `eris yak issue\|suggest\|fatal` | text: `recorded: <id>` on a fresh delivery, `already recorded: <id>` on a dedup replay, or nothing at all on a warn-class outcome (unresolved token/URL, unreachable Actor, timeout, non-2xx — see [YAK.md](YAK.md#exit-codes)); every one of those still exits `0`. JSON: one bare record — `{verb, delivered, id?, idempotency_key?, replay?, contract_version?, key_mismatch?, redactions: {count, classes, skipped}, error?}` — where **`delivered` is the field to branch on, never `id == null`**. **`--output json` on this surface additionally writes NOTHING to stderr** — a scoped exception to §1's general "stderr carries every diagnostic": `redactions.skipped` and `key_mismatch` exist specifically so the `--no-redact`/`yak-key-mismatch` WARNs that text mode prints on stderr are still recoverable from the record ([YAK.md §--output json](YAK.md#--output-json)) | accepted for all three verbs; refused for a bare `eris yak` (no verb) | stable ([YAK.md](YAK.md)) |
114
+
115
+ Everything not listed is a **human surface**. Its stdout may change shape in any
116
+ release. Parsing it is unsupported — ask for a machine surface instead.
117
+
118
+ ### 3.1 No envelope
119
+
120
+ There is **no** `{data, meta, errors}` wrapper, and there deliberately will not
121
+ be one.
122
+
123
+ - The error half would duplicate what the exit code and the stderr block (§4)
124
+ already carry, unambiguously and for every command — including the ones with
125
+ no JSON rendering at all.
126
+ - The `meta`/version half would duplicate what consumers already pin (eris by
127
+ version or SHA), and a payload that needs a version carries its own
128
+ (`yak`'s `contract_version`).
129
+ - Adding one would break every existing consumer at once, to gain a field they
130
+ would each have to learn to unwrap.
131
+
132
+ So each machine surface emits its own payload, bare. The envelope question is
133
+ **closed**, not deferred.
134
+
135
+ ---
136
+
137
+ ## 4. The failure block
138
+
139
+ Every failure — `renderFailureBlock` (`ts/lib/eris-fail.ts`, re-exported to
140
+ plugins by the SDK) — writes this to **stderr**, writes nothing to stdout, and
141
+ exits `1`:
142
+
143
+ ```
144
+ ERROR <what failed>
145
+ slug: <slug>
146
+ fix: <line> ← zero or more, one per line of the fix text
147
+ docs: <local path> ← $ERIS_HOME/<relpath>
148
+ docs: <github url> ← https://github.com/sibylline-labs/eris/blob/main/<relpath>
149
+ ```
150
+
151
+ Grammar, stated exactly because consumers line-prefix-parse it:
152
+
153
+ - Field order is fixed: `ERROR`, then `slug:`, then any `fix:` lines, then the
154
+ two `docs:` lines. The two `docs:` lines are always both present, local first.
155
+ The local line names a file that exists, with one exception:
156
+ [`eris-cache-unwritable`](ERRORS.md#eris-cache-unwritable), from a compiled
157
+ binary whose asset home could not be written, has no home on disk, so its local
158
+ line is the bare repo-relative path and the GitHub line is the readable one.
159
+ - Prefixes are **exact and include their single trailing space**: `slug: `,
160
+ `fix: `, `docs: `. The `ERROR` line is `ERROR ` + message, with no colon.
161
+ - Exactly **one** block per failure. A failure ends the command, so it never
162
+ produces follow-on `ERROR` lines (`tests/output-contract.test.ts`, "one
163
+ failure yields exactly ONE block — no cascade").
164
+ - `<slug>` is a stable machine key matching `^[a-z0-9]+(-[a-z0-9]+)+$`, and is
165
+ the anchor of a heading in [ERRORS.md](ERRORS.md). Every emitted slug has a
166
+ heading — pinned by `tests/errors-doc.test.ts`. Slugs are stable once
167
+ shipped.
168
+ - `fix:` lines are literal; multi-line fixes arrive as several `fix:` lines
169
+ whose indentation is part of the content. The last `fix:` line carries text:
170
+ a trailing newline in the fix renders as an empty `fix: ` line.
171
+ - The `ERROR` line has exactly one rendering: plain text, emitted
172
+ unconditionally. Its shape does not depend on a terminal or on anything being
173
+ installed.
174
+ - No secret value ever appears in the block. Messages name the slot or path
175
+ being resolved, never its contents.
176
+
177
+ **Match on the `slug:` line**, not on the `ERROR` message: messages are prose
178
+ and may be reworded, slugs may not.
179
+
180
+ `die` (`src/runtime/logging.ts`) is the unslugged failure path. It writes the same `ERROR <message>` line to stderr and
181
+ exits `1`, so the stream and exit-code halves of the contract hold, but it
182
+ carries no slug. New failures use the block. A consumer must not assume every
183
+ failure has a `slug:` line.
184
+
185
+ ---
186
+
187
+ ## 5. Decisions recorded here
188
+
189
+ | # | Decision | Rationale |
190
+ |---|---|---|
191
+ | 1 | `--output text\|json`, **global**, parsed in the entrypoint | Per-command format flags drift apart and collide; one selector, parsed in one place. |
192
+ | 2 | `--output json` on a surface with no JSON rendering **fails** | A silent fallback to human text is discovered as a parse error in the consumer, at a distance from the cause. |
193
+ | 3 | **No envelope** (§3.1) | Its error half duplicates the exit code + stderr block; its version half duplicates version pinning and the payloads' own versions; adopting it breaks every consumer at once. Closed, not deferred. |
194
+ | 4 | No `ERIS_OUTPUT` env var | An exported format would silently change the output of scripts written before it existed. |
195
+
196
+ Known gap, deliberately not fixed here: shell tab-completion
197
+ ([COMPLETION.md](COMPLETION.md)) offers each command path's own flags from the
198
+ composed registry and does not yet offer the global `--output`.
199
+
200
+ ---
201
+
202
+ ## 6. Changing this contract
203
+
204
+ 1. A machine surface's shape (§3) changes only with a deprecation path. Adding a
205
+ field is additive and safe; renaming or removing one is not.
206
+ 2. A slug is stable once shipped. Add new slugs; do not repurpose old ones.
207
+ 3. Anything moving between stdout and stderr is a breaking change, full stop.
208
+ 4. The black-box tests in `tests/output-contract.test.ts` are the oracle: they
209
+ invoke the real `eris` entrypoint through `tests/helpers/blackbox.ts` (PATH
210
+ fakes and loopback fixtures) and assert the properties above. A change that
211
+ requires editing them is a change to the contract, and needs this document
212
+ edited in the same commit.
213
+
214
+ ## See also
215
+
216
+ - [ERRORS.md](ERRORS.md) — the slug catalogue the `slug:` line indexes
217
+ - [YAK.md](YAK.md) — the `eris yak` record
218
+ - [dev/PLUGINS.md](dev/PLUGINS.md#--output-json) — how a plugin adds a machine surface
219
+ - `src/cli/output.ts` — the allow-list and its refusal
package/docs/README.md ADDED
@@ -0,0 +1,30 @@
1
+ # Eris documentation
2
+
3
+ Start with the [repo README](../README.md) for install and quickstart; come here for the deep references.
4
+
5
+ ## Start here
6
+
7
+ | Doc | What it covers |
8
+ |-----|----------------|
9
+ | [ARCHITECTURE.md](ARCHITECTURE.md) | The current-state map: the shim, the router and registry, plugin composition, and the constraints every change must survive |
10
+ | [OUTPUT-CONTRACT.md](OUTPUT-CONTRACT.md) | What eris guarantees to a **program**: the stdout/stderr split, exit codes, `--output text\|json`, the machine surfaces and their shapes, and the parseable failure block |
11
+ | [ERRORS.md](ERRORS.md) | Every error slug eris raises, with its fix |
12
+
13
+ ## Command references
14
+
15
+ | Doc | Commands | One-liner |
16
+ |-----|----------|-----------|
17
+ | [YAK.md](YAK.md) | `eris yak …`, `eris login`, `eris logout` | Land a durable, deduplicated observation with a Yaks endpoint |
18
+ | [COMPLETION.md](COMPLETION.md) | `eris completion bash\|zsh` | Shell tab completion: install and the derived command tree |
19
+ | [UPDATE.md](UPDATE.md) | `eris --version`, `eris update` | Which build this is, and replacing a binary with the newest release after checking it |
20
+
21
+ ## Developing Eris
22
+
23
+ | Doc | What it covers |
24
+ |-----|----------------|
25
+ | [dev/DEVELOPMENT.md](dev/DEVELOPMENT.md) | Codebase layout, the shim → router → handler path, conventions |
26
+ | [dev/LAYOUT.md](dev/LAYOUT.md) | The annotated source tree |
27
+ | [dev/TESTING.md](dev/TESTING.md) | The `bun test` suite: layout, fixtures and goldens, loopback servers, writing new tests |
28
+ | [dev/ADDING-A-COMMAND.md](dev/ADDING-A-COMMAND.md) | Walkthrough: a new subcommand or command group, end to end |
29
+ | [dev/PLUGINS.md](dev/PLUGINS.md) | Writing a plugin: a verb group as rows plus handlers, composed at build time |
30
+ | [dev/RELEASING.md](dev/RELEASING.md) | Cutting a release: the tag, the workflow, the assets, npm |
package/docs/UPDATE.md ADDED
@@ -0,0 +1,159 @@
1
+ # `eris --version` and `eris update`
2
+
3
+ eris is an npm package, `@sibylline-labs/eris`, run by [bun](https://bun.sh). npm is
4
+ the only way it is installed, so the package manager that installed it is what
5
+ updates it. `eris --version` says which build this is. `eris update` prints the
6
+ commands that get the latest release.
7
+
8
+ ## Installing
9
+
10
+ ```sh
11
+ npx @sibylline-labs/eris@latest <command> # run the latest release, no install
12
+ bunx @sibylline-labs/eris@latest <command> # the same, through bun
13
+ bun add -g @sibylline-labs/eris@latest # install `eris` globally
14
+ ```
15
+
16
+ Every form needs bun on PATH: the package's `eris` command is TypeScript with a bun
17
+ shebang (`bin/eris.ts`). A pre-release is published under npm's `next` dist-tag
18
+ (`@sibylline-labs/eris@next`), which `latest` never resolves to.
19
+
20
+ ## `eris --version`
21
+
22
+ ```
23
+ $ eris --version
24
+ eris v0.2.0 (sibylline-labs/eris)
25
+ ```
26
+
27
+ The first line is `eris <version> (<release repository>)`. A build that adds a line
28
+ of its own (a distribution reporting the core it was built on) prints it next.
29
+
30
+ `eris --output json --version` prints one record:
31
+
32
+ ```json
33
+ {"version":"v0.2.0","release_repo":"sibylline-labs/eris","build_id":"v0.2.0-3f9c1d0a7b2e4c58","extra":null}
34
+ ```
35
+
36
+ | Field | Meaning |
37
+ |---|---|
38
+ | `version` | The tag a compiled build was given (`build({ tag })`); else, for an installed package, `v` and the version the release stamped into its package.json; else `dev` (an untagged build, a source checkout, a git dependency). |
39
+ | `release_repo` | The repository the build's releases are published to (the one a distribution's release `eris update` reads). |
40
+ | `build_id` | The compiled binary's build id, prefixed by the tag; `null` in a checkout. |
41
+ | `extra` | The line a distribution added (`build({ versionExtra })`); `null` when it added none. |
42
+
43
+ Every field is baked into a compiled binary at build time. A run from source reports
44
+ its package's version (or `dev`), a null `build_id`, and the release repository and line its entry handed
45
+ `runProgram({ identity })` (core's defaults when it handed none); a binary ignores that
46
+ option. `--version` reads no file, so it
47
+ answers even when the binary's asset cache cannot be written.
48
+
49
+ ## `eris update`
50
+
51
+ ```
52
+ $ eris update
53
+ eris v0.3.0. Run the latest release with:
54
+ npx @sibylline-labs/eris@latest <command>
55
+ bunx @sibylline-labs/eris@latest <command>
56
+ or install it globally (bun runs it, so bun must be on PATH):
57
+ bun add -g @sibylline-labs/eris@latest
58
+ ```
59
+
60
+ It reads nothing, fetches nothing, changes nothing, and exits `0`. It takes no flags.
61
+ `eris --output json update` prints `{"current":"v0.3.0","commands":[…]}`: this build's
62
+ version and the same three commands, in the same order.
63
+
64
+ ## The release `eris update` (distributions)
65
+
66
+ A distribution that publishes compiled binaries to GitHub releases composes a second
67
+ `eris update` in place of core's: `RELEASE_UPDATE` from `eris/sdk`
68
+ ([dev/PLUGINS.md](dev/PLUGINS.md#distributions)). It replaces the running binary with
69
+ the newest release of the repository it was built from, in place, after checking it.
70
+ Everything below describes that verb; core's own build never runs it.
71
+
72
+ ```
73
+ eris update [--check] [--to <tag>]
74
+ ```
75
+
76
+ | Flag | Meaning |
77
+ |---|---|
78
+ | none | Install the latest release if it is newer than this build. |
79
+ | `--check` | Say whether an update is available and change nothing. |
80
+ | `--to <tag>` | Install that release instead: a pre-release, or an older one (a downgrade). |
81
+
82
+ ### Exit codes and output
83
+
84
+ | Run | Exit | stdout | stderr |
85
+ |---|---|---|---|
86
+ | update installed | `0` | nothing (`--output json`: the record) | `INFO` progress lines, then `INFO ✓ updated eris v0.2.0 -> v0.2.1 (<path>)` |
87
+ | already current | `0` | nothing (`--output json`: the record) | `INFO ✓ eris v0.2.1 is up to date` |
88
+ | `--check`, current | `0` | `up to date: v0.2.1` | nothing |
89
+ | `--check`, newer exists | `10` | `update available: v0.2.0 -> v0.2.1` | nothing |
90
+ | refused, or `--check` could not tell | `1` | nothing | a failure block ([ERRORS.md](ERRORS.md)) |
91
+
92
+ Exit `10` is not a failure: it is how a script asks "is there something newer?"
93
+ (`eris update --check; [ $? -eq 10 ] && eris update`).
94
+
95
+ `--output json` prints one record on every run that does not fail, `--check` included:
96
+
97
+ ```json
98
+ {"current":"v0.2.0","latest":"v0.2.1","update_available":true,"updated":true}
99
+ ```
100
+
101
+ `current` is this build's version, `latest` the release the run resolved (the newest,
102
+ or the one `--to` named), `update_available` whether that release is newer than this
103
+ build, and `updated` whether the binary was replaced. `--to` an older release gives
104
+ `update_available: false, updated: true`. Versions are ordered as semver orders them
105
+ (`v0.0.10` is after `v0.0.9`, a pre-release before its release); a build whose version
106
+ is not one (`dev`) is offered every release.
107
+
108
+ ### What it does
109
+
110
+ 1. **Refuses before any request** what it cannot update in place: a source checkout
111
+ ([`update-not-compiled`](ERRORS.md#update-not-compiled)), a platform with no release
112
+ binary ([`update-unsupported-platform`](ERRORS.md#update-unsupported-platform)), an
113
+ unusable `ERIS_UPDATE_API` ([`update-api-invalid`](ERRORS.md#update-api-invalid)),
114
+ and, unless `--check`, a directory it cannot write
115
+ ([`update-target-unwritable`](ERRORS.md#update-target-unwritable)).
116
+ 2. **Asks the releases API** of `release_repo` for the latest release, or the tag `--to`
117
+ named. The latest never means a pre-release or a draft.
118
+ 3. **Reads the release's `SHA256SUMS`**, then downloads this platform's
119
+ `eris-<os>-<arch>` (`darwin-arm64` or `linux-x64`) into a temporary file beside the
120
+ binary, hashing it as it arrives. A mismatch removes the file and changes nothing
121
+ ([`update-checksum-mismatch`](ERRORS.md#update-checksum-mismatch)); a release with no
122
+ checksum for the binary is refused too
123
+ ([`update-asset-missing`](ERRORS.md#update-asset-missing)).
124
+ 4. **Replaces** the binary with an atomic rename (resolving a symlink to the file it
125
+ names), keeping the old one as `<path>.old`.
126
+ 5. **Runs the new binary** (`eris --output json --version`) and requires it to report the
127
+ release's tag. If it does not, the old binary is put back from `<path>.old`
128
+ ([`update-smoke-failed`](ERRORS.md#update-smoke-failed)). Otherwise `<path>.old` is removed.
129
+
130
+ An interrupt (Ctrl-C, `TERM`, a hangup) is safe at every step: during the download it
131
+ removes the partial file, and between the rename and the check it puts the old binary
132
+ back, then ends by that signal as it would have.
133
+
134
+ Assets are fetched through the API's asset url with `Accept: application/octet-stream`,
135
+ never through a release's `browser_download_url`: for a private repository only the
136
+ former answers to a token.
137
+
138
+ ### Tokens and the API host
139
+
140
+ | Variable | Meaning |
141
+ |---|---|
142
+ | `GH_TOKEN`, `GITHUB_TOKEN` | A GitHub token, `GH_TOKEN` first; surrounding whitespace is ignored, and a value with a space or line break inside is refused ([`update-token-invalid`](ERRORS.md#update-token-invalid)). Needed to update from a private repository. Anonymous requests share a limit of 60 an hour per address ([`update-rate-limited`](ERRORS.md#update-rate-limited)); a token raises it. |
143
+ | `ERIS_UPDATE_API` | Another API base: a GitHub Enterprise host (`https://ghe.example.com/api/v3`) or a test server. `https`, or `http` for `127.0.0.1`, `localhost` and `[::1]` only; no credentials, query or fragment. Default `https://api.github.com`. A base on any host other than `api.github.com` or a loopback host is sent no token, so a private repository on a GitHub Enterprise host cannot be updated from with this variable. |
144
+
145
+ The token is read only when the API base is `https://api.github.com` or a loopback
146
+ host, so a stray or planted `ERIS_UPDATE_API` never receives it. It is sent to that
147
+ API host and nowhere else. The asset download redirects to
148
+ GitHub's storage host, and the redirect is followed by hand so that hop carries no
149
+ `Authorization` header; an asset url on any other host never receives it. The new
150
+ binary is run without the token in its environment.
151
+
152
+ ### Which repository
153
+
154
+ A binary updates from the repository it was built to be released from, so one build
155
+ never replaces itself with another's: the repository is fixed by `build({ releaseRepo })`
156
+ (default `sibylline-labs/eris`). A run from source reports the one its entry handed
157
+ `runProgram({ identity })`, else the default, and refuses to update in place anyway. A distribution that
158
+ publishes its own releases must publish them in the same shape
159
+ ([dev/RELEASING.md](dev/RELEASING.md)).