Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions .github/workflows/checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -65,3 +65,22 @@ jobs:
with:
go-version-file: go.mod
go-package: ./...

# Builds every release target without signing or publishing, so changes to
# .goreleaser.yaml are caught before a tag is pushed.
release-snapshot:
name: release-snapshot
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
persist-credentials: false
- uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
with:
go-version-file: go.mod
- uses: goreleaser/goreleaser-action@a4f02ab5355a589de60d917a659ce617ed19b3de # v7.3.0
with:
distribution: goreleaser
version: "~> v2"
args: release --snapshot --clean --skip=sign,sbom
26 changes: 26 additions & 0 deletions .github/workflows/release-please.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
name: release-please

# Keeps a release PR open with the next version and changelog. Merging it
# tags vX.Y.Z, which triggers the release workflow.
on:
push:
branches: [main]

permissions:
contents: read

concurrency:
group: ${{ github.workflow }}
cancel-in-progress: false

jobs:
release-please:
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- uses: googleapis/release-please-action@45996ed1f6d02564a971a2fa1b5860e934307cf7 # v5.0.0
with:
config-file: release-please-config.json
manifest-file: .release-please-manifest.json
47 changes: 47 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
name: release

# Builds, signs and publishes a release for a vX.Y.Z tag created by
# release-please. Runs only on tags pushed to this repository, never on
# pull requests, so it never sees untrusted code.
on:
push:
tags: ["v*"]

permissions:
contents: read

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: false

jobs:
goreleaser:
runs-on: ubuntu-latest
permissions:
contents: write # upload release assets
id-token: write # cosign keyless signing and provenance
attestations: write # build-provenance attestations
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
persist-credentials: false
- uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
with:
go-version-file: go.mod
cache: false
- uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2
- uses: anchore/sbom-action/download-syft@66cbf4bc1f1c0d2edc94016e65bc221b6bb0ad6c # v0.24.3
- uses: goreleaser/goreleaser-action@a4f02ab5355a589de60d917a659ce617ed19b3de # v7.3.0
with:
distribution: goreleaser
version: "~> v2"
args: release --clean
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- uses: actions/attest-build-provenance@4d101475d8b20a2381f78447822ac1eab6504dd8 # v4.2.2
with:
subject-path: |
dist/*.tar.gz
dist/*.zip
dist/*_checksums.txt
79 changes: 79 additions & 0 deletions .goreleaser.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# yaml-language-server: $schema=https://goreleaser.com/static/schema.json
version: 2

project_name: devlyft

before:
hooks:
- go mod verify

builds:
- id: devlyft
main: ./cmd/devlyft
binary: devlyft
env:
- CGO_ENABLED=0
goos: [linux, darwin, windows]
goarch: [amd64, arm64]
flags:
- -trimpath
ldflags:
- -s -w
- -X github.com/devlyft/cli/internal/version.Version={{ .Version }}
- -X github.com/devlyft/cli/internal/version.Commit={{ .FullCommit }}
- -X github.com/devlyft/cli/internal/version.Date={{ .CommitDate }}
# Reproducible builds: file timestamps come from the commit, not the clock.
mod_timestamp: "{{ .CommitTimestamp }}"

archives:
- id: devlyft
ids: [devlyft]
formats: [tar.gz]
format_overrides:
- goos: windows
formats: [zip]
name_template: "{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}"
files:
- LICENSE
- README.md

checksum:
name_template: "{{ .ProjectName }}_{{ .Version }}_checksums.txt"
algorithm: sha256

# One SBOM per archive, generated by syft.
sboms:
- artifacts: archive

# Keyless signing of the checksum file with the release workflow's GitHub
# OIDC identity. The checksum file covers every archive and SBOM, so one
# signature covers the whole release.
signs:
- cmd: cosign
artifacts: checksum
signature: "${artifact}.sig"
certificate: "${artifact}.pem"
args:
- sign-blob
- "--output-signature=${signature}"
- "--output-certificate=${certificate}"
- "--bundle=${artifact}.sigstore.json"
- "${artifact}"
- "--yes"
output: true

# release-please writes the changelog and the release notes.
changelog:
disable: true

release:
github:
owner: devlyft
name: cli
# release-please has already created the release; goreleaser only attaches
# the artifacts and keeps its notes.
mode: keep-existing
prerelease: auto

snapshot:
version_template: "{{ incpatch .Version }}-next"
3 changes: 3 additions & 0 deletions .release-please-manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
".": "0.0.0"
}
8 changes: 6 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
# Tool versions are pinned here and in .github/workflows; keep them in sync.
GOLANGCI_LINT_VERSION ?= v2.15.0
GOVULNCHECK_VERSION ?= v1.9.0
GORELEASER_VERSION ?= v2.19.0

BIN ?= bin/devlyft
VERSION ?= $(shell git describe --tags --always --dirty 2>/dev/null || echo dev)
LDFLAGS := -s -w -X github.com/devlyft/cli/internal/version.Version=$(VERSION)

.DEFAULT_GOAL := build
.PHONY: build test cover lint fmt vuln tidy check clean help
.PHONY: build test cover lint fmt vuln tidy check snapshot clean help

build: ## Build the devlyft binary into bin/
go build -trimpath -ldflags '$(LDFLAGS)' -o $(BIN) ./cmd/devlyft
Expand All @@ -33,8 +34,11 @@ tidy: ## Tidy go.mod and go.sum

check: lint test vuln ## Run everything CI runs

snapshot: ## Build unsigned release archives for every platform into dist/
go run github.com/goreleaser/goreleaser/v2@$(GORELEASER_VERSION) release --snapshot --clean --skip=sign,sbom

clean:
rm -rf bin coverage.out
rm -rf bin dist coverage.out

help: ## Show this help
@grep -E '^[a-z]+:.*## ' $(MAKEFILE_LIST) | awk -F':.*## ' '{printf " %-8s %s\n", $$1, $$2}'
57 changes: 57 additions & 0 deletions docs/releasing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# Releasing

Releases are automated. Nobody tags by hand.

## How a release happens

1. Pull requests are squash-merged into `main` with a [Conventional Commits](https://www.conventionalcommits.org/) title (enforced by the `Lint title` check).
2. On every push to `main`, [release-please](https://github.com/googleapis/release-please) updates an open release PR with the next version and the changelog, worked out from those titles.
3. Merging the release PR creates the GitHub release and the `vX.Y.Z` tag.
4. The tag triggers `.github/workflows/release.yml`, which runs [goreleaser](https://goreleaser.com/) on a GitHub-hosted runner to:
- build `devlyft` for Linux, macOS and Windows on amd64 and arm64 (`CGO_ENABLED=0`, `-trimpath`, version, commit and date stamped in);
- package `.tar.gz` archives (`.zip` on Windows) with an SPDX SBOM per archive, generated by syft;
- write `devlyft_<version>_checksums.txt` covering every archive and SBOM;
- sign the checksum file with [cosign](https://docs.sigstore.dev/) keyless signing, using the workflow's GitHub OIDC identity (no stored keys), producing `.sig`, `.pem` and `.sigstore.json`;
- attach everything to the release.
5. The workflow then records a GitHub build-provenance attestation for the archives and the checksum file.

Every pull request also runs `checks / release-snapshot`, which builds all targets with the same config but skips signing, SBOMs and publishing. Run the same thing locally with `make snapshot`; the output lands in `dist/`.

## Verifying a download

Download the archive, `devlyft_<version>_checksums.txt` and `devlyft_<version>_checksums.txt.sigstore.json` from the release, then:

```sh
cosign verify-blob \
--bundle devlyft_<version>_checksums.txt.sigstore.json \
--certificate-identity-regexp '^https://github\.com/devlyft/cli/\.github/workflows/release\.yml@refs/tags/v' \
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
devlyft_<version>_checksums.txt

sha256sum --ignore-missing -c devlyft_<version>_checksums.txt
```

With the GitHub CLI you can check the build provenance instead:

```sh
gh attestation verify devlyft_<version>_linux_amd64.tar.gz --repo devlyft/cli
```

## Versioning: 0.x and 1.0

The CLI follows [semantic versioning](https://semver.org/).

- **While 0.x**, a minor release may contain breaking changes (`feat!:` and `BREAKING CHANGE` bump the minor, not the major). Patch releases never break.
- **1.0** is cut once all of these are true:
- browser login (OAuth 2.0 with PKCE) is released ([devlyft/devlyft#263](https://github.com/devlyft/devlyft/issues/263));
- device login is released ([devlyft/devlyft#264](https://github.com/devlyft/devlyft/issues/264));
- the output-stability and deprecation policy is published.
- 1.0 is **not** gated on server-side pagination or rollout status. Until those ship, `--limit` and `deploy --wait` are documented as best-effort.
- **From 1.0**, breaking the documented JSON or YAML output, exit codes or error `code` strings requires a major release.

To cut 1.0, merge a commit containing `Release-As: 1.0.0` in its body; release-please will propose 1.0.0 in its next release PR.

## Known gaps

- macOS binaries are not notarized and Windows binaries are not code-signed, so browser downloads trigger Gatekeeper and SmartScreen warnings. Installing through Homebrew or the install script avoids this.
- Homebrew, Scoop, deb and rpm channels are tracked separately (devlyft/devlyft#258, devlyft/devlyft#260).
12 changes: 12 additions & 0 deletions release-please-config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json",
"release-type": "go",
"include-component-in-tag": false,
"bump-minor-pre-major": true,
"packages": {
".": {
"package-name": "devlyft",
"changelog-path": "CHANGELOG.md"
}
}
}
Loading