diff --git a/.cspell/project-words.txt b/.cspell/project-words.txt
deleted file mode 100644
index bb883fb..0000000
--- a/.cspell/project-words.txt
+++ /dev/null
@@ -1,45 +0,0 @@
-AOPT
-atinit
-atload
-atpull
-autoload
-blockf
-BOPT
-CFLAGS
-Cgreen
-Checkig
-COLORTERM
-COMPINIT
-CPPFLAGS
-creinstall
-Creset
-distclean
-gdbm
-LDFLAGS
-mgit
-mhas
-mktemp
-mload
-nearcolor
-OMZL
-OMZP
-OMZT
-pname
-robbyrussell
-SAVEHIST
-setopt
-srand
-tcsetpgrp
-truecolor
-WORKDIR
-ZDOTDIR
-zicdreplay
-zicompinit
-zinit
-zmodload
-zmodules
-ZOPT
-ZPMOD
-zstyle
-zunit
-zzinit
diff --git a/.editorconfig b/.editorconfig
deleted file mode 100644
index c25c645..0000000
--- a/.editorconfig
+++ /dev/null
@@ -1,60 +0,0 @@
-# Space or Tabs?
-# https://stackoverflow.com/questions/35649847/objective-reasons-for-using-spaces-instead-of-tabs-for-indentation
-# https://stackoverflow.com/questions/12093748/how-to-use-tabs-instead-of-spaces-in-a-shell-script
-# https://github.com/editorconfig/editorconfig-defaults/blob/master/editorconfig-defaults.json
-#
-# 1. What happens when I press the Tab key in my text editor?
-# 2. What happens when I request my editor to indent one or more lines?
-# 3. What happens when I view a file containing U+0009 HORIZONTAL TAB characters?
-#
-# Answers:
-#
-# 1. Pressing the Tab key should indent the current line (or selected lines) one additional level.
-# 2. As a secondary alternative, I can also tolerate an editor that,
-# like Emacs, uses this key for a context-sensitive fix-my-indentation command.
-# 3. Indenting one or more lines should follow the reigning convention, if consensus is sufficiently strong; otherwise,
-# I greatly prefer 2-space indentation at each level. U+0009 characters should shift subsequent characters to the next tab stop.
-#
-# Note: VIM users should use alternate marks [[[ and ]]] as the original ones can confuse nested substitutions, e.g.: ${${${VAR}}}
-#
-# -*- mode: zsh; sh-indentation: 2; indent-tabs-mode: nil; sh-basic-offset: 2; -*-
-# vim: ft=zsh sw=2 ts=2 et
-
-root = true
-
-[*]
-charset = utf-8
-indent_style = space
-indent_size = 2
-insert_final_newline = true
-trim_trailing_whitespace = true
-
-[*.sln]
-indent_style = tab
-
-[*.{md,mdx,rst}]
-trim_trailing_whitespace = false
-
-[*.{cmd,bat}]
-end_of_line = crlf
-
-[*za-*]
-end_of_line = lf
-
-[*.{sh,bash,zsh,fish}]
-end_of_line = lf
-
-[Makefile]
-indent_style = tab
-indent_size = 4
-
-[*.{py,rb}]
-indent_size = 4
-
-[*.{go,java,scala,groovy,kotlin}]
-indent_style = tab
-indent_size = 4
-
-[*.{cs,csx,cake,vb,vbx}]
-# Default Severity for all .NET Code Style rules below
-dotnet_analyzer_diagnostic.severity = warning
diff --git a/.geminiignore b/.geminiignore
new file mode 100644
index 0000000..e69de29
diff --git a/.github/.cspell/project-ignored.txt b/.github/.cspell/project-ignored.txt
deleted file mode 100644
index 01e9305..0000000
--- a/.github/.cspell/project-ignored.txt
+++ /dev/null
@@ -1,2 +0,0 @@
-mhas
-mload
diff --git a/.github/.cspell/project-usernames.txt b/.github/.cspell/project-usernames.txt
deleted file mode 100644
index 43ee3d2..0000000
--- a/.github/.cspell/project-usernames.txt
+++ /dev/null
@@ -1 +0,0 @@
-robbyrussell
diff --git a/.github/.cspell/project-words.txt b/.github/.cspell/project-words.txt
deleted file mode 100644
index e09d106..0000000
--- a/.github/.cspell/project-words.txt
+++ /dev/null
@@ -1,34 +0,0 @@
-AOPT
-atinit'COMPLETION
-atinit'setopt
-atinit'typeset
-atinit'ZI
-atinit'zstyle
-atload
-atpull
-autoload
-blockf
-BOPT
-CFLAGS
-COMPINIT
-CPPFLAGS
-creinstall
-distclean
-gdbm
-LDFLAGS
-mktemp
-OMZL
-OMZP
-OMZT
-SAVEHIST
-tcsetpgrp
-WORKDIR
-ZDOTDIR
-zicdreplay
-zicompinit
-zinit
-zmodload
-zmodules
-ZOPT
-zpmod
-zzinit
diff --git a/.github/workflows/check-linux.yml b/.github/workflows/check-linux.yml
index 3af9a27..1e9a62a 100644
--- a/.github/workflows/check-linux.yml
+++ b/.github/workflows/check-linux.yml
@@ -5,24 +5,34 @@ on:
branches: [main]
paths:
- "lib/sh/**"
+ - "lib/zsh/**"
+ - "tests/**"
+ - ".github/workflows/check-linux.yml"
pull_request:
branches: [main]
paths:
- "lib/sh/**"
+ - "lib/zsh/**"
+ - "tests/**"
+ - ".github/workflows/check-linux.yml"
workflow_dispatch:
+permissions:
+ contents: read
+
concurrency:
- group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
+ group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
shellcheck:
runs-on: ubuntu-latest
+ timeout-minutes: 10
steps:
- name: ⤵️ Check out code from GitHub
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: ☑️ ShellCheck
- uses: ludeeus/action-shellcheck@00b27aa7cb85167568cb48a3838b75f4265f2bca
+ uses: ludeeus/action-shellcheck@00cae500b08a931fb5698e11e79bfbd38e612a38 # 2.0.0
with:
scandir: "./lib/sh"
@@ -32,24 +42,28 @@ jobs:
needs: [shellcheck]
steps:
- name: ⤵️ Check out code from GitHub
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: ⚙️ Prepare dependencies
run: |
sudo apt-get update
sudo apt-get install -y zsh
+ - name: "⚙️ Check: unit fixtures"
+ run: sh ./tests/installers.sh
- name: "⚙️ Check: install.sh -- -i skip"
- run: sh -x ./lib/sh/install.sh -- -i skip; command rm -rf ~/.zi
- - name: "⚙️ Check: install.sh -- -i annex"
- run: sh -x ./lib/sh/install.sh -- -a annex; command rm -rf ~/.zi
- - name: "⚙️ Check: install.sh -- -i loader"
- run: sh -x ./lib/sh/install.sh -- -a loader; command rm -rf ~/.zi
- - name: "⚙️ Check: install.sh -- -i zunit"
- run: sh -x ./lib/sh/install.sh -- -a zunit; command rm -rf ~/.zi
+ run: sh -x ./lib/sh/install.sh -- -i skip; command rm -rf "${XDG_DATA_HOME:-$HOME/.local/share}/zi" ~/.zi
+ - name: "⚙️ Check: install.sh -- -a annex"
+ run: sh -x ./lib/sh/install.sh -- -a annex; command rm -rf "${XDG_DATA_HOME:-$HOME/.local/share}/zi" ~/.zi
+ - name: "⚙️ Check: install.sh -- -a loader"
+ run: sh -x ./lib/sh/install.sh -- -a loader; command rm -rf "${XDG_DATA_HOME:-$HOME/.local/share}/zi" ~/.zi
+ - name: "⚙️ Check: install.sh -- -a zunit"
+ run: sh -x ./lib/sh/install.sh -- -a zunit; command rm -rf "${XDG_DATA_HOME:-$HOME/.local/share}/zi" ~/.zi
- name: "⚙️ Check: install_zpmod.sh"
run: sh -x ./lib/sh/install_zpmod.sh
- name: ⚙️ Load zpmod module
run: |
- module_path+=( "$HOME/.zi/zmodules/zpmod/Src" )
+ module_path+=( "${XDG_DATA_HOME:-$HOME/.local/share}/zi/zmodules/zpmod/Src" )
zmodload zi/zpmod
zpmod source-study -l
shell: zsh {0}
+ - name: "⚙️ Check: init.zsh sync drift"
+ run: sh lib/sh/sync-init.sh
diff --git a/.github/workflows/check-macos.yml b/.github/workflows/check-macos.yml
index 0acf6d1..1d1d8e0 100644
--- a/.github/workflows/check-macos.yml
+++ b/.github/workflows/check-macos.yml
@@ -6,50 +6,62 @@ on:
branches: [main]
paths:
- "lib/sh/**"
+ - "lib/zsh/**"
+ - "tests/**"
+ - ".github/workflows/check-macos.yml"
pull_request:
branches: [main]
paths:
- "lib/sh/**"
+ - "lib/zsh/**"
+ - "tests/**"
+ - ".github/workflows/check-macos.yml"
workflow_dispatch:
+permissions:
+ contents: read
+
concurrency:
- group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
+ group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
shellcheck:
runs-on: ubuntu-latest
+ timeout-minutes: 10
steps:
- name: ⤵️ Check out code from GitHub
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: ☑️ ShellCheck
- uses: ludeeus/action-shellcheck@00b27aa7cb85167568cb48a3838b75f4265f2bca
+ uses: ludeeus/action-shellcheck@00cae500b08a931fb5698e11e79bfbd38e612a38 # 2.0.0
with:
scandir: "./lib/sh"
build:
- runs-on: ubuntu-latest
+ runs-on: macos-latest
timeout-minutes: 30
needs: [shellcheck]
steps:
- name: ⤵️ Check out code from GitHub
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: ⚙️ Prepare dependencies
run: |
brew install zsh
+ - name: "⚙️ Check: unit fixtures"
+ run: sh ./tests/installers.sh
- name: "⚙️ Check: install.sh -- -i skip"
- run: sh -x ./lib/sh/install.sh -- -i skip; command rm -rf ~/.zi
- - name: "⚙️ Check: install.sh -- -i annex"
- run: sh -x ./lib/sh/install.sh -- -a annex; command rm -rf ~/.zi
- - name: "⚙️ Check: install.sh -- -i loader"
- run: sh -x ./lib/sh/install.sh -- -a loader; command rm -rf ~/.zi
- - name: "⚙️ Check: install.sh -- -i zunit"
- run: sh -x ./lib/sh/install.sh -- -a zunit; command rm -rf ~/.zi
+ run: sh -x ./lib/sh/install.sh -- -i skip; command rm -rf "${XDG_DATA_HOME:-$HOME/.local/share}/zi" ~/.zi
+ - name: "⚙️ Check: install.sh -- -a annex"
+ run: sh -x ./lib/sh/install.sh -- -a annex; command rm -rf "${XDG_DATA_HOME:-$HOME/.local/share}/zi" ~/.zi
+ - name: "⚙️ Check: install.sh -- -a loader"
+ run: sh -x ./lib/sh/install.sh -- -a loader; command rm -rf "${XDG_DATA_HOME:-$HOME/.local/share}/zi" ~/.zi
+ - name: "⚙️ Check: install.sh -- -a zunit"
+ run: sh -x ./lib/sh/install.sh -- -a zunit; command rm -rf "${XDG_DATA_HOME:-$HOME/.local/share}/zi" ~/.zi
- name: "⚙️ Check: install_zpmod.sh"
run: sh -x ./lib/sh/install_zpmod.sh
- name: ⚙️ Load zpmod module
run: |
- module_path+=( "$HOME/.zi/zmodules/zpmod/Src" )
+ module_path+=( "${XDG_DATA_HOME:-$HOME/.local/share}/zi/zmodules/zpmod/Src" )
zmodload zi/zpmod
zpmod source-study -l
shell: zsh {0}
diff --git a/.github/workflows/checksum.yml b/.github/workflows/checksum.yml
index 66080e7..29fdb4e 100644
--- a/.github/workflows/checksum.yml
+++ b/.github/workflows/checksum.yml
@@ -3,20 +3,34 @@ name: "🆗 Checksum"
on:
push:
paths:
- - "lib/**"
+ - "lib/sh/install_zpmod.sh"
+ - "lib/sh/install.sh"
+ - "lib/sh/sync-init.sh"
+ - "lib/zsh/init.zsh"
+ - ".github/workflows/checksum.yml"
workflow_dispatch: {}
+permissions:
+ contents: write
+
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: false
+
jobs:
checksum:
+ if: github.repository == 'z-shell/src'
runs-on: ubuntu-latest
+ timeout-minutes: 10
steps:
- - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
+ - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: "🆗 Generate checksum"
- uses: jmgilman/actions-generate-checksum@521a903edf511407d8bd5535d257402fd9bb5db0
+ uses: jmgilman/actions-generate-checksum@3ea6dc9bf8eecf28e2ecc982fab683484a1a8561 # v1.0.1
with:
patterns: |
lib/sh/install_zpmod.sh
lib/sh/install.sh
+ lib/sh/sync-init.sh
lib/zsh/init.zsh
- run: mv checksum.txt lib/
- name: "🆗 Commit"
diff --git a/.github/workflows/deploy-gh-pages.yml b/.github/workflows/deploy-gh-pages.yml
index 688a65f..351dbeb 100644
--- a/.github/workflows/deploy-gh-pages.yml
+++ b/.github/workflows/deploy-gh-pages.yml
@@ -8,17 +8,35 @@ on:
tags: ["v*.*.*"]
paths:
- "lib/**"
+ - ".github/workflows/deploy-gh-pages.yml"
+
+permissions:
+ contents: write
jobs:
deploy:
+ if: github.repository == 'z-shell/src'
environment:
name: github-pages
runs-on: ubuntu-latest
+ timeout-minutes: 15
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
steps:
- name: ⤵️ Check out code from GitHub
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+ - name: "🏷 Prepare deployment metadata"
+ id: prepare_deploy
+ run: |
+ commit_message="$(jq -r '.head_commit.message // empty' "$GITHUB_EVENT_PATH")"
+ if [ -z "$commit_message" ]; then
+ commit_message="Deploy ${GITHUB_REF_NAME}"
+ fi
+ {
+ echo "commit_message<> "$GITHUB_OUTPUT"
- name: "🏷 Prepare tag"
id: prepare_tag
if: startsWith(github.ref, 'refs/tags/')
@@ -34,6 +52,6 @@ jobs:
publish_dir: ./lib
user_name: ${{ secrets.ACTIONS_USER }}
user_email: ${{ secrets.ACTIONS_MAIL }}
- commit_message: ${{ github.event.head_commit.message }}
+ commit_message: ${{ steps.prepare_deploy.outputs.commit_message }}
tag_name: ${{ steps.prepare_tag.outputs.deploy_tag_name }}
tag_message: "Deployment ${{ steps.prepare_tag.outputs.tag_name }}"
diff --git a/.github/workflows/rclone-action.yml b/.github/workflows/rclone-action.yml
index e4f62b6..5b0f0e0 100644
--- a/.github/workflows/rclone-action.yml
+++ b/.github/workflows/rclone-action.yml
@@ -5,24 +5,29 @@ on:
branches: [main]
paths:
- "lib/**"
+ - ".github/workflows/rclone-action.yml"
workflow_dispatch: {}
+permissions:
+ contents: read
+
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
sync:
- if: github.repository == 'z-shell/zi-src'
+ if: github.repository == 'z-shell/src'
runs-on: ubuntu-latest
+ timeout-minutes: 15
env:
local_path: "lib"
remote_path: "r2:r2-store/src"
steps:
- name: "⤵️ Check out code from GitHub"
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: "⏫ Run rclone/r2-store"
- uses: z-shell/.github/actions/rclone@v1
+ uses: z-shell/.github/actions/rclone@91068ee88e8788deff439d6ee36b77329edeb98a # v1.0.8
with:
config: ${{ secrets.R2_STORE }}
args: "copy --check-first ${{ env.local_path }} ${{ env.remote_path }}"
diff --git a/.github/workflows/rebase-action.yml b/.github/workflows/rebase-action.yml
deleted file mode 100644
index 83e6615..0000000
--- a/.github/workflows/rebase-action.yml
+++ /dev/null
@@ -1,29 +0,0 @@
----
-name: "🔁 Rebase"
-on:
- issue_comment:
- types: [created]
-
-jobs:
- rebase:
- runs-on: ubuntu-latest
- name: 🔁 Rebase
- # Automate with comments: /autosquash, /rebase
- if: >-
- github.event.issue.pull_request != '' &&
- (
- contains(github.event.comment.body, '/rebase') ||
- contains(github.event.comment.body, '/autosquash')
- )
- steps:
- - name: ⤵️ Check out code from GitHub
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- with:
- token: ${{ secrets.ORG_TOKEN }}
- fetch-depth: 0 # otherwise, you will fail to push refs to dest repo
- - name: 🔁 Rebase
- uses: z-shell/.github/actions/rebase@91068ee88e8788deff439d6ee36b77329edeb98a # v1.0.8
- with:
- autosquash: ${{ contains(github.event.comment.body, '/autosquash') || contains(github.event.comment.body, '/rebase-autosquash') }}
- env:
- GITHUB_TOKEN: ${{ secrets.ORG_TOKEN }}
diff --git a/.github/workflows/stale-action.yml b/.github/workflows/stale-action.yml
deleted file mode 100644
index d6fd984..0000000
--- a/.github/workflows/stale-action.yml
+++ /dev/null
@@ -1,35 +0,0 @@
----
-name: "👻 Stale"
-
-on:
- schedule:
- - cron: "0 8 * * *"
- workflow_dispatch:
-
-jobs:
- stale:
- name: "🧹 Clean up stale issues and PRs"
- runs-on: ubuntu-latest
- steps:
- - name: "🚀 Run stale"
- uses: actions/stale@5bef64f19d7facfb25b37b414482c7164d639639 # v9
- with:
- # 📋 https://github.com/actions/stale#all-options
- days-before-stale: 30
- days-before-close: 7
- exempt-all-pr-assignees: true
- exempt-all-pr-milestones: true
- remove-stale-when-updated: true
- stale-issue-label: "stale 👻"
- exempt-issue-labels: "no-stale 🔒,help-wanted 👥"
- stale-issue-message: >
- There hasn't been any activity on this issue recently, and in order to prioritize active issues, it will be
- marked as stale. Please make sure to update to the latest version and check if that solves the issue. Let us
- know if that works for you by leaving a 👍 Because this issue is marked as stale, it will be closed and
- locked in 7 days if no further activity occurs. Thank you for your contributions!
- stale-pr-label: "stale 👻"
- exempt-pr-labels: "no-stale 🔒"
- stale-pr-message: >
- There hasn't been any activity on this pull request recently, and in order to prioritize active work, it has
- been marked as stale. This PR will be closed and locked in 7 days if no further activity occurs. Thank you
- for your contributions!
diff --git a/.github/workflows/win-install.yml b/.github/workflows/win-install.yml
index c9c32f1..365e6bc 100644
--- a/.github/workflows/win-install.yml
+++ b/.github/workflows/win-install.yml
@@ -5,20 +5,28 @@ on:
pull_request:
paths:
- "lib/sh/**"
+ - "lib/zsh/**"
+ - "tests/**"
- ".github/workflows/win-install.yml"
push:
paths:
- "lib/sh/**"
+ - "lib/zsh/**"
+ - "tests/**"
- ".github/workflows/win-install.yml"
workflow_dispatch: {}
+permissions:
+ contents: read
+
jobs:
shellcheck:
runs-on: ubuntu-latest
+ timeout-minutes: 10
steps:
- - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
+ - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: ☑️ ShellCheck
- uses: ludeeus/action-shellcheck@00b27aa7cb85167568cb48a3838b75f4265f2bca
+ uses: ludeeus/action-shellcheck@00cae500b08a931fb5698e11e79bfbd38e612a38 # 2.0.0
with:
scandir: "./lib/sh"
run-install:
@@ -32,22 +40,33 @@ jobs:
- name: 🪟 Set CRLF (Windows)
run: |
git config --global core.autocrlf input
- git config --global --add safe.directory /cygdrive/d/a/zi-src/zi-src
- - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
+ git config --global --add safe.directory /cygdrive/d/a/src/src
+ - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: 🪟 Dependencies (Windows)
- uses: egor-tensin/setup-cygwin@d2c752bab416d4b0662591bd366fc2686297c82d # v4
+ uses: egor-tensin/setup-cygwin@d2c752bab416d4b0662591bd366fc2686297c82d # v4.0.1
with:
platform: x64
packages: curl git zsh
- name: 🪟 Run Install
run: |
+ sh ./tests/installers.sh
sh -x ./lib/sh/install.sh -- -i skip
- command rm -rf /home/runneradmin/.zi
+ command rm -rf "${XDG_DATA_HOME:-$HOME/.local/share}/zi" /home/runneradmin/.zi
sh -x ./lib/sh/install.sh -- -a annex
- command rm -rf /home/runneradmin/.zi
+ command rm -rf "${XDG_DATA_HOME:-$HOME/.local/share}/zi" /home/runneradmin/.zi
sh -x ./lib/sh/install.sh -- -a loader
- command rm -rf /home/runneradmin/.zi
+ command rm -rf "${XDG_DATA_HOME:-$HOME/.local/share}/zi" /home/runneradmin/.zi
sh -x ./lib/sh/install.sh -- -a zunit
- command rm -rf /home/runneradmin/.zi
+ command rm -rf "${XDG_DATA_HOME:-$HOME/.local/share}/zi" /home/runneradmin/.zi
sh -x ./lib/sh/install.sh -- -a zpmod
shell: C:\tools\cygwin\bin\bash.exe --login -o igncr '{0}'
+ - name: 🪟 Smoke-test — verify zi.zsh present
+ run: |
+ ZI_BIN="${XDG_DATA_HOME:-${HOME}/.local/share}/zi/bin"
+ if [ ! -f "${ZI_BIN}/zi.zsh" ]; then
+ printf '%s\n' "FAIL: zi.zsh not found at ${ZI_BIN}/zi.zsh"
+ exit 1
+ fi
+ printf '%s\n' "OK: zi.zsh found at ${ZI_BIN}/zi.zsh"
+ command rm -rf "${XDG_DATA_HOME:-${HOME}/.local/share}/zi" /home/runneradmin/.zi
+ shell: C:\tools\cygwin\bin\bash.exe --login -o igncr '{0}'
diff --git a/.github/workflows/wrangler.yml b/.github/workflows/wrangler.yml
deleted file mode 100644
index 57f590a..0000000
--- a/.github/workflows/wrangler.yml
+++ /dev/null
@@ -1,40 +0,0 @@
----
-name: "🤠 Wrangler"
-
-on:
- # schedule:
- # - cron: "0 03 * * 1/3"
- # push:
- # branches: [main]
- # paths:
- # - "lib/**"
- # - "workers/**"
- workflow_dispatch:
- inputs:
- environment:
- description: "Choose an environment to deploy to: "
- required: true
- default: "dev"
-
-jobs:
- deploy:
- runs-on: ubuntu-latest
- concurrency:
- group: ${{ github.workflow }}-${{ github.ref }}
- cancel-in-progress: true
- timeout-minutes: 15
- environment: wrangler
- steps:
- - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- - name: "🤠 Deploy > cdn"
- uses: cloudflare/wrangler-action@da0e0dfe58b7a431659754fdf3f186c529afbe65 # v3.14.1
- with:
- apiToken: ${{ secrets.CF_API_TOKEN }}
- workingDirectory: "workers/cdn"
- command: "publish"
- - name: "🤠 Deploy > r2-store"
- uses: cloudflare/wrangler-action@da0e0dfe58b7a431659754fdf3f186c529afbe65 # v3.14.1
- with:
- apiToken: ${{ secrets.CF_API_TOKEN }}
- workingDirectory: "workers/r2-store"
- command: "publish"
diff --git a/.gitignore b/.gitignore
index c8968ab..030f2a7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -150,3 +150,7 @@ worker/
node_modules/
.pnpm-store/
.cargo-ok
+AGENTS.md
+CLAUDE.md
+GEMINI.md
+.github/copilot-instructions.md
diff --git a/.trunk/trunk.yaml b/.trunk/trunk.yaml
index edc1935..7cdfae6 100644
--- a/.trunk/trunk.yaml
+++ b/.trunk/trunk.yaml
@@ -1,17 +1,28 @@
version: 0.1
cli:
- version: 1.22.11
+ version: 1.25.0
plugins:
sources:
- id: trunk
- ref: v1.6.7
+ ref: v1.10.0
uri: https://github.com/trunk-io/plugins
repo:
repo:
host: github.com
owner: z-shell
- name: zi-src
+ name: src
lint:
+ definitions:
+ - name: shfmt
+ commands:
+ - name: format
+ output: shfmt
+ run: shfmt -w -s -ln=bash -i 2 ${target}
+ success_codes: [0, 1]
+ cache_results: true
+ formatter: true
+ batch: true
+ in_place: true
disabled:
- yamllint
- trufflehog
@@ -20,13 +31,13 @@ lint:
- trivy
enabled:
- git-diff-check@SYSTEM
- - actionlint@1.7.7
- - gitleaks@8.24.0
- - markdownlint@0.44.0
- - prettier@3.5.3
- - shellcheck@0.10.0
+ - actionlint@1.7.12
+ - gitleaks@8.30.1
+ - markdownlint@0.48.0
+ - prettier@3.8.3
+ - shellcheck@0.11.0
- shfmt@3.6.0
- - taplo@0.9.3
+ - taplo@0.10.0
actions:
enabled:
- trunk-announce
@@ -36,6 +47,6 @@ actions:
- trunk-cache-prune
runtimes:
enabled:
- - python@3.10.8
+ - python@3.14.4
- go@1.21.0
- - node@18.20.5
+ - node@22.16.0
diff --git a/.vscode/settings.json b/.vscode/settings.json
deleted file mode 100644
index c17dc3e..0000000
--- a/.vscode/settings.json
+++ /dev/null
@@ -1,17 +0,0 @@
-{
- "[markdown]": {
- "editor.formatOnSave": true,
- "editor.formatOnPaste": true
- },
- "editor.codeActionsOnSave": {
- "source.fixAll.markdownlint": true
- },
- "conventionalCommits.scopes": [
- "maintenance",
- "workspace",
- "community",
- "general",
- "misc"
- ],
- "shellformat.useEditorConfig": true
-}
diff --git a/docs/README.md b/docs/README.md
index cd6b128..7b2fa8c 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -21,7 +21,7 @@
-
+
@@ -31,13 +31,40 @@
- https://wiki.zshell.dev
- Loader:
- https://init.zshell.dev
- - https://git.io/zi-loader
- Installer:
- https://get.zshell.dev
- - https://git.io/get-zi
- R2:
- https://r2.zshell.dev
- IPFS:
- https://ipfs.zshell.dev
- jsDeliver:
- - https://cdn.jsdelivr.net/gh/z-shell/zi-src@main/
+ - https://cdn.jsdelivr.net/gh/z-shell/src@main/
+
+### Maintainer — Verify and Sync Loader
+
+Check whether the local `lib/zsh/init.zsh` matches the canonical GitHub raw `main` copy:
+
+```sh
+sh lib/sh/sync-init.sh
+```
+
+Replace the local file if it drifts:
+
+```sh
+sh lib/sh/sync-init.sh --write
+```
+
+Run against local fixtures (no network required, useful in tests):
+
+```sh
+sh lib/sh/sync-init.sh \
+ --local /tmp/my-init.zsh \
+ --remote /tmp/remote-init.zsh \
+ --checksum-url /tmp/checksum.txt
+```
+
+Skip checksum validation:
+
+```sh
+sh lib/sh/sync-init.sh --no-checksum
+```
diff --git a/lib/checksum.txt b/lib/checksum.txt
index 7335fdd..de6d2fc 100644
--- a/lib/checksum.txt
+++ b/lib/checksum.txt
@@ -1,3 +1,4 @@
-13668e0d6edce92994b0434932873d8a8a2c2579a2cb53964de19212f02a2954 lib/sh/install_zpmod.sh
-6214cad3026f4150dfe1070592f6b24256cae5c60c596bdfebda07ffeaaf39fc lib/sh/install.sh
-23a563e80249a866c7cba3ac44eaedb1ca38f20c35690fb764a7cb75e95d38be lib/zsh/init.zsh
+5d8fdd881b84548f38c1d5ffc6d5f46a18919d8dd6c1398d69abe055dd76ef98 lib/sh/install_zpmod.sh
+9d643c40be25f71d93dd295dacc58944defe7218f961f3f3ecb4ee9c0a56d486 lib/sh/install.sh
+83d9f0dc013376b5aa03b7a11f3908058da176df3407ac6e2872857d7c9658fd lib/sh/sync-init.sh
+66dc5bb0575d44e4e1192e229204abe1ac47979a05ee3c3bb23afc2b9b493637 lib/zsh/init.zsh
diff --git a/lib/index.html b/lib/index.html
new file mode 100644
index 0000000..be05ff4
--- /dev/null
+++ b/lib/index.html
@@ -0,0 +1,335 @@
+
+
+
+
+
+ Z-Shell / src — CDN Assets
+
+
+
+
+
+
+
+
+
+
+ ❯
+ ls -1 assets/
+
+
+ -
+
+ Zi Loader
+ src.zshell.dev/zsh/init.zsh
+
+
+
+ -
+
+ Zi Installer
+ get.zshell.dev
+
+
+
+ -
+
+ Zi Wiki
+ wiki.zshell.dev
+
+
+
+
+
+ ❯ _
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/lib/sh/i.sh b/lib/sh/i.sh
deleted file mode 100755
index 285a610..0000000
--- a/lib/sh/i.sh
+++ /dev/null
@@ -1,547 +0,0 @@
-#!/usr/bin/env sh
-# -*- mode: sh; sh-indentation: 2; indent-tabs-mode: nil; sh-basic-offset: 2; -*-
-# vim: ft=sh sw=2 ts=2 et
-trap 'rm -rf "$WORKDIR"' EXIT INT
-WORKDIR="$(mktemp -d)"
-
-col_pname="[33m"
-col_error="[31m"
-col_info="[32m"
-col_rst="[0m"
-zsh_current=$(zsh --version &2 <&2
- exit 1
-}
-
-_os_type() {
- OS="$(command -v uname)"
- case $("${OS}" | tr '[:upper:]' '[:lower:]') in
- android*)
- OS='android'
- ;;
- darwin*)
- OS='darwin'
- ;;
- linux*)
- OS='linux'
- ;;
- freebsd*)
- OS='freebsd'
- ;;
- netbsd*)
- OS='netbsd'
- ;;
- openbsd*)
- OS='openbsd'
- ;;
- sunos*)
- OS='solaris'
- ;;
- msys* | cygwin* | mingw*)
- OS='windows'
- ;;
- nt | win*)
- OS='windows'
- ;;
- *)
- echo 'OS not supported'
- ;;
- esac
-}
-
-_cpu_type() {
- case "$(uname -m)" in
- x86_64 | x86-64 | x64 | amd64)
- ARCH='amd64'
- ;;
- i?86 | x86)
- ARCH='386'
- ;;
- armv8* | aarch64 | arm64)
- ARCH='arm64'
- ;;
- armv7*)
- ARCH='armv7'
- ;;
- armv6*)
- ARCH='armv6'
- ;;
- arm*)
- ARCH='arm'
- ;;
- mips64le*)
- ARCH='mips64le'
- ;;
- mips64*)
- ARCH='mips64'
- ;;
- mipsle*)
- ARCH='mipsle'
- ;;
- mips*)
- ARCH='mips'
- ;;
- ppc64le*)
- ARCH='ppc64le'
- ;;
- ppc64*)
- ARCH='ppc64'
- ;;
- ppcle*)
- ARCH='ppcle'
- ;;
- ppc*)
- ARCH='ppc'
- ;;
- s390*)
- ARCH='s390x'
- ;;
- *)
- echo 'OS architecture not supported'
- ;;
- esac
-}
-
-# Synopsis:
-#
-# annex: (available: recommended)
-# -a recommended
-# branch: (available: main or any other existing branch)
-# -b main
-# configuration directory: (default: ~/.config/zi, can be overridden to prefered location)
-# -c ${HOME}/.config/zi
-# ZI home directory (default: ~/.zi, can be overriden prefered location)
-# -d ${HOME}/.zi
-# zshrc header: (available loader, installer)
-# -e loader
-# clone options: (default: --progress, can be overridden to prefered git clone options)
-# -o --progress
-# make/build options: (build, make additonal. Available: zpmod)
-# -m zpmod
-# host to use: (default: host to use. Available: github.com, gitlab.com)
-# -h github.com
-# install profile: (profile to run: install, uninstall)
-# -p install
-# snippets: (group of snippets to install: not-available yet)
-# -s not-available yet
-# plugins: group of plugins to install: not-available yet)
-# -s not-available yet
-#
-# Example: ./install.sh -p install -c ~/.config/zi -d ~/.zi -e loader -o --progress -m zpmod -h github.com
-
-while getopts ":a:b:c:d:e:o:m:h:p:z:s:" opt; do
- case ${opt} in
- a)
- ANNEX="${ANNEX}${OPTARG}"
- ;;
- b)
- BRANCH="${BRANCH}${OPTARG}"
- ;;
- c)
- CONFIG_DIR="${CONFIG_DIR}${OPTARG}"
- ;;
- d)
- ZI_HOME="${ZI_HOME}${OPTARG}"
- ;;
- e)
- ZHEADER="${ZHEADER}${OPTARG}"
- ;;
- o)
- CLONE_OPTS="${CLONE_OPTS}${OPTARG}"
- ;;
- m)
- MAKE="${MAKE}${OPTARG}"
- ;;
- h)
- HOST="${HOST}${OPTARG}"
- ;;
- p)
- PROFILE="${PROFILE}${OPTARG}"
- ;;
- z)
- OMZ="${OMZ}${OPTARG}"
- ;;
- s)
- STD="${STD}${OPTARG}"
- ;;
- \?)
- err "Invalid option: ${OPTARG}" 1>&2
- ;;
- :)
- err "Invalid option: ${OPTARG} requires an argument" 1>&2
- ;;
- *)
- err "Invalid option: ${OPTARG}" 1>&2
-
- ;;
- esac
-done
-shift $((OPTIND - 1))
-
-if [ -z "${PROFILE}" ]; then
- # Available profiles: install, uninstall, main.
- PROFILE="main"
-fi
-if [ -z "${HOST}" ]; then
- # Default host
- HOST="github.com"
-fi
-if [ -z "${BRANCH}" ]; then
- # Default branch
- BRANCH="main"
-fi
-if [ -z "${CLONE_OPTS}" ]; then
- # Default git clone options
- CLONE_OPTS="--progress"
-fi
-if [ -z "${ZI_HOME}" ]; then
- # Installiation time ZI home directory
- ZI_HOME="${ZDOTDIR:-${HOME}}/.zi"
-fi
-if [ -z "${ZI_BIN_DIR_NAME}" ]; then
- # ZI bin directory
- ZI_BIN_DIR_NAME="bin"
-fi
-if [ -z "${CONFIG_DIR}" ]; then
- # Default configuration directory
- CONFIG_DIR="${XDG_CONFIG_HOME:-${HOME}/.config}/zi"
-fi
-if [ -z "${ZHEADER}" ]; then
- # Default header script
- ZHEADER="loader"
-fi
-
-_set_externals() {
- if ! command -v git >/dev/null 2>&1; then
- err "[1;31m▓▒░[0m Something went wrong: [1;32mgit[0m not available, cannot proceed."
- fi
- if [ "${HOST}" = github.com ]; then
- if curl -fsL https://raw.githubusercontent.com/z-shell/zi-src/main/lib/zsh/init.zsh >/dev/null; then
- RAW_LOADER_URL="https://raw.githubusercontent.com/z-shell/zi-src/main/lib/zsh/init.zsh"
- if curl -fsL https://raw.githubusercontent.com/z-shell/zi/main/lib/zsh/git-process-output.zsh >/dev/null; then
- RAW_GIT_OUPUT="https://raw.githubusercontent.com/z-shell/zi/main/lib/zsh/git-process-output.zsh"
- else
- err "Git process script at GitHub is not reachable"
- fi
- else
- err "GitLab and GitHub repositories are unreachable"
- fi
- elif [ "${HOST}" = gitlab.com ]; then
- if curl -fsL https://gitlab.com/ss-o/zi-src/-/raw/main/lib/zsh/init.zsh >/dev/null; then
- RAW_LOADER_URL="https://gitlab.com/ss-o/zi-src/-/raw/main/lib/zsh/init.zsh"
- if curl -fsL https://gitlab.com/ss-o/zi/-/raw/main/lib/zsh/git-process-output.zsh >/dev/null; then
- RAW_GIT_OUPUT="https://gitlab.com/ss-o/zi/-/raw/main/lib/zsh/git-process-output.zsh"
- else
- err "Git process script at GitLab is not reachable"
- fi
- fi
- fi
- # Get the download-progress bar tool
- if command -v curl >/dev/null 2>&1; then
- command mkdir -p "${WORKDIR}"
- cd "${WORKDIR}" || return
- command curl -fsSLO "${RAW_GIT_OUPUT}" && command chmod a+x "${WORKDIR}"/git-process-output.zsh
- elif command -v wget >/dev/null 2>&1; then
- command mkdir -p "${WORKDIR}"
- cd "${WORKDIR}" || return
- command wget -q "${RAW_GIT_OUPUT}" && command chmod a+x "${WORKDIR}"/git-process-output.zsh
- else
- say "[1;31m▓▒░[0m Something went wrong:"
- err "[1;32mcurl[0m or [1;32mwget[0m not available or failed to create temp directory, cannot proceed."
- fi
-}
-
-_check_zshrc() {
- THE_ZDOTDIR="${ZDOTDIR:-${HOME}}"
- say "[34m▓▒░[0m[1;36m Updating ${THE_ZDOTDIR}/.zshrc"
- if grep -E '(zi|init|zinit)\.zsh' "${THE_ZDOTDIR}/.zshrc" >/dev/null 2>&1; then
- say "[34m▓▒░[34m File .zshrc have conflicting commands, backuping..."
- say "[34m▓▒░[34m creating backup at ${CONFIG_DIR}/zshrc..."
- date=$(date +%H%M%S) && command mv -f "${THE_ZDOTDIR}/.zshrc" "${CONFIG_DIR}/zshrc.${date}"
- fi
-}
-
-_set_zshrc_header() {
- # current zshrc headers: (loader, installer)
- _check_zshrc
- if [ "${ZHEADER}" = loader ]; then
- if command -v curl >/dev/null 2>&1; then
- command curl -fsSL "${RAW_LOADER_URL}" -o "${CONFIG_DIR}/init.zsh"
- elif command -v wget >/dev/null 2>&1; then
- command wget -qO "${CONFIG_DIR}/init.zsh" "${RAW_LOADER_URL}"
- else
- err "[1;31m▓▒░[0m Something went wrong:[1;32m curl and wget[0m not available, cannot proceed."
- fi
- command chmod a+x "${CONFIG_DIR}/init.zsh"
- command sed -i "s/branch=\"main\"/branch=\"${BRANCH}\"/g" "${CONFIG_DIR}/init.zsh"
- command cat <<-EOF >>"${THE_ZDOTDIR}/.zshrc"
-if [[ -r "${XDG_CONFIG_HOME:-${HOME}/.config}/zi/init.zsh" ]]; then
- source "${XDG_CONFIG_HOME:-${HOME}/.config}/zi/init.zsh" && zzinit
-fi
-EOF
- say "[34m▓▒░[0m[1;36m Loader added successfully.[0m"
- elif [ "${ZHEADER}" = installer ]; then
- command cat <<-EOF >>"${THE_ZDOTDIR}/.zshrc"
-if [[ ! -f ${ZI_HOME}/${ZI_BIN_DIR_NAME}/zi.zsh ]]; then
- print -P "%F{33}▓▒░ %F{160}Installing (%F{33}z-shell/zi%F{160})…%f"
- command mkdir -p "${ZI_HOME}" && command chmod g-rwX "${ZI_HOME}"
- command git clone ${CLONE_OPTS} --branch "${BRANCH}" https://${HOST}/z-shell/zi "${ZI_HOME}/${ZI_BIN_DIR_NAME}" && \\
- print -P "%F{33}▓▒░ %F{34}Installation successful.%f%b" || \\
- print -P "%F{160}▓▒░ The clone has failed.%f%b"
-fi
-source "${ZI_HOME}/${ZI_BIN_DIR_NAME}/zi.zsh"
-autoload -Uz _zi
-(( \${+_comps} )) && _comps[zi]=_zi
-EOF
- else
- true
- fi
-}
-
-_setup_directories() {
- _set_externals "$@"
- if ! test -d "${ZI_HOME}"; then
- command mkdir "${ZI_HOME}"
- command chmod g-w "${ZI_HOME}"
- command chmod o-w "${ZI_HOME}"
- fi
- if ! test -d "${ZI_HOME}/${ZI_BIN_DIR_NAME}"; then
- command mkdir "${ZI_HOME}/${ZI_BIN_DIR_NAME}"
- command chmod g-w "${ZI_HOME}/${ZI_BIN_DIR_NAME}"
- command chmod o-w "${ZI_HOME}/${ZI_BIN_DIR_NAME}"
- fi
- if ! test -d "${CONFIG_DIR}"; then
- command mkdir "${CONFIG_DIR}"
- command chmod g-w "${CONFIG_DIR}"
- command chmod o-w "${CONFIG_DIR}"
- fi
- _set_zshrc_header "$@"
-}
-
-_setup_profile() {
- _setup_directories "$@"
- if [ "${PROFILE}" = install ]; then
- if test -d "${ZI_HOME}/${ZI_BIN_DIR_NAME}/.git"; then
- cd "${ZI_HOME}/${ZI_BIN_DIR_NAME}" || return
- say "[34m▓▒░[0m Updating [1;36m(z-shell/zi)[1;33m plugin manager[0m at [1;35m${ZI_HOME}/${ZI_BIN_DIR_NAME}[0m"
- command git clean -d -f -f
- command git reset --hard HEAD
- command git pull -q origin HEAD
- command git submodule update --init --recursive
- command git submodule update --recursive --remote
- say "[34m▓▒░[0m [1;32mSuccessfully installed [1;36m(z-shell/zi)[0m at [1;35m${ZI_HOME}/${ZI_BIN_DIR_NAME}[0m"
- else
- cd "${ZI_HOME}" || return
- say "[34m▓▒░[0m Installing [1;36m(z-shell/zi)[1;33m plugin manager[0m at [1;35m${ZI_HOME}/${ZI_BIN_DIR_NAME}[0m"
- { git clone "${CLONE_OPTS}" --branch "${BRANCH}" https://"${HOST}"/z-shell/zi.git "${ZI_BIN_DIR_NAME}" \
- 2>&1 | { "${WORKDIR}/out/git-process-output.zsh" || cat; }; } 2>/dev/null
- if [ -d "${ZI_BIN_DIR_NAME}" ]; then
- say "[34m▓▒░[0m Successfully installed at [1;32m${ZI_HOME}/${ZI_BIN_DIR_NAME}[0m".
- else
- err "[1;31m▓▒░[0m Something went wrong, couldn't install ZI at [1;33m${ZI_HOME}/${ZI_BIN_DIR_NAME}[0m"
- fi
- fi
- elif [ "${PROFILE}" = uninstall ]; then
- clear
- say "▓▒░ Remove ❮ ZI ❯? [y/N]"
- read -r confirmation
- if [ "${confirmation}" != y ] && [ "${confirmation}" != Y ]; then
- echo "Uninstall process cancelled"
- exit 0
- fi
- clear
- say "Removing ❮ ZI ❯ home directory"
- sleep 2
- if [ -d "${HOME}/.zi" ]; then
- rm -rvf "${HOME}/.zi"
- elif [ -d "${ZDOTDIR}/.zi" ]; then
- rm -rvf "${ZDOTDIR}/.zi"
- elif [ -d "${XDG_DATA_HOME}/.zi" ]; then
- rm -rvf "${XDG_DATA_HOME}/.zi"
- fi
- clear
- echo "▓▒░ Clean ❮ ZI ❯ cache? [y/N]"
- read -r confirmation
- if [ "${confirmation}" != y ] && [ "${confirmation}" != Y ]; then
- echo "Cleaning ❮ ZI ❯ cache"
- sleep 2
- if [ -d "${HOME}/.cache/zi" ]; then
- rm -rvf "${HOME}/.cache/zi"
- elif [ -d "${ZDOTDIR}/.cache/zi" ]; then
- rm -rvf "${ZDOTDIR}/.cache/zi"
- elif [ -d "${XDG_DATA_HOME}/.cache/zi" ]; then
- rm -rvf "${XDG_DATA_HOME}/.cache/zi"
- fi
- fi
- clear
- echo "▓▒░ Remove ❮ ZI ❯ config directory? [y/N]"
- read -r confirmation
- if [ "${confirmation}" != y ] && [ "${confirmation}" != Y ]; then
- echo "Removing ❮ ZI ❯ config directory"
- sleep 2
- if [ -d "${XDG_CONFIG_HOME}/zi" ]; then
- rm -rvf "${XDG_CONFIG_HOME}/zi"
- else
- if [ -d "${HOME}/.config/zi" ]; then
- rm -rvf "${HOME}/.config/zi"
- elif [ -d "${XDG_DATA_HOME}/zi" ]; then
- rm -rvf "${XDG_DATA_HOME}/zi"
- fi
- fi
- fi
- clear
- echo "▓▒░ Reload shell? [y/N]"
- read -r confirmation
- if [ "${confirmation}" != y ] && [ "${confirmation}" != Y ]; then
- say "[34m▓▒░[0m Uninstall successful"
- command cat <<-EOF
-[34m▓▒░[0m[1;36m ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ ❮ ZI ❯[0m
-[34m▓▒░[0m[38;5;226m Wiki: https://wiki.zshell.dev[0m
-[34m▓▒░[0m[38;5;226m Issues: https://github.com/z-shell/zi/issues[0m
-[34m▓▒░[0m[38;5;226m Discussions: https://discussions.zshell.dev[0m"
-EOF
- rm -rf "${WORKDIR}"
- exit 0
- else
- rm -rf "${WORKDIR}"
- exec "${SHELL}" -l
- fi
- elif [ "${PROFILE}" = main ]; then
- true
- else
- err "Invalid profile: ${PROFILE}" 1>&2
- fi
-}
-
-_make_build() {
- if [ "${MAKE}" = zpmod ]; then
- ZI_HOME="${ZI_HOME:-${ZDOTDIR:-${HOME}}/.zi}"
- MOD_HOME="${MOD_HOME:-zmodules}/zpmod"
- if ! test -d "${ZI_HOME}/${MOD_HOME}"; then
- mkdir -p "${ZI_HOME}/${MOD_HOME}"
- chmod g-rwX "${ZI_HOME}/${MOD_HOME}"
- fi
- say "${col_pname}== Downloading ZPMOD module to ${ZI_HOME}/${MOD_HOME}"
- if test -d "${ZI_HOME}/${MOD_HOME}/.git"; then
- cd "${ZI_HOME}/${MOD_HOME}" || return
- git pull -q origin main
- else
- cd "${ZI_HOME}" || return
- git clone "${CLONE_OPTS}" https://"${HOST}"/z-shell/zpmod.git "${MOD_HOME}"
- fi
- say "${col_pname}== Done"
- if command -v zsh >/dev/null; then
- say "${col_info}-- Checkig version --${col_rst}"
- if expr "${zsh_current}" \< "${zsh_required}" >/dev/null; then
- say "${col_error}-- Zsh version 5.8.1 and above required --${col_rst}"
-
- else
- say "${col_info}-- Zsh version ${zsh_current} --${col_rst}"
- cd "${ZI_HOME}/${MOD_HOME}" || return
- say "${col_pname}== Building module ZPMOD, running: a make clean, then ./configure and then make ==${col_rst}"
- say "${col_pname}== The module sources are located at: ${ZI_HOME}/${MOD_HOME} ==${col_rst}"
- if test -f Makefile; then
- if [ "$1" = "--clean" ]; then
- say "${col_info}-- make distclean --${col_rst}"
- make -s distclean
- true
- else
- say "${col_info}-- make clean (pass --clean to invoke \`make distclean') --${col_rst}"
- make -s clean
- fi
- fi
- say "${col_info}-- Configuring --${col_rst}"
- if CPPFLAGS=-I/usr/local/include CFLAGS="-g -Wall -O3" LDFLAGS=-L/usr/local/lib ./configure --disable-gdbm --without-tcsetpgrp; then
- say "${col_info}-- Running make --${col_rst}"
- if make -s; then
- command cat <<-EOF
-[38;5;219m▓▒░[0m [38;5;220mModule [38;5;177mhas been built correctly.
-[38;5;219m▓▒░[0m [38;5;220mSee 'zpmod -h' for more information.
-[38;5;219m▓▒░[0m [38;5;220mRun 'zpmod source-study' to see profile data,
-[38;5;219m▓▒░[0m [38;5;177mGuaranteed, automatic compilation of any sourced script.
-EOF
- zpmod_file="${WORKDIR}/zpmod"
- command cat <<-EOF >>"${zpmod_file}"
-module_path+=( "${ZI_HOME}/${MOD_HOME}/Src" )
-zmodload zi/zpmod
-EOF
- say "[34m▓▒░[0m[1;36m Enabling zpmod[0m"
- command cat "${zpmod_file}" >>"${THE_ZDOTDIR}/.zshrc"
- zsh -ic "@zi-scheduler burst"
- else
- say "${col_error}Module did not build.${col_rst}. You can copy the error messages and submit"
- err "error-report at: https://${HOST}/z-shell/zpmod/issues"
- fi
- fi
- fi
- else
- err "${col_error} Zsh is not installed. Please install zsh and try again.${col_rst}"
- fi
- else
- true
- fi
-}
-
-_set_annexes() {
- if [ "${ANNEX}" = recommended ]; then
- file="${WORKDIR}/annex_recommended"
- command cat <<-EOF >>"${file}"
-zi light-mode for \\
- z-shell/z-a-meta-plugins \\
- @annexes # <- https://wiki.zshell.dev/ecosystem/category/-annexes
-# examples here -> https://wiki.zshell.dev/community/gallery/collection
-zicompinit # <- https://wiki.zshell.dev/docs/guides/commands
-EOF
- say "[34m▓▒░[0m[1;36m Installing annexes[0m"
- command cat "${file}" >>"${THE_ZDOTDIR}/.zshrc"
- zsh -ic "@zi-scheduler burst"
- else
- true
- fi
-}
-
-_finish_install() {
- git_refs="$(
- command cd "${ZI_HOME}/${ZI_BIN_DIR_NAME}" || true
- command git log --color --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit | head -3
- )"
- say "[34m▓▒░[0m[38;5;226m Latest changes:[0m"
- say "${git_refs}"
-}
-
-_system() {
- _os_type
- _cpu_type
- _setup_profile "$@"
- _make_build "$@"
- _set_annexes "$@"
- _finish_install "$@"
-}
-
-MAIN() {
- _system "$@"
- say "[34m▓▒░[0m[1;36m System: ${OS} - ${ARCH}"
- command cat <<-EOF
-[34m▓▒░[0m[1;36m ■■■■■■■■■■■■■■■■■ Successfully installed ❮ ZI ❯ ■■■■■■■■■[0m
-[34m▓▒░[0m[38;5;226m Wiki: https://wiki.zshell.dev[0m
-[34m▓▒░[0m[38;5;226m Issues: https://github.com/z-shell/zi/issues[0m
-[34m▓▒░[0m[38;5;226m Discussions: https://discussions.zshell.dev[0m
-[34m▓▒░[0m[1;36m ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■[0m
-EOF
- exit 0
-}
-
-while true; do
- MAIN "${@}"
-done
diff --git a/lib/sh/install.sh b/lib/sh/install.sh
index 0f7bbbc..03522ac 100755
--- a/lib/sh/install.sh
+++ b/lib/sh/install.sh
@@ -2,8 +2,10 @@
# -*- mode: sh; sh-indentation: 2; indent-tabs-mode: nil; sh-basic-offset: 2; -*-
# vim: ft=sh sw=2 ts=2 et
-trap 'rm -rf "$WORKDIR"' EXIT INT
-WORKDIR="$(mktemp -d)"
+set -eu
+
+WORKDIR="$(mktemp -d)" || exit 1
+trap 'rm -rf "${WORKDIR:?}"' EXIT INT TERM
ZOPT=""
AOPT=""
BOPT="main"
@@ -43,21 +45,21 @@ if [ "${AOPT}" = loader ]; then
command wget -qO "${ZI_CONFIG_DIR}/init.zsh" https://raw.githubusercontent.com/z-shell/zi-src/main/lib/zsh/init.zsh
fi
command chmod go-w "${ZI_CONFIG_DIR}" && command chmod a+x "${ZI_CONFIG_DIR}/init.zsh"
- command sed -i "s/branch=\"main\"/branch=\"${BOPT}\"/g" "${ZI_CONFIG_DIR}/init.zsh"
+ # shellcheck disable=SC2016
+ command sed -i 's|: ${ZI\[STREAM\]:="main"}|: ${ZI[STREAM]:="'"${BOPT}"'"}|' "${ZI_CONFIG_DIR}/init.zsh"
fi
-if [ -z "${ZI_HOME}" ]; then
- ZI_HOME="${ZDOTDIR:-${HOME}}/.zi"
+if [ -z "${ZI_HOME-}" ]; then
+ ZI_HOME="${XDG_DATA_HOME:-${HOME}/.local/share}/zi"
fi
-if [ -z "${ZI_BIN_DIR_NAME}" ]; then
+if [ -z "${ZI_BIN_DIR_NAME-}" ]; then
ZI_BIN_DIR_NAME="bin"
fi
if ! test -d "${ZI_HOME}"; then
command mkdir "${ZI_HOME}"
command chmod go-w "${ZI_HOME}"
- command chmod go-w "${ZI_HOME}/${ZI_BIN_DIR_NAME}"
fi
if ! command -v git >/dev/null 2>&1; then
@@ -68,31 +70,32 @@ fi
# Get the download-progress bar tool
if command -v curl >/dev/null 2>&1; then
command mkdir -p /tmp/zi
- cd /tmp/zi || return
+ cd /tmp/zi || exit 1
command curl -fsSLO https://raw.githubusercontent.com/z-shell/zi/main/lib/zsh/git-process-output.zsh &&
command chmod a+x /tmp/zi/git-process-output.zsh
elif command -v wget >/dev/null 2>&1; then
command mkdir -p /tmp/zi
- cd /tmp/zi || return
+ cd /tmp/zi || exit 1
command wget -q https://raw.githubusercontent.com/z-shell/zi/main/lib/zsh/git-process-output.zsh &&
command chmod a+x /tmp/zi/git-process-output.zsh
fi
if test -d "${ZI_HOME}/${ZI_BIN_DIR_NAME}/.git"; then
- cd "${ZI_HOME}/${ZI_BIN_DIR_NAME}" || return
+ cd "${ZI_HOME}/${ZI_BIN_DIR_NAME}" || exit 1
printf '%s\n' "[1;34m▓▒░[0m Updating [1;36m(z-shell/zi)[1;33m plugin manager[0m at [1;35m${ZI_HOME}/${ZI_BIN_DIR_NAME}[0m"
command git clean -d -f -f
command git reset --hard HEAD
- command git pull -q origin HEAD
+ command git pull -q origin "${BOPT}"
else
- cd "${ZI_HOME}" || return
+ cd "${ZI_HOME}" || exit 1
printf '%s\n' "[1;34m▓▒░[0m Installing [1;36m(z-shell/zi)[1;33m plugin manager[0m at [1;35m${ZI_HOME}/${ZI_BIN_DIR_NAME}[0m"
{ git clone --progress --depth=1 --branch "${BOPT}" https://github.com/z-shell/zi.git "${ZI_BIN_DIR_NAME}" \
2>&1 | { /tmp/zi/git-process-output.zsh || cat; }; } 2>/dev/null
- if [ -d "${ZI_BIN_DIR_NAME}" ]; then
+ if [ -d "${ZI_HOME}/${ZI_BIN_DIR_NAME}" ] && [ -f "${ZI_HOME}/${ZI_BIN_DIR_NAME}/zi.zsh" ]; then
printf '%s\n' "[1;34m▓▒░[0m Successfully installed at [1;32m${ZI_HOME}/${ZI_BIN_DIR_NAME}[0m".
else
printf '%s\n' "[1;31m▓▒░[0m Something went wrong, couldn't install ZI at [1;33m${ZI_HOME}/${ZI_BIN_DIR_NAME}[0m"
+ exit 1
fi
fi
@@ -106,7 +109,7 @@ MAIN_PROFILE() {
printf '%s\n' "[34m▓▒░[34m Seems that .zshrc already has content or setup skipped - no changes will be made."
ZOPT='skip'
fi
- if [ "${ZOPT}" != skip ]; then
+ if [ "${ZOPT}" != skip ] && [ "${AOPT}" != loader ]; then
printf '%s\n' "[34m▓▒░[0m Updating ${THE_ZDOTDIR}/.zshrc"
ZI_HOME="$(echo "${ZI_HOME}" | sed "s|${HOME}|\$HOME|")"
command cat <<-EOF >>"${THE_ZDOTDIR}/.zshrc"
@@ -126,10 +129,9 @@ EOF
printf '%s\n' "[34m▓▒░[0m[1;36m Minimal configuration[0m"
fi
if [ "${AOPT}" = loader ] && [ "${ZOPT}" != skip ]; then
- command rm -rf "${THE_ZDOTDIR}/.zshrc"
command cat <<-EOF >>"${THE_ZDOTDIR}/.zshrc"
-if [[ -r "${XDG_CONFIG_HOME:-${HOME}/.config}/zi/init.zsh" ]]; then
- source "${XDG_CONFIG_HOME:-${HOME}/.config}/zi/init.zsh" && zzinit
+if [[ -r "\${XDG_CONFIG_HOME:-\${HOME}/.config}/zi/init.zsh" ]]; then
+ source "\${XDG_CONFIG_HOME:-\${HOME}/.config}/zi/init.zsh" && zzinit
fi
EOF
printf '%s\n' "[34m▓▒░[0m[1;36m Loader added[0m"
@@ -164,86 +166,38 @@ EOF
fi
}
-SETUP_ZPMOD() {
- if ! test -d "${ZI_HOME}/${MOD_HOME}"; then
- command mkdir -p "${ZI_HOME}/${MOD_HOME}"
- command chmod go-w "${ZI_HOME}/${MOD_HOME}"
- fi
+ZPMOD_PROFILE() {
+ _zpmod_sh=""
+ case "$0" in
+ */*)
+ _script_dir="$(cd "$(dirname "$0")" 2>/dev/null && pwd)" || _script_dir=""
+ ;;
+ *)
+ _script_dir=""
+ ;;
+ esac
- printf '%s\n' "${col_pname}== Downloading ZPMOD module to ${ZI_HOME}/${MOD_HOME}"
- if test -d "${ZI_HOME}/${MOD_HOME}/.git"; then
- cd "${ZI_HOME}/${MOD_HOME}" || return
- git pull -q origin main
+ if [ -n "${_script_dir}" ] && [ -f "${_script_dir}/install_zpmod.sh" ]; then
+ _zpmod_sh="${_script_dir}/install_zpmod.sh"
else
- cd "${ZI_HOME}" || return
- git clone -q https://github.com/z-shell/zpmod.git "${MOD_HOME}"
- fi
- printf '%s\n' "${col_pname}== Done"
-}
-
-BUILD_ZPMOD() {
- if command -v zsh >/dev/null; then
- printf '%s\n' "${col_info2}-- Checkig version --${col_rst}"
- ZSH_CURRENT=$(zsh --version /dev/null; then
- printf '%s\n' "${col_error}-- Zsh version 5.8.1 and above required --${col_rst}"
- exit 1
+ _zpmod_sh="${WORKDIR}/install_zpmod.sh"
+ _zpmod_url="https://raw.githubusercontent.com/z-shell/zi-src/main/lib/sh/install_zpmod.sh"
+ if command -v curl >/dev/null 2>&1; then
+ command curl -fsSL "${_zpmod_url}" -o "${_zpmod_sh}"
+ elif command -v wget >/dev/null 2>&1; then
+ command wget -qO "${_zpmod_sh}" "${_zpmod_url}"
else
- printf '%s\n' "${col_info2}-- Zsh version ${ZSH_CURRENT} --${col_rst}"
- cd "${ZI_HOME}/${MOD_HOME}" || return
- printf '%s\n' "${col_pname}== Building module ZPMOD, running: a make clean, then ./configure and then make ==${col_rst}"
- printf '%s\n' "${col_pname}== The module sources are located at: ${ZI_HOME}/${MOD_HOME} ==${col_rst}"
- if test -f Makefile; then
- if [ "$1" = "--clean" ]; then
- printf '%s\n' "${col_info2}-- make distclean --${col_rst}"
- make -s distclean
- true
- else
- printf '%s\n' "${col_info2}-- make clean (pass --clean to invoke \`make distclean') --${col_rst}"
- make -s clean
- fi
- fi
- printf '%s\n' "${col_info2}-- Configuring --${col_rst}"
- if CPPFLAGS=-I/usr/local/include CFLAGS="-g -Wall -O3" LDFLAGS=-L/usr/local/lib ./configure --disable-gdbm --without-tcsetpgrp; then
- printf '%s\n' "${col_info2}-- Running make --${col_rst}"
- if make -s; then
- command cat <<-EOF
-[38;5;219m▓▒░[0m [38;5;220mModule [38;5;177mhas been built correctly.
-[38;5;219m▓▒░[0m [38;5;220mTo [38;5;160mload the module, add following [38;5;220m2 lines to [38;5;172m.zshrc, at top:
-[0m [38;5;51m module_path+=( "${ZI_HOME}/${MOD_HOME}/Src" )
-[0m [38;5;51m zmodload zi/zpmod
-[38;5;219m▓▒░[0m [38;5;220mSee 'zpmod -h' for more information.
-[38;5;219m▓▒░[0m [38;5;220mRun 'zpmod source-study' to see profile data,
-[38;5;219m▓▒░[0m [38;5;177mGuaranteed, automatic compilation of any sourced script.
-EOF
- else
- printf '%s\n' "${col_error}Module didn't build.${col_rst}. You can copy the error messages and submit"
- printf '%s\n' "error-report at: https://github.com/z-shell/zpmod/issues"
- fi
- fi
+ printf '%s\n' "-- ERROR -- curl or wget is required to download install_zpmod.sh" >&2
+ exit 1
fi
- else
- printf '%s\n' "${col_error} Zsh is not installed. Please install zsh and try again.${col_rst}"
+ if [ ! -s "${_zpmod_sh}" ]; then
+ printf '%s\n' "-- ERROR -- failed to download install_zpmod.sh" >&2
+ exit 1
+ fi
+ command chmod a+x "${_zpmod_sh}"
fi
-}
-ZPMOD_PROFILE() {
- col_pname="[33m"
- col_error="[31m"
- col_info="[32m"
- col_info2="[32m"
- col_rst="[0m"
-
- ZI_HOME="${ZI_HOME:-${ZDOTDIR:-${HOME}}/.zi}"
- MOD_HOME="${MOD_HOME:-zmodules}/zpmod"
-
- printf '%s\n' "${col_info}Re-run this script to update (from Github) and rebuild the module.${col_rst}"
- printf '%s\n' "${col_info2}Press any key to continue, or Ctrl-C to exit.${col_rst}"
- read -r
-
- SETUP_ZPMOD
- BUILD_ZPMOD "$@"
+ exec sh "${_zpmod_sh}" "$@"
}
CLOSE_PROFILE() {
@@ -273,6 +227,4 @@ EOF
exit 0
}
-while true; do
- MAIN "${@}"
-done
+MAIN "${@}"
diff --git a/lib/sh/install_alpha.sh b/lib/sh/install_alpha.sh
deleted file mode 100755
index a62d423..0000000
--- a/lib/sh/install_alpha.sh
+++ /dev/null
@@ -1,512 +0,0 @@
-#!/usr/bin/env sh
-# -*- mode: sh; sh-indentation: 2; indent-tabs-mode: nil; sh-basic-offset: 2; -*-
-# vim: ft=sh sw=2 ts=2 et
-
-# shellcheck disable=all
-
-# Temporary work directory
-trap 'rm -rf "$WORKDIR"' EXIT INT
-
-# Variables
-WORKDIR="$(mktemp -d)"
-ZI_REPO="https://github.com/z-shell/zi"
-MOD_REPO="https://github.com/z-shell/zpmod"
-GIT_BAR="${WORKDIR}/git-progress-bar.zsh"
-GIT_BAR_URL="https://raw.githubusercontent.com/z-shell/zi/main/lib/zsh/git-process-output.zsh"
-LOADER_URL="https://raw.githubusercontent.com/z-shell/zi-src/main/lib/zsh/init.zsh"
-
-# Messages
-say() {
- while [ -n "$1" ]; do
- case "$1" in
- -normal) col="\033[00m" ;;
- -black) col="\033[30;01m" ;;
- -red) col="\033[31;01m" ;;
- -green) col="\033[32;01m" ;;
- -yellow) col="\033[33;01m" ;;
- -blue) col="\033[34;01m" ;;
- -magenta) col="\033[35;01m" ;;
- -cyan) col="\033[36;01m" ;;
- -white) col="\033[37;01m" ;;
- -n)
- one_line=1
- shift
- continue
- ;;
- *)
- printf '%s' "$1"
- shift
- continue
- ;;
- esac
- shift
- printf "%s${col}"
- printf '%s' "$1"
- printf "\033[00m"
- shift
- done
- [ -z "${one_line}" ] && printf "\n"
-}
-
-ask() {
- question="$1"
- printf "\033[34;1m▓▒░ \033[00m» "
- say -yellow "$question" -n
- printf " \033[00m[y/N]: "
- read -r answer
- case $answer in
- [yY]*)
- true
- ;;
- *)
- false
- ;;
- esac
-}
-
-err() {
- say -red "$1" >&2
- exit 1
-}
-
-say_ok() {
- printf "\033[34;1m▓▒░\033[32;01m ✔ \033[00m» "
- say -green "$1"
- printf "\033[00m"
-}
-
-say_err() {
- printf "\033[34;01m▓▒░\033[31;01m ✘ \033[00m» "
- say -red "$*" >&2
- printf "\033[00m"
- exit 1
-}
-
-say_info() {
- printf "\033[34;1m▓▒░\033[36;01m ⚡\033[00m» "
- say -cyan "$1"
- printf "\033[00m"
-}
-
-while getopts ":i:a:b:" opt; do
- case ${opt} in
- i)
- ZOPT="${ZOPT}${OPTARG}"
- ;;
- a)
- AOPT="${AOPT}${OPTARG}"
- ;;
- b)
- BOPT="${OPTARG}"
- ;;
- \?)
- say_err "Invalid option: ${OPTARG}"
- ;;
- :)
- say_err "Invalid option: ${OPTARG} requires an argument"
- ;;
- *)
- say_err "Invalid option: ${OPTARG}"
- ;;
- esac
-done
-shift $((OPTIND - 1))
-
-# Default options
-[ -z "$BOPT" ] && BOPT="main"
-
-# Functions
-is_cmd() { command -v "$1" >/dev/null 2>&1; }
-
-check_cmd() {
- if ! is_cmd "$1"; then
- say_err "$1 not found. Please install it and try again."
- fi
-}
-
-download() {
- # Set download command
- if is_cmd curl; then
- command curl -fsSL "$1" -o "$2" && command chmod a+x "$2"
- elif is_cmd wget; then
- command wget -qO "$2" "$1" && command chmod a+x "$2"
- else
- say_err "curl or wget is required. Please install it and try again."
- fi
-}
-
-git_clone() {
- command git clone --progress --depth 1 --branch "$BOPT" "$1" "$2" 2>&1 | { "$GIT_BAR" || cat; } 2>/dev/null
-}
-
-prepare_installer() {
- # Check for required commands
- check_cmd git
- check_cmd zsh
-
- # Establish Zi home directory
- if [ -z "$ZI_HOME" ]; then
- if [ -d "${HOME}" ]; then
- ZSH_HOME_DIR="$HOME"
- ZI_HOME="${HOME}/.zi"
- elif [ -d "${ZDOTDIR}" ]; then
- ZSH_HOME_DIR="$ZDOTDIR"
- ZI_HOME="${ZDOTDIR}/.zi"
- elif [ -d "${XDG_DATA_HOME}" ]; then
- ZSH_HOME_DIR="$XDG_DATA_HOME"
- ZI_HOME="${XDG_DATA_HOME}/.zi"
- fi
- fi
-
- if [ ! -d "$ZI_HOME" ]; then
- command mkdir -p "$ZI_HOME"
- fi
-
- if [ ! -w "$ZI_HOME" ]; then
- command chown -R "$(whoami)"
- command chmod -R go-w "$ZI_HOME"
- fi
-
- # Establish Zi bin directory
- if [ -z "$ZI_BIN_DIR" ]; then
- ZI_BIN_DIR="${ZI_HOME}/bin"
- fi
-
- if [ ! -d "$ZI_BIN_DIR" ]; then
- command mkdir -p "$ZI_BIN_DIR"
- fi
-
- if [ ! -w "$ZI_BIN_DIR" ]; then
- command chown -R "$(whoami)"
- command chmod -R go-w "$ZI_BIN_DIR"
- fi
-
- if [ -z "$ZSH_CACHE_DIR" ]; then
- ZSH_CACHE_DIR="${ZSH_HOME_DIR}/.cache/zi"
- fi
-
- if [ ! -d "$ZSH_CACHE_DIR" ]; then
- command mkdir -p "$ZSH_CACHE_DIR"
- fi
-
- if [ ! -w "$ZSH_CACHE_DIR" ]; then
- command chown -R "$(whoami)"
- command chmod -R go-w "$ZSH_CACHE_DIR"
- fi
-
- if [ -z "$ZSH_LOG_DIR" ]; then
- ZSH_LOG_DIR="${ZSH_HOME_DIR}/.cache/zi/logs"
- fi
-
- if [ -z "$ZSH_LOG_FILE" ]; then
- ZSH_LOG_FILE="${ZSH_LOG_DIR}/$(date +%Y-%m-%d).log"
- fi
-
- if [ ! -f "$GIT_BAR" ]; then
- download "$GIT_BAR_URL" "$GIT_BAR"
- fi
-}
-
-check_zshrc() {
- # Check if Zi is already installed
- if grep -E '(zi|init|zinit)\.zsh' "${ZSH_HOME_DIR}/.zshrc" >/dev/null 2>&1; then
- say_info "Zi already set in .zshrc, backing up to .zshrc.bak"
- command mv "${ZSH_HOME_DIR}/.zshrc" "${ZSH_HOME_DIR}/.zshrc.bak"
- elif [ -f "${ZSH_HOME_DIR}/.zshrc" ]; then
- say_info "Backing up to current .zshrc to .zshrc.bak"
- command mv "${ZSH_HOME_DIR}/.zshrc" "${ZSH_HOME_DIR}/.zshrc.bak"
- fi
-}
-
-set_repository() {
- prepare_installer "$@"
-
- if [ -d "${ZI_BIN_DIR}/.git" ]; then
- builtin cd "${ZI_BIN_DIR}" && say_info "Found Zi at $ZI_BIN_DIR, updating..."
- command git clean --quiet -d -f -f
- command git reset --quiet --hard HEAD
- command git pull --quiet origin HEAD
- say_ok "Update Successful!"
- return 0
- elif [ -d "$ZI_BIN_DIR" ]; then
- git_clone "$ZI_REPO" "$ZI_BIN_DIR"
- if [ -f "${ZI_BIN_DIR}/zi.zsh" ]; then
- command cat <<-EOF
-[34m▓▒░[0m[1;36m ■■■■■■■■■■■■■■■■■ Successfully installed Zi ■■■■■■■■■■■■■[0m
-EOF
- return 0
- else
- say_err "Something went wrong, couldn't proceed with installation."
- fi
- fi
-}
-
-set_loader() {
- check_zshrc
- # Establish Zi config directory
- if [ -z "$ZI_CONFIG_DIR" ]; then
- ZI_CONFIG_DIR="${XDG_CONFIG_HOME:-${HOME}/.config}/zi"
- fi
-
- if [ ! -d "$ZI_CONFIG_DIR" ]; then
- command mkdir -p "$ZI_CONFIG_DIR"
- fi
-
- if [ ! -w "$ZI_CONFIG_DIR" ]; then
- command chmod go-w "$ZI_CONFIG_DIR"
- fi
-
- download "$LOADER_URL" "${ZI_CONFIG_DIR}/init.zsh"
- command sed -i "s/branch=\"main\"/branch=\"${BOPT}\"/g" "${ZI_CONFIG_DIR}/init.zsh"
- command cat <<-EOF >>"$ZSH_HOME_DIR/.zshrc"
-# Zi Loader ========================================================================================================= #
-# https://wiki.zshell.dev/docs/getting_started/installation
-if [[ -r "${XDG_CONFIG_HOME:-${HOME}/.config}/zi/init.zsh" ]]; then
- source "${XDG_CONFIG_HOME:-${HOME}/.config}/zi/init.zsh" && zzinit
-fi
-
-EOF
- return 0
-}
-
-set_installer() {
- check_zshrc
- command cat <<-EOF >>"$ZSH_HOME_DIR/.zshrc"
-# Zi source directory =============================================================================================== #
-# https://wiki.zshell.dev/docs/guides/customization#customizing-paths
-typeset -A ZI
-ZI[BIN_DIR]="$ZI_BIN_DIR"
-
-# Auto install Zi =================================================================================================== #
-if [[ ! -f \${ZI[BIN_DIR]}/zi.zsh ]]; then
- print -P "%F{33}▓▒░ %F{160}Installing (%F{33}z-shell/zi%F{160})…%f"
- command mkdir -p "\$ZI[BIN_DIR]" && \\
- command git clone -q --branch "${BOPT}" $ZI_REPO "\${ZI[BIN_DIR]}" && \\
- print -P "%F{33}▓▒░ %F{34}Installation successful…%f%b" || print -P "%F{160}▓▒░ The clone has failed.%f%b"
-fi
-
-# Enable Zi ========================================================================================================= #
-# https://wiki.zshell.dev/docs/getting_started/installation#manual-setup
-source "\${ZI[BIN_DIR]}/zi.zsh"
-autoload -Uz _zi
-(( \${+_comps} )) && _comps[zi]=_zi
-
-EOF
- return 0
-}
-
-set_omz_lib() {
- command cat <<-EOF >>"$ZSH_HOME_DIR/.zshrc"
-# Oh-My-Zsh lib ===================================================================================================== #
-# https://wiki.zshell.dev/docs/getting_started/migration#omz-library
-zi is-snippet wait lucid for \\
- OMZL::{git,theme-and-appearance,prompt_info_functions,vcs_info}.zsh \\
- atinit'COMPLETION_WAITING_DOTS=true' \\
- OMZL::completion.zsh \\
- atinit'typeset -gx HISTSIZE=290000 SAVEHIST=290000 HISTFILE=${ZSH_CACHE_DIR}/.history' \\
- OMZL::history.zsh
-
-EOF
-}
-
-set_omz_plugins() {
- command cat <<-EOF >>"$ZSH_HOME_DIR/.zshrc"
-# Oh-My-Zsh plugins ================================================================================================= #
-# https://wiki.zshell.dev/docs/getting_started/migration#omz-plugins
-zi is-snippet wait lucid for \\
- atload"unalias grv" \\
- OMZP::git \\
- if'[[ -d ~/.ssh ]]' \\
- OMZP::ssh-agent \\
- if'[[ -d ~/.gnupg ]]' \\
- OMZP::gpg-agent
-
-EOF
-}
-
-set_omz_themes() {
- command cat <<-EOF >>"$ZSH_HOME_DIR/.zshrc"
-# Oh-My-Zsh theme =================================================================================================== #
-# https://wiki.zshell.dev/community/gallery/collection/themes
-# https://wiki.zshell.dev/docs/getting_started/migration#omz-themes
-zi wait'!' lucid for \\
- atinit'setopt prompt_subst' \\
- OMZT::robbyrussell
-
-EOF
-}
-
-set_plugins() {
- command cat <<-EOF >>"$ZSH_HOME_DIR/.zshrc"
-# Popular plugins =================================================================================================== #
-# https://wiki.zshell.dev/ecosystem
-# https://wiki.zshell.dev/community/gallery/collection/plugins
-zi wait lucid for \\
- atinit'ZI[COMPINIT_OPTS]=-C; zicompinit; zicdreplay' \\
- z-shell/F-Sy-H \\
- atload'!_zsh_autosuggest_start' \\
- zsh-users/zsh-autosuggestions \\
- blockf atpull' zi creinstall -q .' \\
- zsh-users/zsh-completions \\
- atinit'zstyle ":history-search-multi-word" page-size "7"' \\
- z-shell/H-S-MW
-EOF
-}
-
-set_themes() {
- command cat <<-EOF >>"$ZSH_HOME_DIR/.zshrc"
-# Popular themes ==================================================================================================== #
-# https://wiki.zshell.dev/community/gallery/collection/themes
-
-EOF
-}
-
-set_annexes() {
- command cat <<-EOF >>"$ZSH_HOME_DIR/.zshrc"
-# Meta-plugins & annexes =========================================================================================== #
-# https://wiki.zshell.dev/ecosystem/category/-annexes
-zi for \\
- z-shell/z-a-meta-plugins \\
- @annexes
-
-EOF
-}
-
-set_zpmod() {
- check_cmd make
-
- # Establish zpmod directory
- if [ -z "$MOD_HOME" ]; then
- MOD_HOME="${ZI_HOME}/zmodules/zpmod"
- fi
-
- if [ ! -d "$MOD_HOME" ]; then
- command mkdir -p "$MOD_HOME"
- fi
-
- if [ ! -w "$MOD_HOME" ]; then
- command chmod go-w "$MOD_HOME"
- fi
-
- if [ -d "${MOD_HOME}/.git" ]; then
- say_info "Updating ZPMOD at $MOD_HOME"
- builtin cd "$MOD_HOME" && command git pull -q --ff-only origin main
- else
- say_info "Downloading ZPMOD to $MOD_HOME"
- command git clone -q "$MOD_REPO" "$MOD_HOME"
- fi
-
- say_info "Checkig version for zsh..."
- ZSH_CURRENT=$(zsh --version /dev/null; then
- say_err "Zsh version 5.8.1 and above required."
- else
- say_info "Zsh version ${ZSH_CURRENT} is compatible."
- builtin cd "$MOD_HOME" || err "Failed to change directory to $MOD_HOME."
- say_info "Building module ZPMOD, running: a make clean, then ./configure and then make."
- say_info "The module source are located at: $MOD_HOME"
- if test -f Makefile; then
- if [ "$1" = "--clean" ]; then
- say_info "Running: make distclean..."
- make distclean
- true
- else
- say_info "Running: make clean (pass --clean to invoke \`make distclean')..."
- make clean
- fi
- fi
- say_info "Configuring..."
- if CPPFLAGS=-I/usr/local/include CFLAGS="-g -Wall -O3" LDFLAGS=-L/usr/local/lib ./configure --disable-gdbm --without-tcsetpgrp; then
- say_info "Building..."
- if make -s; then
- command cat <<-EOF
-[38;5;219m▓▒░[0m [38;5;220mModule [38;5;177mhas been built correctly.
-[38;5;219m▓▒░[0m [38;5;220mTo [38;5;160mload the module, add following [38;5;220m2 lines to [38;5;172m.zshrc, at top:
-[0m [38;5;51m module_path+=( "${MOD_HOME}/Src" )
-[0m [38;5;51m zmodload zi/zpmod
-[38;5;219m▓▒░[0m [38;5;220mSee 'zpmod -h' for more information.
-[38;5;219m▓▒░[0m [38;5;220mRun 'zpmod source-study' to see profile data,
-[38;5;219m▓▒░[0m [38;5;177mGuaranteed, automatic compilation of any sourced script.
-EOF
- else
- say_err "Module failed build. Please report error at: ${MOD_REPO}/issues"
- fi
- fi
- fi
-}
-
-interactive_zshrc() {
- if [ "$ZOPT" != skip ] && [ "$AOPT" = interactive ]; then
- say_info "Creating .zshrc interactively..."
- if ask "Install Zi Loader?"; then
- set_loader
- else
- set_installer
- fi
-
- if ask "Install annexes?"; then
- set_annexes
- fi
-
- if ask "Add recommended Oh-My-Zsh library?"; then
- set_omz_lib
- fi
-
- if ask "Add recommended Oh-My-Zsh plugins?"; then
- set_omz_plugins
- fi
-
- if ask "Add recommended Oh-My-Zsh theme?"; then
- set_omz_themes
- fi
-
- if ask "Add recommended plugins?"; then
- set_plugins
- fi
-
- zsh -ilc "@zi-scheduler burst"
- command cat <<-EOF
-[34m▓▒░[0m[1;36m ■■■■■■■■■■■■■■■■ Successfully created .zshrc ■■■■■■■■■■■■[0m
-EOF
- return 0
- fi
-}
-
-default_zshrc() {
- if [ "$ZOPT" != skip ] && [ "$AOPT" = default ]; then
- say_info "Creating .zshrc file..."
- set_loader
- set_annexes
- set_omz_lib
- set_omz_plugins
- set_omz_themes
- set_plugins
-
- zsh -ilc "@zi-scheduler burst"
- command cat <<-EOF
-[34m▓▒░[0m[1;36m ■■■■■■■■■■■■■■■■ Successfully created .zshrc ■■■■■■■■■■■■[0m
-EOF
- return 0
- fi
-}
-
-MAIN() {
- set_repository "$@"
- default_zshrc
-
- command cat <<-EOF
-
-[34m▓▒░[0m[38;5;226m Wiki: https://wiki.zshell.dev[0m
-[34m▓▒░[0m[38;5;226m Issues: https://github.com/z-shell/zi/issues[0m
-[34m▓▒░[0m[38;5;226m Discussions: https://discussions.zshell.dev[0m
-
-[34m▓▒░[0m[1;36m ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■[0m
-
-EOF
- exit $?
-}
-
-while true; do
- MAIN "${@}"
-done
diff --git a/lib/sh/install_zpmod.sh b/lib/sh/install_zpmod.sh
index d694f88..c49b99c 100755
--- a/lib/sh/install_zpmod.sh
+++ b/lib/sh/install_zpmod.sh
@@ -1,34 +1,56 @@
#!/usr/bin/env sh
+# -*- mode: sh; sh-indentation: 2; indent-tabs-mode: nil; sh-basic-offset: 2; -*-
+# vim: ft=sh sw=2 ts=2 et
+
+set -eu
+
+WORKDIR="$(mktemp -d)" || exit 1
+trap 'rm -rf "${WORKDIR:?}"' EXIT INT TERM
+
+# Returns 0 if version $1 >= version $2 (dot-separated integers)
+_zi_ver_ge() {
+ _vga="$1" _vgb="$2"
+ while [ -n "${_vga}${_vgb}" ]; do
+ _af="${_vga%%.*}" _bf="${_vgb%%.*}"
+ case "${_vga}" in *.*) _vga="${_vga#*.}" ;; *) _vga="" ;; esac
+ case "${_vgb}" in *.*) _vgb="${_vgb#*.}" ;; *) _vgb="" ;; esac
+ _af="${_af:-0}" _bf="${_bf:-0}"
+ if [ "${_af}" -gt "${_bf}" ]; then return 0; fi
+ if [ "${_af}" -lt "${_bf}" ]; then return 1; fi
+ done
+ return 0
+}
setup_environment() {
- if [ -z "${ZI_HOME}" ]; then
- if [ -d "${HOME}"/.zi ]; then
+ if [ -z "${ZI_HOME-}" ]; then
+ _xdg_zi="${XDG_DATA_HOME:-${HOME}/.local/share}/zi"
+ if [ -d "${_xdg_zi}" ]; then
+ ZI_HOME="${_xdg_zi}"
+ elif [ -d "${HOME}/.zi" ]; then
ZI_HOME="${HOME}/.zi"
- elif [ -d "${ZDOTDIR}"/.zi ]; then
+ elif [ -n "${ZDOTDIR-}" ] && [ -d "${ZDOTDIR}/.zi" ]; then
ZI_HOME="${ZDOTDIR}/.zi"
- elif [ -d "${XDG_DATA_HOME}"/.zi ]; then
- ZI_HOME="${XDG_DATA_HOME}/.zi"
else
- ZI_HOME="${HOME}/.zi"
+ ZI_HOME="${_xdg_zi}"
fi
fi
- if [ -z "${MOD_HOME}" ]; then
+ if [ -z "${MOD_HOME-}" ]; then
MOD_HOME="${ZI_HOME}/zmodules/zpmod"
fi
if ! test -d "${MOD_HOME}"; then
mkdir -p "${MOD_HOME}"
- chmod g-rwX "${MOD_HOME}"
+ chmod go-w "${MOD_HOME}"
fi
if [ ! -d "${MOD_HOME}" ]; then
printf '%s\n' "${col_error}== Error: Failed to setup module directory ==${col_rst}"
- exit 255
+ exit 1
fi
}
setup_zpmod_repository() {
printf '%s\n' "${col_pname}== Downloading ZPMOD module to ${MOD_HOME}"
if test -d "${MOD_HOME}/.git"; then
- cd "${MOD_HOME}" || exit 255
+ cd "${MOD_HOME}" || exit 1
git pull -q origin main
else
git clone --depth 10 -q https://github.com/z-shell/zpmod.git "${MOD_HOME}"
@@ -39,18 +61,19 @@ build_zpmod_module() {
if command -v zsh >/dev/null; then
printf '%s\n' "${col_info2}-- Checking version --${col_rst}"
ZSH_CURRENT=$(zsh --version /dev/null; then
- printf '%s\n' "${col_error}-- Zsh version 5.8.1 and above required --${col_rst}"
+ ZSH_REQUIRED="5.8.1"
+ # shellcheck disable=SC2310
+ if ! _zi_ver_ge "${ZSH_CURRENT}" "${ZSH_REQUIRED}"; then
+ printf '%s\n' "${col_error}-- Zsh version ${ZSH_REQUIRED} and above required --${col_rst}"
exit 1
else
(
printf '%s\n' "${col_info2}-- Zsh version ${ZSH_CURRENT} --${col_rst}"
- cd "${MOD_HOME}" || exit 255
+ cd "${MOD_HOME}" || exit 1
printf '%s\n' "${col_pname}== Building module ZPMOD, running: a make clean, then ./configure and then make ==${col_rst}"
printf '%s\n' "${col_pname}== The module sources are located at: ${MOD_HOME} ==${col_rst}"
if test -f Makefile; then
- if [ "$1" = "--clean" ]; then
+ if [ "${1-}" = "--clean" ]; then
printf '%s\n' "${col_info2}-- make distclean --${col_rst}"
make distclean
true
@@ -62,9 +85,10 @@ build_zpmod_module() {
INSTALL_PATH="/usr/local"
export PATH="${INSTALL_PATH}"/bin:"${PATH}"
- export LD_LIBRARY_PATH="${INSTALL_PATH}"/lib:"${LD_LIBRARY_PATH}"
- export CFLAGS=-I"${INSTALL_PATH}"/include
- export CPPFLAGS="-I${INSTALL_PATH}/include" LDFLAGS="-L${INSTALL_PATH}/lib"
+ export LD_LIBRARY_PATH="${INSTALL_PATH}/lib:${LD_LIBRARY_PATH-}"
+ export CFLAGS="-I${INSTALL_PATH}/include"
+ export CPPFLAGS="-I${INSTALL_PATH}/include"
+ export LDFLAGS="-L${INSTALL_PATH}/lib"
CFLAGS="-g -Wall -O3" ./configure --disable-gdbm --without-tcsetpgrp --quiet
printf '%s\n' "${col_info2}-- Running make --${col_rst}"
@@ -84,13 +108,13 @@ EOF
else
printf '%s\n' "${col_error}Module didn't build.${col_rst}. You can copy the error messages and submit"
printf '%s\n' "error-report at: https://github.com/z-shell/zpmod/issues"
- exit 255
+ exit 1
fi
)
fi
else
printf '%s\n' "${col_error} Zsh is not installed. Please install zsh and try again.${col_rst}"
- exit 255
+ exit 1
fi
}
@@ -106,6 +130,4 @@ MAIN() {
exit 0
}
-while true; do
- MAIN "${@}"
-done
+MAIN "${@}"
diff --git a/lib/sh/sync-init.sh b/lib/sh/sync-init.sh
new file mode 100755
index 0000000..5cc30ac
--- /dev/null
+++ b/lib/sh/sync-init.sh
@@ -0,0 +1,252 @@
+#!/usr/bin/env sh
+# -*- mode: sh; sh-indentation: 2; indent-tabs-mode: nil; sh-basic-offset: 2; -*-
+# vim: ft=sh sw=2 ts=2 et
+#
+# sync-init.sh — verify and optionally sync local lib/zsh/init.zsh with remote.
+#
+# Usage:
+# sh lib/sh/sync-init.sh [OPTIONS]
+#
+# Options:
+# --write Replace local file with remote copy (requires valid checksum)
+# --local PATH Local file to compare (default: lib/zsh/init.zsh)
+# --remote URL|PATH Remote URL or local path (default: GitHub raw main)
+# --checksum-url URL|PATH Checksum.txt URL or path (default: GitHub raw main)
+# --no-checksum Skip checksum validation of remote content
+# --help Print this help and exit
+#
+# Exit codes:
+# 0 Files match (or --write sync succeeded)
+# 1 Files differ, fetch error, or checksum mismatch
+# 2 Usage error
+
+set -eu
+
+WORKDIR="$(mktemp -d)" || exit 1
+trap 'rm -rf "${WORKDIR:?}"' EXIT INT TERM
+
+SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
+REPO_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)"
+
+DEFAULT_LOCAL="${REPO_ROOT}/lib/zsh/init.zsh"
+DEFAULT_REMOTE="https://raw.githubusercontent.com/z-shell/zi-src/main/lib/zsh/init.zsh"
+DEFAULT_CHECKSUM_URL="https://raw.githubusercontent.com/z-shell/zi-src/main/lib/checksum.txt"
+CHECKSUM_KEY="lib/zsh/init.zsh"
+
+OPT_LOCAL="${DEFAULT_LOCAL}"
+OPT_REMOTE="${DEFAULT_REMOTE}"
+OPT_CHECKSUM_URL="${DEFAULT_CHECKSUM_URL}"
+OPT_WRITE=0
+OPT_NO_CHECKSUM=0
+
+# ── Helpers ───────────────────────────────────────────────────────────────────
+
+print_help() {
+ cat </dev/null 2>&1; then
+ command curl -fsSL "${_src}"
+ elif command -v wget >/dev/null 2>&1; then
+ command wget -qO- "${_src}"
+ else
+ printf '%s\n' "[1;31m▓▒░[0m No curl or wget available." >&2
+ return 1
+ fi
+ ;;
+ *)
+ if [ -r "${_src}" ]; then
+ command cat "${_src}"
+ else
+ printf '%s\n' "[1;31m▓▒░[0m Cannot read local path: ${_src}" >&2
+ return 1
+ fi
+ ;;
+ esac
+}
+
+# Compute SHA-256 hex digest of a file.
+_sha256() {
+ _file="$1"
+ if command -v sha256sum >/dev/null 2>&1; then
+ sha256sum "${_file}" | command awk '{print $1}'
+ elif command -v shasum >/dev/null 2>&1; then
+ shasum -a 256 "${_file}" | command awk '{print $1}'
+ else
+ printf '%s\n' "[1;31m▓▒░[0m No sha256sum or shasum available." >&2
+ return 1
+ fi
+}
+
+# ── Argument Parsing ──────────────────────────────────────────────────────────
+
+while [ "$#" -gt 0 ]; do
+ case "$1" in
+ --write)
+ OPT_WRITE=1
+ shift
+ ;;
+ --local)
+ [ "$#" -ge 2 ] || {
+ printf '%s\n' "Error: --local requires an argument." >&2
+ exit 2
+ }
+ OPT_LOCAL="$2"
+ shift 2
+ ;;
+ --remote)
+ [ "$#" -ge 2 ] || {
+ printf '%s\n' "Error: --remote requires an argument." >&2
+ exit 2
+ }
+ OPT_REMOTE="$2"
+ shift 2
+ ;;
+ --checksum-url)
+ [ "$#" -ge 2 ] || {
+ printf '%s\n' "Error: --checksum-url requires an argument." >&2
+ exit 2
+ }
+ OPT_CHECKSUM_URL="$2"
+ shift 2
+ ;;
+ --no-checksum)
+ OPT_NO_CHECKSUM=1
+ shift
+ ;;
+ --help | -h)
+ print_help
+ exit 0
+ ;;
+ *)
+ printf '%s\n' "Error: unknown option: $1" >&2
+ exit 2
+ ;;
+ esac
+done
+
+# ── Validate Inputs ───────────────────────────────────────────────────────────
+
+if [ ! -f "${OPT_LOCAL}" ] && [ "${OPT_WRITE}" -eq 0 ]; then
+ printf '%s\n' "[1;31m▓▒░[0m Local file not found: ${OPT_LOCAL}" >&2
+ printf '%s\n' "[1;33m▓▒░[0m Use --write to create it from remote." >&2
+ exit 1
+fi
+
+# ── Fetch Remote ──────────────────────────────────────────────────────────────
+
+REMOTE_FILE="${WORKDIR}/remote-init.zsh"
+printf '%s\n' "▓▒░ Fetching remote: ${OPT_REMOTE}"
+# shellcheck disable=SC2310
+if ! _fetch "${OPT_REMOTE}" >"${REMOTE_FILE}"; then
+ printf '%s\n' "[1;31m▓▒░[0m Failed to fetch remote file." >&2
+ exit 1
+fi
+
+# ── Verify Remote Against Checksum ───────────────────────────────────────────
+
+if [ "${OPT_NO_CHECKSUM}" -eq 0 ]; then
+ CHECKSUM_FILE="${WORKDIR}/checksum.txt"
+ printf '%s\n' "▓▒░ Fetching checksum: ${OPT_CHECKSUM_URL}"
+ # shellcheck disable=SC2310
+ if ! _fetch "${OPT_CHECKSUM_URL}" >"${CHECKSUM_FILE}"; then
+ printf '%s\n' "[1;31m▓▒░[0m Failed to fetch checksum file." >&2
+ exit 1
+ fi
+
+ EXPECTED_HASH="$(grep "${CHECKSUM_KEY}" "${CHECKSUM_FILE}" | command awk '{print $1}')"
+ if [ -z "${EXPECTED_HASH}" ]; then
+ printf '%s\n' "[1;31m▓▒░[0m No checksum entry for '${CHECKSUM_KEY}' in checksum.txt." >&2
+ exit 1
+ fi
+
+ REMOTE_HASH="$(_sha256 "${REMOTE_FILE}")"
+ if [ "${REMOTE_HASH}" != "${EXPECTED_HASH}" ]; then
+ printf '%s\n' "[1;31m▓▒░[0m Remote checksum mismatch!" >&2
+ printf '%s\n' " expected : ${EXPECTED_HASH}" >&2
+ printf '%s\n' " got : ${REMOTE_HASH}" >&2
+ exit 1
+ fi
+ printf '%s\n' "▓▒░ Remote checksum verified: ${REMOTE_HASH}"
+else
+ REMOTE_HASH="$(_sha256 "${REMOTE_FILE}")"
+ printf '%s\n' "▓▒░ Remote hash (checksum validation skipped): ${REMOTE_HASH}"
+fi
+
+# ── Compare ───────────────────────────────────────────────────────────────────
+
+if [ -f "${OPT_LOCAL}" ]; then
+ LOCAL_HASH="$(_sha256 "${OPT_LOCAL}")"
+else
+ LOCAL_HASH="(file not present)"
+fi
+
+if [ "${LOCAL_HASH}" = "${REMOTE_HASH}" ]; then
+ printf '%s\n' "[1;32m▓▒░[0m Local file matches remote. No sync needed."
+ printf '%s\n' " hash : ${LOCAL_HASH}"
+ printf '%s\n' " path : ${OPT_LOCAL}"
+ exit 0
+fi
+
+printf '%s\n' "[1;33m▓▒░[0m Local and remote differ."
+printf '%s\n' " local : ${LOCAL_HASH}"
+printf '%s\n' " remote : ${REMOTE_HASH}"
+printf '%s\n' " source : ${OPT_REMOTE}"
+
+if command -v diff >/dev/null 2>&1 && [ -f "${OPT_LOCAL}" ]; then
+ printf '\n%s\n' "--- diff (local vs remote) ---"
+ diff -u "${OPT_LOCAL}" "${REMOTE_FILE}" || true
+ printf '%s\n' "--- end diff ---"
+fi
+
+# ── Sync ──────────────────────────────────────────────────────────────────────
+
+if [ "${OPT_WRITE}" -eq 0 ]; then
+ printf '\n%s\n' "▓▒░ Run with --write to replace the local file."
+ exit 1
+fi
+
+LOCAL_DIR="$(dirname "${OPT_LOCAL}")"
+TMP_TARGET="${LOCAL_DIR}/.sync-init.zsh.tmp.$$"
+
+command cp "${REMOTE_FILE}" "${TMP_TARGET}"
+
+if [ -f "${OPT_LOCAL}" ]; then
+ ORIG_MODE="$(command stat -c '%a' "${OPT_LOCAL}" 2>/dev/null ||
+ command stat -f '%A' "${OPT_LOCAL}" 2>/dev/null ||
+ printf '755')"
+ command chmod "${ORIG_MODE}" "${TMP_TARGET}"
+else
+ command chmod 755 "${TMP_TARGET}"
+fi
+
+command mv "${TMP_TARGET}" "${OPT_LOCAL}"
+
+NEW_HASH="$(_sha256 "${OPT_LOCAL}")"
+printf '%s\n' "[1;32m▓▒░[0m Sync complete."
+printf '%s\n' " before : ${LOCAL_HASH}"
+printf '%s\n' " after : ${NEW_HASH}"
+printf '%s\n' " path : ${OPT_LOCAL}"
diff --git a/lib/zsh/init.zsh b/lib/zsh/init.zsh
index 49b86a6..01add00 100755
--- a/lib/zsh/init.zsh
+++ b/lib/zsh/init.zsh
@@ -1,88 +1,136 @@
#!/usr/bin/env zsh
-# ZI Loader (Values set: default)
+# -*- mode: zsh; sh-indentation: 2; indent-tabs-mode: nil; sh-basic-offset: 2; -*-
+# vim: ft=zsh sw=2 ts=2 et
#
-# https://z.digitalclouds.dev/community/zsh_plugin_standard
-0="${ZERO:-${${0:#$ZSH_ARGZERO}:-${(%):-%N}}}"
-0="${${(M)0:#/*}:-$PWD/$0}"
-
-# https://z.digitalclouds.dev/docs/guides/customization
-local repo="https://github.com/z-shell/zi.git"
-local branch="main"
-local verbose_mode="${verbose_mode:-false}"
-typeset -A ZI
-# Where ZI should create all working directories, e.g.: "~/.zi"
-ZI[HOME_DIR]="${ZI[HOME_DIR]:-${HOME}/.zi}"
-# Where ZI code resides, e.g.: "~/.zi/bin"
-ZI[BIN_DIR]="$ZI[HOME_DIR]/bin"
-# Zsh modules directory
-ZI[ZMODULES_DIR]="$ZI[HOME_DIR]/zmodules"
-# Where ZI cache is, e.g.: "~/.cache/zi"
-ZI[CACHE_DIR]="${ZI[CACHE_DIR]:-$HOME/.cache/zi}"
-# Path to .zcompdump file, with the file included (i.e. its name can be different)
-ZI[ZCOMPDUMP_PATH]="$ZI[CACHE_DIR]/.zcompdump"
-# If set to 1, then mutes some of the ZI warnings, specifically the plugin already registered warning
-ZI[MUTE_WARNINGS]="${ZI[MUTE_WARNINGS]:-0}"
-
-# Clone ZI repository if it doesn't exist
-zzsetup() {
- [[ $verbose_mode == true ]] && builtin print "(ZI): Checking if ZI (zi.zsh) is available."
+# Zi Loader — bootstrap and source the Zi plugin manager.
+#
+# Sourced early in .zshrc. Defines zzinit() which clones Zi on first run,
+# sources zi.zsh, registers completions, and loads zpmod if built.
+# All helper functions are cleaned up after zzinit() returns.
+
+# ── Zi Configuration ──────────────────────────────────────────────────────────
+
+typeset -ghA ZI
+
+# https://wiki.zshell.dev/docs/guides/customization
+: ${ZI[REPOSITORY]:="https://github.com/z-shell/zi.git"}
+: ${ZI[STREAM]:="main"}
+: ${ZI[HOME_DIR]:="${XDG_DATA_HOME:-$HOME/.local/share}/zi"}
+: ${ZI[BIN_DIR]:="${ZI[HOME_DIR]}/bin"}
+: ${ZI[CACHE_DIR]:="${XDG_CACHE_HOME:-$HOME/.cache}/zi"}
+: ${ZI[CONFIG_DIR]:="${XDG_CONFIG_HOME:-$HOME/.config}/zi"}
+
+# https://wiki.zshell.dev/community/zsh_plugin_standard#global-parameter-with-prefix
+: ${ZPFX:=${ZI[HOME_DIR]}/polaris}
+: ${ZI[ZMODULES_DIR]:=${ZI[HOME_DIR]}/zmodules}
+: ${ZI[ZCOMPDUMP_PATH]:=${ZI[CACHE_DIR]}/.zcompdump}
+: ${ZI[MUTE_WARNINGS]:=0}
+
+# History defaults
+: ${HISTFILE:=${XDG_STATE_HOME:-$HOME/.local/state}/zsh/history}
+[[ -e "$HISTFILE" ]] || { command mkdir -p "${HISTFILE:h}"; command touch "$HISTFILE"; }
+[[ -w "$HISTFILE" ]] && typeset -gx SAVEHIST=440000 HISTSIZE=441000
+
+# ── Bootstrap Helpers ─────────────────────────────────────────────────────────
+
+# Fetch content from a URL to stdout.
+_zi_fetch() {
+ builtin emulate -L zsh
+ if (( $+commands[curl] )); then
+ command curl -fsSL "$1"
+ elif (( $+commands[wget] )); then
+ command wget -qO- "$1"
+ else
+ return 255
+ fi
+}
+
+# Clone Zi repository if it doesn't exist.
+_zi_setup() {
+ builtin emulate -L zsh
+ builtin autoload colors; colors
+ local -a git_refs
+ local tmp_dir show_process process_url
+
if [[ ! -f "${ZI[BIN_DIR]}/zi.zsh" ]]; then
- [[ $verbose_mode == true ]] && builtin print "(ZI): ZI (zi.zsh) is not found. Installing..."
- builtin print -P "%F{33}▓▒░ %F{160}Installing interactive feature-rich plugin manager (%F{33}z-shell/zi%F{160})%f%b"
- command mkdir -p "${ZI[BIN_DIR]}" && command chmod -R go-w "${ZI[HOME_DIR]}"
- command git clone -q --progress --branch "$branch" "$repo" "${ZI[BIN_DIR]}"
+ tmp_dir="${TMPDIR:-/tmp}/zi"
+ [[ -d "$tmp_dir" ]] || command mkdir -p "$tmp_dir"
+
+ show_process="${tmp_dir}/git-process.zsh"
+ process_url="https://raw.githubusercontent.com/z-shell/zi/main/lib/zsh/git-process-output.zsh"
+
+ if [[ ! -f "$show_process" ]]; then
+ if _zi_fetch "$process_url" > "${tmp_dir}/git-process.zsh"; then
+ command chmod a+x "${tmp_dir}/git-process.zsh"
+ else
+ return 1
+ fi
+ fi
+
+ (( $+commands[clear] )) && command clear
+ builtin print -P "%F{33}▓▒░ %F{160}Installing interactive & feature-rich plugin manager (%F{33}z-shell/zi%F{160})%f%b…\n"
+ command mkdir -p "${ZI[BIN_DIR]}" && \
+ command git clone --verbose --progress --branch \
+ "${ZI[STREAM]}" "${ZI[REPOSITORY]}" "${ZI[BIN_DIR]}" \
+ |& { command "$show_process" || command cat; }
+
if [[ -f "${ZI[BIN_DIR]}/zi.zsh" ]]; then
- [[ $verbose_mode == true ]] && builtin print "(ZI): Installed and ZI (zi.zsh) is found"
- local git_refs=("$(cd "${ZI[BIN_DIR]}"; command git log --color --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit | head -10)")
- print -P "%F{33}▓▒░ %F{34}Successfully installed %F{160}(%F{33}z-shell/zi%F{160})%f%b"
- print -P "%F{33}▓▒░ %F{226}Last changes:%f%b"
- print -P "%F{33}▓▒░ %F{160}%F{33}\n${git_refs}%F{160}%f%b"
+ command chmod -R go-w "${ZI[HOME_DIR]}"
+ git_refs=("${(f@)$(builtin cd -q "${ZI[BIN_DIR]}" && command git log --color --graph --abbrev-commit \
+ --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' | command head -5)}")
+ builtin print
+ builtin print -P "%F{33}▓▒░ %F{34}Successfully installed %F{160}(%F{33}z-shell/zi%F{160})%f%b\n"
+ builtin print -rl -- "${git_refs[@]}"
else
- print -P "%F{160}▓▒░ The clone has failed…%f%b"
- print -P "%F{160}▓▒░ %F{33} Please report the issue:%f%b"
- print -P "%F{160}▓▒░ %F{33} https://github.com/z-shell/zi/issues/new%f%b"
+ builtin print -P "%F{160}▓▒░ The clone has failed…%f%b"
+ builtin print -P "%F{160}▓▒░ %F{33} Please report the issue: %F{226}https://github.com/z-shell/zi/issues/new%f%b"
return 1
fi
- return 0
fi
+ return 0
}
-# If setup is successful or ZI is already installed, then load ZI. Otherwise, not continue and exit.
-zzsource() {
- [[ $verbose_mode == true ]] && builtin print "(ZI): If (zzsetup) function status code 0, then load ZI."
- if zzsetup; then
- [[ $verbose_mode == true ]] && builtin print "(ZI): Loading (zi.zsh)"
- source "${ZI[BIN_DIR]}/zi.zsh"
+# Source zi.zsh, bootstrapping first if needed.
+_zi_source() {
+ builtin emulate -L zsh
+ if [[ -f "${ZI[BIN_DIR]}/zi.zsh" ]]; then
+ builtin source "${ZI[BIN_DIR]}/zi.zsh"
else
- [[ $verbose_mode == true ]] && builtin print "(ZI): (zzsetup) function status code 1, not continue and exit."
- exit 1
+ _zi_setup || return 1
+ # Guard: if setup succeeded but zi.zsh is still missing, don't recurse
+ if [[ -f "${ZI[BIN_DIR]}/zi.zsh" ]]; then
+ builtin source "${ZI[BIN_DIR]}/zi.zsh"
+ else
+ return 1
+ fi
fi
}
-# Load zi module if built
-zzpmod() {
- [[ $verbose_mode == true ]] && builtin print "(ZI): Checking for ZI module."
+# Load zpmod module if built.
+_zi_pmod() {
+ builtin emulate -L zsh
if [[ -f "${ZI[ZMODULES_DIR]}/zpmod/Src/zi/zpmod.so" ]]; then
- [[ $verbose_mode == true ]] && builtin print "(ZI): Loading ZI module."
module_path+=( "${ZI[ZMODULES_DIR]}/zpmod/Src" )
- zmodload zi/zpmod &>/dev/null
- ZI[ZPMOD_ENABLED]=1
+ zmodload zi/zpmod 2>/dev/null
fi
+ return 0
}
-# Enable completion (completions should be loaded after zzsource)
-zzcomps() {
- [[ $verbose_mode == true ]] && builtin print "(ZI): Loading completion… (_zi)"
- autoload -Uz _zi
- (( ${+_comps} )) && _comps[zi]=_zi
- ZI[COMPS_ENABLED]=1
+# Register Zi completion if the completion system is active.
+_zi_comps() {
+ builtin emulate -L zsh
+ if (( ${+_comps} )) && [[ -f "${ZI[BIN_DIR]}/lib/_zi" ]]; then
+ (( ${+_comps[zi]} )) || _comps[zi]="${ZI[BIN_DIR]}/lib/_zi"
+ fi
+ return 0
}
-# If ZI is installed, load ZI, enable completion and load zpmod.
+# ── Entry Point ───────────────────────────────────────────────────────────────
+
zzinit() {
- (( ZI[SOURCED] )) && return
- [[ $verbose_mode == true ]] && builtin print "(ZI): Loading ZI (zi.zsh)"
- zzsource
- zzcomps
- zzpmod
+ {
+ _zi_source && _zi_comps && _zi_pmod
+ } always {
+ unset -f _zi_fetch _zi_setup _zi_source _zi_comps _zi_pmod zzinit 2>/dev/null
+ }
}
diff --git a/scripts/add-project-workflow.sh b/scripts/add-project-workflow.sh
new file mode 100755
index 0000000..b5573ba
--- /dev/null
+++ b/scripts/add-project-workflow.sh
@@ -0,0 +1,128 @@
+#!/usr/bin/env sh
+# add-project-workflow.sh — Add project-tracker workflow to all org repos.
+#
+# Usage:
+# ./add-project-workflow.sh [owner] [repos-file]
+#
+# Defaults:
+# owner = z-shell
+# repos-file = scripts/repos.txt
+#
+# Requirements:
+# - gh CLI authenticated with repo scope
+# - SSH key configured for git push
+# - z-shell/.github already has add-to-project.yml deployed
+
+set -e
+
+OWNER="${1:-z-shell}"
+REPOS="${2:-$(dirname "$0")/repos.txt}"
+WORKFLOW_FILE=".github/workflows/project-tracker.yml"
+BRANCH="ci/add-project-tracker"
+
+if [ ! -f "${REPOS}" ]; then
+ printf 'Error: repos file not found: %s\n' "${REPOS}" >&2
+ exit 1
+fi
+
+# Skip repos that already have the workflow or are the .github meta-repo
+SKIP_REPOS=".github"
+
+WORKFLOW_CONTENT='# Add issues and PRs to the Z-Shell Tracker project automatically.
+name: Track in Z-Shell Tracker
+
+on:
+ issues:
+ types: [opened, reopened, transferred]
+ pull_request:
+ types: [opened, reopened]
+
+jobs:
+ track:
+ uses: z-shell/.github/.github/workflows/add-to-project.yml@main
+ secrets: inherit
+'
+
+total=0
+ok=0
+skipped=0
+failed=0
+
+add_workflow() {
+ repo="$1"
+ printf '=== %s/%s ===\n' "${OWNER}" "${repo}"
+
+ # Skip the .github meta-repo itself
+ for skip in ${SKIP_REPOS}; do
+ if [ "${repo}" = "${skip}" ]; then
+ printf 'Skipping meta-repo\n'
+ skipped=$((skipped + 1))
+ return 0
+ fi
+ done
+
+ tmpdir=$(mktemp -d)
+ trap 'rm -rf "$tmpdir"' EXIT INT TERM
+
+ # Clone (shallow) — skip archived/empty repos gracefully
+ if ! git clone --depth=1 "git@github.com:${OWNER}/${repo}.git" "${tmpdir}/${repo}" 2>/dev/null; then
+ printf 'SKIP: cannot clone (archived or empty?)\n'
+ skipped=$((skipped + 1))
+ trap - EXIT INT TERM
+ rm -rf "${tmpdir}"
+ return 0
+ fi
+
+ cd "${tmpdir}/${repo}"
+
+ # Skip if workflow already exists
+ if [ -f "${WORKFLOW_FILE}" ]; then
+ printf 'SKIP: workflow already present\n'
+ skipped=$((skipped + 1))
+ cd - >/dev/null
+ trap - EXIT INT TERM
+ rm -rf "${tmpdir}"
+ return 0
+ fi
+
+ # Get default branch
+ default_branch=$(git symbolic-ref --short HEAD 2>/dev/null || echo "main")
+
+ git checkout -b "${BRANCH}" 2>/dev/null || git checkout "${BRANCH}"
+ mkdir -p "$(dirname "${WORKFLOW_FILE}")"
+ printf '%s' "${WORKFLOW_CONTENT}" >"${WORKFLOW_FILE}"
+
+ git add "${WORKFLOW_FILE}"
+ git commit -m "ci: add project tracker workflow"
+ git push origin "${BRANCH}"
+
+ # Open a PR
+ # shellcheck disable=SC2016
+ gh pr create \
+ --repo "${OWNER}/${repo}" \
+ --base "${default_branch}" \
+ --head "${BRANCH}" \
+ --title "ci: add project tracker workflow" \
+ --body 'Adds the `.github/workflows/project-tracker.yml` workflow so new issues and PRs are automatically added to the [Z-Shell Tracker](https://github.com/orgs/z-shell/projects/28) project.
+
+Uses the shared reusable workflow from `z-shell/.github` — no per-repo secrets needed.' \
+ 2>/dev/null && printf 'PR opened\n' || printf 'PR already open or failed\n'
+
+ ok=$((ok + 1))
+ cd - >/dev/null
+ trap - EXIT INT TERM
+ rm -rf "${tmpdir}"
+}
+
+while IFS= read -r repo || [ -n "${repo}" ]; do
+ [ -z "${repo}" ] && continue
+ total=$((total + 1))
+ # shellcheck disable=SC2310
+ add_workflow "${repo}" || {
+ failed=$((failed + 1))
+ printf 'FAILED: %s/%s\n' "${OWNER}" "${repo}" >&2
+ }
+done <"${REPOS}"
+
+printf '\nDone — %d total, %d ok, %d skipped, %d failed\n' \
+ "${total}" "${ok}" "${skipped}" "${failed}"
diff --git a/scripts/repos.txt b/scripts/repos.txt
new file mode 100644
index 0000000..2aacb30
--- /dev/null
+++ b/scripts/repos.txt
@@ -0,0 +1,94 @@
+F-Sy-H
+z-a-bin-gem-node
+z-a-patch-dl
+z-a-readurl
+z-a-rust
+z-a-submods
+z-a-meta-plugins
+zsh-navigation-tools
+declare-zsh
+zsh-unique-id
+zsh-diff-so-fancy
+zflai
+null
+zconvey
+zui
+z-a-man
+zsh
+z-a-unscope
+z-a-default-ice
+zi-crasis
+zi-vim-syntax
+zsh-lint
+zsh-github-issues
+zzcomplete
+zbrowse
+firefox-dev
+any-node
+any-gem
+apr
+doctoc
+asciidoctor
+ecs-cli
+fzf
+fzy
+github-issues-srv
+github-issues
+ls_colors
+pyenv
+z-a-test
+z-a-linkbin
+z-a-eval
+dircolors-material
+zi-rbenv
+ztrace
+zsnapshot
+zsh-select
+zsh-morpho
+zsh-log-lines
+zservice-py3http
+zsh-editing-workbench
+pm-perf-test
+system-completions
+subversion
+remark
+community
+zsh-tig-plugin
+zsh-string-lib
+zsh-util-lib
+zsh-cmd-architect
+zredis-cmd
+zredis
+zprompts
+zi-console
+playground
+zgdbm
+redis
+VATS
+zi
+H-S-MW
+src
+zsh-bin
+docs
+zsdoc
+vramsteg-zsh
+zd
+status
+.github
+z-shell.github.io
+zpmod
+zsh-eza
+brew-completions
+0
+wiki
+zsh-zoxide
+zsh-fancy-completions
+web
+zsh-web-artwork
+merch
+nb
+.github-private
+z-a-linkman
+zsh-lsd
+.trunk
+zunit
diff --git a/scripts/sync-labels.sh b/scripts/sync-labels.sh
new file mode 100755
index 0000000..95089c7
--- /dev/null
+++ b/scripts/sync-labels.sh
@@ -0,0 +1,92 @@
+#!/usr/bin/env sh
+# sync-labels.sh — Apply canonical Z-Shell label set to org repos.
+#
+# Usage:
+# ./sync-labels.sh [owner] [repos-file]
+#
+# Defaults:
+# owner = z-shell
+# repos-file = scripts/repos.txt (one repo name per line)
+#
+# Requirements:
+# - gh CLI authenticated with repo scope
+# - gh label create supports --force (gh >= 2.14)
+
+OWNER="${1:-z-shell}"
+REPOS="${2:-$(dirname "$0")/repos.txt}"
+
+if [ ! -f "${REPOS}" ]; then
+ printf 'Error: repos file not found: %s\n' "${REPOS}" >&2
+ exit 1
+fi
+
+apply_labels() {
+ repo="$1"
+ printf '=== %s/%s ===\n' "${OWNER}" "${repo}"
+
+ # ── Type labels ──────────────────────────────────────────────────────────────
+ gh label create "bug 🐛" --repo "${OWNER}/${repo}" --color d73a4a \
+ --description "Something isn't working" --force
+ gh label create "feature-request 💡" --repo "${OWNER}/${repo}" --color 0075ca \
+ --description "New feature or request" --force
+ gh label create "enhancement ✨" --repo "${OWNER}/${repo}" --color a2eeef \
+ --description "Improvement to existing functionality" --force
+ gh label create "documentation 📝" --repo "${OWNER}/${repo}" --color 0052cc \
+ --description "Documentation-only changes" --force
+ gh label create "performance 🚀" --repo "${OWNER}/${repo}" --color 006b75 \
+ --description "Performance improvements" --force
+ gh label create "security 🛡️" --repo "${OWNER}/${repo}" --color ee0701 \
+ --description "Security vulnerability or hardening" --force
+ gh label create "breaking-change 💥" --repo "${OWNER}/${repo}" --color d93f0b \
+ --description "Breaks backward compatibility" --force
+ gh label create "dependencies 📦" --repo "${OWNER}/${repo}" --color 0366d6 \
+ --description "Dependency updates" --force
+ gh label create "chore 🔧" --repo "${OWNER}/${repo}" --color c5def5 \
+ --description "Routine maintenance, no behavior change" --force
+ gh label create "refactor ♻️" --repo "${OWNER}/${repo}" --color 5319e7 \
+ --description "Code refactor, no behavior change" --force
+ gh label create "ci ⚙️" --repo "${OWNER}/${repo}" --color 1d76db \
+ --description "CI/CD pipeline changes" --force
+
+ # ── Status / workflow labels ─────────────────────────────────────────────────
+ gh label create "triage 🔍" --repo "${OWNER}/${repo}" --color fbca04 \
+ --description "Needs investigation before action" --force
+ gh label create "in-progress 🚧" --repo "${OWNER}/${repo}" --color f9d0c4 \
+ --description "Currently being worked on" --force
+ gh label create "blocked ⛔" --repo "${OWNER}/${repo}" --color e4e669 \
+ --description "Blocked on external dependency/decision" --force
+ gh label create "stale 💤" --repo "${OWNER}/${repo}" --color f2f2f2 \
+ --description "No activity for an extended period" --force
+ gh label create "invalid ⚠️" --repo "${OWNER}/${repo}" --color e4e669 \
+ --description "Off-topic, cannot reproduce, or incorrect" --force
+ gh label create "wontfix 🚫" --repo "${OWNER}/${repo}" --color ffffff \
+ --description "Will not be fixed" --force
+ gh label create "no-stale 🔒" --repo "${OWNER}/${repo}" --color 0e8a16 \
+ --description "Exempt from stale-bot closing" --force
+
+ # ── Community labels ─────────────────────────────────────────────────────────
+ gh label create "help-wanted 🙋" --repo "${OWNER}/${repo}" --color 008672 \
+ --description "Extra attention or expertise needed" --force
+ gh label create "good-first-issue 🌱" --repo "${OWNER}/${repo}" --color 7057ff \
+ --description "Good for newcomers" --force
+ gh label create "Q&A ✍️" --repo "${OWNER}/${repo}" --color d4c5f9 \
+ --description "Questions and answers" --force
+}
+
+# ── Main loop ────────────────────────────────────────────────────────────────
+total=0
+ok=0
+failed=0
+
+while IFS= read -r repo || [ -n "${repo}" ]; do
+ [ -z "${repo}" ] && continue
+ total=$((total + 1))
+ if apply_labels "${repo}"; then
+ ok=$((ok + 1))
+ else
+ failed=$((failed + 1))
+ printf 'FAILED: %s/%s\n' "${OWNER}" "${repo}" >&2
+ fi
+done <"${REPOS}"
+
+printf '\nDone — %d total, %d ok, %d failed\n' "${total}" "${ok}" "${failed}"
diff --git a/tests/installers.sh b/tests/installers.sh
new file mode 100755
index 0000000..5e58b13
--- /dev/null
+++ b/tests/installers.sh
@@ -0,0 +1,251 @@
+#!/usr/bin/env sh
+# -*- mode: sh; sh-indentation: 2; indent-tabs-mode: nil; sh-basic-offset: 2; -*-
+# vim: ft=sh sw=2 ts=2 et
+
+set -eu
+
+ROOT="$(
+ unset CDPATH
+ cd -- "$(dirname "$0")/.." && pwd
+)"
+TMP_ROOT="$(mktemp -d)" || exit 1
+trap 'rm -rf "${TMP_ROOT:?}"' EXIT INT TERM
+
+fail() {
+ printf '%s\n' "not ok - $*" >&2
+ exit 1
+}
+
+pass() {
+ printf '%s\n' "ok - $*"
+}
+
+contains() {
+ file="$1"
+ pattern="$2"
+ if ! grep -F "${pattern}" "${file}" >/dev/null 2>&1; then
+ printf '%s\n' "--- ${file} ---" >&2
+ if [ -f "${file}" ]; then
+ sed -n '1,120p' "${file}" >&2
+ else
+ printf '%s\n' "(missing)" >&2
+ fi
+ printf '%s\n' "--- end ${file} ---" >&2
+ fail "${file} does not contain: ${pattern}"
+ fi
+}
+
+sha256_file() {
+ file="$1"
+ if command -v sha256sum >/dev/null 2>&1; then
+ sha256sum "${file}" | awk '{print $1}'
+ elif command -v shasum >/dev/null 2>&1; then
+ shasum -a 256 "${file}" | awk '{print $1}'
+ else
+ fail "sha256sum or shasum is required"
+ fi
+}
+
+check_syntax() {
+ sh -n "${ROOT}/lib/sh/install.sh"
+ sh -n "${ROOT}/lib/sh/install_zpmod.sh"
+ sh -n "${ROOT}/lib/sh/sync-init.sh"
+ command -v zsh >/dev/null 2>&1 || fail "zsh is required for init.zsh syntax checks"
+ zsh -n "${ROOT}/lib/zsh/init.zsh"
+ pass "script syntax"
+}
+
+check_checksums() {
+ while read -r expected path; do
+ [ -n "${expected}" ] || continue
+ actual="$(sha256_file "${ROOT}/${path}")"
+ [ "${actual}" = "${expected}" ] || fail "checksum mismatch for ${path}"
+ done <"${ROOT}/lib/checksum.txt"
+ pass "checksums"
+}
+
+write_fake_tools() {
+ FAKE_BIN="${TMP_ROOT}/bin"
+ command mkdir -p "${FAKE_BIN}"
+
+ cat >"${FAKE_BIN}/curl" <<'EOF'
+#!/usr/bin/env sh
+set -eu
+
+out=""
+remote_name=0
+url=""
+
+while [ "$#" -gt 0 ]; do
+ case "$1" in
+ -o)
+ shift
+ out="${1:-}"
+ [ -n "${out}" ] || exit 64
+ shift
+ ;;
+ -*O*)
+ remote_name=1
+ shift
+ ;;
+ -*)
+ shift
+ ;;
+ *)
+ url="$1"
+ shift
+ ;;
+ esac
+done
+
+[ -n "${url}" ] || { printf '%s\n' "curl test double: missing URL" >&2; exit 64; }
+if [ -z "${out}" ] && [ "${remote_name}" -eq 1 ]; then
+ out="${url##*/}"
+fi
+
+case "${url}" in
+ */lib/zsh/init.zsh)
+ if [ -n "${out}" ]; then
+ cp "${ZI_SRC_TEST_ROOT}/lib/zsh/init.zsh" "${out}"
+ else
+ cat "${ZI_SRC_TEST_ROOT}/lib/zsh/init.zsh"
+ fi
+ ;;
+ */git-process-output.zsh)
+ [ -n "${out}" ] || out="git-process-output.zsh"
+ cat > "${out}" <<'SCRIPT'
+#!/usr/bin/env sh
+cat
+SCRIPT
+ chmod a+x "${out}"
+ ;;
+ */lib/sh/install_zpmod.sh)
+ [ -n "${out}" ] || { printf '%s\n' "curl test double: missing output path" >&2; exit 64; }
+ cat > "${out}" <<'SCRIPT'
+#!/usr/bin/env sh
+set -eu
+printf '%s\n' "zpmod fallback executed" > "${ZI_SRC_TEST_MARKER:?}"
+SCRIPT
+ chmod a+x "${out}"
+ ;;
+ *)
+ printf '%s\n' "curl test double: unexpected URL ${url}" >&2
+ exit 65
+ ;;
+esac
+EOF
+
+ cat >"${FAKE_BIN}/git" <<'EOF'
+#!/usr/bin/env sh
+set -eu
+
+cmd="${1:-}"
+[ "$#" -gt 0 ] && shift
+
+case "${cmd}" in
+ clone)
+ dest=""
+ for arg do
+ dest="${arg}"
+ done
+ [ -n "${dest}" ] || { printf '%s\n' "git test double: missing clone destination" >&2; exit 64; }
+ mkdir -p "${dest}/.git" "${dest}/lib"
+ printf '%s\n' '# fake zi.zsh' > "${dest}/zi.zsh"
+ printf '%s\n' '# fake _zi completion' > "${dest}/lib/_zi"
+ ;;
+ clean | reset | pull)
+ ;;
+ log)
+ printf '%s\n' 'abcdef0 - fake zi commit (now) '
+ ;;
+ *)
+ printf '%s\n' "git test double: unexpected command ${cmd}" >&2
+ exit 65
+ ;;
+esac
+EOF
+
+ command chmod a+x "${FAKE_BIN}/curl" "${FAKE_BIN}/git"
+}
+
+test_loader_install() {
+ home="${TMP_ROOT}/loader-home"
+ config="${TMP_ROOT}/loader-config"
+ data="${TMP_ROOT}/loader-data"
+ command mkdir -p "${home}" "${config}" "${data}"
+
+ HOME="${home}" \
+ ZDOTDIR="${home}" \
+ XDG_CONFIG_HOME="${config}" \
+ XDG_DATA_HOME="${data}" \
+ ZI_SRC_TEST_ROOT="${ROOT}" \
+ PATH="${FAKE_BIN}:${PATH}" \
+ sh "${ROOT}/lib/sh/install.sh" -a loader -b feature/test >/dev/null
+
+ # shellcheck disable=SC2016
+ contains "${config}/zi/init.zsh" ': ${ZI[STREAM]:="feature/test"}'
+ # shellcheck disable=SC2016
+ contains "${home}/.zshrc" 'source "${XDG_CONFIG_HOME:-${HOME}/.config}/zi/init.zsh" && zzinit'
+ [ -f "${data}/zi/bin/zi.zsh" ] || fail "loader install did not clone Zi into XDG data home"
+ pass "loader install uses XDG paths and branch override"
+}
+
+test_standalone_zpmod_delegation() {
+ standalone_dir="${TMP_ROOT}/standalone"
+ home="${TMP_ROOT}/zpmod-home"
+ data="${TMP_ROOT}/zpmod-data"
+ marker="${TMP_ROOT}/zpmod-marker"
+ command mkdir -p "${standalone_dir}" "${home}" "${data}"
+ command cp "${ROOT}/lib/sh/install.sh" "${standalone_dir}/install.sh"
+
+ HOME="${home}" \
+ ZDOTDIR="${home}" \
+ XDG_DATA_HOME="${data}" \
+ ZI_SRC_TEST_ROOT="${ROOT}" \
+ ZI_SRC_TEST_MARKER="${marker}" \
+ PATH="${FAKE_BIN}:${PATH}" \
+ sh "${standalone_dir}/install.sh" -a zpmod -i skip >/dev/null
+
+ contains "${marker}" 'zpmod fallback executed'
+ pass "standalone install.sh fetches zpmod helper"
+}
+
+test_sync_init() {
+ local_file="${TMP_ROOT}/local-init.zsh"
+ remote_file="${TMP_ROOT}/remote-init.zsh"
+ checksum_file="${TMP_ROOT}/checksum.txt"
+
+ printf '%s\n' '# remote init fixture' >"${remote_file}"
+ command cp "${remote_file}" "${local_file}"
+ remote_hash="$(sha256_file "${remote_file}")"
+ printf '%s %s\n' "${remote_hash}" 'lib/zsh/init.zsh' >"${checksum_file}"
+
+ sh "${ROOT}/lib/sh/sync-init.sh" \
+ --local "${local_file}" \
+ --remote "${remote_file}" \
+ --checksum-url "${checksum_file}" >/dev/null
+
+ printf '%s\n' '# stale init fixture' >"${local_file}"
+ if sh "${ROOT}/lib/sh/sync-init.sh" \
+ --local "${local_file}" \
+ --remote "${remote_file}" \
+ --checksum-url "${checksum_file}" >/dev/null 2>&1; then
+ fail "sync-init mismatch check unexpectedly succeeded"
+ fi
+
+ sh "${ROOT}/lib/sh/sync-init.sh" \
+ --write \
+ --local "${local_file}" \
+ --remote "${remote_file}" \
+ --checksum-url "${checksum_file}" >/dev/null
+
+ cmp -s "${local_file}" "${remote_file}" || fail "sync-init --write did not replace local file"
+ pass "sync-init fixtures"
+}
+
+check_syntax
+check_checksums
+write_fake_tools
+test_loader_install
+test_standalone_zpmod_delegation
+test_sync_init