Skip to content

Commit 9dadf89

Browse files
authored
Merge pull request GCWing#2145 from kev1n77/fmy/installer
fix(release): publish versioned installers and refine desktop updates
2 parents c4a301e + 58d89b2 commit 9dadf89

19 files changed

Lines changed: 601 additions & 51 deletions

‎.github/workflows/ci.yml‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,12 @@ jobs:
3232
steps:
3333
- uses: actions/checkout@v5
3434

35+
- name: Setup Node.js
36+
uses: actions/setup-node@v5
37+
with:
38+
node-version-file: package.json
39+
package-manager-cache: false
40+
3541
- name: Reject CRLF in shell and deploy assets
3642
run: |
3743
bad=$(git ls-files -z \
@@ -53,6 +59,9 @@ jobs:
5359
done < <(git ls-files -z '*.sh' '*.bash')
5460
exit "$rc"
5561
62+
- name: Verify release and version-generation contracts
63+
run: node --test scripts/tauri-release-manifest.test.mjs scripts/linux-binaries-manifest.test.mjs scripts/version-generation.test.mjs
64+
5665
- name: Verify minisign download fallback
5766
run: |
5867
set -euo pipefail

‎.github/workflows/desktop-package.yml‎

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,9 @@ jobs:
233233
- name: Install dependencies
234234
run: pnpm install --frozen-lockfile
235235

236+
- name: Verify release version metadata
237+
run: node scripts/verify-release-version-sync.mjs --version "${{ needs.prepare.outputs.version }}"
238+
236239
- name: Build desktop app
237240
run: ${{ matrix.platform.build_command }}
238241

@@ -505,6 +508,20 @@ jobs:
505508
echo "Relay image descriptor:"
506509
find relay-image-assets -type f | sort
507510
511+
- name: Prepare versioned Windows installer
512+
run: |
513+
node scripts/prepare-windows-installer-asset.mjs \
514+
--assets-dir release-assets \
515+
--version "${{ needs.prepare.outputs.version }}" \
516+
--out-dir release-manual-assets
517+
518+
- name: Sign versioned Windows installer
519+
shell: bash
520+
env:
521+
BITFUN_SIGNING_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
522+
BITFUN_SIGNING_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
523+
run: bash scripts/sign-release-assets.sh release-manual-assets/*.exe
524+
508525
- name: Collect updater assets
509526
run: |
510527
node scripts/collect-tauri-updater-assets.mjs \
@@ -517,6 +534,7 @@ jobs:
517534
run: |
518535
node scripts/generate-tauri-latest-json.mjs \
519536
--assets-dir release-updater-assets \
537+
--manual-assets-dir release-manual-assets \
520538
--version "${{ needs.prepare.outputs.version }}" \
521539
--tag "${{ needs.prepare.outputs.release_tag }}" \
522540
--repo "GCWing/BitFun" \
@@ -528,7 +546,8 @@ jobs:
528546
node scripts/verify-tauri-latest-json.mjs \
529547
--manifest release-updater-assets/latest.json \
530548
--version "${{ needs.prepare.outputs.version }}" \
531-
--required-platforms "${REQUIRED_UPDATER_PLATFORMS}"
549+
--required-platforms "${REQUIRED_UPDATER_PLATFORMS}" \
550+
--required-manual-platforms "windows-x86_64"
532551
533552
- name: Generate Linux binaries manifest
534553
run: |
@@ -556,7 +575,7 @@ jobs:
556575
mapfile -t assets < <(
557576
find release-assets -type f \
558577
\( -name '*.AppImage' -o -name '*.deb' -o -name '*.rpm' \
559-
-o -name '*.dmg' -o -name '*bitfun-installer.exe' \) | sort
578+
-o -name '*.dmg' \) | sort
560579
)
561580
if [[ "${#assets[@]}" -eq 0 ]]; then
562581
echo "No installer packages found to sign."
@@ -575,11 +594,12 @@ jobs:
575594
generate_release_notes: true
576595
files: |
577596
release-updater-assets/*
597+
release-manual-assets/*.exe
598+
release-manual-assets/*.exe.sig
578599
release-assets/**/*.AppImage
579600
release-assets/**/*.deb
580601
release-assets/**/*.dmg
581602
release-assets/**/*.rpm
582-
release-assets/**/*bitfun-installer.exe
583603
release-assets/**/*.sig
584604
release-assets/minisign.pub
585605
linux-release-assets/bitfun-cli-*.tar.gz
@@ -602,6 +622,7 @@ jobs:
602622
--manifest latest.published.json \
603623
--version "${{ needs.prepare.outputs.version }}" \
604624
--required-platforms "${REQUIRED_UPDATER_PLATFORMS}" \
625+
--required-manual-platforms "windows-x86_64" \
605626
--check-urls true
606627
607628
- name: Verify published Linux binaries manifest

‎.github/workflows/nightly.yml‎

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -180,13 +180,21 @@ jobs:
180180
const pkg = JSON.parse(fs.readFileSync('package.json', 'utf-8'));
181181
pkg.version = process.env.NIGHTLY_VERSION.split('+')[0];
182182
fs.writeFileSync('package.json', JSON.stringify(pkg, null, 2) + '\n');
183+
for (const file of ['BitFun-Installer/package.json', 'BitFun-Installer/package-lock.json']) {
184+
const data = JSON.parse(fs.readFileSync(file, 'utf-8'));
185+
data.version = pkg.version;
186+
if (data.packages?.['']) data.packages[''].version = pkg.version;
187+
fs.writeFileSync(file, JSON.stringify(data, null, 2) + '\n');
188+
}
183189
"
184190
185191
# Patch Cargo workspace version (semver: nightly suffix uses hyphen)
186192
# Cargo.toml only accepts: MAJOR.MINOR.PATCH or MAJOR.MINOR.PATCH-PRE
187193
CARGO_VERSION="$(echo "$NIGHTLY_VERSION" | sed 's/+.*//')"
188194
sed -i.bak "s/^version = \".*\" # x-release-please-version/version = \"${CARGO_VERSION}\" # x-release-please-version/" Cargo.toml
189195
rm -f Cargo.toml.bak
196+
sed -i.bak "0,/^version = \".*\"/s//version = \"${CARGO_VERSION}\"/" BitFun-Installer/src-tauri/Cargo.toml
197+
rm -f BitFun-Installer/src-tauri/Cargo.toml.bak
190198
191199
echo "package.json version: $(jq -r '.version' package.json)"
192200
echo "Cargo.toml version: $(grep 'x-release-please-version' Cargo.toml)"
@@ -418,6 +426,15 @@ jobs:
418426
--repo "GCWing/BitFun" \
419427
--out linux-release-assets/linux-binaries.json
420428
429+
- name: Prepare versioned Windows installer
430+
env:
431+
NIGHTLY_VERSION: ${{ needs.check-changes.outputs.nightly_version }}
432+
run: |
433+
node scripts/prepare-windows-installer-asset.mjs \
434+
--assets-dir release-assets \
435+
--version "${NIGHTLY_VERSION%%+*}" \
436+
--out-dir release-manual-assets
437+
421438
# The Tauri bundler signs the five updater artifacts during `tauri build`,
422439
# but the installers people download by hand from the release page — dmg,
423440
# deb, rpm, the Windows installer and the direct AppImages — shipped with
@@ -432,11 +449,12 @@ jobs:
432449
BITFUN_SIGNING_PUBKEY: ${{ secrets.TAURI_UPDATER_PUBKEY }}
433450
run: |
434451
set -euo pipefail
435-
mapfile -t assets < <(
452+
mapfile -t assets < <({
436453
find release-assets -type f \
437454
\( -name '*.AppImage' -o -name '*.deb' -o -name '*.rpm' \
438-
-o -name '*.dmg' -o -name '*bitfun-installer.exe' \) | sort
439-
)
455+
-o -name '*.dmg' \)
456+
find release-manual-assets -type f -name '*.exe'
457+
} | sort)
440458
if [[ "${#assets[@]}" -eq 0 ]]; then
441459
echo "No installer packages found to sign."
442460
exit 0
@@ -466,8 +484,9 @@ jobs:
466484
release-assets/**/*.deb
467485
release-assets/**/*.dmg
468486
release-assets/**/*.rpm
469-
release-assets/**/*bitfun-installer.exe
470487
release-assets/**/*.sig
488+
release-manual-assets/*.exe
489+
release-manual-assets/*.exe.sig
471490
release-assets/minisign.pub
472491
release-assets/**/bitfun-cli-*-apple-darwin.tar.gz
473492
release-assets/**/bitfun-cli-*-apple-darwin.tar.gz.sha256

‎package.json‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@
1010
"copy-monaco": "copyfiles -u 5 \"src/web-ui/node_modules/monaco-editor/min/vs/**/*\" src/web-ui/public/monaco-editor && node scripts/prune-monaco-nls.cjs",
1111
"copy-icons": "copyfiles -f \"src/apps/desktop/icons/Logo-ICON.png\" \"src/web-ui/public/\"",
1212
"copy-assets": "pnpm run copy-monaco && pnpm run copy-icons",
13-
"generate-version": "node scripts/generate-version.cjs",
13+
"generate-version": "node scripts/generate-version.cjs --build-env production",
14+
"generate-version:dev": "node scripts/generate-version.cjs --build-env development",
1415
"generate-startup-appearance-bootstrap": "node scripts/generate-startup-appearance-bootstrap.mjs",
1516
"generate-all": "pnpm run generate-version && pnpm run generate-startup-appearance-bootstrap",
1617
"postinstall": "pnpm run copy-assets",

‎scripts/desktop-tauri-build.mjs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,7 @@ export function prepareTauriConfig(
216216
endpoints: [primaryEndpoint, fallbackEndpoint],
217217
pubkey,
218218
windows: {
219-
installMode: 'passive',
219+
installMode: 'quiet',
220220
},
221221
},
222222
};

‎scripts/desktop-tauri-build.test.mjs‎

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,39 @@ test('Desktop Tauri projection consumes only the resolved member identity', () =
125125
}
126126
});
127127

128+
test('Windows updater installs NSIS packages without showing its progress window', () => {
129+
const fixture = join(tmpdir(), `bitfun-tauri-updater-${process.pid}-${Date.now()}`);
130+
const baseConfig = join(fixture, 'tauri.conf.json');
131+
const updaterEnv = {
132+
BITFUN_ENABLE_UPDATER_ARTIFACTS: process.env.BITFUN_ENABLE_UPDATER_ARTIFACTS,
133+
TAURI_SIGNING_PRIVATE_KEY: process.env.TAURI_SIGNING_PRIVATE_KEY,
134+
TAURI_UPDATER_PUBKEY: process.env.TAURI_UPDATER_PUBKEY,
135+
};
136+
mkdirSync(fixture, { recursive: true });
137+
writeFileSync(baseConfig, JSON.stringify({ bundle: { resources: {} } }));
138+
process.env.BITFUN_ENABLE_UPDATER_ARTIFACTS = 'true';
139+
process.env.TAURI_SIGNING_PRIVATE_KEY = 'test-private-key';
140+
process.env.TAURI_UPDATER_PUBKEY = 'test-public-key';
141+
142+
try {
143+
const generated = prepareTauriConfig(baseConfig, {
144+
desktopDir: fixture,
145+
flashgrepBinary: join(fixture, 'flashgrep'),
146+
});
147+
const config = JSON.parse(readFileSync(generated, 'utf8'));
148+
assert.equal(config.plugins.updater.windows.installMode, 'quiet');
149+
} finally {
150+
for (const [name, value] of Object.entries(updaterEnv)) {
151+
if (value === undefined) {
152+
delete process.env[name];
153+
} else {
154+
process.env[name] = value;
155+
}
156+
}
157+
rmSync(fixture, { force: true, recursive: true });
158+
}
159+
});
160+
128161
test('Desktop release config bundles models.dev notices and provenance', () => {
129162
const config = JSON.parse(
130163
readFileSync(join(ROOT, 'src', 'apps', 'desktop', 'tauri.conf.json'), 'utf8')

‎scripts/dev.cjs‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -666,7 +666,11 @@ async function main() {
666666
},
667667
{
668668
name: 'Generate version info',
669-
promise: runCommandPrefixed('version', 'node', ['scripts/generate-version.cjs']),
669+
promise: runCommandPrefixed('version', 'node', [
670+
'scripts/generate-version.cjs',
671+
'--build-env',
672+
'development',
673+
]),
670674
},
671675
];
672676

‎scripts/generate-tauri-latest-json.mjs‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ const tag = requireArg(args, 'tag');
99
const repo = requireArg(args, 'repo');
1010
const out = requireArg(args, 'out');
1111
const requiredPlatforms = parseListArg(args['required-platforms'] || '');
12+
const manualAssetsDir = args['manual-assets-dir'];
1213

1314
if (!existsSync(assetsDir)) {
1415
fail(`Assets directory does not exist: ${assetsDir}`);
@@ -55,6 +56,22 @@ const manifest = {
5556
platforms,
5657
};
5758

59+
if (manualAssetsDir) {
60+
const installerName = `BitFun_${version}_windows-x86_64-installer.exe`;
61+
const installerPath = join(manualAssetsDir, installerName);
62+
const signaturePath = `${installerPath}.sig`;
63+
if (!existsSync(installerPath) || !existsSync(signaturePath)) {
64+
fail(`Missing signed manual installer pair: ${installerPath} and ${signaturePath}`);
65+
}
66+
const assetUrl = `https://github.com/${repo}/releases/download/${encodeURIComponent(tag)}/${encodeURIComponent(installerName)}`;
67+
manifest.manual_installers = {
68+
'windows-x86_64': {
69+
url: assetUrl,
70+
signature_url: `${assetUrl}.sig`,
71+
},
72+
};
73+
}
74+
5875
mkdirSync(dirname(out), { recursive: true });
5976
writeFileSync(out, `${JSON.stringify(manifest, null, 2)}\n`, 'utf8');
6077
console.log(`[latest-json] Wrote ${out}`);

‎scripts/generate-version.cjs‎

Lines changed: 28 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,20 @@ const {
1919
const packageJsonPath = path.resolve(__dirname, '../package.json');
2020
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
2121

22+
function parseBuildEnv(args) {
23+
const index = args.indexOf('--build-env');
24+
const buildEnv = index >= 0 ? args[index + 1] : undefined;
25+
if (!['development', 'production', 'preview'].includes(buildEnv)) {
26+
throw new Error('Expected --build-env development|production|preview');
27+
}
28+
return buildEnv;
29+
}
30+
31+
function readArg(args, name) {
32+
const index = args.indexOf(name);
33+
return index >= 0 ? args[index + 1] : undefined;
34+
}
35+
2236
function getGitInfo() {
2337
try {
2438
const gitCommitFull = execSync('git rev-parse HEAD', { encoding: 'utf-8' }).trim();
@@ -40,11 +54,10 @@ function getGitInfo() {
4054
}
4155
}
4256

43-
function generateVersionInfo() {
57+
function generateVersionInfo(buildEnv) {
4458
const gitInfo = getGitInfo();
4559
const buildDate = new Date().toISOString();
4660
const buildTimestamp = Date.now();
47-
const buildEnv = process.env.NODE_ENV || 'development';
4861
const isDev = buildEnv === 'development';
4962

5063
const versionInfo = {
@@ -60,8 +73,8 @@ function generateVersionInfo() {
6073
return versionInfo;
6174
}
6275

63-
function saveVersionInfoToJson(versionInfo) {
64-
const outputPath = path.resolve(__dirname, '../src/web-ui/public/version.json');
76+
function saveVersionInfoToJson(versionInfo, outputRoot) {
77+
const outputPath = path.resolve(outputRoot, 'src/web-ui/public/version.json');
6578

6679
const dir = path.dirname(outputPath);
6780
if (!fs.existsSync(dir)) {
@@ -75,8 +88,8 @@ function saveVersionInfoToJson(versionInfo) {
7588
);
7689
}
7790

78-
function saveVersionInfoToTS(versionInfo) {
79-
const outputPath = path.resolve(__dirname, '../src/web-ui/src/generated/version.ts');
91+
function saveVersionInfoToTS(versionInfo, outputRoot) {
92+
const outputPath = path.resolve(outputRoot, 'src/web-ui/src/generated/version.ts');
8093

8194
const dir = path.dirname(outputPath);
8295
if (!fs.existsSync(dir)) {
@@ -104,13 +117,16 @@ function generateHtmlInjectionScript(versionInfo) {
104117
}
105118

106119
function main() {
107-
const versionInfo = generateVersionInfo();
120+
const args = process.argv.slice(2);
121+
const buildEnv = parseBuildEnv(args);
122+
const outputRoot = path.resolve(readArg(args, '--output-root') || path.resolve(__dirname, '..'));
123+
const versionInfo = generateVersionInfo(buildEnv);
108124

109-
saveVersionInfoToJson(versionInfo);
110-
saveVersionInfoToTS(versionInfo);
125+
saveVersionInfoToJson(versionInfo, outputRoot);
126+
saveVersionInfoToTS(versionInfo, outputRoot);
111127

112128
const htmlScript = generateHtmlInjectionScript(versionInfo);
113-
const htmlScriptPath = path.resolve(__dirname, '../src/web-ui/src/generated/version-injection.html');
129+
const htmlScriptPath = path.resolve(outputRoot, 'src/web-ui/src/generated/version-injection.html');
114130

115131
const htmlDir = path.dirname(htmlScriptPath);
116132
if (!fs.existsSync(htmlDir)) {
@@ -123,12 +139,11 @@ function main() {
123139
printSuccess(`${versionInfo.name} v${versionInfo.version}${gitStr}`);
124140
}
125141

126-
// On failure: warn and exit 0 so build is not interrupted
127142
try {
128143
main();
129144
} catch (err) {
130-
printWarning('Version info generation failed, skipped: ' + (err.message || err));
131-
process.exit(0);
145+
printWarning('Version info generation failed: ' + (err.message || err));
146+
process.exit(1);
132147
}
133148

134149

0 commit comments

Comments
 (0)