-
Notifications
You must be signed in to change notification settings - Fork 3
Add winget manifest generator and release automation #57
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,45 @@ | ||
| # Publishing ClipForge to winget | ||
|
|
||
| Windows is where most ClipForge installs happen. A | ||
| [winget](https://learn.microsoft.com/en-us/windows/package-manager/) package | ||
| lets people run: | ||
|
|
||
| ``` | ||
| winget install JeremySNR.ClipForge | ||
| ``` | ||
|
|
||
| The app is **not code-signed yet**, which winget allows. Users will still see a | ||
| SmartScreen prompt on first launch. | ||
|
|
||
| ## First publish (one-off) | ||
|
|
||
| The first listing is a pull request to | ||
| [microsoft/winget-pkgs](https://github.com/microsoft/winget-pkgs). Generate the | ||
| three YAML files from the latest GitHub release: | ||
|
|
||
| ```bash | ||
| node scripts/print-winget-manifest.mjs --out .tmp/winget | ||
| ``` | ||
|
|
||
| Then either: | ||
|
|
||
| - install [wingetcreate](https://github.com/microsoft/winget-create) and submit | ||
| (`wingetcreate submit .tmp/winget`), or | ||
| - open a PR against `microsoft/winget-pkgs` under | ||
| `manifests/j/JeremySNR/ClipForge/<version>/` with those three files. | ||
|
|
||
| Package identifier: **JeremySNR.ClipForge**. | ||
|
|
||
| ## Later releases (automatic) | ||
|
|
||
| Once the package exists, set two repository secrets/variables: | ||
|
|
||
| | Name | Where | Value | | ||
| | --- | --- | --- | | ||
| | `WINGET_TOKEN` | Actions secret | A PAT with `public_repo` that can open PRs on a fork of `winget-pkgs` | | ||
| | `WINGET_PACKAGE_ID` | Actions variable | `JeremySNR.ClipForge` | | ||
|
|
||
| The [Release workflow](../.github/workflows/release.yml) then runs | ||
| `winget-releaser` after each GitHub Release so the manifest stays current. | ||
| Leave `WINGET_PACKAGE_ID` empty until the first package is accepted, otherwise | ||
| the job would try to update a package that does not exist yet. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,146 @@ | ||
| /** | ||
| * Prints a winget-pkgs manifest trio (version + installer + locale) for the | ||
| * latest (or a given) GitHub release. Used for the first submit to | ||
| * microsoft/winget-pkgs; later versions are published by the Release | ||
| * workflow once WINGET_TOKEN is set (see docs/winget.md). | ||
| * | ||
| * Usage: | ||
| * node scripts/print-winget-manifest.mjs # latest release | ||
| * node scripts/print-winget-manifest.mjs 0.7.0 # specific version | ||
| * | ||
| * Writes YAML to stdout as three files separated by banners, or to a | ||
| * directory if --out DIR is passed. | ||
| */ | ||
| import { mkdir, writeFile } from 'node:fs/promises' | ||
| import { join } from 'node:path' | ||
|
|
||
| const PACKAGE_ID = 'JeremySNR.ClipForge' | ||
| const OWNER = 'JeremySNR' | ||
| const REPO = 'clip-forge' | ||
| const PUBLISHER = 'Jeremy Smith' | ||
| const PACKAGE_NAME = 'ClipForge' | ||
|
|
||
| const args = process.argv.slice(2) | ||
| let versionArg = null | ||
| let outDir = null | ||
| for (let i = 0; i < args.length; i++) { | ||
| if (args[i] === '--out') { | ||
| outDir = args[++i] | ||
| continue | ||
| } | ||
| if (!args[i].startsWith('-')) versionArg = args[i] | ||
| } | ||
|
|
||
| function sha256Of(asset) { | ||
| const digest = asset.digest | ||
| if (typeof digest === 'string' && digest.startsWith('sha256:')) return digest.slice(7) | ||
| throw new Error(`Release asset ${asset.name} has no sha256 digest`) | ||
| } | ||
|
|
||
| export function buildManifests({ version, installerUrl, installerSha256, releaseUrl, publishedAt }) { | ||
| const date = (publishedAt ?? new Date().toISOString()).slice(0, 10) | ||
| const versionYaml = `PackageIdentifier: ${PACKAGE_ID} | ||
| PackageVersion: ${version} | ||
| DefaultLocale: en-US | ||
| ManifestType: version | ||
| ManifestVersion: 1.6.0 | ||
| ` | ||
|
|
||
| const installerYaml = `PackageIdentifier: ${PACKAGE_ID} | ||
| PackageVersion: ${version} | ||
| InstallerLocale: en-US | ||
| InstallerType: nullsoft | ||
| Scope: user | ||
| UpgradeBehavior: install | ||
| ReleaseDate: ${date} | ||
| Installers: | ||
| - Architecture: x64 | ||
| InstallerUrl: ${installerUrl} | ||
| InstallerSha256: ${installerSha256.toUpperCase()} | ||
| ManifestType: installer | ||
| ManifestVersion: 1.6.0 | ||
| ` | ||
|
|
||
| const localeYaml = `PackageIdentifier: ${PACKAGE_ID} | ||
| PackageVersion: ${version} | ||
| PackageLocale: en-US | ||
| Publisher: ${PUBLISHER} | ||
| PublisherUrl: https://github.com/${OWNER} | ||
| PublisherSupportUrl: https://github.com/${OWNER}/${REPO}/issues | ||
| Author: ${PUBLISHER} | ||
| PackageName: ${PACKAGE_NAME} | ||
| PackageUrl: https://github.com/${OWNER}/${REPO} | ||
| License: MIT | ||
| LicenseUrl: https://github.com/${OWNER}/${REPO}/blob/main/LICENSE | ||
| Copyright: Copyright (c) ClipForge Contributors | ||
| ShortDescription: Open-source Opus Clip alternative. Turn long videos into vertical clips on your desktop. | ||
| Description: Turn podcasts, webinars, streams and interviews into ready-to-post vertical clips. AI-picked moments, virality scores, animated captions, auto zoom and speaker-aware reframing. Runs on your machine; you bring an OpenAI API key. | ||
| Moniker: clipforge | ||
| Tags: | ||
| - video | ||
| - captions | ||
| - clips | ||
| - electron | ||
| - openai | ||
| ReleaseNotesUrl: ${releaseUrl} | ||
| ManifestType: defaultLocale | ||
| ManifestVersion: 1.6.0 | ||
| ` | ||
|
|
||
| return { versionYaml, installerYaml, localeYaml } | ||
| } | ||
|
|
||
| async function githubJson(path) { | ||
| const headers = { Accept: 'application/vnd.github+json', 'User-Agent': 'clipforge-winget' } | ||
| if (process.env.GH_TOKEN || process.env.GITHUB_TOKEN) { | ||
| headers.Authorization = `Bearer ${process.env.GH_TOKEN || process.env.GITHUB_TOKEN}` | ||
| } | ||
| const res = await fetch(`https://api.github.com/repos/${OWNER}/${REPO}/${path}`, { headers }) | ||
| if (!res.ok) throw new Error(`GitHub API ${path} failed: HTTP ${res.status}`) | ||
| return res.json() | ||
| } | ||
|
|
||
| async function main() { | ||
| const release = versionArg | ||
| ? await githubJson(`releases/tags/v${versionArg.replace(/^v/, '')}`) | ||
| : await githubJson('releases/latest') | ||
| const version = String(release.tag_name ?? '').replace(/^v/, '') | ||
| const installer = (release.assets ?? []).find((a) => /^ClipForge-Setup-.*\.exe$/.test(a.name)) | ||
| if (!installer) { | ||
| throw new Error(`No ClipForge-Setup-*.exe on ${release.tag_name}`) | ||
| } | ||
| const manifests = buildManifests({ | ||
| version, | ||
| installerUrl: installer.browser_download_url, | ||
| installerSha256: sha256Of(installer), | ||
| releaseUrl: release.html_url, | ||
| publishedAt: release.published_at | ||
| }) | ||
|
|
||
| const files = [ | ||
| [`${PACKAGE_ID}.yaml`, manifests.versionYaml], | ||
| [`${PACKAGE_ID}.installer.yaml`, manifests.installerYaml], | ||
| [`${PACKAGE_ID}.locale.en-US.yaml`, manifests.localeYaml] | ||
| ] | ||
|
|
||
| if (outDir) { | ||
| await mkdir(outDir, { recursive: true }) | ||
| for (const [name, body] of files) { | ||
| await writeFile(join(outDir, name), body, 'utf8') | ||
| } | ||
| console.error(`Wrote ${files.length} files to ${outDir}`) | ||
| return | ||
| } | ||
|
|
||
| for (const [name, body] of files) { | ||
| process.stdout.write(`# --- ${name} ---\n${body}\n`) | ||
| } | ||
| } | ||
|
|
||
| const isMain = process.argv[1] && process.argv[1].endsWith('print-winget-manifest.mjs') | ||
| if (isMain) { | ||
| main().catch((err) => { | ||
| console.error(err instanceof Error ? err.message : err) | ||
| process.exit(1) | ||
| }) | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| import { describe, expect, it } from 'vitest' | ||
| // @ts-expect-error plain ESM helper, no declaration file | ||
| import { buildManifests } from '../scripts/print-winget-manifest.mjs' | ||
|
|
||
| const SHA = 'c0e801a93b2c26dbc529e16f0c6fcf25a560728132a5a05a444406488a5433b5' | ||
|
|
||
| describe('buildManifests', () => { | ||
| const manifests = buildManifests({ | ||
| version: '0.7.0', | ||
| installerUrl: | ||
| 'https://github.com/JeremySNR/clip-forge/releases/download/v0.7.0/ClipForge-Setup-0.7.0.exe', | ||
| installerSha256: SHA, | ||
| releaseUrl: 'https://github.com/JeremySNR/clip-forge/releases/tag/v0.7.0', | ||
| publishedAt: '2026-09-02T06:20:17Z' | ||
| }) | ||
|
|
||
| it('emits the winget-pkgs identifier and NSIS installer', () => { | ||
| expect(manifests.versionYaml).toContain('PackageIdentifier: JeremySNR.ClipForge') | ||
| expect(manifests.versionYaml).toContain('PackageVersion: 0.7.0') | ||
| expect(manifests.installerYaml).toContain('InstallerType: nullsoft') | ||
| expect(manifests.installerYaml).toContain('ClipForge-Setup-0.7.0.exe') | ||
| expect(manifests.installerYaml).toContain(`InstallerSha256: ${SHA.toUpperCase()}`) | ||
| expect(manifests.installerYaml).toContain('ReleaseDate: 2026-09-02') | ||
| }) | ||
|
|
||
| it('fills locale metadata winget-pkgs requires', () => { | ||
| expect(manifests.localeYaml).toContain('PackageName: ClipForge') | ||
| expect(manifests.localeYaml).toContain('License: MIT') | ||
| expect(manifests.localeYaml).toContain('Moniker: clipforge') | ||
| expect(manifests.localeYaml).toContain( | ||
| 'https://github.com/JeremySNR/clip-forge/releases/tag/v0.7.0' | ||
| ) | ||
| }) | ||
| }) |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Winget job uses branch as tag
Medium Severity
The winget job never passes
release-tag, sowinget-releaserfalls back togithub.ref_name. Tag pushes resolve correctly, but aRelease vcommit orworkflow_dispatchfrommainlooks up a release namedmainand publishing fails. Thenotesjob already reads the version frompackage.jsonfor this same workflow.Reviewed by Cursor Bugbot for commit 3344f06. Configure here.