Publish Untrace Desktop 1.6.1 with offline batch workflows

This commit is contained in:
Dario P. committed 2026-10-25 01:22:14 +02:00
1 parent 99b4b22352
commit 7ce81b0727
47 files changed
+5958 -1208

No files matched your search

+27
View File
@@ -0,0 +1,27 @@
import { _electron as electron } from '@playwright/test';
import { mkdtemp, rm, writeFile } from 'node:fs/promises';
import os from 'node:os';
import path from 'node:path';
const data = await mkdtemp(path.join(os.tmpdir(), 'untrace-icon-'));
const env = { ...process.env, UNTRACE_TEST: '1', UNTRACE_DATA_DIR: data };
delete env.ELECTRON_RUN_AS_NODE;
const app = await electron.launch({ args: ['.'], env });
try {
const page = await app.firstWindow();
const encoded = await page.evaluate(async () => {
const img = new Image(); img.src = '/favicon.svg'; await img.decode();
const canvas = document.createElement('canvas'); canvas.width = 256; canvas.height = 256;
canvas.getContext('2d').drawImage(img, 0, 0, 256, 256);
return canvas.toDataURL('image/png').split(',')[1];
});
const png = Buffer.from(encoded, 'base64');
const header = Buffer.alloc(22);
header.writeUInt16LE(1, 2); header.writeUInt16LE(1, 4);
header.writeUInt16LE(1, 10); header.writeUInt16LE(32, 12);
header.writeUInt32LE(png.length, 14); header.writeUInt32LE(22, 18);
await writeFile('desktop/icon.ico', Buffer.concat([header, png]));
} finally {
await app.close();
await rm(data, { recursive: true, force: true, maxRetries: 5, retryDelay: 200 });
}
+20
View File
@@ -0,0 +1,20 @@
import { readFile, writeFile, readdir } from 'node:fs/promises';
import { createHash } from 'node:crypto';
let html = await readFile('dist/index.html', 'utf8');
const { build } = JSON.parse(await readFile('package.json', 'utf8'));
const brand = JSON.parse(await readFile('desktop/brand.json', 'utf8'));
if (build.productName !== brand.name || build.appId !== brand.appId) throw new Error('Desktop brand and package configuration must match.');
html = html.replace(/<link\b[^>]*href="https:\/\/fonts\.[^>]*>/g, '');
html = html.replace(/<title>.*?<\/title>/, `<title>${build.productName}</title>`);
const hashes = [...html.matchAll(/<script(?![^>]*\bsrc=)[^>]*>([\s\S]*?)<\/script>/g)]
.map(match => `'sha256-${createHash('sha256').update(match[1]).digest('base64')}'`);
const policy = `default-src 'self'; script-src 'self' ${hashes.join(' ')}; style-src 'self' 'unsafe-inline'; img-src 'self' blob: data:; font-src 'self'; connect-src 'self' blob:; object-src 'none'; base-uri 'none'; form-action 'none'`;
html = html.replace('<head>', `<head>\n <meta http-equiv="Content-Security-Policy" content="${policy}">`);
await writeFile('dist/index.html', html);
for (const file of await readdir('dist/assets')) {
if (!file.endsWith('.css')) continue;
const location = `dist/assets/${file}`;
const css = await readFile(location, 'utf8');
await writeFile(location, css.replace(/@import\s*(?:url\([^)]*fonts\.googleapis[^)]*\)|["'][^"']*fonts\.googleapis[^"']*["'])\s*;/g, ''));
}
+31
View File
@@ -0,0 +1,31 @@
import { spawn } from 'node:child_process';
import { mkdtemp, rm, readFile } from 'node:fs/promises';
import os from 'node:os';
import path from 'node:path';
import { chromium } from '@playwright/test';
const data = await mkdtemp(path.join(os.tmpdir(), 'untrace-portable-'));
const env = { ...process.env, UNTRACE_TEST: '1', UNTRACE_DATA_DIR: data };
delete env.ELECTRON_RUN_AS_NODE;
const { version, build } = JSON.parse(await readFile('package.json', 'utf8'));
const artifact = build.portable.artifactName.replace('${version}', version);
const processHandle = spawn(path.resolve('release', artifact), ['--remote-debugging-port=19384'], { env, windowsHide: true, stdio: 'ignore' });
let browser;
try {
for (let attempt = 0; attempt < 120; attempt++) {
try { browser = await chromium.connectOverCDP('http://127.0.0.1:19384', { timeout: 1000 }); break; }
catch { await new Promise(resolve => setTimeout(resolve, 500)); }
}
if (!browser) throw new Error('Portable app did not start within 60 seconds.');
const page = browser.contexts()[0].pages()[0];
await page.waitForURL('untrace://app/');
await page.getByLabel('Open image file', { exact: true }).waitFor({ state: 'attached' });
if (await page.title() !== build.productName) throw new Error('Unexpected window');
console.log('PASS: single-file portable extracted and loaded the offline app.');
const cdp = await browser.newBrowserCDPSession();
await Promise.race([cdp.send('Browser.close').catch(() => {}), new Promise(resolve => setTimeout(resolve, 1000))]);
} finally {
if (browser) await Promise.race([browser.close().catch(() => {}), new Promise(resolve => setTimeout(resolve, 1000))]);
await new Promise(resolve => setTimeout(resolve, 1500));
await rm(data, { recursive: true, force: true, maxRetries: 10, retryDelay: 300 });
}