{"id":20219507,"url":"https://github.com/superhighfives/releasecast","last_synced_at":"2025-04-10T16:02:31.963Z","repository":{"id":54496749,"uuid":"330037701","full_name":"superhighfives/releasecast","owner":"superhighfives","description":"Tooling to help you get from .app to release","archived":false,"fork":false,"pushed_at":"2024-03-22T09:14:23.000Z","size":6494,"stargazers_count":43,"open_issues_count":1,"forks_count":3,"subscribers_count":5,"default_branch":"main","last_synced_at":"2024-04-13T23:42:42.180Z","etag":null,"topics":["appcast","apple","deltas","dmg","sparkle"],"latest_commit_sha":null,"homepage":"https://www.npmjs.com/package/releasecast","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/superhighfives.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-01-15T22:38:53.000Z","updated_at":"2024-02-02T17:41:14.000Z","dependencies_parsed_at":"2024-03-22T10:30:21.502Z","dependency_job_id":"01a0d083-7a0b-44be-b4fa-f32af21be45c","html_url":"https://github.com/superhighfives/releasecast","commit_stats":{"total_commits":40,"total_committers":1,"mean_commits":40.0,"dds":0.0,"last_synced_commit":"bb4af325f4b095c0c9e84ce18fa918071dafa30e"},"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/superhighfives%2Freleasecast","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/superhighfives%2Freleasecast/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/superhighfives%2Freleasecast/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/superhighfives%2Freleasecast/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/superhighfives","download_url":"https://codeload.github.com/superhighfives/releasecast/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248248875,"owners_count":21072243,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["appcast","apple","deltas","dmg","sparkle"],"created_at":"2024-11-14T06:42:40.465Z","updated_at":"2025-04-10T16:02:31.958Z","avatar_url":"https://github.com/superhighfives.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# ✨ Releasecast\n\n[![oclif](https://img.shields.io/badge/cli-oclif-brightgreen.svg)](https://oclif.io)\n[![Version](https://img.shields.io/npm/v/releasecast.svg)](https://npmjs.org/package/releasecast)\n[![Downloads/week](https://img.shields.io/npm/dw/releasecast.svg)](https://npmjs.org/package/releasecast)\n[![License](https://img.shields.io/npm/l/releasecast.svg)](https://github.com/superhighfives/releasecast/blob/master/package.json)\n\n\u003cimg width=\"780\" alt=\"A screenshot of releasecast in progress\" src=\"https://user-images.githubusercontent.com/449385/104969449-57789a00-59e0-11eb-8ee2-eeb279ed4123.png\"\u003e\n\n[Releasecast](https://www.npmjs.com/package/releasecast) is a command line tool, built with [Shellac](https://www.npmjs.com/package/shellac), to help you get from a Mac .app (like [Pika](https://superhighfives.com/pika)) to release.\n\nIt requires a .app file as an input along with your Apple Developer email [and a password in your keychain](https://developer.apple.com/documentation/xcode/notarizing_macos_software_before_distribution/customizing_the_notarization_workflow#3087734), and optionally, a folder of previous releases, and generates a dmg, notorizes it via Apple, generates Sparkle project data in markdown format, and creates deltas that can be used as part of a release pipeline.\n\nThis set up is pretty specific to my needs, as I use the markdown to generate the appcast feed in NextJS. That said, the source code may be useful to someone else, so here we are. To really dig into what's happening, [check out the source code](https://github.com/superhighfives/releasecast/blob/main/src/index.ts).\n\n**To install:**\n\n```bash\nnpm install --global releasecast\n```\n---\n\nReleasecast is made up of four key steps:\n\n## ⚡️ 1. Processing DMG\n\n**Dependencies:**\n- [`ditto`](https://ss64.com/osx/ditto.html) (macOS native)\n- [`create-dmg`](https://github.com/sindresorhus/create-dmg)\n\n## ⚡️ 2. Notarising DMG with Apple\n\n**Dependencies:**\n- `xcrun notarytool` (via [XCode / Command Line Tools](https://developer.apple.com/downloads/))\n\nWhen you first run this, you may need to run `xcrun notarytool store-credentials` to ensure the Terminal has access to the appropriate credentials. Be sure to call the profile \"Terminal\".\n\n## ⚡️ 3. Generating release files\n\n**Dependencies:**\n- `generate_appcast` ([via Sparkle project](https://sparkle-project.org/))\n\n*Note:* Releasecast expects the `generate_appcast` executable to be available, so you'll need to add it to your `$PATH`. There doesn't seem to be a `brew install generate_appcast` or similar, but if you know of a better way to do this I missed, please open an issue.\n\n*Note:* Releasecast expects output from Sparkle 1.x—you can find [the latest 1.x version at their GitHub releases](https://github.com/sparkle-project/Sparkle/releases).\n\n## ⚡️ 4. Generating metadata\n\n**Dependencies:**\n- [Node 12.16.3](https://nodejs.org/en/blog/release/v12.16.3/)\n\n## Output\n\nReleasecast will place the dmg, appcast.xml, markdown file, and (if you provided a folder of previous releases) a collection of deltas, into the current directory (or you can pass in your own via the `--output` flag).\n\n---\n\n* [Usage](#usage)\n* [Commands](#commands)\n\n# Usage\n```sh-session\n$ npm install -g releasecast\n$ releasecast App.app\nrunning command...\n$ releasecast (-v|--version|version)\nreleasecast/0.0.0 darwin-x64 node-v12.16.3\n$ releasecast --help\nUSAGE\n  $ releasecast App.app -e your@email.com\n...\n```\n\n# Commands\n```\n-e, --email       Apple Developer email (required)\n-r, --releases    Folder of releases to make deltas with\n-o, --output      Output folder\n-t, --title       Release title\n-b, --beta        Add beta flag to markdown output\n-d, --dry         Don't upload DMG to Apple's servers\n-c, --clean       Clean Sparkle cache (note: clears out the entire Sparkle cache)\n\n-v, --version     Version\n-h, --help        Help\n```\n\nFor example:\n```shell\n$ releasecast Pika.app -e your@email.com -t \"Release Title\" -r past-releases -o exports\n```\n\n# Usage example\n\nFor the exmaple below, let's start with a standard `Appcast.xml`, like the one below:\n\n```xml\n\u003c?xml version=\"1.0\" standalone=\"yes\"?\u003e\n\u003crss xmlns:sparkle=\"http://www.andymatuschak.org/xml-namespaces/sparkle\" version=\"2.0\"\u003e\n    \u003cchannel\u003e\n        \u003ctitle\u003ePika\u003c/title\u003e\n        \u003citem\u003e\n            \u003ctitle\u003e0.0.8\u003c/title\u003e\n            \u003cpubDate\u003eSat, 23 Jan 2021 02:39:00 +0000\u003c/pubDate\u003e\n            \u003csparkle:minimumSystemVersion\u003e10.15\u003c/sparkle:minimumSystemVersion\u003e\n            \u003cenclosure url=\"Pika-0.0.8.dmg\" sparkle:version=\"11\" sparkle:shortVersionString=\"0.0.8\" length=\"3551889\" type=\"application/octet-stream\" sparkle:edSignature=\"/oXF5GUzo09cGPGxIRRulr54ucNnZwb1XwcvEjpP1BVgk5x+SF8HwqFW1nyEA9NGWES//OwcfrJXHGo2LNR7DA==\"/\u003e\n            \u003csparkle:deltas\u003e\n                \u003cenclosure url=\"//Pika11-7.delta\" sparkle:version=\"11\" sparkle:shortVersionString=\"0.0.8\" sparkle:deltaFrom=\"7\" length=\"282332\" type=\"application/octet-stream\" sparkle:edSignature=\"76LUCncDkl/qhlpYni1QHG0YLCyL9BWxT0iSj/44R85+fdfux90QudzMasRG56nDOxF0I16A13Z5NRkGPZl6DA==\"/\u003e\n                \u003cenclosure url=\"//Pika11-6.delta\" sparkle:version=\"11\" sparkle:shortVersionString=\"0.0.8\" sparkle:deltaFrom=\"6\" length=\"282430\" type=\"application/octet-stream\" sparkle:edSignature=\"JGaXJlGmo4VK9JwTdpDZ/IwspWNT7ObkyNSQ6UJHnFOIJI5xG4pukoVlJBaLLpUvZaYGutXYPNIY2p5uQaECAg==\"/\u003e\n                \u003cenclosure url=\"//Pika11-5.delta\" sparkle:version=\"11\" sparkle:shortVersionString=\"0.0.8\" sparkle:deltaFrom=\"5\" length=\"289512\" type=\"application/octet-stream\" sparkle:edSignature=\"cS+4bwgP1BwHoug1KLHtP5npne/cVKUV9EFxnEOxXpjbdCvZZAvnyhFcBhxGB5P51+56od3ySJo9zUZOC4VPDg==\"/\u003e\n                \u003cenclosure url=\"//Pika11-4.delta\" sparkle:version=\"11\" sparkle:shortVersionString=\"0.0.8\" sparkle:deltaFrom=\"4\" length=\"1065098\" type=\"application/octet-stream\" sparkle:edSignature=\"KCe+xbIRHAtaUvg1ufOxIir/pSqejAJx8KVSJ0nnzx3u6Bq0EMIpOn9fIKHjUvj0jXuhPeIrFNDSxLK2kqbaAw==\"/\u003e\n                \u003cenclosure url=\"//Pika11-3.delta\" sparkle:version=\"11\" sparkle:shortVersionString=\"0.0.8\" sparkle:deltaFrom=\"3\" length=\"1095844\" type=\"application/octet-stream\" sparkle:edSignature=\"ALvxWWHMMU/7bYCebccGVgro0eRa8ZuAjz2BHNAZJ8UGFXBqzZJIEihlB9fbXTAevUpM6EQom/rEBFe1lPw8Bg==\"/\u003e\n            \u003c/sparkle:deltas\u003e\n        \u003c/item\u003e\n        \u003c!-- More items... --\u003e\n    \u003c/channel\u003e\n\u003c/rss\u003e\n```\n\nWhen you run the `releasecast` command it will generate your Appcast.xml, your deltas (if you provide a folder of up to five previous releases), and a Markdown file named after the latest version (here, for example, being `0.0.8.md`):\n\n```shell\n$ releasecast App.app -e your@emailaddress.com -t \"A Rad New Release\"\n```\n\n```markdown\n---\ntitle: A Rad New Release\ndate: Sat, 23 Jan 2021 02:39:00 +0000\nsignature: /oXF5GUzo09cGPGxIRRulr54ucNnZwb1XwcvEjpP1BVgk5x+SF8HwqFW1nyEA9NGWES//OwcfrJXHGo2LNR7DA==\nsize: 3551889\nbuild: 11\nsystem: 10.15\ndelta:\n  - from: 7\n    size: 282332\n    signature: 76LUCncDkl/qhlpYni1QHG0YLCyL9BWxT0iSj/44R85+fdfux90QudzMasRG56nDOxF0I16A13Z5NRkGPZl6DA==\n  - from: 6\n    size: 282430\n    signature: JGaXJlGmo4VK9JwTdpDZ/IwspWNT7ObkyNSQ6UJHnFOIJI5xG4pukoVlJBaLLpUvZaYGutXYPNIY2p5uQaECAg==\n  - from: 5\n    size: 289512\n    signature: cS+4bwgP1BwHoug1KLHtP5npne/cVKUV9EFxnEOxXpjbdCvZZAvnyhFcBhxGB5P51+56od3ySJo9zUZOC4VPDg==\n  - from: 4\n    size: 1065098\n    signature: KCe+xbIRHAtaUvg1ufOxIir/pSqejAJx8KVSJ0nnzx3u6Bq0EMIpOn9fIKHjUvj0jXuhPeIrFNDSxLK2kqbaAw==\n  - from: 3\n    size: 1095844\n    signature: ALvxWWHMMU/7bYCebccGVgro0eRa8ZuAjz2BHNAZJ8UGFXBqzZJIEihlB9fbXTAevUpM6EQom/rEBFe1lPw8Bg==\n---\n```\n\nThese values correspond to the following:\n- **title:** The title of the release, if provided. Defaults to \"New Release\". This is not used by Sparkle project, but instead for your own reference / changelog.\n- **date:** The date, in `EEE MMM dd HH:mm:ss zzz yyyy` format. For example: Sat, 25 Jan 2021 07:00:00 +0000\n- **signature:** An EdDSA (Ed25519) signature, encoded into BASE64 url-file safe format. The encoded signatures are 88 characters in length and include two trailing pad characters =.\n- **size:** The file size in bytes. For example, `3551889`.\n- **build:** The build number. For example, `11`.\n- **system:** The minimum system version supported. For example, `10.15` for Catalina and newer.\n- **delta:** An array of up to five deltas, featuring the build number they're updating from, the size in bytes, and the signature.\n  - **from:** The build number the delta updates from. For example, `7`.\n  - **size:** The file size in bytes. For example, `282332`.\n  - **signature:** An EdDSA (Ed25519) signature, encoded into BASE64 url-file safe format. For example, `76LUCncDkl/qhlpYni1QHG0YLCyL9BWxT0iSj/44R85+fdfux90QudzMasRG56nDOxF0I16A13Z5NRkGPZl6DA==`.\n\nThese could then be used to regenerate an `Appcast.xml`, and the markdown can be used to create a changelog on your website. For example, in [NextJS](https://nextjs.org/):\n\n```js\nconst generateRssItem = ({ slug, title, date, content, signature, size, build, system, delta }) =\u003e `\n  \u003citem\u003e\n    \u003ctitle\u003ev${slug} - ${title}\u003c/title\u003e\n    \u003cdescription\u003e\n      \u003c![CDATA[${content}]]\u003e\n    \u003c/description\u003e\n    \u003cpubDate\u003e${date}\u003c/pubDate\u003e\n    \u003csparkle:minimumSystemVersion\u003e${system}\u003c/sparkle:minimumSystemVersion\u003e\n    \u003cenclosure url=\"https://github.com/superhighfives/pika/releases/download/${slug}/Pika-${slug}.dmg\"\n               sparkle:version=\"${build}\"\n               sparkle:shortVersionString=\"${slug}\"\n               sparkle:edSignature=\"${signature}\"\n               length=\"${size}\"\n               type=\"application/octet-stream\" /\u003e\n    ${\n      delta \u0026\u0026 delta.length\n        ? `\n      \u003csparkle:deltas\u003e\n        ${delta\n          .map((update) =\u003e {\n            return `\n            \u003cenclosure url=\"https://github.com/superhighfives/pika/releases/download/${slug}/Pika${build}-${update.from}.delta\"\n                       sparkle:version=\"${build}\"\n                       sparkle:shortVersionString=\"${slug}\"\n                       sparkle:deltaFrom=\"${update.from}\"\n                       length=\"${update.size}\"\n                       type=\"application/octet-stream\"\n                       sparkle:edSignature=\"${update.signature}\" /\u003e\n          `\n          })\n          .join('')}\n      \u003c/sparkle:deltas\u003e\n    `\n        : ``\n    }\n  \u003c/item\u003e\n`\n\nconst generateRss = (posts, options) =\u003e `\n  \u003crss xmlns:sparkle=\"http://www.andymatuschak.org/xml-namespaces/sparkle\" xmlns:dc=\"http://purl.org/dc/elements/1.1/\" version=\"2.0\"\u003e\n    \u003cchannel\u003e\n      \u003ctitle\u003ePika Releases\u003c/title\u003e\n      \u003clink\u003e${url}/releases/pika\u003c/link\u003e\n      \u003cdescription\u003eReleases for Pika, an open-source colour picker app for macOS\u003c/description\u003e\n      \u003clanguage\u003een\u003c/language\u003e\n      ${posts.map((post) =\u003e generateRssItem(post, options)).join('')}\n    \u003c/channel\u003e\n  \u003c/rss\u003e\n`\n```\n\nYou could also grab the latest version of the app from the collection of markdown files, ignoring any beta releases (if you tagged them as such in the front matter of the markdown file). The following is based on NextJS' [statically generated blog example using Next.js and Markdown](https://github.com/vercel/next.js/tree/canary/examples/blog-starter):\n\n```js\nimport getAllChangelogs from './getChangelogs'\nimport round from 'lodash/round'\nimport dayjs from 'dayjs'\n\nexport default async (slug) =\u003e {\n  const changelogs = await getAllChangelogs(slug, ['title', 'date', 'slug', 'size', 'system'])\n  const changelog = changelogs[0] // grab the latest release\n\n  return {\n    version: changelog['slug'],\n    metadata: {\n      size: `${round(changelog['size'] / 1000000, 2)}MB`,\n      released: dayjs(changelog['date']).format('DD/MM/YYYY'),\n      version: `v${changelog['slug']} (${changelog['title']})`,\n      macOS: `${changelog['system']}+ required`,\n    },\n  }\n}\n```\n\nYou can see an example of these ideas in action at https://superhighfives.com/pika, and you can see the output `Appcast.xml` at https://superhighfives.com/releases/pika/\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsuperhighfives%2Freleasecast","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsuperhighfives%2Freleasecast","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsuperhighfives%2Freleasecast/lists"}