Skip to content

go-strictcli@v0.39.0

Choose a tag to compare

@smm-h smm-h released this 08 Oct 11:51
· 20 commits to main since this release

Adds NewGrant, ErrTimedOut, declared scratch directories (WithScratchDir and ctx.ScratchDir), WithArgsAfterSeparator, HTTP bodies streamed from a file, declared reads, redaction, and the Stdin, Timeout, and Mode effect options; App.Test changes nothing process-wide; test coverage is declared by the source-tree root; the check context factory receives the dispatch's Context; the effects-bypass check skips _test.go files; go-toml-edit is required at its new path.

Breaking

  • [go-strictcli] Test() changes nothing process-wide, so calls may overlap. Each app.Test(argv) call captures its output in its own streams, including the stdout and stderr of children run through Spawn or a streamed Run, and the state a dispatch writes belongs to that dispatch instead of the App: a test can run a long-lived command in one goroutine while it tests another, on one app or several. EffectLog() returns the most recently finished dispatch's log. Breaking: a byte a handler writes to os.Stdout directly (fmt.Println) is no longer captured in Result.Stdout, and under --json Test() no longer arms the stray-stdout guard, which stays on Run(); write through ctx.Out, as --lint-framework-use already requires.
  • [go-strictcli] Test coverage is declared by the source-tree root. WithSourceTreeRoot(path) replaces WithTestCoverageDir(path): the app names the root of its source tree, and strictcli keeps the coverage state in .strictmetadata/.cli-test-coverage/ under it, with per-process shard files in shards/ (never committed) and the committed manifest in manifest.json (formerly coverage/ and test-coverage.json). Coverage is on only when that directory exists at construction. WithTestCoverageDir is now refused at registration, naming WithSourceTreeRoot. To migrate, move .strictcli/test-coverage.json to .strictmetadata/.cli-test-coverage/manifest.json (selfdoc layout migrate does this) and declare the root instead of the directory.
  • [go-strictcli] The check context factory receives the dispatch's *Context. SetCheckContext now takes func(*Context) (CheckContext, error): check and failing-checks call it with their own dispatch's context, so a tool can build its check context over that command's effects handle, and an error from the factory refuses the run with its message. A no-argument factory must be rewritten to take the *Context and return (context, nil).

Features

  • [go-strictcli] Stdin and Timeout effect options. strictcli.Stdin(data) on Run and Spawn writes bytes to the child's stdin and closes it, so secrets never enter argv; the would-do log and effect record show only the byte count (stdin: N bytes, content withheld). strictcli.Timeout(d) on Run and Spawned.Wait kills the child once d has passed and returns an error naming the command and the timeout; a non-positive d is refused, and dry mode renders (timeout: 30s).
  • [go-strictcli] Declared reads, a settable HTTP client, and redaction. strictcli.Read() on HTTP and strictcli.Observe() on Run declare that one call changes nothing: it is legal in a read-only command, never recorded, and performed for real under --dry-run (a stale brand once a mutation has been recorded); without Read() every request stays a recorded mutation whatever its method. strictcli.WithHTTPClient(client) sets the client every live HTTP effect uses, so tests can point commands at an httptest server; without it the framework uses its own client with a 60-second timeout instead of http.DefaultClient, and strictcli.Timeout(d) now also bounds one HTTP request. strictcli.Redact(values...) replaces each value with «redacted» in the would-do log, the effect record, and every error the call returns, so an API key in a query string never reaches --dry-run output; an HTTP body renders only as its byte count. strictcli.Mode(mode) on Write sets the file's permission bits, rendered as (mode: 0600).
  • [go-strictcli] HTTP bodies streamed from a file. strictcli.BodyFile(path) on HTTP sends a regular file as the request body, read while the request is sent instead of into memory first, with the file size as the Content-Length; strictcli.BodyFileRange(path, offset, length) sends one byte range of it, as a multipart upload's parts need. A missing or irregular file, a range outside the file, a repeated option, or a combination with Body is refused at the call. Dry mode renders (body: N bytes from <path>), adding at offset N for a range, and the record carries body_file, body_offset, and body_bytes.
  • [go-strictcli] A command can receive the arguments after -- unparsed. WithArgsAfterSeparator(name, help) hands every token after the first bare -- to the handler through ctx.ArgsAfterSeparator(), untouched (a --dry-run or a second -- there included), while the tokens before it parse as before, so a stray word is still refused. Help lists it under Arguments: as -- <name>..., the help document publishes it as args_after_separator (its command default null joins the defaults block), and Call and the MCP tools take it as a list of strings under its name. A command without the declaration is unchanged.
  • [go-strictcli] ErrTimedOut. An effects-handle run, spawned child's wait, or HTTP request that exceeds its Timeout returns an error matching strictcli.ErrTimedOut with errors.Is (a redacted message too), so a caller tells a timeout from any other failure without reading the message.
  • [go-strictcli] NewGrant. strictcli.NewGrant(name, reason, effectClass) builds a grant authorizing one effect class (ProcMutate, FileWrite, ...), so a caller declares a grant without spelling the struct's effect-class field.
  • [go-strictcli] Scratch directories. A command declaring WithScratchDir() can ask ctx.ScratchDir() for a private directory under ~/.cache/strictcli/scratch/, made on the first request and removed when the dispatch ends; making and removing it is never an effect, so a read_only command or a --dry-run can run a program that writes for itself (a package manager's cache) there. A command that does not declare one is refused. The check and failing-checks commands declare one, so a check context factory can hand it to its checks.

Fixes

  • [go-strictcli] The README now says where an app's check list belongs: in the app's own package, passed with WithChecks(path) or embedded with go:embed and passed to WithChecksEmbed; strictcli has no standard folder or default path for it.
  • [go-strictcli] go-toml-edit is required at its new path, github.com/stricttools/go-toml-edit, at 0.5.0. A program using strictcli no longer pulls the module under its old path.
  • [go-strictcli] The effects-bypass check skips _test.go files. A test file builds only into the test binary, never into the program whose command handlers the check guards, so a test's handler or helper writing a fixture directly is no longer reported as a bypass.