Merge pull request #103 from bnema/feat/release-ci
ci: goreleaser release pipeline (linux/amd64 + darwin/arm64)
6 files · +433 -4
A .github/workflows/release.yml +21 -0
| @@ -0,0 +1,21 @@ | |||
| 1 | + | name: release | |
| 2 | + | ||
| 3 | + | on: | |
| 4 | + | push: | |
| 5 | + | tags: ['v*'] | |
| 6 | + | ||
| 7 | + | # Default to no token permissions; release alone needs GitHub Release writes. | |
| 8 | + | permissions: {} | |
| 9 | + | ||
| 10 | + | jobs: | |
| 11 | + | release: | |
| 12 | + | permissions: | |
| 13 | + | contents: write | |
| 14 | + | # Pinned by SHA deliberately: this workflow receives the AUR key. | |
| 15 | + | # Bump the pin manually when bnema/gh-actions changes. | |
| 16 | + | uses: bnema/gh-actions/.github/workflows/go-release.yml@fcda8139e65ec45af95bc385e318351c86712d48 | |
| 17 | + | with: | |
| 18 | + | go-version-file: go.mod | |
| 19 | + | goreleaser-version: '~> v2' | |
| 20 | + | secrets: | |
| 21 | + | AUR_KEY: ${{ secrets.AUR_KEY }} | |
| @@ -0,0 +1,21 @@ | |||||
| 1 | + | name: release | |||
| 2 | + | ||||
| 3 | + | on: | |||
| 4 | + | push: | |||
| 5 | + | tags: ['v*'] | |||
| 6 | + | ||||
| 7 | + | # Default to no token permissions; release alone needs GitHub Release writes. | |||
| 8 | + | permissions: {} | |||
| 9 | + | ||||
| 10 | + | jobs: | |||
| 11 | + | release: | |||
| 12 | + | permissions: | |||
| 13 | + | contents: write | |||
| 14 | + | # Pinned by SHA deliberately: this workflow receives the AUR key. | |||
| 15 | + | # Bump the pin manually when bnema/gh-actions changes. | |||
| 16 | + | uses: bnema/gh-actions/.github/workflows/go-release.yml@fcda8139e65ec45af95bc385e318351c86712d48 | |||
| 17 | + | with: | |||
| 18 | + | go-version-file: go.mod | |||
| 19 | + | goreleaser-version: '~> v2' | |||
| 20 | + | secrets: | |||
| 21 | + | AUR_KEY: ${{ secrets.AUR_KEY }} | |||
A .goreleaser.yaml +142 -0
| @@ -0,0 +1,142 @@ | |||
| 1 | + | version: 2 | |
| 2 | + | ||
| 3 | + | project_name: vev | |
| 4 | + | ||
| 5 | + | before: | |
| 6 | + | hooks: | |
| 7 | + | - go mod tidy | |
| 8 | + | ||
| 9 | + | builds: | |
| 10 | + | - id: vev | |
| 11 | + | main: . | |
| 12 | + | binary: vev | |
| 13 | + | env: | |
| 14 | + | - CGO_ENABLED=0 | |
| 15 | + | goos: | |
| 16 | + | - linux | |
| 17 | + | - darwin | |
| 18 | + | goarch: | |
| 19 | + | - amd64 | |
| 20 | + | - arm64 | |
| 21 | + | ignore: | |
| 22 | + | - goos: linux | |
| 23 | + | goarch: arm64 | |
| 24 | + | - goos: darwin | |
| 25 | + | goarch: amd64 | |
| 26 | + | ldflags: | |
| 27 | + | - -s -w | |
| 28 | + | - -X github.com/bnema/vev/internal/app.version={{.Version}} | |
| 29 | + | - -X github.com/bnema/vev/internal/app.commit={{.ShortCommit}} | |
| 30 | + | - -X github.com/bnema/vev/internal/app.date={{.Date}} | |
| 31 | + | ||
| 32 | + | archives: | |
| 33 | + | - id: default | |
| 34 | + | # Contract consumed by install.sh and the AUR package block: | |
| 35 | + | # vev_linux_x86_64.tar.gz / vev_darwin_arm64.tar.gz | |
| 36 | + | name_template: >- | |
| 37 | + | {{ .ProjectName }}_{{ .Os }}_{{ if eq .Arch "amd64" }}x86_64{{ else }}{{ .Arch }}{{ end }} | |
| 38 | + | formats: ['tar.gz'] | |
| 39 | + | files: | |
| 40 | + | - LICENSE | |
| 41 | + | - README.md | |
| 42 | + | - scripts/vev-bar-top-right | |
| 43 | + | - scripts/vev-bar-bottom-right | |
| 44 | + | ||
| 45 | + | checksum: | |
| 46 | + | # Fixed name: install.sh downloads .../download/<tag>/checksums.txt | |
| 47 | + | name_template: checksums.txt | |
| 48 | + | algorithm: sha256 | |
| 49 | + | ||
| 50 | + | changelog: | |
| 51 | + | sort: asc | |
| 52 | + | use: github | |
| 53 | + | filters: | |
| 54 | + | exclude: | |
| 55 | + | - '^docs(\(.+\))?:' | |
| 56 | + | - '^test(\(.+\))?:' | |
| 57 | + | - '^ci(\(.+\))?:' | |
| 58 | + | - '^chore(\(.+\))?:' | |
| 59 | + | - '^build\(deps.*\):' | |
| 60 | + | - Merge pull request | |
| 61 | + | - Merge branch | |
| 62 | + | groups: | |
| 63 | + | - title: Features | |
| 64 | + | regexp: '^feat(\(.+\))?:' | |
| 65 | + | order: 0 | |
| 66 | + | - title: Bug Fixes | |
| 67 | + | regexp: '^fix(\(.+\))?:' | |
| 68 | + | order: 1 | |
| 69 | + | - title: Performance | |
| 70 | + | regexp: '^perf(\(.+\))?:' | |
| 71 | + | order: 2 | |
| 72 | + | - title: Refactoring | |
| 73 | + | regexp: '^refactor(\(.+\))?:' | |
| 74 | + | order: 3 | |
| 75 | + | - title: Other Changes | |
| 76 | + | order: 999 | |
| 77 | + | ||
| 78 | + | release: | |
| 79 | + | github: | |
| 80 | + | owner: bnema | |
| 81 | + | name: vev | |
| 82 | + | draft: false | |
| 83 | + | replace_existing_draft: true | |
| 84 | + | replace_existing_artifacts: true | |
| 85 | + | prerelease: auto | |
| 86 | + | make_latest: true | |
| 87 | + | footer: | | |
| 88 | + | --- | |
| 89 | + | ||
| 90 | + | ## Install | |
| 91 | + | ||
| 92 | + | ### Script (Linux x86_64, macOS arm64) | |
| 93 | + | ```sh | |
| 94 | + | curl -fsSL https://raw.githubusercontent.com/bnema/vev/{{ .Tag }}/install.sh | VEV_VERSION={{ .Tag }} sh | |
| 95 | + | ``` | |
| 96 | + | ||
| 97 | + | ### Arch Linux | |
| 98 | + | ```sh | |
| 99 | + | yay -S vev-bin | |
| 100 | + | ``` | |
| 101 | + | ||
| 102 | + | ### macOS note | |
| 103 | + | The darwin binary is ad-hoc signed by the Go toolchain, not notarized. | |
| 104 | + | Installing via the script or `curl` avoids the quarantine attribute; | |
| 105 | + | if you downloaded the archive with a browser, Gatekeeper will block it. | |
| 106 | + | Removing the attribute bypasses a Gatekeeper security control — only do | |
| 107 | + | it for the vev binary itself, after verifying its checksum: | |
| 108 | + | ```sh | |
| 109 | + | xattr -d com.apple.quarantine ~/.local/bin/vev | |
| 110 | + | ``` | |
| 111 | + | ||
| 112 | + | {{- if .PreviousTag }} | |
| 113 | + | **Full Changelog**: https://github.com/bnema/vev/compare/{{ .PreviousTag }}...{{ .Tag }} | |
| 114 | + | {{- else }} | |
| 115 | + | **Full Changelog**: https://github.com/bnema/vev/releases/tag/{{ .Tag }} | |
| 116 | + | {{- end }} | |
| 117 | + | ||
| 118 | + | git: | |
| 119 | + | tag_sort: smartsemver | |
| 120 | + | ||
| 121 | + | aurs: | |
| 122 | + | - name: vev-bin | |
| 123 | + | ids: ['default'] | |
| 124 | + | homepage: https://github.com/bnema/vev | |
| 125 | + | description: Terminal multiplexer with a per-user daemon, server-side rendering, and minimal socket/SSH diffs | |
| 126 | + | maintainers: | |
| 127 | + | - bnema <b at bnema dot dev> | |
| 128 | + | license: MIT | |
| 129 | + | private_key: '{{ .Env.AUR_KEY }}' | |
| 130 | + | git_url: ssh://aur@aur.archlinux.org/vev-bin.git | |
| 131 | + | # auto: prerelease tags (v1.2.3-rc.1) are not pushed to AUR. | |
| 132 | + | skip_upload: auto | |
| 133 | + | provides: | |
| 134 | + | - vev | |
| 135 | + | conflicts: | |
| 136 | + | - vev | |
| 137 | + | package: |- | |
| 138 | + | install -Dm755 "./vev" "${pkgdir}/usr/bin/vev" | |
| 139 | + | install -Dm755 "./scripts/vev-bar-top-right" "${pkgdir}/usr/bin/vev-bar-top-right" | |
| 140 | + | install -Dm755 "./scripts/vev-bar-bottom-right" "${pkgdir}/usr/bin/vev-bar-bottom-right" | |
| 141 | + | install -Dm644 "./LICENSE" "${pkgdir}/usr/share/licenses/vev-bin/LICENSE" | |
| 142 | + | commit_msg_template: 'Update to {{ .Tag }}' | |
| @@ -0,0 +1,142 @@ | |||||
| 1 | + | version: 2 | |||
| 2 | + | ||||
| 3 | + | project_name: vev | |||
| 4 | + | ||||
| 5 | + | before: | |||
| 6 | + | hooks: | |||
| 7 | + | - go mod tidy | |||
| 8 | + | ||||
| 9 | + | builds: | |||
| 10 | + | - id: vev | |||
| 11 | + | main: . | |||
| 12 | + | binary: vev | |||
| 13 | + | env: | |||
| 14 | + | - CGO_ENABLED=0 | |||
| 15 | + | goos: | |||
| 16 | + | - linux | |||
| 17 | + | - darwin | |||
| 18 | + | goarch: | |||
| 19 | + | - amd64 | |||
| 20 | + | - arm64 | |||
| 21 | + | ignore: | |||
| 22 | + | - goos: linux | |||
| 23 | + | goarch: arm64 | |||
| 24 | + | - goos: darwin | |||
| 25 | + | goarch: amd64 | |||
| 26 | + | ldflags: | |||
| 27 | + | - -s -w | |||
| 28 | + | - -X github.com/bnema/vev/internal/app.version={{.Version}} | |||
| 29 | + | - -X github.com/bnema/vev/internal/app.commit={{.ShortCommit}} | |||
| 30 | + | - -X github.com/bnema/vev/internal/app.date={{.Date}} | |||
| 31 | + | ||||
| 32 | + | archives: | |||
| 33 | + | - id: default | |||
| 34 | + | # Contract consumed by install.sh and the AUR package block: | |||
| 35 | + | # vev_linux_x86_64.tar.gz / vev_darwin_arm64.tar.gz | |||
| 36 | + | name_template: >- | |||
| 37 | + | {{ .ProjectName }}_{{ .Os }}_{{ if eq .Arch "amd64" }}x86_64{{ else }}{{ .Arch }}{{ end }} | |||
| 38 | + | formats: ['tar.gz'] | |||
| 39 | + | files: | |||
| 40 | + | - LICENSE | |||
| 41 | + | - README.md | |||
| 42 | + | - scripts/vev-bar-top-right | |||
| 43 | + | - scripts/vev-bar-bottom-right | |||
| 44 | + | ||||
| 45 | + | checksum: | |||
| 46 | + | # Fixed name: install.sh downloads .../download/<tag>/checksums.txt | |||
| 47 | + | name_template: checksums.txt | |||
| 48 | + | algorithm: sha256 | |||
| 49 | + | ||||
| 50 | + | changelog: | |||
| 51 | + | sort: asc | |||
| 52 | + | use: github | |||
| 53 | + | filters: | |||
| 54 | + | exclude: | |||
| 55 | + | - '^docs(\(.+\))?:' | |||
| 56 | + | - '^test(\(.+\))?:' | |||
| 57 | + | - '^ci(\(.+\))?:' | |||
| 58 | + | - '^chore(\(.+\))?:' | |||
| 59 | + | - '^build\(deps.*\):' | |||
| 60 | + | - Merge pull request | |||
| 61 | + | - Merge branch | |||
| 62 | + | groups: | |||
| 63 | + | - title: Features | |||
| 64 | + | regexp: '^feat(\(.+\))?:' | |||
| 65 | + | order: 0 | |||
| 66 | + | - title: Bug Fixes | |||
| 67 | + | regexp: '^fix(\(.+\))?:' | |||
| 68 | + | order: 1 | |||
| 69 | + | - title: Performance | |||
| 70 | + | regexp: '^perf(\(.+\))?:' | |||
| 71 | + | order: 2 | |||
| 72 | + | - title: Refactoring | |||
| 73 | + | regexp: '^refactor(\(.+\))?:' | |||
| 74 | + | order: 3 | |||
| 75 | + | - title: Other Changes | |||
| 76 | + | order: 999 | |||
| 77 | + | ||||
| 78 | + | release: | |||
| 79 | + | github: | |||
| 80 | + | owner: bnema | |||
| 81 | + | name: vev | |||
| 82 | + | draft: false | |||
| 83 | + | replace_existing_draft: true | |||
| 84 | + | replace_existing_artifacts: true | |||
| 85 | + | prerelease: auto | |||
| 86 | + | make_latest: true | |||
| 87 | + | footer: | | |||
| 88 | + | --- | |||
| 89 | + | ||||
| 90 | + | ## Install | |||
| 91 | + | ||||
| 92 | + | ### Script (Linux x86_64, macOS arm64) | |||
| 93 | + | ```sh | |||
| 94 | + | curl -fsSL https://raw.githubusercontent.com/bnema/vev/{{ .Tag }}/install.sh | VEV_VERSION={{ .Tag }} sh | |||
| 95 | + | ``` | |||
| 96 | + | ||||
| 97 | + | ### Arch Linux | |||
| 98 | + | ```sh | |||
| 99 | + | yay -S vev-bin | |||
| 100 | + | ``` | |||
| 101 | + | ||||
| 102 | + | ### macOS note | |||
| 103 | + | The darwin binary is ad-hoc signed by the Go toolchain, not notarized. | |||
| 104 | + | Installing via the script or `curl` avoids the quarantine attribute; | |||
| 105 | + | if you downloaded the archive with a browser, Gatekeeper will block it. | |||
| 106 | + | Removing the attribute bypasses a Gatekeeper security control — only do | |||
| 107 | + | it for the vev binary itself, after verifying its checksum: | |||
| 108 | + | ```sh | |||
| 109 | + | xattr -d com.apple.quarantine ~/.local/bin/vev | |||
| 110 | + | ``` | |||
| 111 | + | ||||
| 112 | + | {{- if .PreviousTag }} | |||
| 113 | + | **Full Changelog**: https://github.com/bnema/vev/compare/{{ .PreviousTag }}...{{ .Tag }} | |||
| 114 | + | {{- else }} | |||
| 115 | + | **Full Changelog**: https://github.com/bnema/vev/releases/tag/{{ .Tag }} | |||
| 116 | + | {{- end }} | |||
| 117 | + | ||||
| 118 | + | git: | |||
| 119 | + | tag_sort: smartsemver | |||
| 120 | + | ||||
| 121 | + | aurs: | |||
| 122 | + | - name: vev-bin | |||
| 123 | + | ids: ['default'] | |||
| 124 | + | homepage: https://github.com/bnema/vev | |||
| 125 | + | description: Terminal multiplexer with a per-user daemon, server-side rendering, and minimal socket/SSH diffs | |||
| 126 | + | maintainers: | |||
| 127 | + | - bnema <b at bnema dot dev> | |||
| 128 | + | license: MIT | |||
| 129 | + | private_key: '{{ .Env.AUR_KEY }}' | |||
| 130 | + | git_url: ssh://aur@aur.archlinux.org/vev-bin.git | |||
| 131 | + | # auto: prerelease tags (v1.2.3-rc.1) are not pushed to AUR. | |||
| 132 | + | skip_upload: auto | |||
| 133 | + | provides: | |||
| 134 | + | - vev | |||
| 135 | + | conflicts: | |||
| 136 | + | - vev | |||
| 137 | + | package: |- | |||
| 138 | + | install -Dm755 "./vev" "${pkgdir}/usr/bin/vev" | |||
| 139 | + | install -Dm755 "./scripts/vev-bar-top-right" "${pkgdir}/usr/bin/vev-bar-top-right" | |||
| 140 | + | install -Dm755 "./scripts/vev-bar-bottom-right" "${pkgdir}/usr/bin/vev-bar-bottom-right" | |||
| 141 | + | install -Dm644 "./LICENSE" "${pkgdir}/usr/share/licenses/vev-bin/LICENSE" | |||
| 142 | + | commit_msg_template: 'Update to {{ .Tag }}' | |||
A RELEASING.md +50 -0
| @@ -0,0 +1,50 @@ | |||
| 1 | + | # Releasing vev | |
| 2 | + | ||
| 3 | + | Releases are cut by tagging: `git tag v0.X.Y && git push origin v0.X.Y`. | |
| 4 | + | The `release` workflow cross-compiles linux/amd64 + darwin/arm64 | |
| 5 | + | (`CGO_ENABLED=0`), publishes a GitHub Release with `checksums.txt`, and pushes | |
| 6 | + | the `vev-bin` PKGBUILD to AUR (stable tags only — `skip_upload: auto` skips | |
| 7 | + | AUR for `-rc.*`/`-alpha.*` prerelease tags). | |
| 8 | + | ||
| 9 | + | ## Checklist | |
| 10 | + | ||
| 11 | + | 1. main is green on both CI legs (ubuntu, macos-14). | |
| 12 | + | 2. Tag and push. Watch: `gh run watch`. | |
| 13 | + | 3. Verify the release page: two archives + `checksums.txt` + grouped changelog. | |
| 14 | + | 4. On Linux amd64, download the installer from the exact tag, inspect it, | |
| 15 | + | then run it for that same version: | |
| 16 | + | ```sh | |
| 17 | + | installer=$(mktemp) | |
| 18 | + | curl -fsSL https://raw.githubusercontent.com/bnema/vev/v0.X.Y/install.sh -o "$installer" | |
| 19 | + | less "$installer" | |
| 20 | + | VEV_VERSION=v0.X.Y sh "$installer" | |
| 21 | + | rm -f "$installer" | |
| 22 | + | ``` | |
| 23 | + | Confirm `vev --version` prints `vev 0.X.Y (commit ..., built ...)`. | |
| 24 | + | 5. Stable tags only: confirm https://aur.archlinux.org/packages/vev-bin | |
| 25 | + | picked up the new version. | |
| 26 | + | ||
| 27 | + | ## If GitHub publishes but AUR fails | |
| 28 | + | ||
| 29 | + | Do **not** delete or re-push the tag. The GitHub release is the source of truth; | |
| 30 | + | only the PKGBUILD push failed (usually SSH/key). Fix the cause, then either: | |
| 31 | + | ||
| 32 | + | - Re-run the failed workflow job (`gh run rerun <run-id> --failed`), which | |
| 33 | + | re-executes goreleaser idempotently for the same tag, or | |
| 34 | + | - For manual recovery, clone | |
| 35 | + | `ssh://aur@aur.archlinux.org/vev-bin.git`, update its existing PKGBUILD for | |
| 36 | + | the exact tag using that GitHub Release's `checksums.txt`, and regenerate | |
| 37 | + | `.SRCINFO` with `makepkg --printsrcinfo > .SRCINFO`. Review the diff, then | |
| 38 | + | commit and push only the AUR repository. | |
| 39 | + | ||
| 40 | + | ## If AUR publishes but GitHub fails | |
| 41 | + | ||
| 42 | + | Do **not** delete or re-push the tag. Fix the GitHub workflow failure and rerun | |
| 43 | + | it (`gh run rerun <run-id> --failed`); if necessary, repair the GitHub Release | |
| 44 | + | manually with the same tag's archives and `checksums.txt`, then verify both | |
| 45 | + | publication targets before proceeding. | |
| 46 | + | ||
| 47 | + | ## Version stamping | |
| 48 | + | ||
| 49 | + | `vev --version` reads `internal/app.{version,commit,date}`, stamped via | |
| 50 | + | ldflags in `.goreleaser.yaml`. Local builds report `0.1.0-dev`. | |
| @@ -0,0 +1,50 @@ | |||||
| 1 | + | # Releasing vev | |||
| 2 | + | ||||
| 3 | + | Releases are cut by tagging: `git tag v0.X.Y && git push origin v0.X.Y`. | |||
| 4 | + | The `release` workflow cross-compiles linux/amd64 + darwin/arm64 | |||
| 5 | + | (`CGO_ENABLED=0`), publishes a GitHub Release with `checksums.txt`, and pushes | |||
| 6 | + | the `vev-bin` PKGBUILD to AUR (stable tags only — `skip_upload: auto` skips | |||
| 7 | + | AUR for `-rc.*`/`-alpha.*` prerelease tags). | |||
| 8 | + | ||||
| 9 | + | ## Checklist | |||
| 10 | + | ||||
| 11 | + | 1. main is green on both CI legs (ubuntu, macos-14). | |||
| 12 | + | 2. Tag and push. Watch: `gh run watch`. | |||
| 13 | + | 3. Verify the release page: two archives + `checksums.txt` + grouped changelog. | |||
| 14 | + | 4. On Linux amd64, download the installer from the exact tag, inspect it, | |||
| 15 | + | then run it for that same version: | |||
| 16 | + | ```sh | |||
| 17 | + | installer=$(mktemp) | |||
| 18 | + | curl -fsSL https://raw.githubusercontent.com/bnema/vev/v0.X.Y/install.sh -o "$installer" | |||
| 19 | + | less "$installer" | |||
| 20 | + | VEV_VERSION=v0.X.Y sh "$installer" | |||
| 21 | + | rm -f "$installer" | |||
| 22 | + | ``` | |||
| 23 | + | Confirm `vev --version` prints `vev 0.X.Y (commit ..., built ...)`. | |||
| 24 | + | 5. Stable tags only: confirm https://aur.archlinux.org/packages/vev-bin | |||
| 25 | + | picked up the new version. | |||
| 26 | + | ||||
| 27 | + | ## If GitHub publishes but AUR fails | |||
| 28 | + | ||||
| 29 | + | Do **not** delete or re-push the tag. The GitHub release is the source of truth; | |||
| 30 | + | only the PKGBUILD push failed (usually SSH/key). Fix the cause, then either: | |||
| 31 | + | ||||
| 32 | + | - Re-run the failed workflow job (`gh run rerun <run-id> --failed`), which | |||
| 33 | + | re-executes goreleaser idempotently for the same tag, or | |||
| 34 | + | - For manual recovery, clone | |||
| 35 | + | `ssh://aur@aur.archlinux.org/vev-bin.git`, update its existing PKGBUILD for | |||
| 36 | + | the exact tag using that GitHub Release's `checksums.txt`, and regenerate | |||
| 37 | + | `.SRCINFO` with `makepkg --printsrcinfo > .SRCINFO`. Review the diff, then | |||
| 38 | + | commit and push only the AUR repository. | |||
| 39 | + | ||||
| 40 | + | ## If AUR publishes but GitHub fails | |||
| 41 | + | ||||
| 42 | + | Do **not** delete or re-push the tag. Fix the GitHub workflow failure and rerun | |||
| 43 | + | it (`gh run rerun <run-id> --failed`); if necessary, repair the GitHub Release | |||
| 44 | + | manually with the same tag's archives and `checksums.txt`, then verify both | |||
| 45 | + | publication targets before proceeding. | |||
| 46 | + | ||||
| 47 | + | ## Version stamping | |||
| 48 | + | ||||
| 49 | + | `vev --version` reads `internal/app.{version,commit,date}`, stamped via | |||
| 50 | + | ldflags in `.goreleaser.yaml`. Local builds report `0.1.0-dev`. | |||
A install.sh +182 -0
| @@ -0,0 +1,182 @@ | |||
| 1 | + | #!/bin/sh | |
| 2 | + | # vev installer — Linux x86_64 and macOS arm64. | |
| 3 | + | # Usage: curl -fsSL https://raw.githubusercontent.com/bnema/vev/main/install.sh | sh | |
| 4 | + | # Env: VEV_VERSION=vX.Y.Z to pin a version (default: latest release). | |
| 5 | + | set -eu | |
| 6 | + | ||
| 7 | + | REPO="bnema/vev" | |
| 8 | + | VERSION="${VEV_VERSION:-latest}" | |
| 9 | + | ||
| 10 | + | info() { printf '\033[1;34m==>\033[0m %s\n' "$*"; } | |
| 11 | + | err() { printf '\033[1;31merror:\033[0m %s\n' "$*" >&2; exit 1; } | |
| 12 | + | ||
| 13 | + | # --- Detect platform ------------------------------------------------------- | |
| 14 | + | OS="$(uname -s | tr '[:upper:]' '[:lower:]')" | |
| 15 | + | ARCH="$(uname -m)" | |
| 16 | + | case "$OS/$ARCH" in | |
| 17 | + | linux/x86_64|linux/amd64) TARBALL="vev_linux_x86_64.tar.gz" ;; | |
| 18 | + | darwin/arm64) TARBALL="vev_darwin_arm64.tar.gz" ;; | |
| 19 | + | *) err "unsupported platform: $OS/$ARCH (supported: linux/x86_64, darwin/arm64)" ;; | |
| 20 | + | esac | |
| 21 | + | ||
| 22 | + | # --- Pick install dir ------------------------------------------------------ | |
| 23 | + | if [ -d "$HOME/.local/bin" ]; then | |
| 24 | + | INSTALL_DIR="$HOME/.local/bin" | |
| 25 | + | else | |
| 26 | + | INSTALL_DIR="/usr/local/bin" | |
| 27 | + | fi | |
| 28 | + | ||
| 29 | + | # --- Resolve version ------------------------------------------------------- | |
| 30 | + | if [ "$VERSION" = "latest" ]; then | |
| 31 | + | VERSION="$(curl -fsSL "https://api.github.com/repos/${REPO}/releases/latest" \ | |
| 32 | + | | grep '"tag_name"' | head -1 | cut -d '"' -f 4)" | |
| 33 | + | [ -n "$VERSION" ] || err "could not resolve the latest release tag" | |
| 34 | + | fi | |
| 35 | + | info "installing vev ${VERSION} (${OS}/${ARCH}) to ${INSTALL_DIR}" | |
| 36 | + | ||
| 37 | + | # --- Download + verify ----------------------------------------------------- | |
| 38 | + | TMP_DIR="$(mktemp -d)" | |
| 39 | + | trap 'rm -rf "$TMP_DIR"' EXIT | |
| 40 | + | ||
| 41 | + | BASE_URL="https://github.com/${REPO}/releases/download/${VERSION}" | |
| 42 | + | curl -fsSL -o "$TMP_DIR/$TARBALL" "$BASE_URL/$TARBALL" \ | |
| 43 | + | || err "download failed: $BASE_URL/$TARBALL" | |
| 44 | + | curl -fsSL -o "$TMP_DIR/checksums.txt" "$BASE_URL/checksums.txt" \ | |
| 45 | + | || err "download failed: $BASE_URL/checksums.txt" | |
| 46 | + | ||
| 47 | + | cd "$TMP_DIR" | |
| 48 | + | EXPECTED="$(grep " $TARBALL\$" checksums.txt | cut -d ' ' -f 1)" | |
| 49 | + | [ -n "$EXPECTED" ] || err "$TARBALL not found in checksums.txt" | |
| 50 | + | if command -v sha256sum >/dev/null 2>&1; then | |
| 51 | + | ACTUAL="$(sha256sum "$TARBALL" | cut -d ' ' -f 1)" | |
| 52 | + | else | |
| 53 | + | ACTUAL="$(shasum -a 256 "$TARBALL" | cut -d ' ' -f 1)" | |
| 54 | + | fi | |
| 55 | + | [ "$EXPECTED" = "$ACTUAL" ] || err "checksum mismatch for $TARBALL (expected $EXPECTED, got $ACTUAL)" | |
| 56 | + | info "checksum verified" | |
| 57 | + | ||
| 58 | + | # --- Extract + install ----------------------------------------------------- | |
| 59 | + | tar -xzf "$TARBALL" | |
| 60 | + | ||
| 61 | + | # Keep this list in the archive's required installation order. All payloads | |
| 62 | + | # are checked and staged before a live file is replaced. | |
| 63 | + | PAYLOADS="vev scripts/vev-bar-top-right scripts/vev-bar-bottom-right" | |
| 64 | + | TRANSACTION=".vev-install-$$" | |
| 65 | + | ||
| 66 | + | path_exists() { | |
| 67 | + | [ -e "$1" ] || [ -L "$1" ] | |
| 68 | + | } | |
| 69 | + | ||
| 70 | + | install_path() { | |
| 71 | + | # Run every destination mutation through the same privilege path. | |
| 72 | + | if [ -w "$INSTALL_DIR" ]; then | |
| 73 | + | "$@" | |
| 74 | + | else | |
| 75 | + | sudo "$@" | |
| 76 | + | fi | |
| 77 | + | } | |
| 78 | + | ||
| 79 | + | ensure_install_dir() { | |
| 80 | + | [ -d "$INSTALL_DIR" ] && return 0 | |
| 81 | + | ||
| 82 | + | if [ -w "$(dirname "$INSTALL_DIR")" ]; then | |
| 83 | + | mkdir -p "$INSTALL_DIR" || err "cannot create $INSTALL_DIR" | |
| 84 | + | else | |
| 85 | + | info "escalating with sudo to create $INSTALL_DIR" | |
| 86 | + | sudo mkdir -p "$INSTALL_DIR" || err "cannot create $INSTALL_DIR" | |
| 87 | + | fi | |
| 88 | + | } | |
| 89 | + | ||
| 90 | + | stage_path() { | |
| 91 | + | printf '%s/%s-%s.new\n' "$INSTALL_DIR" "$TRANSACTION" "${1##*/}" | |
| 92 | + | } | |
| 93 | + | ||
| 94 | + | backup_path() { | |
| 95 | + | printf '%s/%s-%s.bak\n' "$INSTALL_DIR" "$TRANSACTION" "${1##*/}" | |
| 96 | + | } | |
| 97 | + | ||
| 98 | + | remove_destination_file() { | |
| 99 | + | install_path rm -f "$1" | |
| 100 | + | } | |
| 101 | + | ||
| 102 | + | cleanup_staging() { | |
| 103 | + | for payload in $PAYLOADS; do | |
| 104 | + | stage="$(stage_path "$payload")" | |
| 105 | + | remove_destination_file "$stage" || return 1 | |
| 106 | + | done | |
| 107 | + | } | |
| 108 | + | ||
| 109 | + | rollback_install() { | |
| 110 | + | rollback_ok=true | |
| 111 | + | ||
| 112 | + | for payload in $PAYLOADS; do | |
| 113 | + | name="${payload##*/}" | |
| 114 | + | target="$INSTALL_DIR/$name" | |
| 115 | + | stage="$(stage_path "$payload")" | |
| 116 | + | backup="$(backup_path "$payload")" | |
| 117 | + | ||
| 118 | + | if path_exists "$backup"; then | |
| 119 | + | # A prior file was moved aside: remove the new one and put it back. | |
| 120 | + | remove_destination_file "$target" || rollback_ok=false | |
| 121 | + | install_path mv "$backup" "$target" || rollback_ok=false | |
| 122 | + | elif ! path_exists "$stage"; then | |
| 123 | + | # No backup means the original was absent. A missing stage was moved | |
| 124 | + | # into place, so restore that original absence. | |
| 125 | + | remove_destination_file "$target" || rollback_ok=false | |
| 126 | + | fi | |
| 127 | + | remove_destination_file "$stage" || rollback_ok=false | |
| 128 | + | done | |
| 129 | + | ||
| 130 | + | [ "$rollback_ok" = true ] | |
| 131 | + | } | |
| 132 | + | ||
| 133 | + | preflight_payloads() { | |
| 134 | + | for payload in $PAYLOADS; do | |
| 135 | + | [ -f "$payload" ] && [ -r "$payload" ] \ | |
| 136 | + | || err "archive is missing readable payload: $payload" | |
| 137 | + | done | |
| 138 | + | } | |
| 139 | + | ||
| 140 | + | # Do not create or alter the destination until every archive payload exists. | |
| 141 | + | preflight_payloads | |
| 142 | + | ensure_install_dir | |
| 143 | + | ||
| 144 | + | # Stage every payload in the destination first, so each later mv is a | |
| 145 | + | # same-filesystem replacement. A staging failure leaves live files untouched. | |
| 146 | + | for source in $PAYLOADS; do | |
| 147 | + | stage="$(stage_path "$source")" | |
| 148 | + | if ! install_path install -m 755 "$source" "$stage"; then | |
| 149 | + | cleanup_staging || err "failed to stage payloads and clean staging files" | |
| 150 | + | err "failed to stage payload: $source" | |
| 151 | + | fi | |
| 152 | + | done | |
| 153 | + | ||
| 154 | + | # Replace each live file only after all three staged copies are ready. Existing | |
| 155 | + | # files are retained as backups until the full transaction succeeds. | |
| 156 | + | for source in $PAYLOADS; do | |
| 157 | + | name="${source##*/}" | |
| 158 | + | target="$INSTALL_DIR/$name" | |
| 159 | + | stage="$(stage_path "$source")" | |
| 160 | + | backup="$(backup_path "$source")" | |
| 161 | + | ||
| 162 | + | if path_exists "$target" && ! install_path mv "$target" "$backup"; then | |
| 163 | + | rollback_install || err "failed to preserve $target and roll back installation" | |
| 164 | + | err "failed to preserve existing file: $target" | |
| 165 | + | fi | |
| 166 | + | if ! install_path mv "$stage" "$target"; then | |
| 167 | + | rollback_install || err "failed to replace $target and roll back installation" | |
| 168 | + | err "failed to install payload: $source" | |
| 169 | + | fi | |
| 170 | + | done | |
| 171 | + | ||
| 172 | + | for source in $PAYLOADS; do | |
| 173 | + | backup="$(backup_path "$source")" | |
| 174 | + | remove_destination_file "$backup" \ | |
| 175 | + | || err "installed payloads but failed to clean backup: $backup" | |
| 176 | + | done | |
| 177 | + | ||
| 178 | + | info "installed: $INSTALL_DIR/vev" | |
| 179 | + | case ":${PATH:-}:" in | |
| 180 | + | *":$INSTALL_DIR:"*) "$INSTALL_DIR/vev" --version ;; | |
| 181 | + | *) info "note: $INSTALL_DIR is not in your PATH" ;; | |
| 182 | + | esac | |
| @@ -0,0 +1,182 @@ | |||||
| 1 | + | #!/bin/sh | |||
| 2 | + | # vev installer — Linux x86_64 and macOS arm64. | |||
| 3 | + | # Usage: curl -fsSL https://raw.githubusercontent.com/bnema/vev/main/install.sh | sh | |||
| 4 | + | # Env: VEV_VERSION=vX.Y.Z to pin a version (default: latest release). | |||
| 5 | + | set -eu | |||
| 6 | + | ||||
| 7 | + | REPO="bnema/vev" | |||
| 8 | + | VERSION="${VEV_VERSION:-latest}" | |||
| 9 | + | ||||
| 10 | + | info() { printf '\033[1;34m==>\033[0m %s\n' "$*"; } | |||
| 11 | + | err() { printf '\033[1;31merror:\033[0m %s\n' "$*" >&2; exit 1; } | |||
| 12 | + | ||||
| 13 | + | # --- Detect platform ------------------------------------------------------- | |||
| 14 | + | OS="$(uname -s | tr '[:upper:]' '[:lower:]')" | |||
| 15 | + | ARCH="$(uname -m)" | |||
| 16 | + | case "$OS/$ARCH" in | |||
| 17 | + | linux/x86_64|linux/amd64) TARBALL="vev_linux_x86_64.tar.gz" ;; | |||
| 18 | + | darwin/arm64) TARBALL="vev_darwin_arm64.tar.gz" ;; | |||
| 19 | + | *) err "unsupported platform: $OS/$ARCH (supported: linux/x86_64, darwin/arm64)" ;; | |||
| 20 | + | esac | |||
| 21 | + | ||||
| 22 | + | # --- Pick install dir ------------------------------------------------------ | |||
| 23 | + | if [ -d "$HOME/.local/bin" ]; then | |||
| 24 | + | INSTALL_DIR="$HOME/.local/bin" | |||
| 25 | + | else | |||
| 26 | + | INSTALL_DIR="/usr/local/bin" | |||
| 27 | + | fi | |||
| 28 | + | ||||
| 29 | + | # --- Resolve version ------------------------------------------------------- | |||
| 30 | + | if [ "$VERSION" = "latest" ]; then | |||
| 31 | + | VERSION="$(curl -fsSL "https://api.github.com/repos/${REPO}/releases/latest" \ | |||
| 32 | + | | grep '"tag_name"' | head -1 | cut -d '"' -f 4)" | |||
| 33 | + | [ -n "$VERSION" ] || err "could not resolve the latest release tag" | |||
| 34 | + | fi | |||
| 35 | + | info "installing vev ${VERSION} (${OS}/${ARCH}) to ${INSTALL_DIR}" | |||
| 36 | + | ||||
| 37 | + | # --- Download + verify ----------------------------------------------------- | |||
| 38 | + | TMP_DIR="$(mktemp -d)" | |||
| 39 | + | trap 'rm -rf "$TMP_DIR"' EXIT | |||
| 40 | + | ||||
| 41 | + | BASE_URL="https://github.com/${REPO}/releases/download/${VERSION}" | |||
| 42 | + | curl -fsSL -o "$TMP_DIR/$TARBALL" "$BASE_URL/$TARBALL" \ | |||
| 43 | + | || err "download failed: $BASE_URL/$TARBALL" | |||
| 44 | + | curl -fsSL -o "$TMP_DIR/checksums.txt" "$BASE_URL/checksums.txt" \ | |||
| 45 | + | || err "download failed: $BASE_URL/checksums.txt" | |||
| 46 | + | ||||
| 47 | + | cd "$TMP_DIR" | |||
| 48 | + | EXPECTED="$(grep " $TARBALL\$" checksums.txt | cut -d ' ' -f 1)" | |||
| 49 | + | [ -n "$EXPECTED" ] || err "$TARBALL not found in checksums.txt" | |||
| 50 | + | if command -v sha256sum >/dev/null 2>&1; then | |||
| 51 | + | ACTUAL="$(sha256sum "$TARBALL" | cut -d ' ' -f 1)" | |||
| 52 | + | else | |||
| 53 | + | ACTUAL="$(shasum -a 256 "$TARBALL" | cut -d ' ' -f 1)" | |||
| 54 | + | fi | |||
| 55 | + | [ "$EXPECTED" = "$ACTUAL" ] || err "checksum mismatch for $TARBALL (expected $EXPECTED, got $ACTUAL)" | |||
| 56 | + | info "checksum verified" | |||
| 57 | + | ||||
| 58 | + | # --- Extract + install ----------------------------------------------------- | |||
| 59 | + | tar -xzf "$TARBALL" | |||
| 60 | + | ||||
| 61 | + | # Keep this list in the archive's required installation order. All payloads | |||
| 62 | + | # are checked and staged before a live file is replaced. | |||
| 63 | + | PAYLOADS="vev scripts/vev-bar-top-right scripts/vev-bar-bottom-right" | |||
| 64 | + | TRANSACTION=".vev-install-$$" | |||
| 65 | + | ||||
| 66 | + | path_exists() { | |||
| 67 | + | [ -e "$1" ] || [ -L "$1" ] | |||
| 68 | + | } | |||
| 69 | + | ||||
| 70 | + | install_path() { | |||
| 71 | + | # Run every destination mutation through the same privilege path. | |||
| 72 | + | if [ -w "$INSTALL_DIR" ]; then | |||
| 73 | + | "$@" | |||
| 74 | + | else | |||
| 75 | + | sudo "$@" | |||
| 76 | + | fi | |||
| 77 | + | } | |||
| 78 | + | ||||
| 79 | + | ensure_install_dir() { | |||
| 80 | + | [ -d "$INSTALL_DIR" ] && return 0 | |||
| 81 | + | ||||
| 82 | + | if [ -w "$(dirname "$INSTALL_DIR")" ]; then | |||
| 83 | + | mkdir -p "$INSTALL_DIR" || err "cannot create $INSTALL_DIR" | |||
| 84 | + | else | |||
| 85 | + | info "escalating with sudo to create $INSTALL_DIR" | |||
| 86 | + | sudo mkdir -p "$INSTALL_DIR" || err "cannot create $INSTALL_DIR" | |||
| 87 | + | fi | |||
| 88 | + | } | |||
| 89 | + | ||||
| 90 | + | stage_path() { | |||
| 91 | + | printf '%s/%s-%s.new\n' "$INSTALL_DIR" "$TRANSACTION" "${1##*/}" | |||
| 92 | + | } | |||
| 93 | + | ||||
| 94 | + | backup_path() { | |||
| 95 | + | printf '%s/%s-%s.bak\n' "$INSTALL_DIR" "$TRANSACTION" "${1##*/}" | |||
| 96 | + | } | |||
| 97 | + | ||||
| 98 | + | remove_destination_file() { | |||
| 99 | + | install_path rm -f "$1" | |||
| 100 | + | } | |||
| 101 | + | ||||
| 102 | + | cleanup_staging() { | |||
| 103 | + | for payload in $PAYLOADS; do | |||
| 104 | + | stage="$(stage_path "$payload")" | |||
| 105 | + | remove_destination_file "$stage" || return 1 | |||
| 106 | + | done | |||
| 107 | + | } | |||
| 108 | + | ||||
| 109 | + | rollback_install() { | |||
| 110 | + | rollback_ok=true | |||
| 111 | + | ||||
| 112 | + | for payload in $PAYLOADS; do | |||
| 113 | + | name="${payload##*/}" | |||
| 114 | + | target="$INSTALL_DIR/$name" | |||
| 115 | + | stage="$(stage_path "$payload")" | |||
| 116 | + | backup="$(backup_path "$payload")" | |||
| 117 | + | ||||
| 118 | + | if path_exists "$backup"; then | |||
| 119 | + | # A prior file was moved aside: remove the new one and put it back. | |||
| 120 | + | remove_destination_file "$target" || rollback_ok=false | |||
| 121 | + | install_path mv "$backup" "$target" || rollback_ok=false | |||
| 122 | + | elif ! path_exists "$stage"; then | |||
| 123 | + | # No backup means the original was absent. A missing stage was moved | |||
| 124 | + | # into place, so restore that original absence. | |||
| 125 | + | remove_destination_file "$target" || rollback_ok=false | |||
| 126 | + | fi | |||
| 127 | + | remove_destination_file "$stage" || rollback_ok=false | |||
| 128 | + | done | |||
| 129 | + | ||||
| 130 | + | [ "$rollback_ok" = true ] | |||
| 131 | + | } | |||
| 132 | + | ||||
| 133 | + | preflight_payloads() { | |||
| 134 | + | for payload in $PAYLOADS; do | |||
| 135 | + | [ -f "$payload" ] && [ -r "$payload" ] \ | |||
| 136 | + | || err "archive is missing readable payload: $payload" | |||
| 137 | + | done | |||
| 138 | + | } | |||
| 139 | + | ||||
| 140 | + | # Do not create or alter the destination until every archive payload exists. | |||
| 141 | + | preflight_payloads | |||
| 142 | + | ensure_install_dir | |||
| 143 | + | ||||
| 144 | + | # Stage every payload in the destination first, so each later mv is a | |||
| 145 | + | # same-filesystem replacement. A staging failure leaves live files untouched. | |||
| 146 | + | for source in $PAYLOADS; do | |||
| 147 | + | stage="$(stage_path "$source")" | |||
| 148 | + | if ! install_path install -m 755 "$source" "$stage"; then | |||
| 149 | + | cleanup_staging || err "failed to stage payloads and clean staging files" | |||
| 150 | + | err "failed to stage payload: $source" | |||
| 151 | + | fi | |||
| 152 | + | done | |||
| 153 | + | ||||
| 154 | + | # Replace each live file only after all three staged copies are ready. Existing | |||
| 155 | + | # files are retained as backups until the full transaction succeeds. | |||
| 156 | + | for source in $PAYLOADS; do | |||
| 157 | + | name="${source##*/}" | |||
| 158 | + | target="$INSTALL_DIR/$name" | |||
| 159 | + | stage="$(stage_path "$source")" | |||
| 160 | + | backup="$(backup_path "$source")" | |||
| 161 | + | ||||
| 162 | + | if path_exists "$target" && ! install_path mv "$target" "$backup"; then | |||
| 163 | + | rollback_install || err "failed to preserve $target and roll back installation" | |||
| 164 | + | err "failed to preserve existing file: $target" | |||
| 165 | + | fi | |||
| 166 | + | if ! install_path mv "$stage" "$target"; then | |||
| 167 | + | rollback_install || err "failed to replace $target and roll back installation" | |||
| 168 | + | err "failed to install payload: $source" | |||
| 169 | + | fi | |||
| 170 | + | done | |||
| 171 | + | ||||
| 172 | + | for source in $PAYLOADS; do | |||
| 173 | + | backup="$(backup_path "$source")" | |||
| 174 | + | remove_destination_file "$backup" \ | |||
| 175 | + | || err "installed payloads but failed to clean backup: $backup" | |||
| 176 | + | done | |||
| 177 | + | ||||
| 178 | + | info "installed: $INSTALL_DIR/vev" | |||
| 179 | + | case ":${PATH:-}:" in | |||
| 180 | + | *":$INSTALL_DIR:"*) "$INSTALL_DIR/vev" --version ;; | |||
| 181 | + | *) info "note: $INSTALL_DIR is not in your PATH" ;; | |||
| 182 | + | esac | |||
M internal/app/run.go +14 -4
| @@ -101,9 +101,19 @@ usage: | |||
| 101 | 101 | vev --help show this help | |
| 102 | 102 | vev --version show version` | |
| 103 | 103 | ||
| 104 | - | // version is vev's reported build version. Wired here for the MVP; a build | |
| 105 | - | // stamp can replace it later. | |
| 106 | - | const version = "0.1.0-dev" | |
| 104 | + | // Build metadata. Defaults describe a plain `go build`; releases overwrite | |
| 105 | + | // them via -ldflags "-X github.com/bnema/vev/internal/app.version=..." (and | |
| 106 | + | // .commit / .date) — see .goreleaser.yaml. | |
| 107 | + | var ( | |
| 108 | + | version = "0.1.0-dev" | |
| 109 | + | commit = "none" | |
| 110 | + | date = "unknown" | |
| 111 | + | ) | |
| 112 | + | ||
| 113 | + | // versionLine renders the --version output. | |
| 114 | + | func versionLine() string { | |
| 115 | + | return fmt.Sprintf("vev %s (commit %s, built %s)", version, commit, date) | |
| 116 | + | } | |
| 107 | 117 | ||
| 108 | 118 | // Run is the entry point invoked by main. It parses args into a command and | |
| 109 | 119 | // dispatches it. | |
| @@ -219,7 +229,7 @@ func dispatch(ctx context.Context, cmd command) error { | |||
| 219 | 229 | fmt.Println(usageText) | |
| 220 | 230 | return nil | |
| 221 | 231 | case kindVersion: | |
| 222 | - | fmt.Println("vev", version) | |
| 232 | + | fmt.Println(versionLine()) | |
| 223 | 233 | return nil | |
| 224 | 234 | case kindDaemon: | |
| 225 | 235 | return runDaemon() | |
| @@ -101,9 +101,19 @@ usage: | |||||
| 101 | vev --help show this help | 101 | vev --help show this help | ||
| 102 | vev --version show version` | 102 | vev --version show version` | ||
| 103 | 103 | ||||
| 104 | - | // version is vev's reported build version. Wired here for the MVP; a build | 104 | + | // Build metadata. Defaults describe a plain `go build`; releases overwrite |
| 105 | - | // stamp can replace it later. | 105 | + | // them via -ldflags "-X github.com/bnema/vev/internal/app.version=..." (and |
| 106 | - | const version = "0.1.0-dev" | 106 | + | // .commit / .date) — see .goreleaser.yaml. |
| 107 | + | var ( | |||
| 108 | + | version = "0.1.0-dev" | |||
| 109 | + | commit = "none" | |||
| 110 | + | date = "unknown" | |||
| 111 | + | ) | |||
| 112 | + | ||||
| 113 | + | // versionLine renders the --version output. | |||
| 114 | + | func versionLine() string { | |||
| 115 | + | return fmt.Sprintf("vev %s (commit %s, built %s)", version, commit, date) | |||
| 116 | + | } | |||
| 107 | 117 | ||||
| 108 | // Run is the entry point invoked by main. It parses args into a command and | 118 | // Run is the entry point invoked by main. It parses args into a command and | ||
| 109 | // dispatches it. | 119 | // dispatches it. | ||
| @@ -219,7 +229,7 @@ func dispatch(ctx context.Context, cmd command) error { | |||||
| 219 | fmt.Println(usageText) | 229 | fmt.Println(usageText) | ||
| 220 | return nil | 230 | return nil | ||
| 221 | case kindVersion: | 231 | case kindVersion: | ||
| 222 | - | fmt.Println("vev", version) | 232 | + | fmt.Println(versionLine()) |
| 223 | return nil | 233 | return nil | ||
| 224 | case kindDaemon: | 234 | case kindDaemon: | ||
| 225 | return runDaemon() | 235 | return runDaemon() | ||
A internal/app/version_test.go +24 -0
| @@ -0,0 +1,24 @@ | |||
| 1 | + | package app | |
| 2 | + | ||
| 3 | + | import ( | |
| 4 | + | "strings" | |
| 5 | + | "testing" | |
| 6 | + | ) | |
| 7 | + | ||
| 8 | + | // The release pipeline stamps these vars via -ldflags -X; the defaults must | |
| 9 | + | // stay meaningful for plain `go build`. | |
| 10 | + | func TestVersionDefaults(t *testing.T) { | |
| 11 | + | if version == "" || commit == "" || date == "" { | |
| 12 | + | t.Fatalf("version metadata must have non-empty defaults: version=%q commit=%q date=%q", version, commit, date) | |
| 13 | + | } | |
| 14 | + | if !strings.Contains(version, "dev") { | |
| 15 | + | t.Fatalf("unstamped version %q should carry a -dev marker", version) | |
| 16 | + | } | |
| 17 | + | } | |
| 18 | + | ||
| 19 | + | func TestVersionLine(t *testing.T) { | |
| 20 | + | want := "vev " + version + " (commit " + commit + ", built " + date + ")" | |
| 21 | + | if got := versionLine(); got != want { | |
| 22 | + | t.Fatalf("versionLine() = %q, want %q", got, want) | |
| 23 | + | } | |
| 24 | + | } | |
| @@ -0,0 +1,24 @@ | |||||
| 1 | + | package app | |||
| 2 | + | ||||
| 3 | + | import ( | |||
| 4 | + | "strings" | |||
| 5 | + | "testing" | |||
| 6 | + | ) | |||
| 7 | + | ||||
| 8 | + | // The release pipeline stamps these vars via -ldflags -X; the defaults must | |||
| 9 | + | // stay meaningful for plain `go build`. | |||
| 10 | + | func TestVersionDefaults(t *testing.T) { | |||
| 11 | + | if version == "" || commit == "" || date == "" { | |||
| 12 | + | t.Fatalf("version metadata must have non-empty defaults: version=%q commit=%q date=%q", version, commit, date) | |||
| 13 | + | } | |||
| 14 | + | if !strings.Contains(version, "dev") { | |||
| 15 | + | t.Fatalf("unstamped version %q should carry a -dev marker", version) | |||
| 16 | + | } | |||
| 17 | + | } | |||
| 18 | + | ||||
| 19 | + | func TestVersionLine(t *testing.T) { | |||
| 20 | + | want := "vev " + version + " (commit " + commit + ", built " + date + ")" | |||
| 21 | + | if got := versionLine(); got != want { | |||
| 22 | + | t.Fatalf("versionLine() = %q, want %q", got, want) | |||
| 23 | + | } | |||
| 24 | + | } | |||