diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 8b83613da..67ca0cfbd 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -41,3 +41,49 @@ jobs: ./themes/*.zsh-theme; do zsh -n "$file" || return 1 done + + - name: Focused syntax check for bootstrap-adjacent touched files + if: github.event_name == 'pull_request' + run: | + mapfile -t touched < <( + git diff --name-only "${{ github.event.pull_request.base.sha }}" "${{ github.sha }}" \ + | grep -E '^(lib/bootstrap\.zsh|lib/tests/bootstrap.*\.zsh|oh-my-zsh\.sh|lib/(completion|compfix|functions|theme-and-appearance)\.zsh|plugins/.+/.+\.plugin\.zsh|themes/.+\.zsh-theme)$' \ + || true + ) + + if [ "${#touched[@]}" -eq 0 ]; then + echo "No bootstrap-adjacent files touched" + exit 0 + fi + + for file in "${touched[@]}"; do + echo "Syntax checking $file" + zsh -n "$file" + done + + - name: Run bootstrap unit test + if: github.event_name == 'pull_request' + run: zsh ./lib/tests/bootstrap.test.zsh + + - name: Run inline bootstrap invariants smoke test + if: github.event_name == 'pull_request' + run: zsh ./lib/tests/bootstrap-inline-ci.test.zsh + + plugin-manager-smoke: + name: Plugin manager smoke (${{ matrix.manager }}:${{ matrix.scenario }}) + runs-on: ubuntu-latest + if: github.repository == 'ohmyzsh/ohmyzsh' && github.event_name == 'pull_request' + strategy: + fail-fast: false + matrix: + manager: [antigen, zinit] + scenario: [positive, negative] + steps: + - name: Set up git repository + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + + - name: Install zsh + run: sudo apt-get update; sudo apt-get install zsh + + - name: Run plugin-manager bootstrap integration + run: zsh ./lib/tests/plugin-manager-bootstrap-integration.test.zsh "${{ matrix.manager }}" "${{ matrix.scenario }}" diff --git a/.github/workflows/plugin-manager-nightly.yml b/.github/workflows/plugin-manager-nightly.yml new file mode 100644 index 000000000..134603e1d --- /dev/null +++ b/.github/workflows/plugin-manager-nightly.yml @@ -0,0 +1,37 @@ +name: Plugin manager integration (advisory) + +on: + schedule: + - cron: '0 6 * * *' + workflow_dispatch: + +permissions: + contents: read + +jobs: + plugin-manager-matrix: + name: ${{ matrix.manager }}:${{ matrix.scenario }} + runs-on: ubuntu-latest + if: github.repository == 'ohmyzsh/ohmyzsh' + continue-on-error: true + strategy: + fail-fast: false + matrix: + manager: [antigen, zinit, zgen, zplug, antibody, zulu] + scenario: [positive, negative] + + steps: + - name: Set up git repository + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + + - name: Install zsh and go + run: sudo apt-get update; sudo apt-get install zsh golang + + - name: Run plugin-manager bootstrap integration + run: zsh ./lib/tests/plugin-manager-bootstrap-integration.test.zsh "${{ matrix.manager }}" "${{ matrix.scenario }}" + + - name: Rollout note + if: always() + run: | + echo "Phase 2 active: full manager matrix is advisory/nightly." + echo "Phase 3 target: promote full matrix to required after stability window." diff --git a/lib/tests/bootstrap-inline-ci.test.zsh b/lib/tests/bootstrap-inline-ci.test.zsh new file mode 100755 index 000000000..1dc48a1f9 --- /dev/null +++ b/lib/tests/bootstrap-inline-ci.test.zsh @@ -0,0 +1,35 @@ +#!/usr/bin/zsh -df + +set -eu + +bootstrap_file="${0:A:h:h}/bootstrap.zsh" + +assert() { + local condition="$1" message="$2" + if ! eval "$condition"; then + print -u2 "\e[31mError\e[0m: $message" + exit 1 + fi +} + +tmp==(:) +mkdir -p "$tmp/ohmyzsh"/{functions,completions,cache} "$tmp/ohmyzsh/custom"/{functions,completions} + +export ZSH="$tmp/ohmyzsh" +unset ZSH_CUSTOM ZSH_CACHE_DIR OMZ_IS_BOOTSTRAPPED +fpath=() + +source "$bootstrap_file" + +assert '[[ "$OMZ_IS_BOOTSTRAPPED" == true ]]' "OMZ bootstrap signal should be true" +assert '[[ -d "$ZSH_CACHE_DIR/completions" ]]' "cache completions directory should exist" +assert '[[ "${fpath[(Ie)$ZSH/functions]}" -gt 0 ]]' "fpath should include OMZ functions" +assert '[[ "${fpath[(Ie)$ZSH/completions]}" -gt 0 ]]' "fpath should include OMZ completions" +assert '[[ "${fpath[(Ie)$ZSH_CUSTOM/functions]}" -gt 0 ]]' "fpath should include custom functions" +assert '[[ "${fpath[(Ie)$ZSH_CUSTOM/completions]}" -gt 0 ]]' "fpath should include custom completions" +assert '[[ "${fpath[(Ie)$ZSH_CACHE_DIR/completions]}" -gt 0 ]]' "fpath should include cache completions" + +touch "$ZSH_CACHE_DIR/completions/_bootstrap_ci_smoke" +assert '[[ -f "$ZSH_CACHE_DIR/completions/_bootstrap_ci_smoke" ]]' "completion cache write should succeed" + +print -u2 "\e[32mSuccess\e[0m bootstrap inline invariants" diff --git a/lib/tests/plugin-manager-bootstrap-integration.test.zsh b/lib/tests/plugin-manager-bootstrap-integration.test.zsh new file mode 100755 index 000000000..5ce834ef0 --- /dev/null +++ b/lib/tests/plugin-manager-bootstrap-integration.test.zsh @@ -0,0 +1,101 @@ +#!/usr/bin/zsh -df + +set -euo pipefail + +if (( $# != 2 )); then + print -u2 "Usage: $0 " + exit 1 +fi + +manager="$1" +scenario="$2" + +if [[ "$scenario" != "positive" && "$scenario" != "negative" ]]; then + print -u2 "scenario must be 'positive' or 'negative'" + exit 1 +fi + +repo_root="${0:A:h:h:h}" +tmp==(:) +workspace="$tmp/workspace" +home_dir="$tmp/home" +cache_dir="$tmp/cache" +zshrc="$tmp/.zshrc" + +mkdir -p "$workspace" "$home_dir" "$cache_dir" + +manager_install='' +manager_source='' + +case "$manager" in + antigen) + manager_install='git clone https://github.com/zsh-users/antigen.git "$MANAGER_HOME/antigen" >/dev/null 2>&1' + manager_source='source "$MANAGER_HOME/antigen/antigen.zsh"' + ;; + zinit) + manager_install='git clone https://github.com/zdharma-continuum/zinit.git "$MANAGER_HOME/zinit" >/dev/null 2>&1' + manager_source='source "$MANAGER_HOME/zinit/zinit.zsh"' + ;; + zgen) + manager_install='git clone https://github.com/tarjoilija/zgen.git "$MANAGER_HOME/zgen" >/dev/null 2>&1' + manager_source='source "$MANAGER_HOME/zgen/zgen.zsh"' + ;; + zplug) + manager_install='git clone https://github.com/zplug/zplug "$MANAGER_HOME/zplug" >/dev/null 2>&1' + manager_source='export ZPLUG_HOME="$MANAGER_HOME/zplug"; source "$ZPLUG_HOME/init.zsh"' + ;; + antibody) + manager_install='command -v go >/dev/null 2>&1; export GOBIN="$MANAGER_HOME/bin"; mkdir -p "$GOBIN"; go install github.com/getantibody/antibody@latest >/dev/null 2>&1' + manager_source='"$MANAGER_HOME/bin/antibody" --version >/dev/null 2>&1' + ;; + zulu) + manager_install='git clone https://github.com/zulu-zsh/zulu.git "$MANAGER_HOME/zulu" >/dev/null 2>&1' + manager_source='source "$MANAGER_HOME/zulu/zulu.zsh"' + ;; + *) + print -u2 "Unsupported manager: $manager" + exit 1 + ;; +esac + +cat > "$zshrc" <> "$zshrc" <<'RC' +source "$OMZ_ROOT/lib/bootstrap.zsh" +[[ "${OMZ_IS_BOOTSTRAPPED:-}" == true ]] || { print -u2 "bootstrap signal missing before OMZ load"; return 1; } +RC +else + cat >> "$zshrc" <<'RC' +[[ -z "${OMZ_IS_BOOTSTRAPPED:-}" ]] || { print -u2 "bootstrap signal unexpectedly set without hook"; return 1; } +source "$OMZ_ROOT/lib/bootstrap.zsh" +RC +fi + +cat >> "$zshrc" <