Compare commits

..

1 Commits

Author SHA1 Message Date
sudacode 17d97f0b7e fix: rename Windows ZIPs and fix macOS manual update checks (#81) 2026-05-24 23:47:02 -07:00
11 changed files with 133 additions and 8 deletions
+1 -1
View File
@@ -1,4 +1,4 @@
type: fixed
area: updater
- Fixed tray update checks for builds that cannot install native app updates, showing a manual install message instead of a restart prompt that cannot apply the update.
- Fixed macOS tray update checks for builds that cannot install native app updates, so newer stable or prerelease GitHub releases are reported instead of incorrectly saying the current build is up to date.
+4
View File
@@ -0,0 +1,4 @@
type: fixed
area: release
- Fixed macOS updater metadata mismatches by giving macOS and Windows ZIP release assets distinct build-time filenames.
+1 -1
View File
@@ -173,7 +173,7 @@ If you prefer to install it manually, see [manual launcher install](#manual-laun
Download the latest installer from [GitHub Releases](https://github.com/ksyasuda/SubMiner/releases/latest):
- `SubMiner-<version>.exe` — installer (recommended)
- `SubMiner-<version>.zip` — portable fallback
- `SubMiner-<version>-win.zip` — portable fallback
Make sure `mpv.exe` is on your `PATH`, or set `mpv.executablePath` in the config during first-run setup.
+3 -2
View File
@@ -88,10 +88,11 @@ Notes:
- AUR publish is best-effort: the workflow retries transient SSH clone/push failures, then warns and leaves the GitHub Release green if AUR still fails. Follow up with a manual `git push aur master` from the AUR checkout when needed.
- Required GitHub Actions secret: `AUR_SSH_PRIVATE_KEY`. Add the matching public key to your AUR account before relying on the automation.
- Release and prerelease workflows upload updater metadata (`*.yml`) and blockmaps (`*.blockmap`) alongside platform artifacts. Do not remove those files while `electron-updater` is enabled.
- macOS tray app updates use the standard `electron-updater`/Squirrel path. Keep `latest-mac.yml`, the macOS ZIP, and ZIP blockmap published; Squirrel uses the ZIP payload even when the DMG remains the user-facing installer.
- macOS tray app updates use the standard `electron-updater`/Squirrel path. Keep `latest-mac.yml`, the macOS `SubMiner-<version>-mac.zip`, and ZIP blockmap published; Squirrel uses the ZIP payload even when the DMG remains the user-facing installer.
- macOS update metadata and full ZIP downloads are routed through `/usr/bin/curl` before Squirrel installation to avoid Electron main-process network crashes on update checks.
- Windows tray app updates use the standard `electron-updater`/NSIS path. Keep `latest.yml`, the Windows NSIS installer, and installer blockmap published; updater HTTP is routed through main-process fetch to avoid Electron main-process network crashes during update checks.
- Build config emits distinct ZIP names: `SubMiner-<version>-mac.zip` for the macOS Squirrel updater payload and `SubMiner-<version>-win.zip` for the Windows portable fallback. The user-facing DMG and Windows installer keep the unqualified `SubMiner-<version>` basename.
- Linux GitHub release metadata and asset downloads also use `/usr/bin/curl` instead of Electron networking for the same reason.
- Local macOS build-output apps outside `/Applications` or `~/Applications` skip native update checks. To validate auto-update end to end, install the signed and notarized app bundle into one of those Applications folders and point it at a published updater feed.
- Local macOS build-output apps outside `/Applications` or `~/Applications` skip native update checks. Manual tray and launcher checks still use GitHub release metadata to report newer releases, but automatic notifications stay quiet when native app installation is unsupported. To validate auto-update end to end, install the signed and notarized app bundle into one of those Applications folders and point it at a published updater feed.
- The first updater-enabled release cannot update older installs automatically. Users need one manual install to get the updater code.
- Stable auto-update checks ignore beta/RC prereleases by default. Set `updates.channel` to `"prerelease"` on a test install when validating beta/RC updater behavior.
+6
View File
@@ -158,6 +158,7 @@
]
},
"mac": {
"artifactName": "SubMiner-${version}-mac.${ext}",
"target": [
"dmg",
"zip"
@@ -174,7 +175,11 @@
}
]
},
"dmg": {
"artifactName": "SubMiner-${version}.${ext}"
},
"win": {
"artifactName": "SubMiner-${version}-win.${ext}",
"target": [
"nsis",
"zip"
@@ -182,6 +187,7 @@
"icon": "assets/SubMiner.ico"
},
"nsis": {
"artifactName": "SubMiner-${version}.${ext}",
"oneClick": false,
"perMachine": false,
"allowToChangeInstallationDirectory": true,
+2 -2
View File
@@ -5227,8 +5227,8 @@ function getUpdateService() {
readState: () => updateStateStore.readState(),
writeState: (state) => updateStateStore.writeState(state),
checkAppUpdate: (channel) => appUpdater.checkForUpdates(channel),
shouldFetchReleaseMetadata: ({ appUpdate }) =>
shouldFetchReleaseMetadataForPlatform(process.platform, appUpdate),
shouldFetchReleaseMetadata: ({ request, appUpdate }) =>
shouldFetchReleaseMetadataForPlatform(process.platform, appUpdate, request),
fetchLatestStableRelease: (channel) =>
fetchLatestStableRelease({ fetch: getFetchForUpdater(), channel }),
updateLauncher: (launcherPath, channel, release) =>
@@ -2,7 +2,7 @@ import assert from 'node:assert/strict';
import test from 'node:test';
import { shouldFetchReleaseMetadataForPlatform } from './release-metadata-policy';
test('macOS release metadata fetch is skipped only when native updater is unsupported', () => {
test('macOS automatic release metadata fetch is skipped when native updater is unsupported', () => {
assert.equal(
shouldFetchReleaseMetadataForPlatform('darwin', {
available: false,
@@ -28,6 +28,33 @@ test('macOS release metadata fetch is skipped only when native updater is unsupp
);
});
test('macOS manual checks fetch release metadata when native updater is unsupported', () => {
const unsupportedUpdate = {
available: false,
version: '0.15.0-beta.4',
canUpdate: false,
};
assert.equal(
shouldFetchReleaseMetadataForPlatform('darwin', unsupportedUpdate, {
source: 'manual',
}),
true,
);
assert.equal(
shouldFetchReleaseMetadataForPlatform('darwin', unsupportedUpdate, {
source: 'launcher',
}),
true,
);
assert.equal(
shouldFetchReleaseMetadataForPlatform('darwin', unsupportedUpdate, {
source: 'automatic',
}),
false,
);
});
test('non-macOS release metadata fetch is not gated by native updater support', () => {
assert.equal(
shouldFetchReleaseMetadataForPlatform('linux', {
@@ -4,12 +4,20 @@ type AppUpdateMetadata = {
canUpdate?: boolean;
};
type UpdateMetadataRequest = {
source?: 'manual' | 'automatic' | 'launcher';
};
export function shouldFetchReleaseMetadataForPlatform(
platform: NodeJS.Platform,
appUpdate: AppUpdateMetadata,
request: UpdateMetadataRequest = {},
): boolean {
if (platform !== 'darwin') {
return true;
}
return appUpdate.canUpdate !== false;
if (appUpdate.canUpdate !== false) {
return true;
}
return request.source === 'manual' || request.source === 'launcher';
}
@@ -1,5 +1,6 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import { shouldFetchReleaseMetadataForPlatform } from './release-metadata-policy';
import { createUpdateService, type UpdateServiceDeps, type UpdateState } from './update-service';
function createDeps(overrides: Partial<UpdateServiceDeps> = {}) {
@@ -362,6 +363,57 @@ test('manual prerelease update check uses prerelease release and launcher channe
]);
});
test('manual macOS prerelease check reports GitHub update when native updater is unsupported', async () => {
const { deps, calls } = createDeps({
getConfig: () => ({
enabled: true,
checkIntervalHours: 24,
notificationType: 'system',
channel: 'prerelease',
}),
getCurrentVersion: () => '0.15.0-beta.4',
checkAppUpdate: async (channel) => {
calls.push(`app:${channel}`);
return {
available: false,
version: '0.15.0-beta.4',
canUpdate: false,
};
},
shouldFetchReleaseMetadata: ({ request, appUpdate }) =>
shouldFetchReleaseMetadataForPlatform('darwin', appUpdate, request),
fetchLatestStableRelease: async (channel) => {
calls.push(`fetch:${channel}`);
return {
tag_name: 'v0.15.0-beta.5',
prerelease: true,
draft: false,
assets: [],
};
},
showUpdateAvailableDialog: async (version) => {
calls.push(`available-dialog:${version}`);
return 'update';
},
updateLauncher: async (_launcherPath, channel, release) => {
calls.push(`launcher:${channel}:${release?.tag_name ?? 'none'}`);
return { status: 'skipped' };
},
});
const service = createUpdateService(deps);
const result = await service.checkForUpdates({ source: 'manual' });
assert.equal(result.status, 'update-available');
assert.deepEqual(calls, [
'app:prerelease',
'fetch:prerelease',
'available-dialog:0.15.0-beta.5',
'launcher:prerelease:v0.15.0-beta.5',
'manual-install:0.15.0-beta.5',
]);
});
test('manual update check keeps current prerelease builds on configured stable channel', async () => {
const { deps, calls } = createDeps({
getCurrentVersion: () => '0.15.0-beta.3',
+5
View File
@@ -87,6 +87,11 @@ test('prerelease workflow writes checksum entries using release asset basenames'
);
});
test('prerelease workflow relies on builder artifact names without post-build zip renames', () => {
assert.doesNotMatch(prereleaseWorkflow, /Rename Windows ZIP artifacts/);
assert.doesNotMatch(prereleaseWorkflow, /Rename-Item[\s\S]*-win\.zip/);
});
test('prerelease workflow validates artifacts before publishing the release and only undrafts after upload', () => {
const artifactsIndex = prereleaseWorkflow.indexOf('artifacts=(');
const createIndex = prereleaseWorkflow.indexOf('gh release create');
+22
View File
@@ -18,15 +18,28 @@ const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8')) as {
afterPack?: string;
electronUpdaterCompatibility?: string;
files?: string[];
artifactName?: string;
dmg?: {
artifactName?: string;
};
extraResources?: Array<{
from?: string;
to?: string;
}>;
mac?: {
artifactName?: string;
};
nsis?: {
artifactName?: string;
};
publish?: Array<{
provider?: string;
owner?: string;
repo?: string;
}>;
win?: {
artifactName?: string;
};
};
};
@@ -199,6 +212,15 @@ test('windows release workflow publishes unsigned artifacts directly without Sig
assert.ok(!releaseWorkflow.includes('SIGNPATH_'));
});
test('release artifact names are distinct before upload', () => {
assert.equal(packageJson.build?.mac?.artifactName, 'SubMiner-${version}-mac.${ext}');
assert.equal(packageJson.build?.dmg?.artifactName, 'SubMiner-${version}.${ext}');
assert.equal(packageJson.build?.win?.artifactName, 'SubMiner-${version}-win.${ext}');
assert.equal(packageJson.build?.nsis?.artifactName, 'SubMiner-${version}.${ext}');
assert.doesNotMatch(releaseWorkflow, /Rename Windows ZIP artifacts/);
assert.doesNotMatch(releaseWorkflow, /Rename-Item[\s\S]*-win\.zip/);
});
test('release workflow publishes subminer-bin to AUR from tagged release artifacts', () => {
assert.match(releaseWorkflow, /aur-publish:/);
assert.match(releaseWorkflow, /needs:\s*\[release\]/);