Skip to content

Commit cee66b7

Browse files
authored
ci: add GitHub Actions workflow (#1)
* ci: add github actions workflow * ci: update moon registry before checks * fix(tty): remove raw fd isatty surface * ci: handle cross-platform test output * ci: use msvc for windows native tests * test(tty): skip pty probes on windows * ci: force windows execution policy update
1 parent 2526934 commit cee66b7

7 files changed

Lines changed: 110 additions & 28 deletions

File tree

.github/workflows/ci.yml

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
name: CI
2+
3+
on:
4+
pull_request:
5+
push:
6+
branches:
7+
- main
8+
9+
permissions:
10+
contents: read
11+
12+
jobs:
13+
test:
14+
name: ${{ matrix.os }}
15+
runs-on: ${{ matrix.os }}
16+
strategy:
17+
fail-fast: false
18+
matrix:
19+
os:
20+
- ubuntu-latest
21+
- macos-latest
22+
- windows-latest
23+
24+
steps:
25+
- name: Checkout
26+
uses: actions/checkout@v4
27+
28+
- name: Setup MSVC
29+
if: runner.os == 'Windows'
30+
uses: ilammy/msvc-dev-cmd@v1
31+
32+
- name: Configure Windows C compiler
33+
if: runner.os == 'Windows'
34+
shell: pwsh
35+
run: '"MOON_CC=cl.exe" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append'
36+
37+
- name: Install MoonBit
38+
if: runner.os != 'Windows'
39+
shell: bash
40+
run: |
41+
curl -fsSL https://cli.moonbitlang.com/install/unix.sh | bash
42+
echo "$HOME/.moon/bin" >> "$GITHUB_PATH"
43+
echo "MOON_HOME=$HOME/.moon" >> "$GITHUB_ENV"
44+
45+
- name: Install MoonBit
46+
if: runner.os == 'Windows'
47+
shell: pwsh
48+
run: |
49+
Set-ExecutionPolicy RemoteSigned -Scope CurrentUser -Force
50+
irm https://cli.moonbitlang.com/install/powershell.ps1 | iex
51+
"$env:USERPROFILE\.moon\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
52+
"MOON_HOME=$env:USERPROFILE\.moon" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
53+
54+
- name: Show MoonBit version
55+
run: moon version --all
56+
57+
- name: Update package registry
58+
run: moon update
59+
60+
- name: Check formatting
61+
run: moon fmt --check
62+
63+
- name: Check packages
64+
run: moon check
65+
66+
- name: Run tests
67+
run: moon test
68+
69+
- name: Generate interfaces
70+
if: runner.os != 'Windows'
71+
run: moon info
72+
73+
- name: Verify generated files are current
74+
if: runner.os != 'Windows'
75+
run: git diff --exit-code

docs/plans/2026-05-21-isatty-os-error.md

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,15 @@ Use `moonbitlang/async/os_error` for `isatty` failures instead of a local
1919
- `isatty` changes from raising `TtyError` to raising `@os_error.OSError`.
2020
- The local `TtyError` type is removed from the root interface.
2121
- Add the public `Fd` trait used by generic `isatty`.
22-
- Add `Fd` impls for `@async/types.Fd`, `@async/fs.File`,
22+
- Add `Fd` impls for concrete async I/O handles: `@async/fs.File`,
2323
`@async/pipe.PipeRead`, `@async/pipe.PipeWrite`, `@async/stdio.Input`, and
24-
`@async/stdio.Output`.
24+
`@async/stdio.Output`. Raw `@async/types.Fd` is intentionally not a
25+
supported public input.
2526

2627
## Acceptance
2728

2829
- `isatty` returns `true` for terminal handles.
2930
- `isatty` returns `false` for valid non-terminal handles.
30-
- Invalid fds raise `@os_error.OSError`.
3131
- `.mbti` contains only the intended `isatty` and `Fd` surface changes.
3232

3333
## Validation
@@ -46,12 +46,10 @@ Use `moonbitlang/async/os_error` for `isatty` failures instead of a local
4646
- Removed the root `TtyError` error type.
4747
- Kept non-terminal handles as `false` and invalid fd failures as OS errors.
4848
- Kept the fd-like generic shape by adding a public `Fd` trait and impls for
49-
the async fd-bearing handle types already supported by root reader/writer
49+
the concrete async I/O handle types already supported by root reader/writer
5050
traits.
5151
- Made `Reader` and `Writer` inherit `Fd` so fd-bearing constraints have one
5252
source of truth.
53-
- Updated the invalid fd white-box test to assert the `isatty` OS error
54-
context.
5553
- Updated tests and the `examples/isatty` demo to pass stdio handles directly.
5654

5755
## Public API Audit
@@ -60,8 +58,9 @@ Use `moonbitlang/async/os_error` for `isatty` failures instead of a local
6058
`Bool raise @os_error.OSError`.
6159
- Root `.mbti` removes `TtyError`.
6260
- Root `.mbti` adds only the intended `Fd` trait and impls for
63-
`@async/types.Fd`, `@async/fs.File`, `@async/pipe.PipeRead`,
64-
`@async/pipe.PipeWrite`, `@async/stdio.Input`, and `@async/stdio.Output`.
61+
`@async/fs.File`, `@async/pipe.PipeRead`, `@async/pipe.PipeWrite`,
62+
`@async/stdio.Input`, and `@async/stdio.Output`; it does not expose raw
63+
`@async/types.Fd` as an accepted `isatty` input.
6564
- Root `.mbti` changes `Reader` and `Writer` to inherit `Fd` instead of
6665
repeating their own `fd` method.
6766
- No parser state, terminal handle storage, platform resize behavior, or VT
@@ -76,3 +75,11 @@ Use `moonbitlang/async/os_error` for `isatty` failures instead of a local
7675
- `moon info`
7776
- generated `.mbti` diff reviewed
7877
- `git diff --check`
78+
79+
## Follow-up CI Adjustment
80+
81+
- Removed the raw `@async/types.Fd` impl from the public `Fd` surface. Unix
82+
aliases that type to `Int`, but Windows keeps it opaque, so accepting the raw
83+
type leaks platform handle details into `isatty`.
84+
- Removed the `isatty(-1)` white-box test because invalid integer fd behavior is
85+
Unix-specific and not part of the cross-platform public contract.

isatty.mbt

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,6 @@ pub(open) trait Fd {
2020
fn fd(Self) -> @async/types.Fd
2121
}
2222

23-
///|
24-
pub impl Fd for @async/types.Fd with fn fd(self) {
25-
self
26-
}
27-
2823
///|
2924
pub impl Fd for @async/fs.File with fn fd(self) {
3025
@async/fs.File::fd(self)

pkg.generated.mbti

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,6 @@ pub struct WindowSize {
8585
pub(open) trait Fd {
8686
fn fd(Self) -> Int
8787
}
88-
pub impl Fd for Int
8988
pub impl Fd for @fs.File
9089
pub impl Fd for @pipe.PipeRead
9190
pub impl Fd for @pipe.PipeWrite

tests/isatty_test.mbt

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,21 +38,35 @@ async fn read_until_nul(pty : @pty.Pty, timeout_ms? : Int = 3000) -> String {
3838
@utf8.decode(buf.to_bytes())
3939
}
4040

41+
///|
42+
fn parse_build_output(output : &@io.Data) -> String raise {
43+
let output_text = output.text()
44+
for line in output_text.split("\n") {
45+
let line = line.trim()
46+
if line.has_prefix("{") {
47+
let data : &@io.Data = line
48+
let build_output = data.json()
49+
guard build_output is { "artifacts_path": [String(artifacts_path)], .. } else {
50+
fail("invalid build output: \{output_text}")
51+
}
52+
return artifacts_path
53+
}
54+
}
55+
fail("missing build output JSON: \{output_text}")
56+
}
57+
4158
///|
4259
async fn build(name : String) -> String {
4360
let (build_exit, build_stdout, build_stderr) = @process.collect_output(
4461
"moon",
4562
["-C", "tests/\{name}", "run", ".", "--build-only"],
4663
)
4764
guard build_exit is 0 else { fail("build failed: \{build_stderr.text()}") }
48-
let build_output = build_stdout.json()
49-
guard build_output is { "artifacts_path": [String(artifacts_path)], .. } else {
50-
fail("invalid build output: \{build_stdout.text()}")
51-
}
52-
artifacts_path
65+
parse_build_output(build_stdout)
5366
}
5467

5568
///|
69+
#cfg(not(platform="windows"))
5670
async test "isatty" {
5771
@async.with_task_group() <| group => {
5872
let artifacts_path = build("isatty")

tests/moon.pkg

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import {
22
"moonbitlang/async",
3+
"moonbitlang/async/io",
34
"moonbitlang/async/process",
45
"moonbit-community/pty",
56
"moonbitlang/core/encoding/utf8",

tty_wbtest.mbt

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -144,15 +144,6 @@ async test "tty window size rejects non tty output" {
144144
)
145145
}
146146

147-
///|
148-
test "isatty rejects invalid fd" {
149-
try isatty(-1) catch {
150-
@os_error.OSError(_, context~) => @debug.assert_eq(context, "isatty")
151-
} noraise {
152-
_ => fail("expected invalid fd error")
153-
}
154-
}
155-
156147
///|
157148
test "state buffer can be made raw" {
158149
assert_true(sizeof_state > 0)

0 commit comments

Comments
 (0)