@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.
- package/LICENSE +202 -0
- package/NOTICE +2 -0
- package/README.md +146 -2
- package/bin/eris.ts +42 -0
- package/docs/ARCHITECTURE.md +188 -0
- package/docs/COMPLETION.md +106 -0
- package/docs/ERRORS.md +632 -0
- package/docs/OUTPUT-CONTRACT.md +219 -0
- package/docs/README.md +30 -0
- package/docs/UPDATE.md +159 -0
- package/docs/YAK.md +395 -0
- package/docs/dev/ADDING-A-COMMAND.md +79 -0
- package/docs/dev/DEVELOPMENT.md +88 -0
- package/docs/dev/LAYOUT.md +74 -0
- package/docs/dev/PLUGINS.md +382 -0
- package/docs/dev/RELEASING.md +157 -0
- package/docs/dev/TESTING.md +84 -0
- package/docs/dev/surfaces/secrets.md +95 -0
- package/package.json +55 -4
- package/packages/yak-contract/package.json +28 -0
- package/packages/yak-contract/schema/yak-v2.schema.json +416 -0
- package/packages/yak-contract/src/index.ts +1 -0
- package/packages/yak-contract/src/v2.ts +375 -0
- package/plugins/index.ts +12 -0
- package/plugins/yak/handlers.ts +128 -0
- package/plugins/yak/index.ts +51 -0
- package/plugins/yak/login.ts +300 -0
- package/plugins/yak/yak-context.ts +280 -0
- package/plugins/yak/yak-identity.ts +399 -0
- package/plugins/yak/yak-redact.ts +686 -0
- package/plugins/yak/yak-setup.ts +133 -0
- package/plugins/yak/yak-submit.ts +1166 -0
- package/plugins/yak/yak.ts +485 -0
- package/src/build.ts +248 -0
- package/src/cli/args.ts +434 -0
- package/src/cli/commands/completion/bash.ts +24 -0
- package/src/cli/commands/completion/domain.ts +203 -0
- package/src/cli/commands/completion/emit.ts +246 -0
- package/src/cli/commands/completion/shell.ts +24 -0
- package/src/cli/commands/completion/zsh.ts +24 -0
- package/src/cli/commands/completion.ts +48 -0
- package/src/cli/commands/update/github.ts +328 -0
- package/src/cli/commands/update/opt-in.ts +32 -0
- package/src/cli/commands/update/refusal.ts +21 -0
- package/src/cli/commands/update/release.ts +351 -0
- package/src/cli/commands/update/replace.ts +143 -0
- package/src/cli/commands/update/semver.ts +48 -0
- package/src/cli/commands/update.ts +47 -0
- package/src/cli/commands/usage.ts +68 -0
- package/src/cli/commands/version.ts +29 -0
- package/src/cli/compose.ts +273 -0
- package/src/cli/output.ts +81 -0
- package/src/cli/plugin.ts +85 -0
- package/src/cli/registry.ts +180 -0
- package/src/cli/router.ts +31 -0
- package/src/cli/ts-verb.ts +126 -0
- package/src/main.ts +223 -0
- package/src/runtime/env.ts +16 -0
- package/src/runtime/eris-assets.d.ts +5 -0
- package/src/runtime/home.ts +306 -0
- package/src/runtime/logging.ts +43 -0
- package/src/runtime/process.ts +388 -0
- package/src/runtime/settings.ts +191 -0
- package/src/sdk.ts +62 -0
- package/testing/build-child.ts +55 -0
- package/testing/fake-runner.ts +23 -0
- package/testing/index.ts +16 -0
- package/ts/lib/eris-fail.ts +180 -0
- 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)).
|