Skip to content

Commit cd8095c

Browse files
tannevaledclaude
andcommitted
feat(windows): scaffolding pass for Windows support
First-pass changes to let pkgm at least start on Windows so CI can surface real gaps instead of failing at "/usr/local doesn't exist": - standardPath(): add Windows case returning the baseline System32 / System32\Wbem dirs joined with `;`. Introduces PATH_SEP module constant used wherever we tokenise $PATH (a literal `:` split would shred `C:\` drive prefixes on Windows). - install_prefix(): on Windows, skip the /usr/local probe and return Path.home()/.local. System-wide installs under %ProgramFiles% need UAC modelling — out of scope here. - get_pkgx(): use PATH_SEP and look for `pkgx.exe` on Windows. - install() PATH-contains check: use PATH_SEP. - Stub writing: skip the Deno.chmod(0o755) on Windows (it throws). The stub content is still POSIX shell — proper `.cmd`/`.ps1` stub emission is a follow-up. - CI: add windows-latest to the `test` matrix with `strategy.fail-fast: false`. The existing `continue-on-error: true` keeps the matrix overall green; the Windows leg surfaces what's still broken so we can iterate. Open design questions deliberately left for the draft-PR description: - System-wide install prefix on Windows (%ProgramFiles%? user-only?) - Elevation model (UAC? refuse?) - Stub format (.cmd, .ps1, or compiled wrapper?) - libpkgx + pkgxdev/setup Windows-readiness verification Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 4ffae15 commit cd8095c

2 files changed

Lines changed: 40 additions & 4 deletions

File tree

‎.github/workflows/ci.yml‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,16 @@ jobs:
2727
test:
2828
continue-on-error: true
2929
strategy:
30+
fail-fast: false
3031
matrix:
3132
os:
3233
- macos-latest
3334
- ubuntu-latest
35+
# Windows: scaffolding pass — steps below rely on shebang
36+
# interpretation and POSIX paths, so most will fail until
37+
# pkgm grows native Windows install/stub support. See the
38+
# draft PR description for the open design questions.
39+
- windows-latest
3440
runs-on: ${{ matrix.os }}
3541
steps:
3642
- uses: actions/checkout@v4

‎pkgm.ts‎

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,23 @@ import { ensureDir, existsSync, walk } from "jsr:@std/fs@^1";
1313
import { parseArgs } from "jsr:@std/cli@^1";
1414
const { hydrate } = plumbing;
1515

16+
// Path-separator: `;` on Windows, `:` on POSIX. Used everywhere we
17+
// split or join $PATH, since C:\ on Windows would tokenise wrong with `:`.
18+
const PATH_SEP = Deno.build.os == "windows" ? ";" : ":";
19+
1620
function standardPath() {
21+
if (Deno.build.os == "windows") {
22+
// Windows: no /usr/local hierarchy and no homebrew. Return the
23+
// baseline system dirs so subprocesses can still locate built-in
24+
// commands (cmd, find, etc.) when we feed them an explicit PATH.
25+
const systemRoot = Deno.env.get("SystemRoot") ?? "C:\\Windows";
26+
return [
27+
`${systemRoot}\\System32`,
28+
systemRoot,
29+
`${systemRoot}\\System32\\Wbem`,
30+
].join(PATH_SEP);
31+
}
32+
1733
let path = "/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin";
1834

1935
// for pkgx installed via homebrew
@@ -194,7 +210,12 @@ async function install(args: string[], basePath: string) {
194210

195211
await Deno.remove(to_stub); //FIXME inefficient to symlink for no reason
196212
await Deno.writeTextFile(to_stub, sh.trim() + "\n");
197-
await Deno.chmod(to_stub, 0o755);
213+
// Windows has no POSIX exec bit; Deno.chmod throws there. Stub
214+
// content is still POSIX shell at this point — proper `.cmd`/`.ps1`
215+
// stub emission is a separate TODO (see draft PR description).
216+
if (Deno.build.os != "windows") {
217+
await Deno.chmod(to_stub, 0o755);
218+
}
198219

199220
rv.push(to_stub);
200221
}
@@ -204,7 +225,7 @@ async function install(args: string[], basePath: string) {
204225
if (
205226
!Deno.env
206227
.get("PATH")
207-
?.split(":")
228+
?.split(PATH_SEP)
208229
?.includes(new Path(basePath).join("bin").string)
209230
) {
210231
console.error(
@@ -518,8 +539,9 @@ function symlink_with_overwrite(src: string, dst: string) {
518539
}
519540

520541
function get_pkgx() {
521-
for (const path of Deno.env.get("PATH")!.split(":")) {
522-
const pkgx = join(path, "pkgx");
542+
const exe = Deno.build.os == "windows" ? "pkgx.exe" : "pkgx";
543+
for (const path of Deno.env.get("PATH")!.split(PATH_SEP)) {
544+
const pkgx = join(path, exe);
523545
if (existsSync(pkgx)) {
524546
const out = new Deno.Command(pkgx, { args: ["--version"] }).outputSync();
525547
const stdout = new TextDecoder().decode(out.stdout);
@@ -758,6 +780,14 @@ async function update() {
758780
}
759781

760782
function install_prefix() {
783+
// Windows has no /usr/local analogue; the per-user prefix is the
784+
// only sensible default for now. System-wide installs on Windows
785+
// (e.g. under %ProgramFiles%) require UAC elevation modelling and
786+
// are out of scope for the initial port — see the draft PR
787+
// description for the design questions.
788+
if (Deno.build.os == "windows") {
789+
return Path.home().join(".local");
790+
}
761791
// if /usr/local is writable, use that
762792
if (writable("/usr/local")) {
763793
return new Path("/usr/local");

0 commit comments

Comments
 (0)