Skip to content
Closed
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
9 changes: 9 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Windows launcher scripts MUST be CRLF (rant 2026-08-12T12:30:41).
# LF-only .cmd files are misparsed by cmd.exe: the whole file is joined into
# one serial command stream (@echo off ignored, PATH collisions produce
# arbitrary errors, exit code non-zero) → the installer aborts with
# "stop-emrg.cmd exit code 1". .ps1 is PowerShell (LF-tolerant) but kept CRLF
# for consistency. Git normalizes checkout to CRLF on every platform.
*.cmd text eol=crlf
*.bat text eol=crlf
*.ps1 text eol=crlf
87 changes: 85 additions & 2 deletions .github/workflows/build-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,15 @@ name: Build Release
#
# matrix:
# macos-15(arm64) → EMRG-<ver>-macos-arm64.pkg
# ubuntu-24.04(x86_64) → EMRG-<ver>-linux-x86_64.AppImage + tar.gz
# ubuntu-24.04-arm(aarch64) → EMRG-<ver>-linux-aarch64.AppImage + tar.gz
# ubuntu-24.04(x86_64) → EMRG-<ver>-linux-x86_64.AppImage + tar.gz + .run
# ubuntu-24.04-arm(aarch64) → EMRG-<ver>-linux-aarch64.AppImage + tar.gz + .run
# windows-2025(x64) → EMRG-<ver>-windows-x64.exe
#
# 冒烟在无 Python runner 上跑(R111 验证干净机器承诺);离线冒烟(R96)
# 在 Linux job 用 iptables 断网执行。
#
# .run = Linux 无头服务器离线一键安装器(rant 2026-08-17T10:16:54):
# bash 自解压头 + runtime tarball payload,零网络零依赖。

on:
push:
Expand Down Expand Up @@ -153,6 +156,65 @@ jobs:
shell: bash
run: bash packaging/build-runtime.sh

# ── CRLF 断言(rant 2026-08-13T09:44:32)────────────────
# git blob 的 *.cmd 是 LF,工作区 CRLF 靠 .gitattributes checkout 转换;
# 若 CI 检出未应用 .gitattributes,build-runtime 拷到的就是 LF →
# cmd.exe 整文件串行拼接 → 安装器 "stop-emrg.cmd exit code 1"。
# build-runtime.sh 已强制转 CRLF(R126),此步双保险验证产物确实是纯 CRLF。
- name: Verify runtime *.cmd are pure CRLF
shell: bash
run: |
set -euo pipefail
for f in dist/runtime/bin/emrg.cmd dist/runtime/bin/emrgd.cmd dist/runtime/bin/stop-emrg.cmd; do
[ -f "$f" ] || continue
cr=$(tr -cd '\r' < "$f" | wc -c)
lf=$(tr -cd '\n' < "$f" | wc -c)
if [ "$cr" -ne "$lf" ] || [ "$lf" -eq 0 ]; then
echo "::error::$f has bare-LF line(s) ($((lf-cr)) of $lf) — cmd.exe misparses LF → installer exit 1"
exit 1
fi
echo "OK $f: pure CRLF ($lf lines)"
done

# ── 图标资产生成(rant 2026-08-11T18:28:09:仓库只保留 icon.svg 设计源,
# png/icns/ico 由 CI 构建时现生成,必须先于 GUI 构建执行)──
- name: Generate icon assets (icon.svg → png/icns/ico)
shell: bash
run: |
set -euo pipefail
if command -v rsvg-convert >/dev/null 2>&1; then
echo "rsvg-convert already available"
elif [ "$RUNNER_OS" = "Linux" ]; then
# v0.2.55 lesson: a hung `sudo apt-get update`/install on a slow or
# network-starved runner blocks the job forever (no step timeout).
# Guard both with `timeout`; on failure gen-assets.sh falls back to
# Chrome headless (now with --no-sandbox + its own timeout).
timeout 180 sudo apt-get update -qq \
|| echo "apt-get update timed out/failed — falling back to Chrome headless"
timeout 180 sudo apt-get install -y -qq librsvg2-bin \
|| echo "apt-get install failed — falling back to Chrome headless"
elif [ "$RUNNER_OS" = "macOS" ]; then
brew install librsvg
else
# Windows: try to install librsvg (best effort); the fixed Chrome/Edge
# headless HTML-wrapper path in gen-assets.sh is the fallback
# (rant 2026-08-12T17:25:28: raw SVG screenshot was fully transparent).
if command -v choco >/dev/null 2>&1 && choco install -y librsvg >/dev/null 2>&1; then
echo "choco installed librsvg"
elif command -v winget >/dev/null 2>&1 && winget install --silent --accept-package-agreements librsvg >/dev/null 2>&1; then
echo "winget installed librsvg"
else
echo "no rsvg-convert; gen-assets.sh will fall back to Chrome/Edge headless (HTML wrapper)"
fi
fi
bash packaging/gen-assets.sh
# 产物必须存在(GUI 构建引用 ../packaging/assets/*.png/icns/ico)
test -s packaging/assets/icon.png
if [ "$RUNNER_OS" = "macOS" ]; then
test -s packaging/assets/icon.icns
fi
test -s packaging/assets/icon.ico

# ── GUI dist(electron-builder;linux extraResources runtime §5 R64)──
- name: Build GUI (electron-builder)
working-directory: emrg/gui
Expand Down Expand Up @@ -180,6 +242,25 @@ jobs:
shell: bash
run: bash packaging/make-installer.sh

# ── zip 安装包(Windows only,rant 2026-08-10T20:10:41)──
# 宿主反馈:除 exe 安装包外再提供 zip 压缩版(内容 = 同名 exe),
# 方便直接解压使用/分发。压缩后产物 EMRG-<ver>-windows-x64.zip
# 走下方 Upload artifacts(glob 需含 *.zip)。
- name: Zip installer (Windows only)
if: runner.os == 'Windows'
shell: bash
run: |
cd dist/artifacts
EXE="$(find . -maxdepth 1 -name 'EMRG-*-windows-x64.exe' | head -1)"
if [ -z "$EXE" ]; then
echo "::error::no EMRG-*-windows-x64.exe found in dist/artifacts"
exit 1
fi
ZIP="${EXE%.exe}.zip"
powershell -NoProfile -Command "Compress-Archive -Force -Path \"$EXE\" -DestinationPath \"$ZIP\""
ls -la
test -s "$ZIP"

# ── pkg 签名(macOS only,P2;Secret 未配则跳过降级)──
# ⚠️ productsign 需要 Developer ID Installer 身份(非 Application 身份)——
# 实测 #462:MACOS_SIGNING_IDENTITY 配 Application 身份报
Expand Down Expand Up @@ -284,6 +365,8 @@ jobs:
dist/artifacts/*.exe
dist/artifacts/*.AppImage
dist/artifacts/*.tar.gz
dist/artifacts/*.zip
dist/artifacts/*.run

release:
needs: build
Expand Down
53 changes: 53 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,56 @@ jobs:
[ -f "$f" ] || continue
node --check "$f" || exit 1
done

# v0.2.29 教训:Windows pytest 此前只在 tag 触发的 Build Release 跑
# (test.yml 仅 ubuntu)——FakeGitRun git.EXE 大小写 bug(#723)等 Windows-only
# 测试回归要到发版构建才暴露。每个 PR 都应在 Windows 上跑 pytest 门禁。
test-windows:
runs-on: windows-2025
steps:
- uses: actions/checkout@v5
- uses: astral-sh/setup-uv@v5
with:
python-version: "3.13"
- run: uv sync
- name: Python tests (Windows)
run: uv run pytest tests/ -v
# v0.2.30 教训(Build Release 31661378619):Test 全绿 ≠ .iss 能编译。
# make-installer.sh 的 emrg.iss 只在 tag 触发 Build Release 时经 iscc 编译,
# #727 误用 LoadStringFromFile 单参数形式(Inno 真实签名是 2 参数 out-param)
# → iscc "Invalid number of parameters" 发版才暴露。此处用 runner 预装的
# iscc 编译渲染出的 emrg.iss(stub payload),PR CI 即拦截 .iss 语法/签名错误。
- name: Inno Setup script compile smoke test
shell: bash
run: |
set -euo pipefail
STAGE="$(mktemp -d)"
mkdir -p "$STAGE/payload/bin" "$STAGE/dist/artifacts"
touch "$STAGE/payload/bin/stop_all.py"
# icon.ico 是 gen-assets 产物(未入库)——生成最小合法 .ico 供 iscc 编译期
# SetupIconFile 检查;{app}(={%USERPROFILE}\.emrg\install)引用的文件
# (UninstallDisplayIcon/[Icons]/[UninstallRun])也需存在。
uv run python - <<'PY'
import struct
def write_ico(path):
hdr = struct.pack('<HHH', 0, 1, 1)
entry = struct.pack('<BBBBHHII', 1, 1, 0, 0, 1, 32, 44, 22)
bih = struct.pack('<IiiHHIIiiII', 40, 1, 1, 1, 32, 0, 4, 0, 0, 0, 0)
open(path, 'wb').write(hdr + entry + bih + b'\x00\x00\x00\x00')
write_ico('packaging/assets/icon.ico')
PY
APP="$USERPROFILE/.emrg/install"
mkdir -p "$APP/bin" "$APP/emrg-gui/EMRG"
touch "$APP/bin/emrg.cmd" "$APP/bin/python.exe" "$APP/emrg-gui/EMRG/EMRG.exe"
# 渲染 make-installer.sh 的 emrg.iss heredoc(变量展开与真实构建一致)。
# 先赋值再统一 export(单行 export 内自引用触发 shellcheck SC2318/SC2097/SC2098)。
VERSION="0.0.0-smoke"
DIST_WIN="$(cygpath -m "$STAGE/dist")"
STAGE_WIN="$(cygpath -m "$STAGE")"
ROOT_WIN="$(cygpath -m "$PWD")"
export VERSION DIST_WIN STAGE_WIN ROOT_WIN STAGE
# 单引号内避免 \$ 字面量(shellcheck SC2016),用无 $ 的锚定模式
sed -n '/cat > .*emrg\.iss.*<</,/^EOF$/p' packaging/make-installer.sh > "$STAGE/gen.sh"
bash "$STAGE/gen.sh"
command -v iscc >/dev/null 2>&1 || { echo "::error::iscc not on PATH (runner image regression)"; exit 1; }
iscc "$STAGE/emrg.iss"
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,10 @@ node_modules/
.pytest_cache/
/dist/
/emrg/gui/dist/
# icon products generated at build time from packaging/assets/icon.svg (design source)
# (rant 2026-08-11T18:28:09: repo keeps only the SVG; png/icns/ico generated by gen-assets.sh)
packaging/assets/icon.png
packaging/assets/icon-512.png
packaging/assets/icon-256.png
packaging/assets/icon.icns
packaging/assets/icon.ico
Loading