From 710a6ffc03e13dc96fb6e0dd757e417ec5fafe95 Mon Sep 17 00:00:00 2001 From: Johnny Huynh <27847622+johnnyhuy@users.noreply.github.com> Date: Sat, 21 Feb 2026 11:03:25 +1100 Subject: [PATCH] feat(release): add chocolatey packaging --- .mise.toml | 3 + README.md | 3 +- docs/distribution/chocolatey.md | 31 +++++++++ package.json | 3 +- packaging/chocolatey/skillet.nuspec | 17 +++++ .../chocolatey/tools/chocolateyinstall.ps1 | 10 +++ .../chocolatey/tools/chocolateyuninstall.ps1 | 2 + scripts/render-chocolatey-package.ts | 63 +++++++++++++++++++ src/distribution/chocolatey.ts | 63 +++++++++++++++++++ tests/distribution/chocolatey.test.ts | 23 +++++++ 10 files changed, 216 insertions(+), 2 deletions(-) create mode 100644 docs/distribution/chocolatey.md create mode 100644 packaging/chocolatey/skillet.nuspec create mode 100644 packaging/chocolatey/tools/chocolateyinstall.ps1 create mode 100644 packaging/chocolatey/tools/chocolateyuninstall.ps1 create mode 100644 scripts/render-chocolatey-package.ts create mode 100644 src/distribution/chocolatey.ts create mode 100644 tests/distribution/chocolatey.test.ts diff --git a/.mise.toml b/.mise.toml index 54782e6..bcba45d 100644 --- a/.mise.toml +++ b/.mise.toml @@ -18,3 +18,6 @@ run = "mise run install && bun scripts/build-release.ts" [tasks.render-homebrew-formula] run = "mise run install && bun scripts/render-homebrew-formula.ts" + +[tasks.render-chocolatey-package] +run = "mise run install && bun scripts/render-chocolatey-package.ts" diff --git a/README.md b/README.md index 2899d5d..9b560b3 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ Skillet installs, discovers, and updates `SKILL.md`-based skills across supporte | --- | --- | --- | | Binary release | Download from GitHub Releases | Planned | | Homebrew | `brew install skillet` | Configured | -| Chocolatey | `choco install skillet` | Planned | +| Chocolatey | `choco install skillet` | Configured | | winget | `winget install skillet` | Planned | | npm / npx | `npx skillet ...` | Planned | | Docker | `docker run ... skillet ...` | Planned | @@ -142,3 +142,4 @@ AGENTS instructions live in `AGENTS.md`. Distribution docs: - Homebrew: `docs/distribution/homebrew.md` +- Chocolatey: `docs/distribution/chocolatey.md` diff --git a/docs/distribution/chocolatey.md b/docs/distribution/chocolatey.md new file mode 100644 index 0000000..26263a8 --- /dev/null +++ b/docs/distribution/chocolatey.md @@ -0,0 +1,31 @@ +# Chocolatey Distribution + +Skillet ships a Chocolatey package for Windows CLI installs. + +## Release Flow + +1. Build release artifacts and checksums. +2. Render `packaging/chocolatey/` from `SHA256SUMS`. +3. Build and push the `.nupkg` with `choco pack` and `choco push`. + +## Commands + +```bash +mise run build -- --targets=windows-x64 +bun scripts/write-checksums.ts +mise run render-chocolatey-package -- --version +``` + +Generate package and publish: + +```powershell +cd packaging/chocolatey +choco pack +choco push skillet..nupkg --source https://push.chocolatey.org/ +``` + +User install command: + +```powershell +choco install skillet +``` diff --git a/package.json b/package.json index a293acc..d91f62d 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,8 @@ "dev": "bun src/cli.ts", "test": "vitest run", "build": "bun scripts/build-release.ts", - "render:homebrew": "bun scripts/render-homebrew-formula.ts" + "render:homebrew": "bun scripts/render-homebrew-formula.ts", + "render:choco": "bun scripts/render-chocolatey-package.ts" }, "dependencies": { "cac": "^6.7.14", diff --git a/packaging/chocolatey/skillet.nuspec b/packaging/chocolatey/skillet.nuspec new file mode 100644 index 0000000..0f496b8 --- /dev/null +++ b/packaging/chocolatey/skillet.nuspec @@ -0,0 +1,17 @@ + + + + skillet + 0.0.0 + skillet + echohello-dev + https://github.com/echohello-dev/skillet + https://github.com/echohello-dev/skillet + false + Portable CLI for managing agent skills. + cli skills agent + + + + + diff --git a/packaging/chocolatey/tools/chocolateyinstall.ps1 b/packaging/chocolatey/tools/chocolateyinstall.ps1 new file mode 100644 index 0000000..78fa0e6 --- /dev/null +++ b/packaging/chocolatey/tools/chocolateyinstall.ps1 @@ -0,0 +1,10 @@ +$ErrorActionPreference = 'Stop' + +$packageName = 'skillet' +$toolsDir = Split-Path -Parent $MyInvocation.MyCommand.Definition +$binaryPath = Join-Path $toolsDir 'skillet.exe' +$url64 = 'https://github.com/echohello-dev/skillet/releases/download/v0.0.0/skillet-windows-x64.exe' +$checksum64 = '51cddefde243f0f27e501ca420d5e1d1b9cad548dc9884ea4052d2915afef179' + +Get-ChocolateyWebFile -PackageName $packageName -FileFullPath $binaryPath -Url64bit $url64 -Checksum64 $checksum64 -ChecksumType64 'sha256' +Install-BinFile -Name 'skillet' -Path $binaryPath diff --git a/packaging/chocolatey/tools/chocolateyuninstall.ps1 b/packaging/chocolatey/tools/chocolateyuninstall.ps1 new file mode 100644 index 0000000..08d57dd --- /dev/null +++ b/packaging/chocolatey/tools/chocolateyuninstall.ps1 @@ -0,0 +1,2 @@ +$ErrorActionPreference = 'Stop' +Uninstall-BinFile -Name 'skillet' diff --git a/scripts/render-chocolatey-package.ts b/scripts/render-chocolatey-package.ts new file mode 100644 index 0000000..a3ac611 --- /dev/null +++ b/scripts/render-chocolatey-package.ts @@ -0,0 +1,63 @@ +import fs from "node:fs"; +import path from "node:path"; +import { parseSha256Sums } from "../src/distribution/checksums"; +import { renderChocolateyPackageFiles } from "../src/distribution/chocolatey"; + +function main(): void { + const args = process.argv.slice(2); + const version = readArg(args, "--version") ?? readVersionFromPackageJson(); + const checksumsPath = path.resolve(readArg(args, "--checksums") ?? "dist/SHA256SUMS"); + const outputDir = path.resolve(readArg(args, "--output-dir") ?? "packaging/chocolatey"); + const releaseUrlBase = + readArg(args, "--release-url-base") ?? + `https://github.com/echohello-dev/skillet/releases/download/v${version}`; + + if (!fs.existsSync(checksumsPath)) { + throw new Error(`Checksums file not found: ${checksumsPath}`); + } + + const checksums = parseSha256Sums(fs.readFileSync(checksumsPath, "utf8")); + const files = renderChocolateyPackageFiles({ version, releaseUrlBase, checksumsByArtifact: checksums }); + + fs.mkdirSync(path.join(outputDir, "tools"), { recursive: true }); + fs.writeFileSync(path.join(outputDir, "skillet.nuspec"), files.nuspec); + fs.writeFileSync(path.join(outputDir, "tools", "chocolateyinstall.ps1"), files.installScript); + fs.writeFileSync(path.join(outputDir, "tools", "chocolateyuninstall.ps1"), files.uninstallScript); + console.log(`Wrote Chocolatey package files to ${outputDir}`); +} + +function readArg(args: string[], name: string): string | undefined { + for (let i = 0; i < args.length; i += 1) { + const token = args[i]; + if (token === name) { + const next = args[i + 1]; + if (!next || next.startsWith("--")) { + throw new Error(`Missing value for ${name}`); + } + return next; + } + + const prefix = `${name}=`; + if (token.startsWith(prefix)) { + const value = token.slice(prefix.length); + if (value.length === 0) { + throw new Error(`Missing value for ${name}`); + } + return value; + } + } + + return undefined; +} + +function readVersionFromPackageJson(): string { + const packageJsonPath = path.resolve("package.json"); + const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8")) as { version?: string }; + if (!packageJson.version || packageJson.version.length === 0) { + throw new Error("package.json version is missing"); + } + + return packageJson.version; +} + +main(); diff --git a/src/distribution/chocolatey.ts b/src/distribution/chocolatey.ts new file mode 100644 index 0000000..38a6699 --- /dev/null +++ b/src/distribution/chocolatey.ts @@ -0,0 +1,63 @@ +const WINDOWS_X64_ARTIFACT = "skillet-windows-x64.exe"; + +export type ChocolateyPackageOptions = { + version: string; + releaseUrlBase: string; + checksumsByArtifact: Map; +}; + +export type ChocolateyPackageFiles = { + nuspec: string; + installScript: string; + uninstallScript: string; +}; + +export function renderChocolateyPackageFiles(options: ChocolateyPackageOptions): ChocolateyPackageFiles { + const releaseUrlBase = options.releaseUrlBase.replace(/\/+$/, ""); + const downloadUrl = `${releaseUrlBase}/${WINDOWS_X64_ARTIFACT}`; + const checksum = requireChecksum(options.checksumsByArtifact, WINDOWS_X64_ARTIFACT); + + return { + nuspec: ` + + + skillet + ${options.version} + skillet + echohello-dev + https://github.com/echohello-dev/skillet + https://github.com/echohello-dev/skillet + false + Portable CLI for managing agent skills. + cli skills agent + + + + + +`, + installScript: `$ErrorActionPreference = 'Stop' + +$packageName = 'skillet' +$toolsDir = Split-Path -Parent $MyInvocation.MyCommand.Definition +$binaryPath = Join-Path $toolsDir 'skillet.exe' +$url64 = '${downloadUrl}' +$checksum64 = '${checksum}' + +Get-ChocolateyWebFile -PackageName $packageName -FileFullPath $binaryPath -Url64bit $url64 -Checksum64 $checksum64 -ChecksumType64 'sha256' +Install-BinFile -Name 'skillet' -Path $binaryPath +`, + uninstallScript: `$ErrorActionPreference = 'Stop' +Uninstall-BinFile -Name 'skillet' +`, + }; +} + +function requireChecksum(checksumsByArtifact: Map, artifactName: string): string { + const checksum = checksumsByArtifact.get(artifactName); + if (!checksum) { + throw new Error(`Missing checksum for artifact: ${artifactName}`); + } + + return checksum; +} diff --git a/tests/distribution/chocolatey.test.ts b/tests/distribution/chocolatey.test.ts new file mode 100644 index 0000000..36e0d86 --- /dev/null +++ b/tests/distribution/chocolatey.test.ts @@ -0,0 +1,23 @@ +import { describe, expect, it } from "vitest"; +import { renderChocolateyPackageFiles } from "../../src/distribution/chocolatey"; + +describe("renderChocolateyPackageFiles", () => { + it("renders nuspec and install scripts from checksum data", () => { + const files = renderChocolateyPackageFiles({ + version: "1.2.3", + releaseUrlBase: "https://github.com/echohello-dev/skillet/releases/download/v1.2.3", + checksumsByArtifact: new Map([ + ["skillet-windows-x64.exe", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"], + ]), + }); + + expect(files.nuspec).toContain("1.2.3"); + expect(files.installScript).toContain( + "$url64 = 'https://github.com/echohello-dev/skillet/releases/download/v1.2.3/skillet-windows-x64.exe'", + ); + expect(files.installScript).toContain( + "$checksum64 = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'", + ); + expect(files.uninstallScript).toContain("Uninstall-BinFile -Name 'skillet'"); + }); +});