import assert from 'node:assert/strict';
import { mkdirSync, readFileSync, rmSync, utimesSync, writeFileSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import test from 'node:test';
import {
prepareMacOSFlashgrepForSigning,
prepareTauriConfig,
shouldRetryMacDmgBuild,
} from './desktop-tauri-build.mjs';
import { resolveProductDefinition } from './product-customization/resolver.mjs';
const FAILED_BUILD = { status: 1 };
const DMG_ARGS = ['--target', 'x86_64-apple-darwin', '--bundles', 'app,dmg'];
const ROOT = join(import.meta.dirname, '..');
test('release builds do not mutate DMGs after Tauri signs and notarizes them', () => {
const source = readFileSync(join(ROOT, 'scripts', 'desktop-tauri-build.mjs'), 'utf8');
assert.doesNotMatch(source, /patchDmgExtras/);
assert.doesNotMatch(source, /patch-dmg-extras\.sh/);
assert.match(source, /TAURI_BUNDLER_DMG_IGNORE_CI = 'true'/);
});
test('Desktop DMG uses the branded installer layout', () => {
const config = JSON.parse(
readFileSync(join(ROOT, 'src', 'apps', 'desktop', 'tauri.conf.json'), 'utf8')
);
assert.deepEqual(config.bundle.macOS.dmg, {
background: 'dmg/background.png',
windowSize: { width: 660, height: 400 },
appPosition: { x: 180, y: 170 },
applicationFolderPosition: { x: 480, y: 170 },
});
});
test('macOS release signing covers the bundled flashgrep executable', () => {
const fixture = join(tmpdir(), `bitfun-flashgrep-signing-${process.pid}-${Date.now()}`);
const desktopDir = join(fixture, 'src', 'apps', 'desktop');
const source = join(fixture, 'flashgrep-aarch64-apple-darwin');
const calls = [];
mkdirSync(desktopDir, { recursive: true });
writeFileSync(source, 'test-binary');
try {
const signed = prepareMacOSFlashgrepForSigning(source, desktopDir, {
platform: 'darwin',
signingIdentity: 'Developer ID Application: Test (TEAMID)',
spawnSync: (...args) => {
calls.push(args);
return { status: 0 };
},
});
assert.notEqual(signed, source);
assert.equal(readFileSync(signed, 'utf8'), 'test-binary');
assert.deepEqual(calls[0][0], 'codesign');
assert.deepEqual(calls[0][1], [
'--force',
'--sign',
'Developer ID Application: Test (TEAMID)',
'--options',
'runtime',
'--timestamp',
signed,
]);
} finally {
rmSync(fixture, { force: true, recursive: true });
}
});
test('unsigned and non-macOS builds keep the original flashgrep executable', () => {
assert.equal(
prepareMacOSFlashgrepForSigning('/tmp/flashgrep', '/tmp/desktop', {
platform: 'darwin',
signingIdentity: '',
}),
'/tmp/flashgrep',
);
assert.equal(
prepareMacOSFlashgrepForSigning('/tmp/flashgrep', '/tmp/desktop', {
platform: 'linux',
signingIdentity: 'unused',
}),
'/tmp/flashgrep',
);
});
test('macOS packaging fails when bundled flashgrep signing fails', () => {
const fixture = join(tmpdir(), `bitfun-flashgrep-signing-failure-${process.pid}-${Date.now()}`);
const desktopDir = join(fixture, 'src', 'apps', 'desktop');
const source = join(fixture, 'flashgrep-x86_64-apple-darwin');
mkdirSync(desktopDir, { recursive: true });
writeFileSync(source, 'test-binary');
try {
assert.throws(
() => prepareMacOSFlashgrepForSigning(source, desktopDir, {
platform: 'darwin',
signingIdentity: 'Developer ID Application: Test (TEAMID)',
spawnSync: () => ({ status: 1, stderr: 'identity unavailable' }),
}),
/Failed to sign bundled flashgrep binary: identity unavailable/,
);
} finally {
rmSync(fixture, { force: true, recursive: true });
}
});
function retryFixture() {
const root = join(tmpdir(), `bitfun-dmg-retry-${process.pid}-${Date.now()}`);
const desktopDir = join(root, 'src', 'apps', 'desktop');
const targetDir = join(root, 'target');
const appDir = join(
targetDir,
'x86_64-apple-darwin',
'release',
'bundle',
'macos',
'BitFun.app'
);
mkdirSync(desktopDir, { recursive: true });
mkdirSync(appDir, { recursive: true });
return {
appDir,
desktopDir,
runtime: {
cargoTargetDir: targetDir,
githubActions: 'true',
platform: 'darwin',
root,
},
cleanup: () => rmSync(root, { force: true, recursive: true }),
};
}
test('retries a failed GitHub Actions DMG bundle after a fresh app bundle', () => {
const fixture = retryFixture();
try {
assert.equal(
shouldRetryMacDmgBuild(
FAILED_BUILD,
DMG_ARGS,
fixture.desktopDir,
Date.now(),
fixture.runtime
),
true
);
} finally {
fixture.cleanup();
}
});
test('does not retry failures outside the narrow DMG bundling boundary', () => {
const fixture = retryFixture();
try {
const cases = [
[{ status: 0 }, DMG_ARGS, fixture.runtime],
[FAILED_BUILD, DMG_ARGS, { ...fixture.runtime, platform: 'linux' }],
[FAILED_BUILD, DMG_ARGS, { ...fixture.runtime, githubActions: 'false' }],
[FAILED_BUILD, ['--no-bundle'], fixture.runtime],
[FAILED_BUILD, ['--bundles=app'], fixture.runtime],
[
FAILED_BUILD,
DMG_ARGS,
{ ...fixture.runtime, cargoTargetDir: join(fixture.runtime.root, 'missing') },
],
];
for (const [result, args, runtime] of cases) {
assert.equal(
shouldRetryMacDmgBuild(result, args, fixture.desktopDir, Date.now(), runtime),
false
);
}
const staleTime = new Date(Date.now() - 60_000);
utimesSync(fixture.appDir, staleTime, staleTime);
assert.equal(
shouldRetryMacDmgBuild(
FAILED_BUILD,
DMG_ARGS,
fixture.desktopDir,
Date.now(),
fixture.runtime
),
false
);
} finally {
fixture.cleanup();
}
});
test('Desktop Tauri projection consumes only the resolved member identity', () => {
const fixture = join(tmpdir(), `bitfun-tauri-product-${process.pid}-${Date.now()}`);
mkdirSync(fixture, { recursive: true });
const baseConfig = join(fixture, 'tauri.conf.json');
writeFileSync(baseConfig, JSON.stringify({
productName: 'BitFun',
identifier: 'com.bitfun.desktop',
bundle: { resources: {} },
}));
try {
const resolution = resolveProductDefinition({
rootDir: ROOT,
productConfig: join(ROOT, 'products', 'fixtures', 'acme', 'product.jsonc'),
member: 'desktop',
});
const generated = prepareTauriConfig(baseConfig, {
desktopDir: fixture,
flashgrepBinary: join(fixture, 'flashgrep'),
resolution,
});
const config = JSON.parse(readFileSync(generated, 'utf8'));
assert.equal(config.productName, 'Acme Desktop');
assert.equal(config.mainBinaryName, 'acme-desktop');
assert.equal(config.identifier, 'com.acme.desktop');
assert.equal(config.bundle.icon, undefined);
} finally {
rmSync(fixture, { force: true, recursive: true });
}
});
test('Windows updater installs NSIS packages without showing its progress window', () => {
const fixture = join(tmpdir(), `bitfun-tauri-updater-${process.pid}-${Date.now()}`);
const baseConfig = join(fixture, 'tauri.conf.json');
const updaterEnv = {
BITFUN_ENABLE_UPDATER_ARTIFACTS: process.env.BITFUN_ENABLE_UPDATER_ARTIFACTS,
BITFUN_RELEASE_CHANNEL: process.env.BITFUN_RELEASE_CHANNEL,
BITFUN_UPDATER_FALLBACK_ENDPOINT: process.env.BITFUN_UPDATER_FALLBACK_ENDPOINT,
BITFUN_UPDATER_PRIMARY_ENDPOINT: process.env.BITFUN_UPDATER_PRIMARY_ENDPOINT,
TAURI_SIGNING_PRIVATE_KEY: process.env.TAURI_SIGNING_PRIVATE_KEY,
TAURI_UPDATER_ENDPOINT: process.env.TAURI_UPDATER_ENDPOINT,
TAURI_UPDATER_FALLBACK_ENDPOINT: process.env.TAURI_UPDATER_FALLBACK_ENDPOINT,
TAURI_UPDATER_PUBKEY: process.env.TAURI_UPDATER_PUBKEY,
};
mkdirSync(fixture, { recursive: true });
writeFileSync(baseConfig, JSON.stringify({ bundle: { resources: {} } }));
process.env.BITFUN_ENABLE_UPDATER_ARTIFACTS = 'true';
process.env.TAURI_SIGNING_PRIVATE_KEY = 'test-private-key';
process.env.TAURI_UPDATER_PUBKEY = 'test-public-key';
try {
const generated = prepareTauriConfig(baseConfig, {
desktopDir: fixture,
flashgrepBinary: join(fixture, 'flashgrep'),
});
const config = JSON.parse(readFileSync(generated, 'utf8'));
assert.equal(config.plugins.updater.windows.installMode, 'quiet');
assert.match(config.plugins.updater.endpoints[0], /releases\/latest\/download/);
} finally {
for (const [name, value] of Object.entries(updaterEnv)) {
if (value === undefined) {
delete process.env[name];
} else {
process.env[name] = value;
}
}
rmSync(fixture, { force: true, recursive: true });
}
});
test('beta Desktop artifacts compile and bundle only beta updater endpoints', () => {
const fixture = join(tmpdir(), `bitfun-tauri-beta-${process.pid}-${Date.now()}`);
const baseConfig = join(fixture, 'tauri.conf.json');
const names = [
'BITFUN_ENABLE_UPDATER_ARTIFACTS',
'BITFUN_RELEASE_CHANNEL',
'BITFUN_UPDATER_FALLBACK_ENDPOINT',
'BITFUN_UPDATER_PRIMARY_ENDPOINT',
'TAURI_SIGNING_PRIVATE_KEY',
'TAURI_UPDATER_ENDPOINT',
'TAURI_UPDATER_FALLBACK_ENDPOINT',
'TAURI_UPDATER_PUBKEY',
];
const previous = Object.fromEntries(names.map((name) => [name, process.env[name]]));
mkdirSync(fixture, { recursive: true });
writeFileSync(baseConfig, JSON.stringify({ bundle: { resources: {} } }));
process.env.BITFUN_ENABLE_UPDATER_ARTIFACTS = 'true';
process.env.BITFUN_RELEASE_CHANNEL = 'beta';
process.env.TAURI_SIGNING_PRIVATE_KEY = 'test-private-key';
process.env.TAURI_UPDATER_PUBKEY = 'test-public-key';
delete process.env.TAURI_UPDATER_ENDPOINT;
delete process.env.TAURI_UPDATER_FALLBACK_ENDPOINT;
try {
const generated = prepareTauriConfig(baseConfig, {
desktopDir: fixture,
flashgrepBinary: join(fixture, 'flashgrep'),
});
const config = JSON.parse(readFileSync(generated, 'utf8'));
assert.equal(
config.plugins.updater.endpoints[0],
'https://github.com/GCWing/BitFun/releases/download/channel-beta/latest.json',
);
assert.equal(
config.plugins.updater.endpoints[1],
'https://openbitfun.com/release/beta/latest.json',
);
assert.equal(
process.env.BITFUN_UPDATER_PRIMARY_ENDPOINT,
config.plugins.updater.endpoints[0],
);
assert.equal(
process.env.BITFUN_UPDATER_FALLBACK_ENDPOINT,
config.plugins.updater.endpoints[1],
);
} finally {
for (const [name, value] of Object.entries(previous)) {
if (value === undefined) delete process.env[name];
else process.env[name] = value;
}
rmSync(fixture, { force: true, recursive: true });
}
});
test('static desktop Tauri configs do not require the DeepSeek profile at compile time', () => {
for (const name of ['tauri.conf.json', 'tauri.dev.conf.json']) {
const config = JSON.parse(
readFileSync(join(ROOT, 'src', 'apps', 'desktop', name), 'utf8')
);
assert.equal(
config.bundle.resources['../../../packages/dsh-acp/dist-profile'],
undefined,
`${name} must not list dist-profile as a compile-time resource`,
);
}
});
test('official packaging injects the DeepSeek profile resource', () => {
const fixture = join(tmpdir(), `bitfun-tauri-dsh-${process.pid}-${Date.now()}`);
mkdirSync(fixture, { recursive: true });
const baseConfig = join(fixture, 'tauri.conf.json');
writeFileSync(baseConfig, JSON.stringify({
bundle: { resources: { 'resources/worker_host.js': 'resources/worker_host.js' } },
}));
try {
const generated = prepareTauriConfig(baseConfig, {
desktopDir: fixture,
flashgrepBinary: join(fixture, 'flashgrep'),
});
const config = JSON.parse(readFileSync(generated, 'utf8'));
assert.equal(
config.bundle.resources['../../../packages/dsh-acp/dist-profile'],
'resources/dsh-profile',
);
assert.equal(
config.bundle.resources['resources/worker_host.js'],
'resources/worker_host.js',
);
} finally {
rmSync(fixture, { force: true, recursive: true });
}
});
test('Desktop release config bundles models.dev notices and provenance', () => {
const config = JSON.parse(
readFileSync(join(ROOT, 'src', 'apps', 'desktop', 'tauri.conf.json'), 'utf8')
);
assert.equal(
config.bundle.resources['../../../THIRD_PARTY_NOTICES.md'],
'THIRD_PARTY_NOTICES.md'
);
assert.equal(
config.bundle.resources[
'../../crates/services/services-integrations/assets/models-dev.LICENSE.txt'
],
'third-party/models.dev/LICENSE.txt'
);
assert.equal(
config.bundle.resources[
'../../crates/services/services-integrations/assets/models-dev.provenance.json'
],
'third-party/models.dev/provenance.json'
);
});