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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
165 changes: 133 additions & 32 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,50 +1,93 @@
name: CI

# 迭代分层(详见 docs/ITERATION.md):
# - pull_request → 只跑 quick(~1 分钟):PR 反馈快,重矩阵留给 main
# - push:main → quick + 重矩阵(仅当有代码改动时;纯文档/站点提交自动跳过)
# - workflow_dispatch → 手动选 fast / full,用于「只跑一次快速迭代」
on:
push:
branches: [main]
pull_request:
branches: [main]
workflow_dispatch:
inputs:
scope:
description: 'fast = 只跑快速门禁(约 1 分钟);full = 全量平台矩阵'
type: choice
options: [fast, full]
default: fast

jobs:
shell-lint:
name: Shell lint (${{ matrix.os }})
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest]
permissions:
contents: read

# 单次迭代只占一条流水线:同分支新提交立刻取消仍在跑的旧运行
concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true

env:
GO_VERSION: '1.24'

jobs:
changes:
name: Changed paths
runs-on: ubuntu-latest
timeout-minutes: 5
outputs:
code: ${{ steps.filter.outputs.code }}
steps:
- name: Checkout
uses: actions/checkout@v5

- name: Cache Tools
uses: actions/cache@v5
with:
path: |
/usr/local/bin/shellcheck
/usr/local/bin/shfmt
~/go/bin/shfmt
key: ${{ runner.os }}-easywork-shell-tools-${{ hashFiles('.github/workflows/ci.yml') }}
fetch-depth: 0

- name: Install ShellCheck
- name: Detect code changes
id: filter
shell: bash
run: |
if command -v brew >/dev/null 2>&1; then
brew install shellcheck
set -euo pipefail
base=""
if [[ "${GITHUB_EVENT_NAME}" == "pull_request" ]]; then
base="${{ github.event.pull_request.base.sha }}"
else
base="${{ github.event.before }}"
fi
# 基线不可用(首次推送、force push、手动触发)→ 按"有代码改动"处理
if [[ -z "$base" || "$base" =~ ^0+$ ]] || ! git cat-file -e "${base}^{commit}" 2> /dev/null; then
echo "基线不可用,按有代码改动处理"
echo "code=true" >> "$GITHUB_OUTPUT"
exit 0
fi
changed="$(git diff --name-only "$base" HEAD)"
printf '改动的文件:\n%s\n' "$changed"
# 纯 docs/site/markdown 提交不触发重矩阵
if printf '%s\n' "$changed" | grep -Eqv '^(docs/|site/|.*\.md$|LICENSE$)'; then
echo "code=true" >> "$GITHUB_OUTPUT"
else
sudo apt-get update -y && sudo apt-get install -y shellcheck
echo "code=false" >> "$GITHUB_OUTPUT"
fi

- name: Install shfmt
# ── 快车道:每次迭代必过,目标 < 2 分钟 ─────────────────────
quick:
name: Quick gate (${{ github.event_name }})
runs-on: ubuntu-latest
needs: changes
timeout-minutes: 10
steps:
- name: Checkout
uses: actions/checkout@v5

- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: ${{ env.GO_VERSION }}
cache: false

- name: Install shell tools
shell: bash
run: |
if command -v brew >/dev/null 2>&1; then
brew install shfmt
else
sudo apt-get install -y shfmt || go install mvdan.cc/sh/v3/cmd/shfmt@v3.10.0
fi
sudo apt-get update -y
sudo apt-get install -y shellcheck shfmt

# Bash 引擎已于 0.4.0 退役;仅引导/辅助脚本保留 shell 检查。
- name: ShellCheck(bootstrap + scripts)
Expand All @@ -63,9 +106,60 @@ jobs:
shell: bash
run: shfmt -d -i 4 -bn -ci -sr bootstrap.sh scripts

- name: gofmt + go vet
shell: bash
run: |
set -euo pipefail
unformatted="$(gofmt -l ./cmd ./internal)"
if [[ -n "$unformatted" ]]; then
echo "需要 gofmt:"
echo "$unformatted"
exit 1
fi
go vet ./...

- name: Go unit tests
shell: bash
run: go test ./...

- name: Install BATS
shell: bash
run: |
git clone --depth 1 https://github.com/bats-core/bats-core.git "$RUNNER_TEMP/bats"
sudo "$RUNNER_TEMP/bats/install.sh" /usr/local

- name: Conformance (Go)
shell: bash
run: tests/conformance/run.sh go

# ── 重矩阵:仅 main 推送 / 手动 full,且确有代码改动 ────────
shell-lint:
name: Shell lint (macos-latest)
runs-on: macos-latest
needs: [changes, quick]
if: needs.changes.outputs.code == 'true' && github.event_name != 'pull_request' && inputs.scope != 'fast'
timeout-minutes: 10
steps:
- name: Checkout
uses: actions/checkout@v5

- name: Install shell tools
shell: bash
run: brew install shellcheck shfmt

- name: ShellCheck + shfmt(macOS 自带 bash 3.2 语义)
shell: bash
run: |
set -euo pipefail
shellcheck bootstrap.sh scripts/*.sh
shfmt -d -i 4 -bn -ci -sr bootstrap.sh scripts

install-e2e:
name: Install chain e2e (${{ matrix.os }})
runs-on: ${{ matrix.os }}
needs: [changes, quick]
if: needs.changes.outputs.code == 'true' && github.event_name != 'pull_request' && inputs.scope != 'fast'
timeout-minutes: 30
strategy:
fail-fast: false
matrix:
Expand All @@ -78,7 +172,7 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: '1.24'
go-version: ${{ env.GO_VERSION }}
cache: false

# 安装入口必须有自动化门禁;所有链路只访问 127.0.0.1 的本地镜像,不触网。
Expand Down Expand Up @@ -110,10 +204,14 @@ jobs:
go-engine:
name: Go engine (${{ matrix.os }})
runs-on: ${{ matrix.os }}
needs: [changes, quick]
if: needs.changes.outputs.code == 'true' && github.event_name != 'pull_request' && inputs.scope != 'fast'
timeout-minutes: 20
strategy:
fail-fast: false
# ubuntu 已由 quick 覆盖(单测 + conformance),这里只补平台差异
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
os: [macos-latest, windows-latest]

steps:
- name: Checkout
Expand All @@ -122,7 +220,7 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: '1.24'
go-version: ${{ env.GO_VERSION }}
cache: false

- name: Go unit tests
Expand All @@ -148,7 +246,7 @@ jobs:
if: runner.os != 'Windows'
shell: bash
run: |
if command -v brew >/dev/null 2>&1; then
if command -v brew > /dev/null 2>&1; then
brew install bats-core
else
git clone --depth 1 https://github.com/bats-core/bats-core.git /tmp/bats
Expand Down Expand Up @@ -187,19 +285,22 @@ jobs:
"$RUNNER_TEMP/ew.exe" config set git.identities.personal.name 'Win User'
"$RUNNER_TEMP/ew.exe" config get git.identities.personal.name | grep -q 'Win User'
"$RUNNER_TEMP/ew.exe" install shell git --yes --offline
grep -q 'managed section' "$RUNNER_TEMP/home/.sh_config_custom" 2>/dev/null || grep -rq 'managed section' "$RUNNER_TEMP/home/Documents" 2>/dev/null
grep -q 'managed section' "$RUNNER_TEMP/home/.sh_config_custom" 2> /dev/null || grep -rq 'managed section' "$RUNNER_TEMP/home/Documents" 2> /dev/null

cross-compile:
name: Cross-compile (6 targets)
runs-on: ubuntu-latest
needs: [changes, quick]
if: needs.changes.outputs.code == 'true' && github.event_name != 'pull_request' && inputs.scope != 'fast'
timeout-minutes: 10
steps:
- name: Checkout
uses: actions/checkout@v5

- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: '1.24'
go-version: ${{ env.GO_VERSION }}
cache: false

- name: Build darwin/linux/windows x amd64/arm64
Expand Down
14 changes: 9 additions & 5 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@
## 3. 常用命令

```bash
make check # lint + fmt-check + gotest + conformance + 安装链路(提交前必跑)
make quick # 快速门禁(< 1 分钟:shfmt 检查 + go test + conformance;提交前跑这个)
make check # 完整质量门禁(lint + fmt-check + gotest + conformance + 安装链路)
make gotest # go test ./...
make test # 黑盒 conformance(tests/conformance,仅 Go)
make lint # ShellCheck(bootstrap.sh + scripts/)
Expand Down Expand Up @@ -77,6 +78,7 @@ bootstrap.sh|.ps1 # 下载并安装二进制(唯一安装入口)
| `README.md` | 用户入口:安装、命令、模块、配置、防休眠 |
| `docs/spec/` | **规范性契约**(行为等价判据),改行为先改这里 |
| `docs/ACCEPTANCE.md` | 验收项与 CI 对应关系 |
| `docs/ITERATION.md` | **迭代流程**:分层门禁、单次快速迭代、提交/发布规范 |
| `docs/RETIREMENT.md` / `docs/ROLLBACK.md` | 退役记录与回滚方式 |
| `DESIGN.md` | 设计决策与迁移历史(**非规范**,冲突以 `docs/spec/` 为准) |
| `site/` | 产品页(GitHub Pages,`https://easywork.jokerhub.cn/`) |
Expand All @@ -101,8 +103,10 @@ bootstrap.sh|.ps1 # 下载并安装二进制(唯一安装入口)
3. **新增模块**:`internal/modules/xxx.go` → 加入 `registry`(字母序)→ 补 `*_test.go`
→ 视需要补 `tests/conformance/*.bats`。
4. **目标文件写入**只允许经 engine 的声明式函数,禁止模块手写备份/标记逻辑。
5. 新增功能必须补测试;改动后先跑 `make check`。
5. 新增功能必须补测试;改动后先跑 `make quick`,提交前如需完整验证再跑 `make check`。
6. **不要恢复 Bash 引擎**;`make retire-check` 会拦。
7. **依赖宿主环境的诊断项**(`pmset`/`powercfg`/电源/时间等)的 conformance 用例必须
注入假命令固定输出,不得断言宿主真实状态(否则会像 GitHub macOS 虚拟机那样长期红灯)。

## 9. 测试约定

Expand All @@ -115,9 +119,9 @@ bootstrap.sh|.ps1 # 下载并安装二进制(唯一安装入口)
解析无 BOM 脚本(脚本内有自检,CI 也有断言)
- Go 单测:纯函数与可注入依赖(`Runner`/`Clock` 等),不得改动宿主真实状态
(如 keepawake 测试注入 fake runner,不真的起 caffeinate/launchctl)
- CI:`shell-lint`(shellcheck+shfmt)、`install-e2e`(三平台:Unix 跑 `bootstrap-e2e.sh`,
Windows 跑 `bootstrap-e2e.ps1`)、`go-engine`(三平台 go test + conformance + Windows smoke)、
`cross-compile`(6 目标)
- CI:分两层——`quick`(每次 push/PR:shell 检查 + gofmt/vet + go test + conformance,
~1–2 分钟)与重矩阵(仅 main 推送且确有代码改动:macOS ShellCheck、macOS/Windows
conformance、三平台安装链路、6 目标交叉编译)。PR 只跑快车道,流程见 `docs/ITERATION.md`
- Windows 安装链路在 CI 上跑**两个宿主**:Windows PowerShell 5.1 与 PowerShell 7
(`-ChildShell powershell|pwsh`),并额外覆盖文档里的 `irm | iex` 用法
- `scripts/bootstrap-e2e.ps1` 必须保持**纯 ASCII**:Windows PowerShell 5.1 会按 ANSI
Expand Down
21 changes: 20 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,26 @@

## [Unreleased]

(暂无)
### 修复

- **`ew doctor` 在台式机 / 虚拟机上丢失 `sleep-policy` 检查**:原实现要求 `pmset -g custom`
同时提供 AC 与电池两个分支,否则整项跳过;Mac mini/Studio/iMac 与 CI 虚拟机只有
`AC Power:` 分支,因此这项诊断从未生效(GitHub macOS 虚拟机上的 CI 一直因此红灯)。
现在按分支是否存在分别诊断并回显(`AC` 在前、`Battery` 在后),`sleep=0` 仍不判为矛盾
- **conformance 的 `sleep-policy` 用例改为注入假 `pmset`**:原先直接断言宿主 `pmset` 输出,
随 CI 机型漂移而失败;现覆盖「两侧」「仅 AC(台式机)」「矛盾组合」三组确定性用例

### 变更

- **CI 分层 + 单次快速迭代**:新增 `Changed paths` 判定与 `Quick gate`(Ubuntu,~1–2 分钟,
含 ShellCheck / `bash -n` / shfmt / `gofmt` / `go vet` / `go test` / conformance);
PR 只跑快车道,重矩阵(macOS ShellCheck、macOS/Windows conformance + Windows 原生冒烟、
三平台安装链路 e2e、6 目标交叉编译)仅在 `main` 推送且确有代码改动时运行,
纯 docs/site/markdown 提交自动跳过;`workflow_dispatch` 支持 `scope=fast|full`;
同分支新推送取消旧运行;重矩阵 `needs: quick`,红灯不再浪费平台机时
- 新增 `make quick`(本地 < 1 分钟门禁)与 `docs/ITERATION.md`(分层门禁、单次快速迭代、
提交/分支/发布规范);`CONTRIBUTING.md`、`AGENTS.md`、`docs/ACCEPTANCE.md` 同步
- conformance 用例数 71 → 73

## [0.4.4] - 2026-10-23

Expand Down
18 changes: 13 additions & 5 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,22 @@
## 提 Pull Request

1. Fork 本仓库
2. 创建特性分支:
2. 创建短生命周期分支(`feat/…`、`fix/…`、`docs/…`、`ci/…`):
```bash
git checkout -b feature/my-feature
git switch -c fix/my-fix
```
3. 开发并确保质量通过:
3. 开发并确保快速门禁通过(< 1 分钟):
```bash
make check # ShellCheck + shfmt + go test + conformance + 安装链路
make quick # shfmt 检查 + go test + conformance
```
4. 提交 PR 到 `main` 分支
需要完整验证时跑 `make check`(外加 ShellCheck 与安装链路 e2e)。
4. 提交到特性分支并创建 PR 到 `main`:
```bash
git commit -m 'fix(doctor): 一句话说明改了什么、为什么'
git push -u origin HEAD && gh pr create --fill
```
5. PR 上 CI 只跑**快车道**(约 2 分钟);合并到 `main` 后自动跑 macOS/Windows 矩阵
与安装链路。提交规范与发布流程见 [docs/ITERATION.md](docs/ITERATION.md)。

## 开发环境

Expand Down Expand Up @@ -49,6 +56,7 @@ sudo /tmp/bats/install.sh /usr/local
| `make test` | 黑盒 conformance(`tests/conformance`,只测 Go) |
| `make lint` / `make fmt` / `make fmt-check` | shell 脚本检查与格式化 |
| `make check` | 完整质量检查(含安装链路 e2e) |
| `make quick` | 快速门禁(< 1 分钟,提交前用) |
| `make install-e2e` | bootstrap + update 本地镜像端到端(不触网) |
| `make acceptance` | 一键验收(含交叉编译、退役不变量) |
| `make retire-check` | 断言 Bash 引擎没有复活 |
Expand Down
5 changes: 4 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
.PHONY: lint test fmt fmt-check check check-all install clean help dry-run conform build gotest acceptance update-e2e bootstrap-e2e install-e2e retire-check site
.PHONY: lint test fmt fmt-check check check-all quick install clean help dry-run conform build gotest acceptance update-e2e bootstrap-e2e install-e2e retire-check site

# ── EasyWork Makefile ──────────────────────────────────────────
#
Expand Down Expand Up @@ -52,6 +52,9 @@ endif
check: lint fmt-check gotest test install-e2e ## Run all checks (shell + Go + conformance + 安装链路)
@echo "✅ All checks passed"

quick: fmt-check gotest test ## 快速门禁(< 1 分钟:shell 格式 + 单测 + conformance;提交前必跑)
@echo "✅ Quick gate passed(发 PR 前跑 quick 即可;重矩阵由 CI 在 main 上跑)"

check-all: check ## 兼容旧习惯:check-all 等同 check

build: ## Build the Go binary into .build/ew
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ Windows 上 `--json` 恒为 UTF-8;人类可读输出在被管道/重定向时

```bash
make check # ShellCheck + shfmt + go test + conformance + 安装链路
make test # 黑盒 conformance(tests/conformance,71 用例)
make test # 黑盒 conformance(tests/conformance,73 用例)
make install # 安装到 PATH
make site # 本地预览产品页 http://127.0.0.1:8765/
make acceptance # 全量验收(含交叉编译与退役不变量)
Expand Down
Loading
Loading