Skip to content

Commit 638cbfc

Browse files
committed
Add curl installers and Windows release support
1 parent f9a5caf commit 638cbfc

File tree

5 files changed

+154
-24
lines changed

5 files changed

+154
-24
lines changed

.github/workflows/release.yml

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@ jobs:
3535
target: aarch64-macos
3636
dist_os: darwin
3737
dist_arch: arm64
38+
- runner: windows-latest
39+
target: x86_64-windows
40+
dist_os: windows
41+
dist_arch: amd64
3842

3943
runs-on: ${{ matrix.runner }}
4044
steps:
@@ -53,7 +57,8 @@ jobs:
5357
echo "tag=$tag" >> "$GITHUB_OUTPUT"
5458
echo "version=${tag#v}" >> "$GITHUB_OUTPUT"
5559
56-
- name: Build tarball
60+
- name: Build archive (macOS/Linux)
61+
if: runner.os != 'Windows'
5762
shell: bash
5863
run: |
5964
set -euo pipefail
@@ -65,14 +70,30 @@ jobs:
6570
cp LICENSE dist/LICENSE
6671
cp README.md dist/README.md
6772
68-
tarball="nytgames-cli_${version}_${{ matrix.dist_os }}_${{ matrix.dist_arch }}.tar.gz"
69-
tar -C dist -czf "${tarball}" nytgames LICENSE README.md
70-
echo "built ${tarball}"
73+
archive="nytgames-cli_${version}_${{ matrix.dist_os }}_${{ matrix.dist_arch }}.tar.gz"
74+
tar -C dist -czf "${archive}" nytgames LICENSE README.md
75+
echo "built ${archive}"
76+
77+
- name: Build archive (Windows)
78+
if: runner.os == 'Windows'
79+
shell: pwsh
80+
run: |
81+
$version = "${{ steps.meta.outputs.version }}"
82+
zig build -Doptimize=ReleaseSafe -Dtarget=${{ matrix.target }} -Dversion="$version"
83+
84+
New-Item -ItemType Directory -Path dist -Force | Out-Null
85+
Copy-Item zig-out/bin/nytgames.exe dist/nytgames.exe
86+
Copy-Item LICENSE dist/LICENSE
87+
Copy-Item README.md dist/README.md
88+
89+
$archive = "nytgames-cli_${version}_${{ matrix.dist_os }}_${{ matrix.dist_arch }}.zip"
90+
Compress-Archive -Path dist/nytgames.exe, dist/LICENSE, dist/README.md -DestinationPath $archive
91+
Write-Host "built $archive"
7192
7293
- uses: actions/upload-artifact@v4
7394
with:
7495
name: tarballs-${{ matrix.dist_os }}-${{ matrix.dist_arch }}
75-
path: nytgames-cli_*.tar.gz
96+
path: nytgames-cli_*
7697
if-no-files-found: error
7798

7899
publish:
@@ -124,7 +145,7 @@ jobs:
124145
./scripts/build-rpm.sh --version "${version}" --arch amd64 --tar "dist/nytgames-cli_${version}_linux_amd64.tar.gz" --out "dist/nytgames-cli_${version}_linux_amd64.rpm"
125146
126147
# Final checksums include all artifacts (excluding checksums.txt itself).
127-
(cd dist && sha256sum nytgames-cli_*.tar.gz nytgames-cli_*.deb nytgames-cli_*.rpm nytgames-cli.rb > checksums.txt)
148+
(cd dist && sha256sum nytgames-cli_*.tar.gz nytgames-cli_*.zip nytgames-cli_*.deb nytgames-cli_*.rpm nytgames-cli.rb > checksums.txt)
128149
129150
- name: Upload release assets
130151
uses: softprops/action-gh-release@v2
@@ -134,6 +155,7 @@ jobs:
134155
generate_release_notes: true
135156
files: |
136157
dist/nytgames-cli_*.tar.gz
158+
dist/nytgames-cli_*.zip
137159
dist/nytgames-cli_*.deb
138160
dist/nytgames-cli_*.rpm
139161
dist/nytgames-cli.rb

README.md

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,28 +13,35 @@ Not affiliated with The New York Times.
1313

1414
## Install
1515

16-
### Homebrew (macOS + Linux)
16+
### One-line installer (macOS + Linux)
1717

18-
This project publishes a self-contained Homebrew formula as a release asset.
18+
Installs the prebuilt binary from GitHub Releases.
1919

2020
```bash
21-
brew install --formula https://github.com/ph8n/nytgames-cli/releases/latest/download/nytgames-cli.rb
21+
curl -fsSL https://raw.githubusercontent.com/ph8n/nytgames-cli/main/scripts/install.sh | bash
2222
```
2323

24-
### Curl (macOS + Linux)
24+
Options:
25+
- Pin a version:
26+
- `curl -fsSL https://raw.githubusercontent.com/ph8n/nytgames-cli/main/scripts/install.sh | NYTGAMES_CLI_VERSION=X.Y.Z bash`
27+
- Choose install dir: `NYTGAMES_CLI_INSTALL_DIR=~/.local/bin`
28+
29+
### One-line installer (Windows PowerShell)
2530

2631
Installs the prebuilt binary from GitHub Releases.
2732

28-
```bash
29-
curl -fsSL https://raw.githubusercontent.com/ph8n/nytgames-cli/main/scripts/install.sh | bash
33+
```powershell
34+
curl.exe -fsSL https://raw.githubusercontent.com/ph8n/nytgames-cli/main/scripts/install.ps1 | powershell -NoProfile -ExecutionPolicy Bypass -Command -
3035
```
3136

37+
Windows builds are currently x64 only.
38+
3239
Options:
3340
- Pin a version:
34-
- `curl -fsSL https://raw.githubusercontent.com/ph8n/nytgames-cli/main/scripts/install.sh | NYTGAMES_CLI_VERSION=X.Y.Z bash`
35-
- Choose install dir: `NYTGAMES_CLI_INSTALL_DIR=~/.local/bin`
41+
- `$env:NYTGAMES_CLI_VERSION="X.Y.Z"; curl.exe -fsSL https://raw.githubusercontent.com/ph8n/nytgames-cli/main/scripts/install.ps1 | powershell -NoProfile -ExecutionPolicy Bypass -Command -`
42+
- Choose install dir: `$env:NYTGAMES_CLI_INSTALL_DIR="C:\\Users\\You\\bin"`
3643

37-
### Linux packages
44+
### Linux packages (optional)
3845

3946
Download the package from the GitHub Release page:
4047
- Debian/Ubuntu (`.deb`): `sudo apt install ./nytgames-cli_X.Y.Z_linux_amd64.deb`

scripts/install.ps1

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
param(
2+
[string]$Repo = $env:NYTGAMES_CLI_REPO,
3+
[string]$Version = $env:NYTGAMES_CLI_VERSION,
4+
[string]$InstallDir = $env:NYTGAMES_CLI_INSTALL_DIR
5+
)
6+
7+
$ErrorActionPreference = "Stop"
8+
9+
if ([string]::IsNullOrWhiteSpace($Repo)) {
10+
$Repo = "ph8n/nytgames-cli"
11+
}
12+
13+
$headers = @{ "User-Agent" = "nytgames-cli-installer" }
14+
15+
if ([string]::IsNullOrWhiteSpace($InstallDir)) {
16+
$base = $env:LOCALAPPDATA
17+
if ([string]::IsNullOrWhiteSpace($base)) {
18+
$base = Join-Path $HOME "AppData\\Local"
19+
}
20+
$InstallDir = Join-Path $base "nytgames-cli\\bin"
21+
}
22+
23+
$arch = [System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture
24+
switch ($arch) {
25+
"X64" { $arch = "amd64" }
26+
"Arm64" { throw "unsupported architecture: arm64 (Windows builds are amd64 only)" }
27+
default { throw "unsupported architecture: $arch" }
28+
}
29+
30+
if ([string]::IsNullOrWhiteSpace($Version)) {
31+
$release = Invoke-RestMethod -Headers $headers "https://api.github.com/repos/$Repo/releases/latest"
32+
$tag = $release.tag_name
33+
if (-not $tag) {
34+
throw "failed to resolve latest version from GitHub for $Repo"
35+
}
36+
$Version = $tag.TrimStart("v")
37+
} else {
38+
$Version = $Version.TrimStart("v")
39+
}
40+
41+
$asset = "nytgames-cli_${Version}_windows_${arch}.zip"
42+
$baseUrl = "https://github.com/$Repo/releases/download/v$Version"
43+
$url = "$baseUrl/$asset"
44+
$checksumsUrl = "$baseUrl/checksums.txt"
45+
46+
$tempDir = Join-Path ([System.IO.Path]::GetTempPath()) ("nytgames-cli-" + [Guid]::NewGuid().ToString("N"))
47+
New-Item -ItemType Directory -Path $tempDir | Out-Null
48+
49+
try {
50+
$assetPath = Join-Path $tempDir $asset
51+
Write-Host "Downloading $url"
52+
Invoke-WebRequest -Uri $url -OutFile $assetPath -UseBasicParsing -Headers $headers
53+
54+
$checksumsPath = Join-Path $tempDir "checksums.txt"
55+
$expected = $null
56+
try {
57+
Invoke-WebRequest -Uri $checksumsUrl -OutFile $checksumsPath -UseBasicParsing -Headers $headers
58+
$expectedLine = Get-Content $checksumsPath |
59+
Where-Object { $_ -match ("\s+" + [regex]::Escape($asset) + "$") } |
60+
Select-Object -First 1
61+
if ($expectedLine) {
62+
$expected = ($expectedLine -split "\s+")[0].ToLower()
63+
}
64+
} catch {
65+
$expected = $null
66+
}
67+
if ($expected) {
68+
$actual = (Get-FileHash -Algorithm SHA256 $assetPath).Hash.ToLower()
69+
if ($expected -ne $actual) {
70+
throw "checksum mismatch for $asset`nexpected: $expected`nactual: $actual"
71+
}
72+
}
73+
74+
Expand-Archive -Path $assetPath -DestinationPath $tempDir -Force
75+
76+
$binPath = Join-Path $tempDir "nytgames.exe"
77+
if (-not (Test-Path $binPath)) {
78+
$bin = Get-ChildItem -Path $tempDir -Recurse -Filter "nytgames.exe" | Select-Object -First 1
79+
if ($bin) {
80+
$binPath = $bin.FullName
81+
} else {
82+
throw "failed to find nytgames.exe in archive"
83+
}
84+
}
85+
86+
New-Item -ItemType Directory -Path $InstallDir -Force | Out-Null
87+
Copy-Item -Force $binPath (Join-Path $InstallDir "nytgames.exe")
88+
89+
Write-Host "Installed nytgames.exe to $InstallDir"
90+
$userPath = [System.Environment]::GetEnvironmentVariable("PATH", "User")
91+
if ($userPath -and $userPath -notlike "*$InstallDir*") {
92+
Write-Warning "$InstallDir is not on your PATH. Add it to run 'nytgames' from any shell."
93+
}
94+
} finally {
95+
if (Test-Path $tempDir) {
96+
Remove-Item -Recurse -Force $tempDir
97+
}
98+
}

src/ui/stats.zig

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1066,12 +1066,15 @@ fn computeAllQuickStats(allocator: std.mem.Allocator, storage: *storage_db.Stora
10661066

10671067
fn localHourWday(ts: i64) ?struct { hour: u8, wday: u8 } {
10681068
if (ts < 0) return null;
1069-
if (builtin.os.tag == .windows) return null;
10701069

10711070
var t: ctime.time_t = @intCast(ts);
10721071
var tm: ctime.tm = undefined;
1073-
const tm_ptr = ctime.localtime_r(&t, &tm);
1074-
if (tm_ptr == null) return null;
1072+
if (builtin.os.tag == .windows) {
1073+
if (ctime.localtime_s(&tm, &t) != 0) return null;
1074+
} else {
1075+
const tm_ptr = ctime.localtime_r(&t, &tm);
1076+
if (tm_ptr == null) return null;
1077+
}
10751078

10761079
if (tm.tm_hour < 0 or tm.tm_hour > 23) return null;
10771080
if (tm.tm_wday < 0 or tm.tm_wday > 6) return null;

src/utils/date.zig

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -89,14 +89,14 @@ pub fn todayUtc() error{NegativeTimestamp}!Date {
8989
pub fn localDateFromUnixTimestampSeconds(timestamp_seconds: i64) error{NegativeTimestamp}!Date {
9090
if (timestamp_seconds < 0) return error.NegativeTimestamp;
9191

92-
if (builtin.os.tag == .windows) {
93-
@compileError("localDateFromUnixTimestampSeconds is not implemented for Windows yet.");
94-
}
95-
9692
var t: c.time_t = @intCast(timestamp_seconds);
9793
var tm: c.tm = undefined;
98-
const tm_ptr = c.localtime_r(&t, &tm);
99-
if (tm_ptr == null) return error.NegativeTimestamp;
94+
if (builtin.os.tag == .windows) {
95+
if (c.localtime_s(&tm, &t) != 0) return error.NegativeTimestamp;
96+
} else {
97+
const tm_ptr = c.localtime_r(&t, &tm);
98+
if (tm_ptr == null) return error.NegativeTimestamp;
99+
}
100100

101101
const year: i32 = tm.tm_year + 1900;
102102
if (year < 0) return error.NegativeTimestamp;

0 commit comments

Comments
 (0)