From ec27abacd077e1c15c5204b8d100f81076c0b7e9 Mon Sep 17 00:00:00 2001 From: Johnny Huynh <27847622+johnnyhuy@users.noreply.github.com> Date: Sat, 21 Feb 2026 11:06:39 +1100 Subject: [PATCH] feat(release): add winget manifests --- .mise.toml | 3 + README.md | 3 +- docs/distribution/winget.md | 25 +++++++ package.json | 3 +- .../echohello-dev.skillet.installer.yaml | 13 ++++ .../echohello-dev.skillet.locale.en-US.yaml | 17 +++++ .../winget/0.0.0/echohello-dev.skillet.yaml | 5 ++ scripts/render-winget-manifest.ts | 63 +++++++++++++++++ src/distribution/winget.ts | 70 +++++++++++++++++++ tests/distribution/winget.test.ts | 24 +++++++ 10 files changed, 224 insertions(+), 2 deletions(-) create mode 100644 docs/distribution/winget.md create mode 100644 packaging/winget/0.0.0/echohello-dev.skillet.installer.yaml create mode 100644 packaging/winget/0.0.0/echohello-dev.skillet.locale.en-US.yaml create mode 100644 packaging/winget/0.0.0/echohello-dev.skillet.yaml create mode 100644 scripts/render-winget-manifest.ts create mode 100644 src/distribution/winget.ts create mode 100644 tests/distribution/winget.test.ts diff --git a/.mise.toml b/.mise.toml index bcba45d..56adefe 100644 --- a/.mise.toml +++ b/.mise.toml @@ -21,3 +21,6 @@ run = "mise run install && bun scripts/render-homebrew-formula.ts" [tasks.render-chocolatey-package] run = "mise run install && bun scripts/render-chocolatey-package.ts" + +[tasks.render-winget-manifest] +run = "mise run install && bun scripts/render-winget-manifest.ts" diff --git a/README.md b/README.md index 9b560b3..e85fbb1 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,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` | Configured | -| winget | `winget install skillet` | Planned | +| winget | `winget install skillet` | Configured | | npm / npx | `npx skillet ...` | Planned | | Docker | `docker run ... skillet ...` | Planned | | Local dev | `mise run dev -- --help` | Available | @@ -143,3 +143,4 @@ Distribution docs: - Homebrew: `docs/distribution/homebrew.md` - Chocolatey: `docs/distribution/chocolatey.md` +- winget: `docs/distribution/winget.md` diff --git a/docs/distribution/winget.md b/docs/distribution/winget.md new file mode 100644 index 0000000..7ec6224 --- /dev/null +++ b/docs/distribution/winget.md @@ -0,0 +1,25 @@ +# winget Distribution + +Skillet ships a winget manifest set for Windows installs. + +## Release Flow + +1. Build Windows artifact and checksums. +2. Render `packaging/winget//` from `SHA256SUMS`. +3. Validate manifests with `winget validate`. +4. Submit manifests to [`microsoft/winget-pkgs`](https://github.com/microsoft/winget-pkgs). + +## Commands + +```bash +mise run build -- --targets=windows-x64 +bun scripts/write-checksums.ts +mise run render-winget-manifest -- --version +``` + +Validation and install checks (Windows): + +```powershell +winget validate packaging/winget//echohello-dev.skillet.yaml +winget install echohello-dev.skillet +``` diff --git a/package.json b/package.json index d91f62d..1a07296 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,8 @@ "test": "vitest run", "build": "bun scripts/build-release.ts", "render:homebrew": "bun scripts/render-homebrew-formula.ts", - "render:choco": "bun scripts/render-chocolatey-package.ts" + "render:choco": "bun scripts/render-chocolatey-package.ts", + "render:winget": "bun scripts/render-winget-manifest.ts" }, "dependencies": { "cac": "^6.7.14", diff --git a/packaging/winget/0.0.0/echohello-dev.skillet.installer.yaml b/packaging/winget/0.0.0/echohello-dev.skillet.installer.yaml new file mode 100644 index 0000000..8690aa1 --- /dev/null +++ b/packaging/winget/0.0.0/echohello-dev.skillet.installer.yaml @@ -0,0 +1,13 @@ +PackageIdentifier: echohello-dev.skillet +PackageVersion: 0.0.0 +Installers: + - Architecture: x64 + InstallerType: exe + InstallerUrl: https://github.com/echohello-dev/skillet/releases/download/v0.0.0/skillet-windows-x64.exe + InstallerSha256: 51CDDEFDE243F0F27E501CA420D5E1D1B9CAD548DC9884EA4052D2915AFEF179 + AppsAndFeaturesEntries: + - DisplayName: skillet +Commands: + - skillet +ManifestType: installer +ManifestVersion: 1.6.0 diff --git a/packaging/winget/0.0.0/echohello-dev.skillet.locale.en-US.yaml b/packaging/winget/0.0.0/echohello-dev.skillet.locale.en-US.yaml new file mode 100644 index 0000000..90af23e --- /dev/null +++ b/packaging/winget/0.0.0/echohello-dev.skillet.locale.en-US.yaml @@ -0,0 +1,17 @@ +PackageIdentifier: echohello-dev.skillet +PackageVersion: 0.0.0 +PackageLocale: en-US +Publisher: echohello-dev +PublisherUrl: https://github.com/echohello-dev +PublisherSupportUrl: https://github.com/echohello-dev/skillet/issues +Author: echohello-dev +PackageName: skillet +PackageUrl: https://github.com/echohello-dev/skillet +License: MIT +ShortDescription: Portable CLI for managing agent skills. +Moniker: skillet +Tags: + - cli + - skills +ManifestType: defaultLocale +ManifestVersion: 1.6.0 diff --git a/packaging/winget/0.0.0/echohello-dev.skillet.yaml b/packaging/winget/0.0.0/echohello-dev.skillet.yaml new file mode 100644 index 0000000..0c3d2c5 --- /dev/null +++ b/packaging/winget/0.0.0/echohello-dev.skillet.yaml @@ -0,0 +1,5 @@ +PackageIdentifier: echohello-dev.skillet +PackageVersion: 0.0.0 +DefaultLocale: en-US +ManifestType: version +ManifestVersion: 1.6.0 diff --git a/scripts/render-winget-manifest.ts b/scripts/render-winget-manifest.ts new file mode 100644 index 0000000..fdbc729 --- /dev/null +++ b/scripts/render-winget-manifest.ts @@ -0,0 +1,63 @@ +import fs from "node:fs"; +import path from "node:path"; +import { parseSha256Sums } from "../src/distribution/checksums"; +import { renderWingetManifestFiles } from "../src/distribution/winget"; + +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/winget/${version}`); + 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 = renderWingetManifestFiles({ version, releaseUrlBase, checksumsByArtifact: checksums }); + + fs.mkdirSync(outputDir, { recursive: true }); + fs.writeFileSync(path.join(outputDir, "echohello-dev.skillet.yaml"), files.versionManifest); + fs.writeFileSync(path.join(outputDir, "echohello-dev.skillet.installer.yaml"), files.installerManifest); + fs.writeFileSync(path.join(outputDir, "echohello-dev.skillet.locale.en-US.yaml"), files.localeManifest); + console.log(`Wrote winget manifests 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/winget.ts b/src/distribution/winget.ts new file mode 100644 index 0000000..fc6b93f --- /dev/null +++ b/src/distribution/winget.ts @@ -0,0 +1,70 @@ +const WINDOWS_X64_ARTIFACT = "skillet-windows-x64.exe"; +const PACKAGE_IDENTIFIER = "echohello-dev.skillet"; + +export type WingetManifestOptions = { + version: string; + releaseUrlBase: string; + checksumsByArtifact: Map; +}; + +export type WingetManifestFiles = { + versionManifest: string; + installerManifest: string; + localeManifest: string; +}; + +export function renderWingetManifestFiles(options: WingetManifestOptions): WingetManifestFiles { + const releaseUrlBase = options.releaseUrlBase.replace(/\/+$/, ""); + const installerUrl = `${releaseUrlBase}/${WINDOWS_X64_ARTIFACT}`; + const installerSha256 = requireChecksum(options.checksumsByArtifact, WINDOWS_X64_ARTIFACT).toUpperCase(); + + return { + versionManifest: `PackageIdentifier: ${PACKAGE_IDENTIFIER} +PackageVersion: ${options.version} +DefaultLocale: en-US +ManifestType: version +ManifestVersion: 1.6.0 +`, + installerManifest: `PackageIdentifier: ${PACKAGE_IDENTIFIER} +PackageVersion: ${options.version} +Installers: + - Architecture: x64 + InstallerType: exe + InstallerUrl: ${installerUrl} + InstallerSha256: ${installerSha256} + AppsAndFeaturesEntries: + - DisplayName: skillet +Commands: + - skillet +ManifestType: installer +ManifestVersion: 1.6.0 +`, + localeManifest: `PackageIdentifier: ${PACKAGE_IDENTIFIER} +PackageVersion: ${options.version} +PackageLocale: en-US +Publisher: echohello-dev +PublisherUrl: https://github.com/echohello-dev +PublisherSupportUrl: https://github.com/echohello-dev/skillet/issues +Author: echohello-dev +PackageName: skillet +PackageUrl: https://github.com/echohello-dev/skillet +License: MIT +ShortDescription: Portable CLI for managing agent skills. +Moniker: skillet +Tags: + - cli + - skills +ManifestType: defaultLocale +ManifestVersion: 1.6.0 +`, + }; +} + +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/winget.test.ts b/tests/distribution/winget.test.ts new file mode 100644 index 0000000..2e68fba --- /dev/null +++ b/tests/distribution/winget.test.ts @@ -0,0 +1,24 @@ +import { describe, expect, it } from "vitest"; +import { renderWingetManifestFiles } from "../../src/distribution/winget"; + +describe("renderWingetManifestFiles", () => { + it("renders winget manifest trio with checksum and installer URL", () => { + const files = renderWingetManifestFiles({ + 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.versionManifest).toContain("PackageIdentifier: echohello-dev.skillet"); + expect(files.versionManifest).toContain("PackageVersion: 1.2.3"); + expect(files.installerManifest).toContain( + "InstallerUrl: https://github.com/echohello-dev/skillet/releases/download/v1.2.3/skillet-windows-x64.exe", + ); + expect(files.installerManifest).toContain( + "InstallerSha256: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", + ); + expect(files.localeManifest).toContain("PackageName: skillet"); + }); +});