fix(installer): guide Windows shell users to PowerShell (#1294) (#2048)

Recognize MINGW, MSYS, and Cygwin and print the exact PowerShell installer command.
Keep Darwin/Linux behavior unchanged and exit non-zero before downloading.
Cover shell dispatch and real Windows ARM64 Git Bash behavior.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Colby Mchenry
2026-09-27 15:10:20 +00:00
committed by GitHub
parent 1008b34e69
commit d3671faf04
2 changed files with 42 additions and 0 deletions
+37
View File
@@ -18,6 +18,7 @@ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
import * as fs from 'fs';
import * as path from 'path';
import * as os from 'os';
import { spawnSync } from 'child_process';
import { parse as parseJsonc } from 'jsonc-parser';
import { ALL_TARGETS, getTarget, resolveTargetFlag } from '../src/installer/targets/registry';
import { uninstallTargets, refreshTargets } from '../src/installer';
@@ -28,6 +29,42 @@ function mkTmpDir(label: string): string {
return fs.mkdtempSync(path.join(os.tmpdir(), `cg-targets-${label}-`));
}
describe('standalone installer Windows shell guidance (#1294)', () => {
const command = 'irm https://raw.githubusercontent.com/colbymchenry/codegraph/main/install.ps1 | iex';
const source = fs.readFileSync(path.join(__dirname, '..', 'install.sh'), 'utf8');
// Stub uname and downloads, but execute the complete shipped installer.
// This exercises shell dispatch on POSIX too without installing anything.
it.runIf(process.platform !== 'win32').each([
'MINGW64_NT-10.0-26200-ARM64', 'MSYS_NT-10.0-26200', 'CYGWIN_NT-10.0',
])('gives actionable guidance for %s before downloading', (platform) => {
const result = spawnSync('sh', ['-s'], {
input: `uname() { echo '${platform}'; }\ncurl() { echo UNEXPECTED_DOWNLOAD >&2; exit 99; }\n${source}`,
encoding: 'utf8',
});
expect(result.status).toBe(1);
expect(result.stderr).toContain('open PowerShell');
expect(result.stderr).toContain(command);
expect(result.stderr).not.toContain('UNEXPECTED_DOWNLOAD');
expect(result.stderr).not.toContain('unsupported OS');
});
it.runIf(process.platform === 'win32')('guides real Git Bash users to PowerShell', () => {
const bash = path.join(process.env.ProgramFiles || 'C:\\Program Files', 'Git', 'bin', 'bash.exe');
const result = spawnSync(bash, ['--noprofile', '--norc', '-s'], {
input: `curl() { echo UNEXPECTED_DOWNLOAD >&2; exit 99; }\n${source}`,
encoding: 'utf8',
timeout: 15000,
});
expect(result.error).toBeUndefined();
expect(result.status).toBe(1);
expect(result.stderr).toContain('open PowerShell');
expect(result.stderr).toContain(command);
expect(result.stderr).not.toContain('UNEXPECTED_DOWNLOAD');
expect(result.stderr).not.toContain('unsupported OS');
});
});
// `os.homedir` is non-configurable on Node, so we redirect it via the
// `$HOME` (POSIX) / `$USERPROFILE` (Windows) env vars that
// `os.homedir()` reads first. Same trick the rest of the suite uses
+5
View File
@@ -34,6 +34,11 @@ arch="$(uname -m)"
case "$os" in
Darwin) os="darwin" ;;
Linux) os="linux" ;;
MINGW*|MSYS*|CYGWIN*)
echo "codegraph: on Windows, open PowerShell and run:" >&2
echo " irm https://raw.githubusercontent.com/colbymchenry/codegraph/main/install.ps1 | iex" >&2
exit 1
;;
*) echo "codegraph: unsupported OS '$os'." >&2; exit 1 ;;
esac
case "$arch" in