Skip to content

Commit 6a681a4

Browse files
authored
Merge pull request #83 from 0xmariowu/feature/windows-support
fix: Windows install support (#82)
2 parents c2bb676 + eeb8a53 commit 6a681a4

10 files changed

Lines changed: 735 additions & 10 deletions

File tree

.gitattributes

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# Force LF line endings for source files so Windows checkouts (default
2+
# core.autocrlf=true) do not mangle shell shebangs, heredocs, or Node scripts.
3+
# Without this, `bash src/scanner.sh` on Windows fails the moment it hits
4+
# `#!/usr/bin/env bash\r`.
5+
* text=auto eol=lf
6+
7+
*.sh text eol=lf
8+
*.bash text eol=lf
9+
*.js text eol=lf
10+
*.mjs text eol=lf
11+
*.cjs text eol=lf
12+
*.json text eol=lf
13+
*.jsonl text eol=lf
14+
*.md text eol=lf
15+
*.yml text eol=lf
16+
*.yaml text eol=lf
17+
*.toml text eol=lf
18+
*.py text eol=lf
19+
Dockerfile* text eol=lf
20+
21+
# Binary (never touch)
22+
*.png binary
23+
*.jpg binary
24+
*.jpeg binary
25+
*.gif binary
26+
*.ico binary
27+
*.webp binary
28+
*.pdf binary

.github/workflows/ci.yml

Lines changed: 131 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,15 @@ jobs:
3838

3939
test:
4040
needs: lint
41-
runs-on: ubuntu-latest
41+
runs-on: ${{ matrix.os }}
42+
timeout-minutes: 20
4243
strategy:
44+
fail-fast: false
4345
matrix:
46+
os:
47+
- ubuntu-latest
48+
- macos-latest
49+
- windows-latest
4450
node-version:
4551
- 20
4652
- 22
@@ -52,11 +58,34 @@ jobs:
5258
node-version: ${{ matrix.node-version }}
5359

5460
- name: Install jq
61+
shell: bash
5562
run: |
56-
sudo apt-get update
57-
sudo apt-get install -y jq
63+
case "$RUNNER_OS" in
64+
Linux) sudo apt-get update && sudo apt-get install -y jq ;;
65+
macOS) brew install jq ;;
66+
Windows) choco install jq -y ;;
67+
esac
5868
59-
- name: Run tests
69+
- name: Toolchain versions
70+
shell: bash
71+
run: |
72+
echo "RUNNER_OS=$RUNNER_OS"
73+
bash --version | head -1
74+
node --version
75+
jq --version
76+
git --version
77+
78+
- name: Cross-platform smoke test
79+
shell: bash
80+
run: bash tests/windows/smoke.sh
81+
82+
- name: Run postinstall detection unit test
83+
shell: bash
84+
run: node tests/unit/test-postinstall-detection.js
85+
86+
- name: Run full Linux test suite
87+
if: runner.os == 'Linux'
88+
shell: bash
6089
run: |
6190
bash tests/test-scanner.sh
6291
bash tests/test-action-smoke.sh
@@ -72,13 +101,111 @@ jobs:
72101
node tests/fixer-safety/test-traversal.js
73102
74103
- name: Prepare deep-analyzer corpus fixture
104+
if: runner.os == 'Linux'
105+
shell: bash
75106
run: |
76107
mkdir -p /tmp/al-corpus/small /tmp/al-corpus/medium /tmp/al-corpus/large
77108
printf '# Small\nbrief.\n' > /tmp/al-corpus/small/CLAUDE.md
78109
{ printf '# Medium\n'; for i in $(seq 1 100); do printf 'line %s of content\n' "$i"; done; } > /tmp/al-corpus/medium/CLAUDE.md
79110
{ printf '# Large\n'; for i in $(seq 1 500); do printf 'line %s of content here\n' "$i"; done; } > /tmp/al-corpus/large/CLAUDE.md
80111
81112
- name: Run deep-analyzer test
113+
if: runner.os == 'Linux'
82114
env:
83115
AL_CORPUS_DIR: /tmp/al-corpus
116+
shell: bash
84117
run: bash tests/test-deep-analyzer.sh
118+
119+
npm-e2e:
120+
needs: test
121+
runs-on: ${{ matrix.os }}
122+
timeout-minutes: 15
123+
strategy:
124+
fail-fast: false
125+
matrix:
126+
os: [ubuntu-latest, macos-latest, windows-latest]
127+
steps:
128+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
129+
130+
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
131+
with:
132+
node-version: 20
133+
134+
- name: Install jq
135+
shell: bash
136+
run: |
137+
case "$RUNNER_OS" in
138+
Linux) sudo apt-get update && sudo apt-get install -y jq ;;
139+
macOS) brew install jq ;;
140+
Windows) choco install jq -y ;;
141+
esac
142+
143+
# Path A: Pack the npm tarball and install it with --ignore-scripts.
144+
# The published package is a BOOTSTRAPPER (README + package.json +
145+
# postinstall.js = ~5KB). The scanner itself ships via the Claude Code
146+
# plugin mechanism, not inside this tarball. So the only meaningful
147+
# install-time assertion here is: the `os` field that caused #82 is
148+
# really gone and the tarball installs cleanly on every OS.
149+
- name: Pack npm tarball
150+
shell: bash
151+
run: |
152+
cd npm
153+
npm pack
154+
ls -la 0xmariowu-agent-lint-*.tgz
155+
TARBALL_DEST="${RUNNER_TEMP:-/tmp}/al.tgz"
156+
mv 0xmariowu-agent-lint-*.tgz "$TARBALL_DEST"
157+
echo "TARBALL_PATH=$TARBALL_DEST" >> "$GITHUB_ENV"
158+
159+
- name: Install globally with --ignore-scripts
160+
shell: bash
161+
run: |
162+
npm install -g --ignore-scripts "$TARBALL_PATH"
163+
164+
- name: Verify installed package has no os field (regression test for #82)
165+
shell: bash
166+
run: |
167+
PREFIX="$(npm prefix -g)"
168+
PKG="$PREFIX/node_modules/@0xmariowu/agent-lint/package.json"
169+
[ -f "$PKG" ] || PKG="$PREFIX/lib/node_modules/@0xmariowu/agent-lint/package.json"
170+
echo "Inspecting: $PKG"
171+
[ -f "$PKG" ]
172+
# Pass the path via argv so Windows backslashes aren't reinterpreted
173+
# as JS escape sequences inside a `node -e` source string.
174+
node -e '
175+
const p = JSON.parse(require("fs").readFileSync(process.argv[1], "utf8"));
176+
if (Array.isArray(p.os) || p.os) {
177+
console.error("FAIL: os field still present in installed package:", JSON.stringify(p.os));
178+
process.exit(1);
179+
}
180+
console.log("OK: no os field in installed package.json");
181+
' "$PKG"
182+
183+
- name: Verify postinstall.js ships with Windows detection
184+
shell: bash
185+
run: |
186+
PREFIX="$(npm prefix -g)"
187+
PI="$PREFIX/node_modules/@0xmariowu/agent-lint/postinstall.js"
188+
[ -f "$PI" ] || PI="$PREFIX/lib/node_modules/@0xmariowu/agent-lint/postinstall.js"
189+
[ -f "$PI" ]
190+
grep -q "where claude" "$PI"
191+
grep -q "bash --version" "$PI"
192+
grep -q "Git for Windows\|WSL" "$PI"
193+
194+
# Path B: simulate the failure modes of postinstall.js.
195+
# Runners do not have the `claude` CLI installed, so simply running
196+
# postinstall.js on a clean PATH should hit the "Claude Code not found"
197+
# branch. We don't strip PATH (that would break `node` itself on
198+
# Windows); we rely on the runner's default state.
199+
- name: Path B — assert claude-missing branch fires on clean runner
200+
shell: bash
201+
run: |
202+
set +e
203+
output=$(node npm/postinstall.js 2>&1)
204+
code=$?
205+
set -e
206+
echo "exit=$code"
207+
echo "--- output ---"
208+
echo "$output"
209+
echo "--- end ---"
210+
test "$code" -eq 1
211+
echo "$output" | grep -qi "Claude Code not found"

CHANGELOG.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,21 @@
11
# Changelog
22

3+
## v0.7.1 (unreleased)
4+
5+
### Added
6+
7+
- **You can now install AgentLint on Windows** from inside Git Bash or WSL (#82). `npm install -g @0xmariowu/agent-lint` previously rejected Windows with `EBADPLATFORM` before the installer could even run; that block is gone.
8+
- **Clear guidance when bash is missing on Windows.** Running the installer from `cmd.exe` or PowerShell now exits with a message pointing to Git for Windows or WSL instead of a cryptic shell error.
9+
10+
### Fixed
11+
12+
- Postinstall detects `claude` cross-platform (`where` on win32, `command -v` elsewhere) and verifies `bash` availability on Windows before invoking the installer.
13+
- `.gitattributes` now forces LF on `*.sh`, `*.js`, `*.md`, and other text files so Windows checkouts with default `core.autocrlf=true` no longer convert shell scripts to CRLF and break shebangs.
14+
15+
### Notes
16+
17+
- The scanner itself (`src/scanner.sh`) is still bash. Running it on Windows requires Git Bash or WSL — see the Platform requirements table in `README.md`. A native cmd.exe / PowerShell flow is out of scope for this patch.
18+
319
## v0.7.0 (2026-04-15)
420

521
Audit-driven minor release. Dimension count grows from 6 to 8, check count from 42 to 49 — your repo score will shift, because the scanner is now finally counting checks it had been silently dropping.

README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,18 @@ Then start a new Claude Code session:
4444

4545
That's it. AgentLint scans your projects, scores them, shows what's wrong, and fixes what it can.
4646

47+
### Platform requirements
48+
49+
AgentLint's scanner is a bash script, so the host needs a POSIX shell:
50+
51+
| Platform | Requirement |
52+
|----------|-------------|
53+
| macOS | Works out of the box (system bash). |
54+
| Linux | Works out of the box. `jq` and `git` must be on `PATH`. |
55+
| Windows | Requires **Git Bash** (from [Git for Windows](https://git-scm.com/download/win)) or **WSL** ([install guide](https://learn.microsoft.com/windows/wsl/install)). Run `npm install -g @0xmariowu/agent-lint` from inside the bash shell. A pure `cmd.exe` / PowerShell install will exit with a guidance message pointing to one of the two options above. |
56+
57+
Node.js 20+ is required on every platform.
58+
4759
## Supported AI coding agents
4860

4961
AgentLint auto-detects the entry file for major AI coding agents. Claude-specific checks skip gracefully for other platforms so they aren't penalized unfairly.

npm/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ AgentLint scans your repository and scores how well it supports AI coding agents
3838

3939
- [Claude Code](https://claude.com/download)
4040
- Node.js 20+
41+
- `bash`, `jq`, `git` on `PATH`
42+
- **Windows**: install from inside **Git Bash** (ships with [Git for Windows](https://git-scm.com/download/win)) or **WSL** ([install guide](https://learn.microsoft.com/windows/wsl/install)). Installing from `cmd.exe` / PowerShell will exit with a guidance message.
4143

4244
## Links
4345

npm/package.json

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,6 @@
2121
"scripts": {
2222
"postinstall": "node postinstall.js"
2323
},
24-
"os": [
25-
"darwin",
26-
"linux"
27-
],
2824
"engines": {
2925
"node": ">=20"
3026
}

npm/postinstall.js

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,14 +38,29 @@ function download(url) {
3838
async function main() {
3939
try {
4040
try {
41-
execSync("command -v claude", { stdio: "ignore" });
41+
const claudeCheck = process.platform === "win32" ? "where claude" : "command -v claude";
42+
execSync(claudeCheck, { stdio: "ignore" });
4243
} catch {
4344
console.error(
4445
"\n Claude Code not found. Install it first: https://claude.com/download\n"
4546
);
4647
process.exit(1);
4748
}
4849

50+
if (process.platform === "win32") {
51+
try {
52+
execSync("bash --version", { stdio: "ignore" });
53+
} catch {
54+
console.error(
55+
"\nAgentLint requires bash on Windows. Install one of:\n" +
56+
" - Git for Windows (includes Git Bash): https://git-scm.com/download/win\n" +
57+
" - WSL: https://learn.microsoft.com/windows/wsl/install\n" +
58+
"Then re-run: npm install -g @0xmariowu/agent-lint\n"
59+
);
60+
process.exit(1);
61+
}
62+
}
63+
4964
console.log("Downloading AgentLint installer...");
5065
const script = await download(INSTALL_URL);
5166

@@ -60,8 +75,14 @@ async function main() {
6075
console.error(`\nInstallation failed: ${err.message}`);
6176
console.error("Try the manual method:");
6277
console.error(
63-
` curl -fsSL https://raw.githubusercontent.com/0xmariowu/AgentLint/v${PKG_VERSION}/scripts/install.sh | bash\n`
78+
` curl -fsSL https://raw.githubusercontent.com/0xmariowu/AgentLint/v${PKG_VERSION}/scripts/install.sh | bash`
6479
);
80+
if (process.platform === "win32") {
81+
console.error(
82+
"\nOn Windows, run the command above from inside Git Bash or WSL — it will not work in cmd.exe or PowerShell."
83+
);
84+
}
85+
console.error("");
6586
process.exit(1);
6687
}
6788
}

0 commit comments

Comments
 (0)