diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..2c225076 --- /dev/null +++ b/.gitattributes @@ -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 diff --git a/Agent.md b/Agent.md index 254a12d2..620e8730 100644 --- a/Agent.md +++ b/Agent.md @@ -112,7 +112,7 @@ Community needs voiced in HN agent-UI discussions map directly to EMRG's design: pkill -f "emrg.server"; rm -f ~/.emrg/emrgd.port; python -m emrg ``` -Python: `uv run pytest tests/ -v` (729) — import check: `uv run python -c "from emrg.client.app import run_client"` +Python: `uv run pytest tests/ -v` (730) — import check: `uv run python -c "from emrg.client.app import run_client"` GUI: `cd emrg/gui && npm test` (221: 43 daemon_client + 19 conn-manager + 22 app-commands + 100 renderer smoke + 15 i18n + 7 integration + 3 commands + 5 build-config + 7 gui-state) — syntax: `node --check main.js preload.js daemon_client.js renderer/js/*.js` CI: `uv run pytest` + GUI tests + **actionlint workflow lint** (`rhysd/actionlint@v1.7.12` gate, #444 — workflow 解析错误在 PR CI 即失败,如 `if:` secrets 上下文) Re-trigger: `scripts/re-trigger-ci.sh [branch]` (workflow_dispatch, #527 — 替代空 commit 重触发:Actions outage 会整段丢弃 push 事件,dispatch 走 API 路径不受影响) diff --git a/bin/stop-git.ps1 b/bin/stop-git.ps1 index 4b57f0ee..dee4c175 100644 --- a/bin/stop-git.ps1 +++ b/bin/stop-git.ps1 @@ -32,4 +32,11 @@ Get-CimInstance Win32_Process | Start-Sleep -Milliseconds 300 $left = @(Get-CimInstance Win32_Process | Where-Object { $_.ExecutablePath -like $prefix }) -if ($left.Count -gt 0) { exit 1 } else { exit 0 } +if ($left.Count -gt 0) { + # Truthful failure (rant 2026-08-12T12:30:41): never report success while a + # process still holds the prefix. Name the survivors so the installer can + # show a useful message instead of a silent bogus exit=0. + $left | ForEach-Object { Write-Host ("still running: {0} (pid {1})" -f $_.Name, $_.ProcessId) } + exit 1 +} +exit 0 diff --git a/tests/test_cmd_crlf.py b/tests/test_cmd_crlf.py new file mode 100644 index 00000000..e057713e --- /dev/null +++ b/tests/test_cmd_crlf.py @@ -0,0 +1,39 @@ +"""Guard: Windows batch/PowerShell files MUST be CRLF (rant 2026-08-12T12:30:41). + +Root cause of the 0.2.25/0.2.26/0.2.27 installer "exit code 1" series: the +repo's bin/*.cmd were LF-only, so cmd.exe misparsed them (whole file joined +serially → @echo off ignored → arbitrary commands executed → non-zero exit +→ installer aborted). .gitattributes (`*.cmd/*.bat/*.ps1 text eol=crlf`) +enforces CRLF at checkout; this test verifies the working tree never +regresses (a future PR that adds a LF-only .cmd fails CI). +""" + +import subprocess +from pathlib import Path + +REPO_ROOT = Path(__file__).resolve().parent.parent +WIN_SCRIPT_EXTS = (".cmd", ".bat", ".ps1") + + +def _win_scripts(): + out = subprocess.check_output(["git", "ls-files"], cwd=str(REPO_ROOT), text=True) + for rel in out.splitlines(): + if rel.lower().endswith(WIN_SCRIPT_EXTS): + yield REPO_ROOT / rel + + +def test_windows_scripts_are_crlf(): + files = list(_win_scripts()) + assert files, "expected at least one tracked .cmd/.bat/.ps1 file" + bad = [] + for p in files: + data = p.read_bytes() + crlf = data.count(b"\r\n") + lf = data.count(b"\n") + if crlf != lf: # bare-LF lines present (LF-only or mixed endings) + bad.append(f"{p.relative_to(REPO_ROOT)}: {lf - crlf} bare-LF line(s)") + assert not bad, ( + "Windows scripts must use CRLF line endings (LF-only .cmd files are " + "misparsed by cmd.exe → installer 'exit code 1'; see rant " + "2026-08-12T12:30:41):\n" + "\n".join(bad) + ) diff --git a/tests/test_installer_stop.py b/tests/test_installer_stop.py index 6d742a9a..fbfeeffd 100644 --- a/tests/test_installer_stop.py +++ b/tests/test_installer_stop.py @@ -93,7 +93,11 @@ def test_stop_git_ps1_guilt_by_association(): assert "Stop-Process -Id $_.ProcessId -Force" in content # 退出码语义:无残留(或本来无进程)→ 0;仍有存活 → 1 assert "exit 1" in content and "exit 0" in content - assert 'if ($left.Count -gt 0) { exit 1 } else { exit 0 }' in content + # rant 2026-08-12T12:30:41(spec B):存活检查用最新快照 + 如实报失败—— + # 幸存进程必须点名输出(不静默吞掉),有存活即 exit 1,绝不误报成功 + assert 'if ($left.Count -gt 0) {' in content + assert "still running: {0} (pid {1})" in content + assert "Write-Host" in content # 独立脚本 + 非交互(无 cmd 内联转义问题,0.2.26 转义 bug 根因规避) assert "$ErrorActionPreference = 'SilentlyContinue'" in content