Guidance for AI agents working on the go-ios codebase. Keep changes minimal and idiomatic; match the style of surrounding code.
(Looking for how to use the ios CLI to automate a device? See the README and
ios help — this file is about developing go-ios itself.)
- Build:
go build ./...(CI usesmake build, which also wires up thego.workworkspace). Use the Go toolchain pinned ingo.mod. - Unit tests:
go test ./...(device-free; this is what CI runs). - Device/integration testing lives only in the
test/e2e/suite, gated by thee2ebuild tag andGO_IOS_E2E_DEVICES, and runs on the self-hosted CI runners. Put device-dependent tests there, not in the package unit tests.
go-ios logs through its internal ios/golog seam (a thin slog wrapper) — library
code must not import logrus or call slog directly. Every log line carries
consistent, filterable attributes:
- module: each package declares
const logModule = "go-ios/<pkg>"once and passes"module", logModuleas the first attr on everygologcall. (In an external_testpackage, use the literal string — the const is unexported.) - udid: add
"udid", device.Properties.SerialNumber(or the in-scope udid) wherever a device is in scope. Don't fabricate one where there's no device. - instance ids: where a connection/instance identifier is already in scope (conn id, forward host/device ports, image path, pid…), add it so the log is self-identifying.
- Do not add a
funcattribute (use a handler withAddSourceif you want call sites). Prefer static messages + key/value attrs overfmt.Sprintf(useSprintfonly as an attr value for verbs slog lacks:%x/%T/%#v).
golog.Info("tunnel started", "module", logModule, "udid", device.Properties.SerialNumber)main.go (the application, not a library module) uses stdlib slog directly and
calls slog.SetDefault. Library embedders route only go-ios's logs via
ios.SetLogger(*slog.Logger), which is scoped and never touches their
slog.Default(). golog falls back to slog.Default() when SetLogger is
unset, so doing nothing yields standard slog behavior.
- PRs from branches in this repo run the real-device e2e suite automatically, after the unit tests pass.
- PRs from forks do not (untrusted code can't get the self-hosted runners
and device secrets). A maintainer runs them by commenting
/test-deviceson the PR; only OWNER/MEMBER/COLLABORATOR comments are honored.
Releases are dispatch-only — merging a PR never ships anything.
- Cut a release from the Actions tab ("Release-Go-iOS" → Run workflow) or:
gh workflow run release.yml -f bump=patch -f release_notes="<markdown notes>"bumpispatch|minor|major. - Write the notes yourself: summarize everything merged since the last
release (review
git log <latest-tag>..mainand the merged PRs) into markdown and pass it asrelease_notes. Group user-facing changes and fixes; reference PR numbers. - The workflow then computes the next version from the latest tag, writes a
## [version] - datesection intoCHANGELOG.md, pushes it tomain, tagsvX.Y.Z, creates the GitHub release with your notes, npm-publishes via OIDC, and verifies the published package installs and runs on Windows/Linux/macOS. All mutating/outbound steps are in the final job, so a failed build ships nothing. - The
RELEASE_PATsecret (admin fine-grained PAT, Contents: read/write) is required — it's how the workflow pushes the changelog commit to protectedmain. The defaultGITHUB_TOKENcannot. - Test any change to the release/publish/packaging path on the canary first:
It publishes a throwaway
gh workflow run release-canary.yml --ref <your-branch>go-ios-canarypackage and runs the same cross-OS install verification. Never debug the release pipeline with real releases.
- Publishing uses npm OIDC trusted publishing (registered per-package on
npmjs.com). Do not add an npm auth token — even an empty
NODE_AUTH_TOKENenv var or an.npmrctoken line breaks OIDC. - The release workflow owns version numbers and the
CHANGELOG.mdhistory; don't hand-write version sections. - In
npm_publish/postinstall.js, the Windows install path must place the binary directly in the npm prefix (on Windows the prefix itself is the bin dir, not abin/subdir), orioswon't be onPATH. mainis protected (1 approving review). Branch for changes and open a PR; the release workflow is the only thing that writes tomaindirectly.