feat(desktop): port v0.11.4-v0.15.2 from Tauri to Electron + Discord-parity audio (v0.16.0)
Chronological port of every Tauri release commit (v0.11.4 -> v0.15.2)
into the Electron rebuild, plus Discord-style audio handling that goes
beyond the Tauri original.
Highlights:
- All 17 Tauri release commits ported (audio fixes, custom notification
sound, Discord-style chat UX, profile banner, changelog page,
Discord-parity call UX, screen-share echo + re-watch UX, audio-loop
fix).
- Native napi-rs audio-loopback addon with WASAPI process-loopback:
* EXCLUDE_TARGET_PROCESS_TREE for full-screen shares -> peers
never hear themselves echoed back through the capture.
* INCLUDE_TARGET_PROCESS_TREE for window shares -> only the
picked window's audio is captured, not the whole OS mixer
(Discord parity).
* HWND -> PID resolution via Win32 GetWindowThreadProcessId.
- Discord-style screen-source picker (thumbnail grid, screens vs
apps tabs, live-refreshing thumbnails).
- Hash routing fix for packaged builds (file:// can't resolve
BrowserRouter paths).
- Tauri sources removed (apps/desktop/src-tauri).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
+88
-72
@@ -1,10 +1,20 @@
|
||||
#!/usr/bin/env node
|
||||
// Release script — builds the Windows desktop installer, signs it with the
|
||||
// Tauri updater key, and uploads the artifacts + latest.json to the update
|
||||
// host over scp. Reads credentials from .env.release (not committed).
|
||||
// Electron release script — bumps the app version, builds the Windows
|
||||
// installer via electron-builder, and uploads the produced artifacts
|
||||
// (latest.yml + .exe + .blockmap) to the update host over scp. Reads
|
||||
// credentials from .env.release (not committed). electron-updater on
|
||||
// installed clients then sees the new latest.yml on the next launch
|
||||
// poll and triggers the update toast.
|
||||
//
|
||||
// We deliberately push to the same host the Tauri build pushed to
|
||||
// (`update.netralax.cloud:/var/www/updates/windows/`) but use a
|
||||
// different manifest filename (`latest.yml` for electron-updater vs
|
||||
// `latest.json` for Tauri), so the legacy Tauri manifest can stay in
|
||||
// place untouched and any users still on Tauri don't get pushed a
|
||||
// release they can't verify.
|
||||
//
|
||||
// Usage:
|
||||
// pnpm release 0.10.2 "Ringtone cap auf 8 MB, bugfixes"
|
||||
// pnpm release <x.y.z> "Multi-line release notes…"
|
||||
|
||||
import { execSync } from 'node:child_process';
|
||||
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
||||
@@ -18,7 +28,12 @@ if (!versionArg || !/^\d+\.\d+\.\d+$/.test(versionArg)) {
|
||||
console.error('Usage: pnpm release <x.y.z> "release notes"');
|
||||
process.exit(1);
|
||||
}
|
||||
const notes = notesParts.join(' ').trim() || `Release ${versionArg}`;
|
||||
// Bash + cmd.exe pass `\n` from CLI args as the two literal chars `\` `n`,
|
||||
// not as a newline. Normalize so multi-line release notes render correctly
|
||||
// in the in-app updater toast (which uses whitespace-pre-line) and in the
|
||||
// Changelog page.
|
||||
const notes = (notesParts.join(' ').trim() || `Release ${versionArg}`)
|
||||
.replace(/\\r\\n|\\n/g, '\n');
|
||||
|
||||
const envPath = join(ROOT, '.env.release');
|
||||
if (!existsSync(envPath)) {
|
||||
@@ -36,22 +51,13 @@ const env = Object.fromEntries(
|
||||
}),
|
||||
);
|
||||
|
||||
const required = [
|
||||
'TAURI_SIGNING_PRIVATE_KEY_PATH',
|
||||
'UPDATE_HOST',
|
||||
'UPDATE_SSH_USER',
|
||||
'UPDATE_REMOTE_PATH',
|
||||
];
|
||||
const required = ['UPDATE_HOST', 'UPDATE_SSH_USER', 'UPDATE_REMOTE_PATH'];
|
||||
for (const key of required) {
|
||||
if (!env[key]) {
|
||||
console.error(`Missing ${key} in .env.release`);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
if (!existsSync(env.TAURI_SIGNING_PRIVATE_KEY_PATH)) {
|
||||
console.error(`Signing key not found at ${env.TAURI_SIGNING_PRIVATE_KEY_PATH}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const gitStatus = execSync('git status --porcelain', { cwd: ROOT, encoding: 'utf8' });
|
||||
if (gitStatus.trim()) {
|
||||
@@ -60,76 +66,49 @@ if (gitStatus.trim()) {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// --- Bump version --------------------------------------------------------
|
||||
|
||||
const pkgJsonPath = join(ROOT, 'apps/desktop/package.json');
|
||||
const tauriConfPath = join(ROOT, 'apps/desktop/src-tauri/tauri.conf.json');
|
||||
const cargoTomlPath = join(ROOT, 'apps/desktop/src-tauri/Cargo.toml');
|
||||
|
||||
function bumpJson(path, version) {
|
||||
const obj = JSON.parse(readFileSync(path, 'utf8'));
|
||||
obj.version = version;
|
||||
writeFileSync(path, JSON.stringify(obj, null, 2) + '\n', 'utf8');
|
||||
}
|
||||
function bumpCargo(path, version) {
|
||||
const text = readFileSync(path, 'utf8');
|
||||
const next = text.replace(/^version = "[^"]+"$/m, `version = "${version}"`);
|
||||
if (next === text) throw new Error(`No version line found in ${path}`);
|
||||
writeFileSync(path, next, 'utf8');
|
||||
}
|
||||
|
||||
bumpJson(pkgJsonPath, versionArg);
|
||||
bumpJson(tauriConfPath, versionArg);
|
||||
bumpCargo(cargoTomlPath, versionArg);
|
||||
console.log(`Version -> ${versionArg}. Building NSIS bundle via electron-builder…`);
|
||||
|
||||
console.log(`Version -> ${versionArg}. Building NSIS bundle…`);
|
||||
// --- Build ----------------------------------------------------------------
|
||||
|
||||
const signingKey = readFileSync(env.TAURI_SIGNING_PRIVATE_KEY_PATH, 'utf8').trim();
|
||||
const buildEnv = {
|
||||
...process.env,
|
||||
TAURI_SIGNING_PRIVATE_KEY: signingKey,
|
||||
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: env.TAURI_SIGNING_PRIVATE_KEY_PASSWORD ?? '',
|
||||
};
|
||||
// Call the tauri binary directly (not via the desktop `build` script) so the
|
||||
// `--` separator pnpm normally injects doesn't get forwarded to cargo. When
|
||||
// run from cmd.exe (Node's default execSync shell on Windows) pnpm preserves
|
||||
// the `--`, which cargo then rejects with "unexpected argument '--bundles'".
|
||||
execSync('pnpm --filter @chat-app/desktop exec tauri build --bundles nsis', {
|
||||
execSync('pnpm --filter @chat-app/desktop run build:win', {
|
||||
cwd: ROOT,
|
||||
env: buildEnv,
|
||||
stdio: 'inherit',
|
||||
});
|
||||
|
||||
// Tauri v2 ships a single .exe + .exe.sig for NSIS updates — no .nsis.zip
|
||||
// wrapper like v1. The updater downloads the .exe directly, verifies the
|
||||
// minisign signature, then launches it in passive mode.
|
||||
const bundleDir = join(ROOT, 'apps/desktop/src-tauri/target/release/bundle/nsis');
|
||||
const exeName = `ChatApp_${versionArg}_x64-setup.exe`;
|
||||
const sigName = `${exeName}.sig`;
|
||||
const exePath = join(bundleDir, exeName);
|
||||
const sigPath = join(bundleDir, sigName);
|
||||
for (const p of [exePath, sigPath]) {
|
||||
// --- Locate artifacts -----------------------------------------------------
|
||||
|
||||
const releaseDir = join(ROOT, 'apps/desktop/release');
|
||||
const exeName = `ChatApp Setup ${versionArg}.exe`;
|
||||
const blockmapName = `${exeName}.blockmap`;
|
||||
const latestYml = 'latest.yml';
|
||||
|
||||
const exePath = join(releaseDir, exeName);
|
||||
const blockmapPath = join(releaseDir, blockmapName);
|
||||
const latestYmlPath = join(releaseDir, latestYml);
|
||||
|
||||
for (const p of [exePath, blockmapPath, latestYmlPath]) {
|
||||
if (!existsSync(p)) {
|
||||
console.error(`Missing build artifact: ${p}`);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
const signature = readFileSync(sigPath, 'utf8').trim();
|
||||
const latest = {
|
||||
version: versionArg,
|
||||
notes,
|
||||
pub_date: new Date().toISOString(),
|
||||
platforms: {
|
||||
'windows-x86_64': {
|
||||
signature,
|
||||
url: `https://${env.UPDATE_HOST}/windows/${exeName}`,
|
||||
},
|
||||
},
|
||||
};
|
||||
const latestJsonPath = join(bundleDir, 'latest.json');
|
||||
writeFileSync(latestJsonPath, JSON.stringify(latest, null, 2), 'utf8');
|
||||
// --- scp helpers ----------------------------------------------------------
|
||||
|
||||
const sshTarget = `${env.UPDATE_SSH_USER}@${env.UPDATE_HOST}`;
|
||||
const keyFlag = env.UPDATE_SSH_KEY ? ` -i "${env.UPDATE_SSH_KEY}"` : '';
|
||||
|
||||
function scp(localPath) {
|
||||
execSync(`scp${keyFlag} "${localPath}" ${sshTarget}:${env.UPDATE_REMOTE_PATH}/`, {
|
||||
cwd: ROOT,
|
||||
@@ -137,15 +116,51 @@ function scp(localPath) {
|
||||
});
|
||||
}
|
||||
|
||||
console.log('Uploading artifacts (JSON last so clients never see a stale ref)…');
|
||||
scp(exePath);
|
||||
scp(sigPath);
|
||||
scp(latestJsonPath);
|
||||
// --- Upload — manifest LAST so clients never see a stale ref --------------
|
||||
|
||||
execSync(
|
||||
`git add apps/desktop/package.json apps/desktop/src-tauri/tauri.conf.json apps/desktop/src-tauri/Cargo.toml`,
|
||||
{ cwd: ROOT, stdio: 'inherit' },
|
||||
);
|
||||
console.log('Uploading artifacts (manifest last)…');
|
||||
scp(exePath);
|
||||
scp(blockmapPath);
|
||||
scp(latestYmlPath);
|
||||
|
||||
// --- Changelog feed -------------------------------------------------------
|
||||
//
|
||||
// The in-app changelog page fetches `${UPDATE_HOST}/windows/changelog.json`
|
||||
// and renders the list. We fetch the existing list, prepend the new entry
|
||||
// (keyed/deduped by version), cap at 200 to keep the file size bounded,
|
||||
// then upload the merged list back.
|
||||
|
||||
const changelogUrl = `https://${env.UPDATE_HOST}/windows/changelog.json`;
|
||||
let history = [];
|
||||
try {
|
||||
const res = await fetch(changelogUrl, { cache: 'no-store' });
|
||||
if (res.ok) {
|
||||
const parsed = await res.json();
|
||||
if (Array.isArray(parsed)) history = parsed;
|
||||
} else if (res.status !== 404) {
|
||||
console.warn(`changelog.json fetch returned ${res.status} — starting from empty list`);
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn(
|
||||
`changelog.json fetch failed (${err.message ?? err}); starting from empty list`,
|
||||
);
|
||||
}
|
||||
const newEntry = {
|
||||
version: versionArg,
|
||||
pub_date: new Date().toISOString(),
|
||||
notes,
|
||||
};
|
||||
const updatedHistory = [
|
||||
newEntry,
|
||||
...history.filter((e) => e?.version !== versionArg),
|
||||
].slice(0, 200);
|
||||
const changelogPath = join(releaseDir, 'changelog.json');
|
||||
writeFileSync(changelogPath, JSON.stringify(updatedHistory, null, 2), 'utf8');
|
||||
scp(changelogPath);
|
||||
|
||||
// --- Commit + tag ---------------------------------------------------------
|
||||
|
||||
execSync(`git add apps/desktop/package.json`, { cwd: ROOT, stdio: 'inherit' });
|
||||
execSync(`git commit -m "chore(desktop): release v${versionArg}"`, {
|
||||
cwd: ROOT,
|
||||
stdio: 'inherit',
|
||||
@@ -153,6 +168,7 @@ execSync(`git commit -m "chore(desktop): release v${versionArg}"`, {
|
||||
execSync(`git tag v${versionArg}`, { cwd: ROOT, stdio: 'inherit' });
|
||||
|
||||
console.log(`\nReleased v${versionArg}`);
|
||||
console.log(` Manifest: https://${env.UPDATE_HOST}/windows/latest.json`);
|
||||
console.log(` Installer: https://${env.UPDATE_HOST}/windows/${exeName}`);
|
||||
console.log(` Run 'git push && git push --tags' to sync to remote.`);
|
||||
console.log(` Manifest: https://${env.UPDATE_HOST}/windows/${latestYml}`);
|
||||
console.log(` Installer: https://${env.UPDATE_HOST}/windows/${encodeURIComponent(exeName)}`);
|
||||
console.log(` Changelog: https://${env.UPDATE_HOST}/windows/changelog.json`);
|
||||
console.log(` Run 'git push && git push --tags' to sync the tag to remote.`);
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
[
|
||||
{
|
||||
"version": "0.13.0",
|
||||
"pub_date": "2026-04-25T15:37:24Z",
|
||||
"notes": "- Profil-Banner: Eigenes Bannerbild im Profil hochladbar (3:1 Format, max 8 MB)\n- Anzeigename direkt in den Einstellungen bearbeitbar\n- Profil-Vorschau in Einstellungen zeigt jetzt Banner + Avatar wie im Popover"
|
||||
},
|
||||
{
|
||||
"version": "0.12.3",
|
||||
"pub_date": "2026-04-25T14:17:15Z",
|
||||
"notes": "- Fix: Update-Changelog zeigt jetzt mehrzeilige Release Notes korrekt an\n- Fix: Hover-Leiste über Nachrichten verschwindet nicht mehr beim Hochfahren der Maus"
|
||||
},
|
||||
{
|
||||
"version": "0.12.2",
|
||||
"pub_date": "2026-04-25T14:10:36Z",
|
||||
"notes": "- Fix: Hover-Leiste über der Nachricht verschwindet nicht mehr beim Hochfahren der Maus"
|
||||
},
|
||||
{
|
||||
"version": "0.12.1",
|
||||
"pub_date": "2026-04-24T22:35:29Z",
|
||||
"notes": "- Publisher-Name im Windows Autostart & Task-Manager korrigiert (Netralax statt \"meinname\")"
|
||||
},
|
||||
{
|
||||
"version": "0.12.0",
|
||||
"pub_date": "2026-04-24T16:07:15Z",
|
||||
"notes": "- Auto-Start: ChatApp kann jetzt mit Windows starten (Einstellungen → Start)\n- Umfrage-Anzeige: bessere Kontraste in eigenen Nachrichten, scharfe Options-Nummern statt verpixelter Emojis\n- Fix: Hover-Leiste springt nicht mehr wenn man auf 'Mehr' klickt\n- Archiv-Icon im Chats-Header entfernt (die Tabs Aktiv/Archiv übernehmen das)"
|
||||
}
|
||||
]
|
||||
Reference in New Issue
Block a user