Skip to content
Merged
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
17 changes: 0 additions & 17 deletions .github/dependabot.yml

This file was deleted.

61 changes: 57 additions & 4 deletions .github/workflows/commit-lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,12 @@ jobs:
checked=0

while IFS= read -r sha; do
if git show -s --format='%B' "$sha" | grep -qiE "$DISALLOWED_TRAILER_PATTERN"; then
# grep -c, not grep -q. Under pipefail, grep -q exits on its first
# match while git is still writing, git takes SIGPIPE and returns
# 141, and the pipeline result turns a real match into a miss for
# any message larger than the pipe buffer. grep -c reads to the end,
# so the writer always finishes (z-shell/zi#486).
if [ "$(git show -s --format='%B' "$sha" | grep -ciE "$DISALLOWED_TRAILER_PATTERN")" -gt 0 ]; then
echo "❌ Disallowed trailer found (${sha:0:7}): remove before merging"
errors=$((errors + 1))
fi
Expand Down Expand Up @@ -95,14 +100,62 @@ jobs:
env:
BRANCH: ${{ github.head_ref }}
run: |
PATTERN='^(feature|bug|hotfix)-[1-9][0-9]*$'
if echo "$BRANCH" | grep -qE '^(dependabot|renovate)/' || \
# decisions/0022: a shape check, not an identifier check.
# Traceability moved to Validate Issue Link.
PATTERN='^((feature|bug|hotfix)-[1-9][0-9]*(-[a-z0-9]+)*|(feat|fix|perf|refactor|docs|test|build|ci|style|chore|revert|feature|bug|hotfix)/[a-z0-9]+(-[a-z0-9]+)*)$'
if echo "$BRANCH" | grep -qE '^(dependabot|renovate|copilot|codex)/' || \
[ "$BRANCH" = "next" ]; then
echo "✅ OK"
exit 0
fi
if ! echo "$BRANCH" | grep -qE "$PATTERN"; then
echo "::error::Branch name must be feature-<id>, bug-<id>, or hotfix-<id>"
echo "::error::Branch name must be feature-<id>, bug-<id>, or hotfix-<id> with an optional lowercase slug, or <type>/<slug> over the Conventional Commits types (z-shell/.github decisions/0022)"
exit 1
fi
echo "✅ Branch name valid"

issue-link:
name: Validate Issue Link
runs-on: ubuntu-latest
steps:
- name: "🔗 Check the pull request is traceable to an issue"
env:
PR_BODY: ${{ github.event.pull_request.body }}
PR_LABELS: ${{ join(github.event.pull_request.labels.*.name, ',') }}
BRANCH: ${{ github.head_ref }}
EXEMPT_LABEL: meta:no-issue
run: |
set -euo pipefail

# z-shell/.github decisions/0022 moves traceability off the branch
# name and onto the pull request. Three outcomes pass, and the job
# says which applied, so an exemption is visible in review rather
# than silent. Everything comes from the pull_request event payload,
# so the job needs no token.

if printf '%s\n' "$BRANCH" | grep -qE '^(dependabot|renovate|copilot|codex)/'; then
echo "✅ Exempt: $BRANCH is an automation branch"
exit 0
fi

if [ "$BRANCH" = "next" ]; then
echo "✅ Exempt: next is the persistent integration branch"
exit 0
fi

if printf '%s\n' "$PR_LABELS" | tr ',' '\n' | grep -qxF "$EXEMPT_LABEL"; then
echo "✅ Exempt: labelled $EXEMPT_LABEL"
exit 0
fi

# A bare #123, the cross-repository owner/repo#123 shorthand, or a
# full issue or pull-request URL.
ISSUE_REFERENCE_PATTERN='(^|[^A-Za-z0-9_])#[1-9][0-9]*([^0-9]|$)|[A-Za-z0-9._-]+/[A-Za-z0-9._-]+#[1-9][0-9]*|https://github\.com/[^/ ]+/[^/ ]+/(issues|pull)/[1-9][0-9]*'

if printf '%s\n' "${PR_BODY:-}" | grep -qE "$ISSUE_REFERENCE_PATTERN"; then
echo "✅ The pull request references an issue"
exit 0
fi

echo "::error::No issue reference found. Link the owning issue in the pull-request body (Closes #123, or a plain #123 for work an issue tracks but this does not close). If this pull request genuinely has no owning issue, a maintainer applies the ${EXEMPT_LABEL} label (z-shell/.github decisions/0022)."
exit 1
21 changes: 21 additions & 0 deletions .github/workflows/release-prepare.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
---
name: Release Prepare

on:
push:
branches: [main]

permissions:
contents: read
issues: write
models: read

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

jobs:
propose:
uses: z-shell/.github/.github/workflows/release-prepare.yml@6f3d88335ca0ae77b795ec2883b4402b51f15c6a # main
with:
signed_tag: true
48 changes: 48 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
---
name: Release

on:
push:
tags: ["v*.*.*"]

permissions:
actions: read
contents: write

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

jobs:
publish:
name: Verify and publish
if: github.repository == 'z-shell/zi'
runs-on: ubuntu-latest
steps:
- name: Check out the tagged commit
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
fetch-tags: true
persist-credentials: false

- name: Verify release authorization
env:
GH_TOKEN: ${{ github.token }}
run: zsh -f scripts/verify-release-tag.zsh

- name: Publish release
env:
GH_TOKEN: ${{ github.token }}
TAG: ${{ github.ref_name }}
run: |
if gh release view "$TAG" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then
echo "Release $TAG already exists."
exit 0
fi
gh release create "$TAG" \
--repo "$GITHUB_REPOSITORY" \
--verify-tag \
--title "Zi $TAG" \
--generate-notes \
--latest
63 changes: 63 additions & 0 deletions .github/workflows/zsh-n.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,16 @@ on:
- "tests/message-formatting.zsh"
- "tests/path-resolution.zsh"
- "tests/parallel-update.zsh"
- "tests/plugin-autoload-fpath-scope.zsh"
- "tests/plugin-autoload-ice.zsh"
- "tests/nested-load-state.zsh"
- "tests/plugin-autoload-ownership.zsh"
- "tests/plugin-standard-callbacks.zsh"
- "tests/scheduler-idle.zsh"
- "tests/fixtures/plugin-standard-callbacks/**"
- "tests/self-update-reload.zsh"
- "tests/snippet-directory-mirror.zsh"
- "tests/subst-nesting.zsh"
- "tests/version-reporting.zsh"
pull_request:
paths:
Expand All @@ -31,11 +36,16 @@ on:
- "tests/message-formatting.zsh"
- "tests/path-resolution.zsh"
- "tests/parallel-update.zsh"
- "tests/plugin-autoload-fpath-scope.zsh"
- "tests/plugin-autoload-ice.zsh"
- "tests/nested-load-state.zsh"
- "tests/plugin-autoload-ownership.zsh"
- "tests/plugin-standard-callbacks.zsh"
- "tests/scheduler-idle.zsh"
- "tests/fixtures/plugin-standard-callbacks/**"
- "tests/self-update-reload.zsh"
- "tests/snippet-directory-mirror.zsh"
- "tests/subst-nesting.zsh"
- "tests/version-reporting.zsh"
workflow_dispatch: {}

Expand Down Expand Up @@ -117,6 +127,48 @@ jobs:
- name: Test parallel update
run: zsh -f tests/parallel-update.zsh

plugin-autoload-fpath-scope:
name: Plugin Autoload Fpath Scope
runs-on: ubuntu-latest
steps:
- name: Check out code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Install Zsh
run: sudo apt update && sudo apt-get install -yq zsh
- name: Test plugin autoload fpath scope
run: zsh -f tests/plugin-autoload-fpath-scope.zsh
plugin-autoload-ice:
name: Plugin Autoload Ice
runs-on: ubuntu-latest
steps:
- name: Check out code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Install Zsh
run: sudo apt update && sudo apt-get install -yq zsh
- name: Test autoload ice forms
run: zsh -f tests/plugin-autoload-ice.zsh
nested-load-state:
name: Nested Load State
runs-on: ubuntu-latest
steps:
- name: Check out code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Install Zsh
run: sudo apt update && sudo apt-get install -yq zsh
- name: Test nested load state
run: zsh -f tests/nested-load-state.zsh

plugin-autoload-ownership:
name: Plugin Autoload Ownership
runs-on: ubuntu-latest
steps:
- name: Check out code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Install Zsh
run: sudo apt update && sudo apt-get install -yq zsh
- name: Test plugin autoload ownership
run: zsh -f tests/plugin-autoload-ownership.zsh

plugin-standard-callbacks:
name: Plugin Standard Callbacks
runs-on: ubuntu-latest
Expand Down Expand Up @@ -194,6 +246,17 @@ jobs:
- name: Test message formatting
run: zsh -f tests/message-formatting.zsh

subst-nesting:
name: Subst Nesting
runs-on: ubuntu-latest
steps:
- name: Check out code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Install Zsh
run: sudo apt update && sudo apt-get install -yq zsh
- name: Test substitution nesting
run: zsh -f tests/subst-nesting.zsh

snippet-directory-mirror:
name: Snippet directory mirror
runs-on: ubuntu-latest
Expand Down
55 changes: 55 additions & 0 deletions scripts/verify-release-tag.zsh
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
#!/usr/bin/env zsh

emulate -L zsh
setopt err_return no_unset pipe_fail

fail() {
print -u2 -- "release verification: $*"
return 1
}

tag=${GITHUB_REF_NAME:-}
repository=${GITHUB_REPOSITORY:-}

[[ $repository == z-shell/zi ]] || fail "unexpected repository: ${repository:-unset}"
[[ $tag =~ '^v(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$' ]] ||
fail "tag must match vX.Y.Z: ${tag:-unset}"

tag_ref="refs/tags/${tag}"
[[ $(git cat-file -t "$tag_ref" 2>/dev/null) == tag ]] ||
fail "tag must be annotated: $tag"

git fetch --quiet --force --no-tags origin \
refs/heads/main:refs/remotes/origin/main ||
fail "could not fetch origin/main"

target=$(git rev-parse "${tag_ref}^{}") || fail "could not resolve tag target"
main=$(git rev-parse refs/remotes/origin/main) || fail "could not resolve origin/main"
[[ $target == $main ]] || fail "tag target is not the current origin/main"

tag_object=$(git rev-parse "$tag_ref") || fail "could not resolve tag object"
tag_json=$(gh api "repos/${repository}/git/tags/${tag_object}") ||
fail "could not read tag verification"
jq -e --arg target "$target" \
'.verification.verified == true and
.object.type == "commit" and
.object.sha == $target' <<<"$tag_json" >/dev/null ||
fail "GitHub did not verify the signed tag and target"

runs_json=$(gh api --method GET "repos/${repository}/actions/runs" \
-f branch=main -f head_sha="$target" -f per_page=100) ||
fail "could not read workflow runs"

for workflow in Zsh 'ZD Integration' CodeQL 'Trunk Code Quality'; do
jq -e --arg name "$workflow" --arg target "$target" \
'.workflow_runs | any(
.name == $name and
.head_branch == "main" and
.head_sha == $target and
.status == "completed" and
.conclusion == "success"
)' <<<"$runs_json" >/dev/null ||
fail "required workflow did not succeed: $workflow"
done

print -- "Release authorization verified for ${tag} at ${target}."
Loading
Loading