You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 8c69648
Browse filesBrowse the repository at this point in the historyBrowse files
Copy file name to clipboardExpand all lines: docs/releases/0.26.0.md
+27-3Lines changed: 27 additions & 3 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -12,10 +12,10 @@ Until now each basic-cli module had grown its own habits. A timeout was a bare `
12
12
13
13
0.26.0 settles these questions once and applies the answers everywhere:
14
14
15
-
-**A timeout is a`Duration`**, and `Duration.zero` always expires immediately.
15
+
-**Basic-cli timeout options use`Duration`**, and `Duration.zero` always expires immediately. The shared HTTP package keeps its existing request-timeout representation, described below.
16
16
-**The end of input is `Err(EndOfInput)`**, on stdin, files and sockets alike. An empty `Ok` is real data.
17
17
-**Anything you can read, you can loop over**: `lines()` and `chunks()` return a `Stream` on stdin, file readers, TCP connections and HTTP responses, and `rows` does the same for SQLite.
18
-
-**A module's failures arrive under one tag** (`TcpErr(IOErr)`, `CmdErr(Cmd.Err)`, `SqliteErr(Sqlite.Err)`), and every error can be compared with `==`.
18
+
-**A module's failures arrive under one tag** (`TcpErr(IOErr)`, `CmdErr(Cmd.Err)`, `SqliteErr(Sqlite.Err)`), while conditions such as `EndOfInput`and `Interrupted` remain bare tags. Every error can be compared with `==`.
19
19
20
20
The result is that code written against one module reads like code written against another:
21
21
@@ -58,6 +58,8 @@ stale = Utc.now!().since(path.time_modified!()?) > Duration.minutes(5)
58
58
-**File times** (`time_accessed!`, `time_modified!`, `time_created!`) return `Utc` and succeed for files older than 1970.
59
59
- Sleep is now fallible: use `Sleep.millis!(250)?` or `duration.sleep!()?`. A caught stop request returns `Err(Interrupted)`.
60
60
61
+
`Utc` supplies clock readings and signed epoch conversion. Timestamp parsing, calendar calculations and richer formatting belong in packages such as [roc-time](https://github.com/lukewilliamboswell/roc-time), rather than the native host. See its [supported profiles](https://lukewilliamboswell.github.io/roc-time/profiles.html#profiles): roc-time 0.3 uses microseconds, while `Utc` stores nanoseconds. Convert units explicitly between them. `Utc.to_iso_8601` remains a whole-second display; `to_nanos_since_epoch` and `from_nanos_since_epoch` preserve the full stored precision.
62
+
61
63
## Loop over input
62
64
63
65
Reading lines used to mean hand-written recursion. Now it is a loop, and the same loop works on every source:
- You see the status before any of the body is read, so you can decline a 404's body.
131
133
-`idle_timeout` (30 seconds unless you set it) bounds each single wait, not the whole download. A slow but healthy download keeps going; a stalled one stops. After an idle timeout the response is still usable.
134
+
- The shared HTTP `Request` keeps its existing timeout representation: `TimeoutMilliseconds(n)` sets the overall deadline, while `NoTimeout` (the default) and `TimeoutMilliseconds(0)` mean no deadline. Streaming `idle_timeout` uses `After(Duration)` or `NoTimeout`; `After(Duration.zero)` expires immediately.
132
135
-`response.read_to_end!(MaxBytes(1_048_576))` reads the rest with a bound, and `response.content_length()` is `Known(n)` or `Unknown`.
133
136
-`Http.send!`, `send_json!`, `get!` and `get_utf8!` now take trailing options: pass `{}` to return the first response, as before.
137
+
- Requests send `User-Agent: roc-basic-cli/0.26` by default, so APIs that require a user agent can be called directly. An explicit `User-Agent` overrides it regardless of header-name casing. `Content-Type: text/plain` is added only for a non-empty body without an explicit content type; bodyless GETs add none ([#531](https://github.com/roc-lang/basic-cli/issues/531)).
134
138
- Redirects are opt-in: `Http.send!(request, { max_redirects: 5 })` follows 301, 302, 303, 307 and 308. Streaming accepts the same `max_redirects` option. Intermediate bodies are closed without collecting them; the final status, including a 404, is still a response.
135
139
- Redirects use one overall request deadline, including the final body. Cross-origin hops remove authorization and cookie headers, and HTTPS-to-HTTP redirects fail with `HttpErr(InsecureRedirect)`. A POST becomes GET after 301/302, and a non-HEAD request becomes GET after 303; 307/308 preserve the method and body.
136
140
- A positive redirect budget that runs out is `HttpErr(RedirectLimitExceeded(n))`. Duplicate `Location` headers and invalid destinations return `AmbiguousRedirectLocation` and `InvalidRedirectUrl(Url.ParseErr)` under `HttpErr`; a missing `Location` returns the original 3xx response. With the default budget of zero, the host returns the first response without validating redirects.
@@ -151,7 +155,7 @@ in_msg = conn.read_line!()?
151
155
```
152
156
153
157
-`Tcp.Stream` is now `Tcp.Connection`. It holds its I/O timeout and line limit; `conn.with_timeout(d)` and `conn.with_line_limit(n)` return the same connection with another setting.
154
-
-Every failure is `TcpErr(IOErr)`, so `result == Err(TcpErr(TimedOut))` and `Err(TcpErr(ConnectionRefused))` can be matched directly. Windows takes about two seconds to report a refused connection, so a shorter `connect_timeout` gives `TimedOut` there. `IOErr` gains `TimedOut`, `ConnectionRefused`, `ConnectionReset`, `AddrInUse` and `AddrNotAvailable`.
158
+
-TCP I/O failures use `TcpErr(IOErr)`, so `result == Err(TcpErr(TimedOut))` and `Err(TcpErr(ConnectionRefused))` can be matched directly. Conditions such as `EndOfInput`, `BadUtf8`, `ReadLimitExceeded` and `Interrupted` remain bare tags. Windows takes about two seconds to report a refused connection, so a shorter `connect_timeout` gives `TimedOut` there. `IOErr` gains `TimedOut`, `ConnectionRefused`, `ConnectionReset`, `AddrInUse` and `AddrNotAvailable`.
155
159
-**A read that times out or is interrupted consumes nothing.** The bytes that had arrived are returned by the next read, so a client that tolerates idle periods just calls `conn.lines()` again. Caught pending signals return bare `Interrupted`; a failed write may have sent a prefix.
156
160
- A line longer than the limit is `Err(ReadLimitExceeded(n))`; the rest of it is discarded and the next read starts at the next line.
157
161
-`listener.accept!({ accept_timeout: NoTimeout, io_timeout: Duration.seconds(30) })` separates how long to wait for a client from the timeout that client's connection gets.
@@ -210,7 +214,10 @@ child = Cmd.new("server").stdout(Pipe).spawn!()? # run in th
210
214
-`.timeout(Duration.minutes(5))` bounds the whole run, including a spawned child, which is killed at the deadline.
211
215
-**Errors and `to_inspect` no longer contain environment values**, so a secret passed with `.env` cannot leak into a log.
212
216
- Compact `Cmd.call!(program, args)`, `capture!`, `run!`, and `spawn!` complement the reusable `Cmd.Command` builder. Builder execution uses `.call!()` too.
217
+
- Write a child's output directly to disk with `.stdout(File("server.log", Append, {}))` or `.stderr(File(path, Truncate, {}))` ([#532](https://github.com/roc-lang/basic-cli/issues/532)). The modes and options are the same as `File.Writer`, including `RequireNew` and `{ create_permissions: OwnerOnly }`. Relative paths use the command's working directory. With `.stderr(MergeWithStdout)`, both streams share the same open file. No output is captured or queued in memory for a file-backed stream, so capture and pending-output limits do not restrict it. Files are opened before the child starts; a failure returns `CmdErr(StartFailed(...))` and may leave newly created or truncated files.
213
218
-`cmd.inherit_stdio().run!()?.status` waits without recording output in memory. `.stdout(InheritStderr)` sends child stdout directly to parent stderr.
219
+
- Keep a spawned `Child` for later reads, waits or cleanup: releasing its last reference kills and reaps a running child. An unused binding such as `_child` can release it immediately.
220
+
- A spawned child's `Pipe` output is available only through `child.read!(duration)`; it is absent from `child.wait!()`'s output. Choose `Capture` to retain that stream for `wait!`, subject to the output limit.
214
221
-`.stderr(MergeWithStdout)` and `.output_limit(NoLimit)` say directly what used to be encoded in a `Bool` or a zero.
215
222
- Unix `Cmd.exec!(unix, executable_path, args)` replaces the current process and preserves its PID. It uses an exact path with no PATH lookup or shell fallback; success never returns. Finish buffered writers, transactions, terminal raw mode and child cleanup first. A failed replacement leaves the Roc process running.
216
223
@@ -255,6 +262,18 @@ The Windows x64 host keeps the MSVC ABI and now supplies the startup and runtime
255
262
256
263
Update the platform URL and rebuild: the native host interface changed, and mixing hosts and platform sources from different versions is not supported. Then work through the modules you use.
257
264
265
+
With `nightly-2026-10-12-2664a27`, Linux x64 and Intel macOS builds can crash when reusing a cache warmed by `roc check`/`roc test`. For that compiler bug, use `roc build --no-cache your-app.roc` ([reproduction](https://github.com/roc-lang/basic-cli/actions/runs/39512913704)). This is a temporary compiler workaround, not a requirement of the platform upgrade.
266
+
267
+
**Target discovery**
268
+
269
+
| 0.25 | 0.26 |
270
+
|---|---|
271
+
|`Env.platform!()` returns a record |`System.target!()` returns an opaque `System.Target`|
272
+
|`Env.platform!().arch`|`System.target!().arch()` returns `X64` or `Arm64`; `AARCH64` is now `Arm64`|
273
+
|`Env.platform!().os` returns `LINUX`, `MACOS` or `WINDOWS`|`System.target!().os()` returns `Linux(os)`, `MacOS(os)` or `Windows(os)`|
274
+
275
+
Keep the witness returned by an OS match when calling OS-specific operations: `os.unix()` on Linux or macOS supplies the evidence required by `Cmd.exec!` and `Terminate`.
276
+
258
277
**Time**
259
278
260
279
| 0.25 | 0.26 |
@@ -306,6 +325,7 @@ When upgrading an earlier 0.26 draft, add writer options: `File.open_writer!(pat
306
325
307
326
| 0.25 | 0.26 |
308
327
|---|---|
328
+
|`Cmd` as a command-builder type annotation |`Cmd.Command`; `Cmd` is the namespace |
@@ -337,7 +357,9 @@ When upgrading an earlier 0.26 draft, add writer options: `File.open_writer!(pat
337
357
338
358
**Everything else**
339
359
360
+
-**Caught signals add bare `Interrupted` results** to sleep, standard I/O, command waits and pipe I/O, TCP/Unix sockets, HTTP and SQLite operations. Update exhaustive error matches. Before effectful cleanup or retry, acknowledge pending events with `Signal.poll!()`; propagating `Interrupted` to `main!` exits without running application cleanup.
340
361
-**`IOErr` has five new variants** (`TimedOut`, `ConnectionRefused`, `ConnectionReset`, `AddrInUse`, `AddrNotAvailable`). A `match` that lists every `IOErr` variant needs new arms, even in code that never uses TCP.
362
+
-**`Path` representation tags are renamed**: `Unix(bytes)` becomes `UnixBytes(bytes)`, and `Windows(units)` becomes `WindowsU16s(units)`, matching `OsStr`. `Utf8` and the `Path.unix_bytes`/`Path.windows_u16s` constructors keep their names.
341
363
-**`Path` and `OsStr` values that differed only in representation are now equal**, and collapse to one `Dict` or `Set` key.
342
364
-**`Tty.enable_raw_mode!` and `disable_raw_mode!` return a `Try`**; add `?`.
343
365
-**Write `True` and `False`**, not `Bool.True` and `Bool.False`, to match the platform's own docs and examples.
@@ -354,6 +376,8 @@ When upgrading an earlier 0.26 draft, add writer options: `File.open_writer!(pat
354
376
- CI now formats, lints and unit-tests the Rust host on Linux, macOS and Windows ([#514](https://github.com/roc-lang/basic-cli/pull/514)).
355
377
- New examples: [`file-write-stream.roc`](https://github.com/roc-lang/basic-cli/blob/0.26.0/examples/file-write-stream.roc) and [`http-download.roc`](https://github.com/roc-lang/basic-cli/blob/0.26.0/examples/http-download.roc). The TCP, command, SQLite, stdin and file examples are rewritten for the new APIs.
356
378
379
+
When copying an example from an RC tag or a source checkout, replace its pinned platform URL with the 0.26.0 bundle URL above before running it. The stable release workflow opens a follow-up PR to update checked-in example URLs after publication.
0 commit comments