Skip to content

Add WASI (wasip1) build target - #3

Merged
lancekrogers merged 1 commit into
mainfrom
wasm
Jun 12, 2026
Merged

Add WASI (wasip1) build target#3
lancekrogers merged 1 commit into
mainfrom
wasm

Conversation

@lancekrogers

@lancekrogers lancekrogers commented Jun 11, 2026

Copy link
Copy Markdown
Owner

Summary

Adds a supported way to compile tree2scaffold to a portable
GOOS=wasip1 GOARCH=wasm (WASI Preview 1) module that runs under any WASI
runtime (wasmtime, wasmer, wazero, Node) with a preopened directory, scaffolding
real files inside the sandbox from a single architecture-independent .wasm.

The native CLI is unaffected and the public API of pkg/scaffold is unchanged.

What changed

  • internal/env — a small Environment interface abstracting the host
    probes that don't work under WASI (go version, git remote, cwd, clipboard),
    with build-tag-selected implementations:

    • env_exec.go (//go:build !wasip1) — exec-backed, as before.
    • env_wasip1.go (//go:build wasip1) — ErrUnsupported sentinels + os.Getwd.

    This keeps os/exec out of the WASI binary entirely (verified via
    go list -deps), while native builds keep their existing behavior.

  • pkg/scaffold — generators probe the host through the injected
    Environment and degrade gracefully when a probe is unavailable. Also:

    • replaced exec("pwd") with os.Getwd() (portable, strictly better on native too);
    • fixed Go-version parsing so a patch-less go1.24 yields 1.24 (was 1);
    • guarded the module-name fallback so the WASI guest root doesn't produce module /.
  • cmd/tree2scaffoldgetInput now reads stdin directly under WASI, where
    the clipboard is unavailable and ModeCharDevice pipe-detection is unreliable;
    empty input yields an actionable "pipe a tree via stdin" error.

  • just — opt-in recipes in .justfiles/, deliberately excluded from
    just build all:

    • just build wasm · wasm-vet · wasm-run · wasm-release
    • just test wasm
  • CI.github/workflows/wasm.yml builds, smoke-tests under wasmtime, runs
    the integration test, and uploads the .wasm + SHA-256 (attaching to releases on v* tags).

  • Teststest/wasm_integration_test.go, skip-aware (runs only when a
    runtime is present), reusing the existing scaffold-and-verify pattern.

  • README — a "Running as WebAssembly (WASI)" section: build, runtime contract,
    embedding, and sandbox limitations.

Runtime contract

WASI is capability-based, so the runtime must preopen a writable directory
mapped to the guest root / and set PWD=/:

printf 'myproj/\n├── cmd/\n│   └── main.go\n└── go.mod\n' \
  | wasmtime run --dir .::/ --env PWD=/ bin/tree2scaffold.wasm -- -root . -yes

Sandbox limitations (by design)

WASI Preview 1 has no process model, so exec-backed niceties are inert and fall
back gracefully: clipboard input is unavailable (stdin only), the generated
go.mod Go version uses the built-in default, and module-name inference uses a
default. All path-based scaffolding, content generation, and stdio work normally.

Verification

  • go build ./..., go vet ./..., and the full test suite (TEST_ALL=1 go test ./...) pass.
  • GOOS=wasip1 GOARCH=wasm go build + go vet pass; os/exec is absent from the
    WASI binary's dependency graph.
  • End-to-end scaffold confirmed under a real WASI runtime (Node's node:wasi):
    files written to the host via the preopen, exit code 0.

Toolchain

Standard Go (the project's go 1.24.2). TinyGo was evaluated and deferred: its
only edge is binary size, which is irrelevant for a sandbox CLI, and it would
require linking-out os/exec before it builds at all.

Note on scope

This branch contains two commits ahead of main:

  1. Add justfile and ran go fmt (pre-existing) — introduces the modular just
    system (Justfile + .justfiles/) and runs go fmt. The WASI recipes build on
    this. Its go fmt pass is what produces the formatting-only churn in
    pkg/parser, pkg/scaffold, and test/ (no behavior change).
  2. Add WASI (wasip1) build target — the WASI work described above.

Compile tree2scaffold to a portable GOOS=wasip1 GOARCH=wasm module that runs
under any WASI runtime (wasmtime/wasmer/wazero/Node) with a preopened directory.

- internal/env: a small Environment interface with build-tagged implementations
  (native exec-backed; wasip1 no-op). Keeps os/exec out of the WASI binary while
  native behavior is unchanged.
- pkg/scaffold generators probe the host (go version, git remote, cwd) through the
  injected Environment and fall back gracefully when unavailable; replace the
  exec("pwd") call with os.Getwd(); fix go-version parsing for patch-less versions.
- getInput reads stdin directly under WASI, where the clipboard is unavailable and
  char-device detection is unreliable; empty input yields an actionable error.
- just: opt-in `build wasm{,-vet,-run,-release}` and `test wasm` recipes, excluded
  from `build all`.
- Add a skip-aware WASI integration test and a GitHub Actions wasm workflow.
- Document the WASI build, runtime contract, and sandbox limitations in the README.

The public API of pkg/scaffold is unchanged.
@lancekrogers
lancekrogers merged commit 1ad9ebe into main Jun 12, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant