Publish Untrace Desktop 1.6.1 with offline batch workflows
This commit is contained in:
1 parent
99b4b22352
commit
7ce81b0727
47 files changed
+5958
-1208
No files matched your search
@@ -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 });
|
||||
}
|
||||
@@ -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, ''));
|
||||
}
|
||||
@@ -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 });
|
||||
}
|
||||
Reference in new issue
Block a user