fix(desktop): publish mac updater metadata (#9945)

Co-authored-by: Douwe M Osinga <douwe@sidewalklabs.com>
This commit is contained in:
Douwe Osinga
2026-06-23 03:29:17 -04:00
committed by GitHub
parent d58f841342
commit 156445ed16
5 changed files with 145 additions and 17 deletions
+8
View File
@@ -108,11 +108,16 @@ jobs:
id-token: write # Required for Sigstore OIDC signing
attestations: write # Required for SLSA build provenance attestations
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Download all artifacts
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
merge-multiple: true
- name: Generate macOS update manifest
run: node ui/desktop/scripts/generate-mac-update-manifest.js --version "${GITHUB_REF_NAME}" --directory .
- name: Attest build provenance
uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v4.1.0
with:
@@ -124,6 +129,7 @@ jobs:
*.deb
*.rpm
*.flatpak
latest-mac.yml
download_cli.sh
# Create/update the versioned release
@@ -139,6 +145,7 @@ jobs:
*.deb
*.rpm
*.flatpak
latest-mac.yml
download_cli.sh
allowUpdates: true
omitBody: true
@@ -159,6 +166,7 @@ jobs:
*.deb
*.rpm
*.flatpak
latest-mac.yml
download_cli.sh
allowUpdates: true
omitBody: true
Generated
+1 -1
View File
@@ -4995,7 +4995,7 @@ dependencies = [
"pem",
"pkcs1",
"pkcs8 0.11.0",
"rand 0.8.6",
"rand 0.10.1",
"regex",
"reqwest 0.13.4",
"rmcp",
@@ -0,0 +1,112 @@
#!/usr/bin/env node
const crypto = require('node:crypto');
const fs = require('node:fs');
const path = require('node:path');
function usage() {
console.error(
'Usage: node scripts/generate-mac-update-manifest.js --version <version> [--directory <path>]'
);
}
function parseArgs(argv) {
const args = {
directory: process.cwd(),
version: '',
};
for (let i = 0; i < argv.length; i += 1) {
const arg = argv[i];
if (arg === '--version') {
args.version = argv[++i] || '';
} else if (arg === '--directory') {
args.directory = argv[++i] || '';
} else {
usage();
process.exit(1);
}
}
if (!args.version || !args.directory) {
usage();
process.exit(1);
}
args.version = args.version.replace(/^v/, '');
args.directory = path.resolve(args.directory);
return args;
}
function ensureFile(filePath) {
if (!fs.existsSync(filePath)) {
throw new Error(`Missing required file: ${filePath}`);
}
}
function copyIfDifferent(source, target) {
ensureFile(source);
if (path.resolve(source) === path.resolve(target)) {
return;
}
fs.copyFileSync(source, target);
}
function sha512(filePath) {
const hash = crypto.createHash('sha512');
hash.update(fs.readFileSync(filePath));
return hash.digest('base64');
}
function yamlString(value) {
return JSON.stringify(value);
}
function writeManifest({ directory, version }) {
const files = [
{
sourceName: 'Goose.zip',
updateName: 'Goose-darwin-arm64.zip',
},
{
sourceName: 'Goose_intel_mac.zip',
updateName: 'Goose-darwin-x64.zip',
},
];
const entries = files.map(({ sourceName, updateName }) => {
const sourcePath = path.join(directory, sourceName);
const updatePath = path.join(directory, updateName);
copyIfDifferent(sourcePath, updatePath);
const stats = fs.statSync(updatePath);
return {
url: updateName,
sha512: sha512(updatePath),
size: stats.size,
};
});
const manifest = [
`version: ${yamlString(version)}`,
'files:',
...entries.flatMap((entry) => [
` - url: ${yamlString(entry.url)}`,
` sha512: ${yamlString(entry.sha512)}`,
` size: ${entry.size}`,
]),
`path: ${yamlString(entries[0].url)}`,
`sha512: ${yamlString(entries[0].sha512)}`,
`releaseDate: ${yamlString(new Date().toISOString())}`,
'',
].join('\n');
fs.writeFileSync(path.join(directory, 'latest-mac.yml'), manifest);
}
try {
writeManifest(parseArgs(process.argv.slice(2)));
} catch (error) {
console.error(error instanceof Error ? error.message : error);
process.exit(1);
}
@@ -81,7 +81,8 @@ const i18n = defineMessages({
},
autoDownload: {
id: 'updateSection.autoDownload',
defaultMessage: 'Update will be downloaded automatically in the background.',
defaultMessage:
'Goose will download the update in the background and install it the next time you quit or restart.',
},
manualInstallNote: {
id: 'updateSection.manualInstallNote',
@@ -89,7 +90,7 @@ const i18n = defineMessages({
},
autoInstallNote: {
id: 'updateSection.autoInstallNote',
defaultMessage: 'The update will be installed automatically when you quit the app.',
defaultMessage: 'No manual install is needed.',
},
readyInstallManual: {
id: 'updateSection.readyInstallManual',
@@ -101,11 +102,12 @@ const i18n = defineMessages({
},
readyInstallAuto: {
id: 'updateSection.readyInstallAuto',
defaultMessage: '✓ Update is ready! It will be installed when you quit Goose.',
defaultMessage:
"✓ Update is ready. Restart Goose to finish installing it, or quit when you're done.",
},
installNowHint: {
id: 'updateSection.installNowHint',
defaultMessage: 'Or click "Install & Restart" to update now.',
defaultMessage: 'Click "Install & Restart" to update now.',
},
});
@@ -176,7 +178,6 @@ export default function UpdateSection() {
// Listen for updater events
window.electron.onUpdaterEvent((event) => {
switch (event.event) {
case 'checking-for-update':
setUpdateStatus('checking');
@@ -354,10 +355,15 @@ export default function UpdateSection() {
<div className="text-text-primary text-2xl font-mono">
{updateInfo.currentVersion || intl.formatMessage(i18n.loading)}
</div>
<div className="text-xs text-text-secondary">{intl.formatMessage(i18n.currentVersion)}</div>
<div className="text-xs text-text-secondary">
{intl.formatMessage(i18n.currentVersion)}
</div>
</div>
{updateInfo.latestVersion && updateInfo.isUpdateAvailable && (
<span className="text-text-secondary"> {intl.formatMessage(i18n.versionAvailable, { version: updateInfo.latestVersion })}</span>
<span className="text-text-secondary">
{' '}
{intl.formatMessage(i18n.versionAvailable, { version: updateInfo.latestVersion })}
</span>
)}
{updateInfo.currentVersion && updateInfo.isUpdateAvailable === false && (
<span className="text-text-primary"> {intl.formatMessage(i18n.upToDate)}</span>
@@ -375,11 +381,13 @@ export default function UpdateSection() {
{intl.formatMessage(i18n.checkForUpdates)}
</Button>
{updateInfo.isUpdateAvailable && updateStatus === 'idle' && autoDownloadEffectivelyDisabled && (
<Button onClick={downloadUpdate} variant="secondary" size="sm">
{intl.formatMessage(i18n.downloadNow)}
</Button>
)}
{updateInfo.isUpdateAvailable &&
updateStatus === 'idle' &&
autoDownloadEffectivelyDisabled && (
<Button onClick={downloadUpdate} variant="secondary" size="sm">
{intl.formatMessage(i18n.downloadNow)}
</Button>
)}
{updateStatus === 'ready' && (
<Button onClick={installUpdate} variant="default" size="sm">
+4 -4
View File
@@ -4698,7 +4698,7 @@
"defaultMessage": "URL:"
},
"updateSection.autoDownload": {
"defaultMessage": "Update will be downloaded automatically in the background."
"defaultMessage": "Goose will download the update in the background and install it the next time you quit or restart."
},
"updateSection.autoDownloadDisabledByEnv": {
"defaultMessage": "Automatic downloads are disabled via the GOOSE_DISABLE_AUTO_DOWNLOAD environment variable."
@@ -4707,7 +4707,7 @@
"defaultMessage": "Automatic download is disabled. Click \"Download Now\" to download manually."
},
"updateSection.autoInstallNote": {
"defaultMessage": "The update will be installed automatically when you quit the app."
"defaultMessage": "No manual install is needed."
},
"updateSection.checkForUpdates": {
"defaultMessage": "Check for Updates"
@@ -4740,7 +4740,7 @@
"defaultMessage": "Install & Restart"
},
"updateSection.installNowHint": {
"defaultMessage": "Or click \"Install & Restart\" to update now."
"defaultMessage": "Click \"Install & Restart\" to update now."
},
"updateSection.latestVersion": {
"defaultMessage": "You are running the latest version!"
@@ -4755,7 +4755,7 @@
"defaultMessage": "Manual installation required for this update method."
},
"updateSection.readyInstallAuto": {
"defaultMessage": "✓ Update is ready! It will be installed when you quit Goose."
"defaultMessage": "✓ Update is ready. Restart Goose to finish installing it, or quit when you're done."
},
"updateSection.readyInstallManual": {
"defaultMessage": "✓ Update is ready! Click \"Install & Restart\" for installation instructions."