// ZSH-TRAVIS — ENGINEERING REPORT

Travis CI build-page opener · tg main · tb builds · tbr branches · tpr PRs · detects .travis.yml, probes .com then .org

>_EXECUTIVE SUMMARY

zsh-travis opens the Travis CI build page for the current project directly from the prompt. cd into a repo that has a .travis.yml at its top level, type tb, and the latest build log opens in the browser — no tab-switching, no URL-pasting. The plugin gates on .travis.yml, reads git config --get remote.origin.url for the upstream slug, strips the scheme + .com + .git, probes https://travis-ci.com/<slug> and falls back to https://travis-ci.org/<slug> on a 404, then hands the URL to a platform-detected browser opener. Source: 103 zsh lines across 1 plugin entry + 8 autoload functions. Pinned by 12 @test blocks across 3 zunit files.

103
Zsh Lines
12
@test Blocks (zunit)
3
Test Files
8
Autoload Fns
4
Aliases (tg/tb/tbr/tpr)
4
Private Helpers (__trav_*)
4
Platforms (getOpenCommand)
0
Completion Files

~ARCHITECTURE

Single entry point that defines getOpenCommand, caches ZPWR_OPEN_CMD, declares the four t* aliases, then autoloads every function under autoload/ via autoload -Uz "${0:h}/autoload/"*(.:t). Each function is its own one-file unit — lazy-loaded on first use. The three branch/build/PR verbs are thin wrappers that call trav-git with a path suffix; trav-git chains the four private helpers.

File / GroupLinesRole
travis.plugin.zsh 42 Plugin entry (symlinked as zsh-travis.plugin.zsh). Defines getOpenCommand (guarded by (( $+functions[getOpenCommand] ))); caches its result in ZPWR_OPEN_CMD only when unset; declares the four aliases tg/tb/tbr/tpr; sets the Zsh Plugin Standard 0= header; appends ${0:h}/autoload to fpath; autoloads all 8 functions.
autoload/trav-git 10 Main verb behind tg. Runs __trav_check_yml; on success calls __trav_get_url "$@" then __trav_open $finalurl; on failure prints No .travis.yml file found. to stderr.
autoload/trav-git-build · trav-git-br · trav-git-pr 4 / 4 / 5 Path-suffix wrappers. trav-git-build calls trav-git "/builds", trav-git-br"/branches", trav-git-pr"/pull_requests". Behind tb / tbr / tpr.
autoload/__trav_check_yml 6 Gate. Tests $(git rev-parse --show-toplevel)/.travis.yml exists; returns 1 when absent.
autoload/__trav_common_url 14 Slug builder. Reads remote.origin.url, strips https:// / http:// / ssh:// / git:// + .com + .git, appends the path-suffix arg.
autoload/__trav_get_url 12 Host probe. Builds https://travis-ci.com/$url; if curl -s --head --request GET sees 404 Not Found, falls back to https://travis-ci.org/$url; sets finalurl.
autoload/__trav_open 6 Launcher. Runs ${=ZPWR_OPEN_CMD} "$1" 2>/dev/null — word-split so nohup xdg-open / cmd.exe /c start '' expand to argv correctly.
Total source 103 1 entry (42) + 8 autoloads (61) · pure zsh · no completion files

$VERB CONTRACT

Four user-facing verbs, each fronted by a two-letter alias. The three suffixed verbs delegate to trav-git with a literal path argument; trav-git chains the four private helpers. The behavior table is the exact dispatch each verb performs.

Verb (alias)Path suffixBehavior
trav-git (tg)(none)Main Travis page. __trav_check_yml__trav_get_url__trav_open $finalurl. Prints No .travis.yml file found. to stderr if the gate fails.
trav-git-build (tb)/buildstrav-git "/builds" — latest build log.
trav-git-br (tbr)/branchestrav-git "/branches" — current-branch build history.
trav-git-pr (tpr)/pull_requeststrav-git "/pull_requests" — open PR builds.

=HELPERS & URL REWRITE

The slug is built by string-substitution on remote.origin.url — no parsing library, the substitution chain is the contract. Each row is the exact transform applied in __trav_common_url and the probe logic in __trav_get_url.

HelperInputTransform / output
__trav_check_ymlcwdSets trav_file="$(git rev-parse --show-toplevel)/.travis.yml"; test -f $trav_file || return 1.
__trav_common_urlremote.origin.urlStrip https://, http://, ssh://, git://; strip .com; strip .git; append $1 (path suffix). Result in $url.
__trav_get_url$urltempurl="https://travis-ci.com/$url"; if curl -s --head --request GET $tempurl | grep "404 Not Found" matches, finalurl="https://travis-ci.org/$url", else finalurl="$tempurl".
__trav_open$finalurl${=ZPWR_OPEN_CMD} "$1" 2>/dev/null.
getOpenCommand$ZPWR_OS_TYPEMaps darwin→open, cygwin→cygstart, linux→nohup xdg-open (or cmd.exe /c start '' on WSL), msys→start ''; unknown prints unsupported + returns 1.

#TEST COVERAGE

12 @test blocks across 3 zunit files. t-aliases.zsh carries the bulk (7) — it sources the plugin and asserts each alias resolves to its canonical trav-git* function, that all four verbs + getOpenCommand are defined, and that re-sourcing is idempotent. t-contract.zsh pins the install-path shape; t-syntax.zsh runs zsh -n over the entry + autoload tree. The CI suite runs them under zunit on Ubuntu — no live Travis API required.

FileTestsPins
t-aliases.zsh7tgtrav-git, tbtrav-git-build, tbrtrav-git-br, tprtrav-git-pr alias bodies; all four trav-git* fns defined; getOpenCommand defined; idempotent re-source keeps the same t* alias count.
t-contract.zsh3Entrypoint stem (travis) matches the dir basename under the zsh- prefix rule; entrypoint parses under zsh -n; every _* completion file starts with #compdef (passes trivially — there are none).
t-syntax.zsh2zsh -n over every *.zsh in the plugin root and every file under autoload/.
Total123 zunit files · alias-surface + install-contract + syntax

/INTEGRATION

.com / .org host fallback

__trav_get_url probes travis-ci.com first with a curl --head request and only falls back to travis-ci.org when the probe returns 404 Not Found. The split came from the historical .org/.com Travis migration — the plugin works against whichever host actually serves the repo.

Cross-platform opener

getOpenCommand resolves the browser-launch command from $ZPWR_OS_TYPE: open on macOS, nohup xdg-open on Linux, cygstart on Cygwin, start on MSYS, and cmd.exe /c start under WSL (kernel release contains microsoft). Cached in ZPWR_OPEN_CMD at source time.

Fail-safe gating

Every verb runs __trav_check_yml first. Outside a Travis-tracked repo it prints a single stderr line and does nothing — so the t* aliases are safe to keep bound globally without misfiring in non-Travis directories.

git as the source of truth

The repo slug comes straight from git config --get remote.origin.url and the top level from git rev-parse --show-toplevel. No config file, no hardcoded org — the URL tracks wherever origin points.

Lazy autoload + Zsh Plugin Standard

Each verb and helper is one file under autoload/, registered via autoload -Uz — nothing is parsed until first call. The plugin honors the Zsh Plugin Standard 0= header so ${0:h} resolves under zinit / oh-my-zsh / antibody / antigen / bare source.

zpwr plugin chain

Pairs with gh_reveal and the rest of the zpwr plugin family — ZPWR_OS_TYPE and ZPWR_OPEN_CMD are shared with the broader zpwr environment when it is loaded.


!DESIGN DECISIONS

Verb-per-path-suffix, not flags

Rather than one trav-git --builds with option parsing, the plugin ships four named verbs. trav-git-build / -br / -pr are three-line wrappers that call trav-git "/builds" etc. The page you want is the verb you type — and each gets its own two-letter alias.

Probe the host, don't guess it

The Travis .org/.com split means a static base URL would break for half of repos. The curl --head probe in __trav_get_url resolves the live host at call time instead of hardcoding one.

Fail-safe over fail-loud

No .travis.yml = one stderr line, no error spew, no nonzero-cascade. The verbs stay bindable everywhere without polluting non-Travis directories.

Word-split open command

__trav_open uses ${=ZPWR_OPEN_CMD} (the = forces word-splitting) so multi-word openers like nohup xdg-open and cmd.exe /c start '' reach the OS as separate argv entries rather than one impossible command name.

Guarded function definition

getOpenCommand is defined behind (( $+functions[getOpenCommand] )) || so re-sourcing the plugin (or co-existing with zpwr's own copy) does not redefine it — the idempotency the alias test pins.

Verb-as-file, not verb-as-block

8 separate files under autoload/ instead of one mega-file. Each lazy-loads on first call, so cold-startup cost is just the 42-line entry parse. Editing one verb does not invalidate a compiled .zwc for the others.


@FOOTPRINT