mirror of
https://github.com/BigBodyCobain/Shadowbroker.git
synced 2026-07-11 22:46:38 +02:00
release: prepare v0.9.7
This commit is contained in:
@@ -0,0 +1,122 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
|
||||
const scriptDir = __dirname;
|
||||
const tauriDir = path.resolve(scriptDir, '..');
|
||||
const repoRoot = path.resolve(tauriDir, '..', '..');
|
||||
const backendDir = path.join(repoRoot, 'backend');
|
||||
const outputDir = path.join(tauriDir, 'src-tauri', 'backend-runtime');
|
||||
const venvMarkerPath = path.join(backendDir, '.venv-dir');
|
||||
const releaseAttestationPath = path.join(backendDir, 'data', 'release_attestation.json');
|
||||
const stagedReleaseAttestationPath = path.join(
|
||||
outputDir,
|
||||
'data',
|
||||
'release_attestation.json',
|
||||
);
|
||||
|
||||
const excludedNames = new Set([
|
||||
'.env',
|
||||
'.pytest_cache',
|
||||
'__pycache__',
|
||||
'backend.egg-info',
|
||||
'build',
|
||||
'data',
|
||||
'tests',
|
||||
]);
|
||||
|
||||
const excludedFiles = new Set([
|
||||
'pytest.ini',
|
||||
]);
|
||||
|
||||
function backendPythonPath() {
|
||||
let venvDir = 'venv';
|
||||
try {
|
||||
const persisted = fs.readFileSync(venvMarkerPath, 'utf8').trim();
|
||||
if (persisted) {
|
||||
venvDir = persisted;
|
||||
}
|
||||
} catch {}
|
||||
|
||||
if (process.platform === 'win32') {
|
||||
return path.join(backendDir, venvDir, 'Scripts', 'python.exe');
|
||||
}
|
||||
return path.join(backendDir, venvDir, 'bin', 'python3');
|
||||
}
|
||||
|
||||
function shouldCopy(srcPath) {
|
||||
const relativePath = path.relative(backendDir, srcPath);
|
||||
if (!relativePath) return true;
|
||||
|
||||
const parts = relativePath.split(path.sep);
|
||||
return parts.every((part, index) => {
|
||||
const isLeaf = index === parts.length - 1;
|
||||
if (excludedNames.has(part)) return false;
|
||||
if (isLeaf && excludedFiles.has(part)) return false;
|
||||
if (/^test_.*\.py$/i.test(part)) return false;
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
function ensureRuntimePrereqs() {
|
||||
if (!fs.existsSync(path.join(backendDir, 'main.py'))) {
|
||||
throw new Error(`Missing backend/main.py at ${backendDir}`);
|
||||
}
|
||||
if (!fs.existsSync(backendPythonPath())) {
|
||||
throw new Error(
|
||||
`Missing bundled backend Python runtime at ${backendPythonPath()}. ` +
|
||||
'Create the backend venv before packaging the desktop app.',
|
||||
);
|
||||
}
|
||||
if (!fs.existsSync(path.join(backendDir, 'node_modules', 'ws'))) {
|
||||
throw new Error(
|
||||
`Missing backend/node_modules/ws at ${path.join(backendDir, 'node_modules', 'ws')}. ` +
|
||||
'Install backend Node dependencies before packaging the desktop app.',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function stageBackendRuntime() {
|
||||
fs.rmSync(outputDir, { recursive: true, force: true });
|
||||
fs.cpSync(backendDir, outputDir, {
|
||||
recursive: true,
|
||||
filter: shouldCopy,
|
||||
});
|
||||
stageReleaseAttestation();
|
||||
}
|
||||
|
||||
function stageReleaseAttestation() {
|
||||
if (!fs.existsSync(releaseAttestationPath)) {
|
||||
console.warn(`backend-runtime staged without release attestation: ${releaseAttestationPath}`);
|
||||
return;
|
||||
}
|
||||
fs.mkdirSync(path.dirname(stagedReleaseAttestationPath), { recursive: true });
|
||||
fs.copyFileSync(releaseAttestationPath, stagedReleaseAttestationPath);
|
||||
}
|
||||
|
||||
function writeBundleVersion() {
|
||||
const versionPath = path.join(outputDir, '.bundle-version');
|
||||
const pkg = JSON.parse(
|
||||
fs.readFileSync(path.join(repoRoot, 'desktop-shell', 'package.json'), 'utf8'),
|
||||
);
|
||||
fs.writeFileSync(versionPath, `${pkg.version || '0.0.0'}\n`, 'utf8');
|
||||
}
|
||||
|
||||
function fileCount(root) {
|
||||
let count = 0;
|
||||
for (const entry of fs.readdirSync(root, { withFileTypes: true })) {
|
||||
const fullPath = path.join(root, entry.name);
|
||||
if (entry.isDirectory()) {
|
||||
count += fileCount(fullPath);
|
||||
} else {
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
ensureRuntimePrereqs();
|
||||
stageBackendRuntime();
|
||||
writeBundleVersion();
|
||||
console.log(`backend-runtime staged: ${fileCount(outputDir)} files`);
|
||||
@@ -0,0 +1,101 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
const { spawnSync } = require('node:child_process');
|
||||
|
||||
const scriptDir = __dirname;
|
||||
const tauriDir = path.resolve(scriptDir, '..');
|
||||
const repoRoot = path.resolve(tauriDir, '..', '..');
|
||||
const frontendDir = path.join(repoRoot, 'frontend');
|
||||
const buildRoot = path.join(repoRoot, '.desktop-export-build');
|
||||
const buildFrontendDir = path.join(buildRoot, 'frontend');
|
||||
const buildOutDir = path.join(buildFrontendDir, 'out');
|
||||
const liveOutDir = path.join(frontendDir, 'out');
|
||||
const excludedPaths = [
|
||||
'node_modules',
|
||||
'.next',
|
||||
'out',
|
||||
'src/app/api',
|
||||
'src/middleware.ts',
|
||||
];
|
||||
|
||||
function normalizeRelativePath(target) {
|
||||
return target.split(path.sep).join('/');
|
||||
}
|
||||
|
||||
function shouldCopy(srcPath) {
|
||||
const relativePath = path.relative(frontendDir, srcPath);
|
||||
if (!relativePath) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const normalized = normalizeRelativePath(relativePath);
|
||||
return !excludedPaths.some(
|
||||
(excluded) => normalized === excluded || normalized.startsWith(`${excluded}/`),
|
||||
);
|
||||
}
|
||||
|
||||
function prepareBuildTree() {
|
||||
fs.rmSync(buildRoot, { recursive: true, force: true });
|
||||
fs.cpSync(frontendDir, buildFrontendDir, {
|
||||
recursive: true,
|
||||
filter: shouldCopy,
|
||||
});
|
||||
|
||||
const liveNodeModules = path.join(frontendDir, 'node_modules');
|
||||
const stagedNodeModules = path.join(buildFrontendDir, 'node_modules');
|
||||
if (!fs.existsSync(liveNodeModules)) {
|
||||
throw new Error(`Missing frontend/node_modules at ${liveNodeModules}`);
|
||||
}
|
||||
fs.symlinkSync(liveNodeModules, stagedNodeModules, 'junction');
|
||||
}
|
||||
|
||||
function runExportBuild() {
|
||||
const env = {
|
||||
...process.env,
|
||||
NEXT_OUTPUT: 'export',
|
||||
};
|
||||
|
||||
const result =
|
||||
process.platform === 'win32'
|
||||
? spawnSync(
|
||||
process.env.ComSpec || 'cmd.exe',
|
||||
['/d', '/s', '/c', 'npm.cmd run build -- --webpack'],
|
||||
{
|
||||
cwd: buildFrontendDir,
|
||||
env,
|
||||
stdio: 'inherit',
|
||||
},
|
||||
)
|
||||
: spawnSync('npm', ['run', 'build', '--', '--webpack'], {
|
||||
cwd: buildFrontendDir,
|
||||
env,
|
||||
stdio: 'inherit',
|
||||
});
|
||||
|
||||
if (result.error) {
|
||||
throw result.error;
|
||||
}
|
||||
if (typeof result.status === 'number' && result.status !== 0) {
|
||||
throw new Error(`Frontend export build failed with exit code ${result.status}.`);
|
||||
}
|
||||
}
|
||||
|
||||
function syncBuildOutput() {
|
||||
if (!fs.existsSync(buildOutDir)) {
|
||||
throw new Error(`Desktop export did not produce ${buildOutDir}`);
|
||||
}
|
||||
fs.rmSync(liveOutDir, { recursive: true, force: true });
|
||||
fs.cpSync(buildOutDir, liveOutDir, {
|
||||
recursive: true,
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
prepareBuildTree();
|
||||
runExportBuild();
|
||||
syncBuildOutput();
|
||||
} finally {
|
||||
fs.rmSync(buildRoot, { recursive: true, force: true });
|
||||
}
|
||||
@@ -0,0 +1,228 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
const zlib = require('node:zlib');
|
||||
|
||||
const root = path.resolve(__dirname, '..');
|
||||
const iconsDir = path.join(root, 'src-tauri', 'icons');
|
||||
|
||||
const pngOutputs = {
|
||||
'32x32.png': 32,
|
||||
'128x128.png': 128,
|
||||
'128x128@2x.png': 256,
|
||||
'icon.png': 512,
|
||||
'Square30x30Logo.png': 30,
|
||||
'Square44x44Logo.png': 44,
|
||||
'Square71x71Logo.png': 71,
|
||||
'Square89x89Logo.png': 89,
|
||||
'Square107x107Logo.png': 107,
|
||||
'Square142x142Logo.png': 142,
|
||||
'Square150x150Logo.png': 150,
|
||||
'Square284x284Logo.png': 284,
|
||||
'Square310x310Logo.png': 310,
|
||||
'StoreLogo.png': 50,
|
||||
};
|
||||
|
||||
const internalPngSizes = [16, 32, 64, 128, 256, 512, 1024];
|
||||
|
||||
function clamp(value, lower = 0, upper = 1) {
|
||||
return Math.max(lower, Math.min(upper, value));
|
||||
}
|
||||
|
||||
function smoothstep(edge0, edge1, value) {
|
||||
if (edge0 === edge1) return 0;
|
||||
const t = clamp((value - edge0) / (edge1 - edge0));
|
||||
return t * t * (3 - 2 * t);
|
||||
}
|
||||
|
||||
function blend(dst, srcRgb, srcAlpha) {
|
||||
if (srcAlpha <= 0) return dst;
|
||||
const alpha = clamp(srcAlpha);
|
||||
const inv = 1 - alpha;
|
||||
return [
|
||||
Math.round(dst[0] * inv + srcRgb[0] * alpha),
|
||||
Math.round(dst[1] * inv + srcRgb[1] * alpha),
|
||||
Math.round(dst[2] * inv + srcRgb[2] * alpha),
|
||||
255,
|
||||
];
|
||||
}
|
||||
|
||||
function roundedRectAlpha(nx, ny, half, radius, feather) {
|
||||
const qx = Math.abs(nx) - (half - radius);
|
||||
const qy = Math.abs(ny) - (half - radius);
|
||||
const outside = Math.hypot(Math.max(qx, 0), Math.max(qy, 0));
|
||||
const inside = Math.min(Math.max(qx, qy), 0);
|
||||
const signedDistance = outside + inside - radius;
|
||||
return smoothstep(feather, -feather, signedDistance);
|
||||
}
|
||||
|
||||
function drawIcon(size) {
|
||||
const pixels = Buffer.alloc(size * size * 4);
|
||||
const feather = 2.4 / Math.max(size, 1);
|
||||
for (let y = 0; y < size; y += 1) {
|
||||
for (let x = 0; x < size; x += 1) {
|
||||
const nx = ((x + 0.5) / size) * 2 - 1;
|
||||
const ny = ((y + 0.5) / size) * 2 - 1;
|
||||
|
||||
const bgAlpha = roundedRectAlpha(nx, ny, 0.93, 0.28, feather);
|
||||
if (bgAlpha <= 0) continue;
|
||||
|
||||
const gradientMix = clamp((nx - ny + 2) / 4);
|
||||
const bg = [
|
||||
Math.round(7 + 8 * gradientMix),
|
||||
Math.round(20 + 26 * (1 - gradientMix)),
|
||||
Math.round(28 + 42 * gradientMix),
|
||||
];
|
||||
let rgba = [bg[0], bg[1], bg[2], Math.round(255 * bgAlpha)];
|
||||
const r = Math.hypot(nx, ny);
|
||||
|
||||
const ringDistance = Math.abs(r - 0.53) - 0.085;
|
||||
const ringAlpha = smoothstep(feather * 2.2, -feather * 2.2, ringDistance) * bgAlpha;
|
||||
rgba = blend(rgba, [27, 196, 157], ringAlpha);
|
||||
|
||||
const glowAlpha = smoothstep(0.74, 0.16, r) * 0.18 * bgAlpha;
|
||||
rgba = blend(rgba, [32, 228, 190], glowAlpha);
|
||||
|
||||
const diamondDistance = Math.abs(nx) + Math.abs(ny) - 0.34;
|
||||
const diamondAlpha = smoothstep(feather * 2.6, -feather * 2.6, diamondDistance) * bgAlpha;
|
||||
rgba = blend(rgba, [13, 41, 45], diamondAlpha);
|
||||
|
||||
const barVDistance = Math.max(Math.abs(nx) - 0.055, Math.abs(ny) - 0.44);
|
||||
const barHDistance = Math.max(Math.abs(ny) - 0.055, Math.abs(nx) - 0.44);
|
||||
const barAlpha = Math.max(
|
||||
smoothstep(feather * 2.6, -feather * 2.6, barVDistance),
|
||||
smoothstep(feather * 2.6, -feather * 2.6, barHDistance),
|
||||
) * 0.92 * bgAlpha;
|
||||
rgba = blend(rgba, [183, 251, 239], barAlpha);
|
||||
|
||||
const coreDistance = r - 0.108;
|
||||
const coreAlpha = smoothstep(feather * 2.4, -feather * 2.4, coreDistance) * bgAlpha;
|
||||
rgba = blend(rgba, [244, 255, 253], coreAlpha);
|
||||
|
||||
const index = (y * size + x) * 4;
|
||||
pixels[index] = rgba[0];
|
||||
pixels[index + 1] = rgba[1];
|
||||
pixels[index + 2] = rgba[2];
|
||||
pixels[index + 3] = rgba[3];
|
||||
}
|
||||
}
|
||||
return pixels;
|
||||
}
|
||||
|
||||
function crc32(buffer) {
|
||||
let crc = ~0;
|
||||
for (let i = 0; i < buffer.length; i += 1) {
|
||||
crc ^= buffer[i];
|
||||
for (let j = 0; j < 8; j += 1) {
|
||||
crc = (crc >>> 1) ^ (0xEDB88320 & -(crc & 1));
|
||||
}
|
||||
}
|
||||
return (~crc) >>> 0;
|
||||
}
|
||||
|
||||
function chunk(type, data) {
|
||||
const out = Buffer.alloc(8 + data.length + 4);
|
||||
out.writeUInt32BE(data.length, 0);
|
||||
out.write(type, 4, 4, 'ascii');
|
||||
data.copy(out, 8);
|
||||
out.writeUInt32BE(crc32(Buffer.concat([Buffer.from(type, 'ascii'), data])), out.length - 4);
|
||||
return out;
|
||||
}
|
||||
|
||||
function encodePng(size, rgba) {
|
||||
const stride = size * 4;
|
||||
const rows = [];
|
||||
for (let y = 0; y < size; y += 1) {
|
||||
rows.push(Buffer.from([0]));
|
||||
rows.push(rgba.subarray(y * stride, (y + 1) * stride));
|
||||
}
|
||||
const raw = Buffer.concat(rows);
|
||||
return Buffer.concat([
|
||||
Buffer.from([0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]),
|
||||
chunk('IHDR', Buffer.from([
|
||||
(size >>> 24) & 0xff,
|
||||
(size >>> 16) & 0xff,
|
||||
(size >>> 8) & 0xff,
|
||||
size & 0xff,
|
||||
(size >>> 24) & 0xff,
|
||||
(size >>> 16) & 0xff,
|
||||
(size >>> 8) & 0xff,
|
||||
size & 0xff,
|
||||
8,
|
||||
6,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
])),
|
||||
chunk('IDAT', zlib.deflateSync(raw, { level: 9 })),
|
||||
chunk('IEND', Buffer.alloc(0)),
|
||||
]);
|
||||
}
|
||||
|
||||
function writeIco(targetPath, pngData) {
|
||||
const header = Buffer.alloc(6);
|
||||
header.writeUInt16LE(0, 0);
|
||||
header.writeUInt16LE(1, 2);
|
||||
header.writeUInt16LE(1, 4);
|
||||
|
||||
const entry = Buffer.alloc(16);
|
||||
entry.writeUInt8(0, 0);
|
||||
entry.writeUInt8(0, 1);
|
||||
entry.writeUInt8(0, 2);
|
||||
entry.writeUInt8(0, 3);
|
||||
entry.writeUInt16LE(1, 4);
|
||||
entry.writeUInt16LE(32, 6);
|
||||
entry.writeUInt32LE(pngData.length, 8);
|
||||
entry.writeUInt32LE(22, 12);
|
||||
|
||||
fs.writeFileSync(targetPath, Buffer.concat([header, entry, pngData]));
|
||||
}
|
||||
|
||||
function writeIcns(targetPath, pngBySize) {
|
||||
const typeMap = new Map([
|
||||
[16, 'icp4'],
|
||||
[32, 'icp5'],
|
||||
[64, 'icp6'],
|
||||
[128, 'ic07'],
|
||||
[256, 'ic08'],
|
||||
[512, 'ic09'],
|
||||
[1024, 'ic10'],
|
||||
]);
|
||||
|
||||
const blocks = [];
|
||||
for (const [size, type] of typeMap) {
|
||||
const png = pngBySize.get(size);
|
||||
if (!png) continue;
|
||||
const header = Buffer.alloc(8);
|
||||
header.write(type, 0, 4, 'ascii');
|
||||
header.writeUInt32BE(png.length + 8, 4);
|
||||
blocks.push(header, png);
|
||||
}
|
||||
|
||||
const payload = Buffer.concat(blocks);
|
||||
const icnsHeader = Buffer.alloc(8);
|
||||
icnsHeader.write('icns', 0, 4, 'ascii');
|
||||
icnsHeader.writeUInt32BE(payload.length + 8, 4);
|
||||
fs.writeFileSync(targetPath, Buffer.concat([icnsHeader, payload]));
|
||||
}
|
||||
|
||||
fs.mkdirSync(iconsDir, { recursive: true });
|
||||
|
||||
const pngBySize = new Map();
|
||||
for (const size of internalPngSizes) {
|
||||
pngBySize.set(size, encodePng(size, drawIcon(size)));
|
||||
}
|
||||
|
||||
for (const [filename, size] of Object.entries(pngOutputs)) {
|
||||
fs.writeFileSync(path.join(iconsDir, filename), pngBySize.get(size) ?? encodePng(size, drawIcon(size)));
|
||||
}
|
||||
|
||||
writeIco(path.join(iconsDir, 'icon.ico'), pngBySize.get(256));
|
||||
writeIcns(path.join(iconsDir, 'icon.icns'), pngBySize);
|
||||
|
||||
const created = fs.readdirSync(iconsDir).sort();
|
||||
console.log(`Generated ${created.length} desktop icons in ${iconsDir}`);
|
||||
for (const name of created) {
|
||||
console.log(` - ${name}`);
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const crypto = require('node:crypto');
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
|
||||
if (process.argv.length < 3) {
|
||||
throw new Error('Usage: write-release-manifest.cjs <bundle_dir>');
|
||||
}
|
||||
const bundleDir = path.resolve(process.argv[2]);
|
||||
const tauriConfigPath = path.resolve(__dirname, '..', 'src-tauri', 'tauri.conf.json');
|
||||
const updateBaseUrl = (
|
||||
process.env.SHADOWBROKER_UPDATE_BASE_URL ||
|
||||
'https://github.com/BigBodyCobain/Shadowbroker/releases/latest/download'
|
||||
).replace(/\/+$/, '');
|
||||
|
||||
const releaseSuffixes = [
|
||||
'.AppImage',
|
||||
'.app.tar.gz',
|
||||
'.deb',
|
||||
'.dmg',
|
||||
'.exe',
|
||||
'.msi',
|
||||
'.pkg',
|
||||
'.rpm',
|
||||
'.sig',
|
||||
'.tar.gz',
|
||||
'.zip',
|
||||
'latest.json',
|
||||
];
|
||||
|
||||
function collectFiles(dir) {
|
||||
const files = [];
|
||||
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
||||
const fullPath = path.join(dir, entry.name);
|
||||
if (entry.isDirectory()) {
|
||||
files.push(...collectFiles(fullPath));
|
||||
} else if (entry.isFile()) {
|
||||
files.push(fullPath);
|
||||
}
|
||||
}
|
||||
return files;
|
||||
}
|
||||
|
||||
function sha256File(filePath) {
|
||||
const hash = crypto.createHash('sha256');
|
||||
hash.update(fs.readFileSync(filePath));
|
||||
return hash.digest('hex');
|
||||
}
|
||||
|
||||
function readReleaseVersion() {
|
||||
try {
|
||||
const config = JSON.parse(fs.readFileSync(tauriConfigPath, 'utf8'));
|
||||
return String(config.version || '').trim() || '0.0.0';
|
||||
} catch {
|
||||
return '0.0.0';
|
||||
}
|
||||
}
|
||||
|
||||
function updaterPlatformForArtifact(relativePath) {
|
||||
if (/\.msi$/i.test(relativePath) || /\.exe$/i.test(relativePath)) {
|
||||
return 'windows-x86_64';
|
||||
}
|
||||
if (/\.app\.tar\.gz$/i.test(relativePath) || /\.dmg$/i.test(relativePath)) {
|
||||
return 'darwin-x86_64';
|
||||
}
|
||||
if (/\.AppImage$/i.test(relativePath) || /\.deb$/i.test(relativePath) || /\.rpm$/i.test(relativePath)) {
|
||||
return 'linux-x86_64';
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function updaterArtifactPriority(relativePath) {
|
||||
if (/\.msi$/i.test(relativePath)) return 0;
|
||||
if (/setup\.exe$/i.test(relativePath) || /\.exe$/i.test(relativePath)) return 1;
|
||||
if (/\.app\.tar\.gz$/i.test(relativePath)) return 0;
|
||||
if (/\.AppImage$/i.test(relativePath)) return 0;
|
||||
return 10;
|
||||
}
|
||||
|
||||
function writeUpdaterManifest(files) {
|
||||
const signedArtifacts = files
|
||||
.filter((filePath) => filePath.endsWith('.sig'))
|
||||
.map((signaturePath) => {
|
||||
const artifactPath = signaturePath.slice(0, -4);
|
||||
if (!fs.existsSync(artifactPath)) return null;
|
||||
const relativePath = path.relative(bundleDir, artifactPath).replaceAll(path.sep, '/');
|
||||
const platform = updaterPlatformForArtifact(relativePath);
|
||||
if (!platform) return null;
|
||||
return {
|
||||
platform,
|
||||
relativePath,
|
||||
signature: fs.readFileSync(signaturePath, 'utf8').trim(),
|
||||
};
|
||||
})
|
||||
.filter(Boolean)
|
||||
.sort((a, b) => updaterArtifactPriority(a.relativePath) - updaterArtifactPriority(b.relativePath));
|
||||
|
||||
const platforms = {};
|
||||
for (const artifact of signedArtifacts) {
|
||||
if (platforms[artifact.platform]) continue;
|
||||
platforms[artifact.platform] = {
|
||||
signature: artifact.signature,
|
||||
url: `${updateBaseUrl}/${encodeURIComponent(path.basename(artifact.relativePath))}`,
|
||||
};
|
||||
}
|
||||
|
||||
if (Object.keys(platforms).length === 0) return false;
|
||||
|
||||
const latest = {
|
||||
version: readReleaseVersion(),
|
||||
notes: `ShadowBroker ${readReleaseVersion()}`,
|
||||
pub_date: new Date().toISOString(),
|
||||
platforms,
|
||||
};
|
||||
|
||||
fs.writeFileSync(path.join(bundleDir, 'latest.json'), `${JSON.stringify(latest, null, 2)}\n`);
|
||||
return true;
|
||||
}
|
||||
|
||||
fs.mkdirSync(bundleDir, { recursive: true });
|
||||
|
||||
let files = collectFiles(bundleDir);
|
||||
const wroteUpdaterManifest = writeUpdaterManifest(files);
|
||||
if (wroteUpdaterManifest) {
|
||||
files = collectFiles(bundleDir);
|
||||
}
|
||||
const artifacts = files.filter((file) => releaseSuffixes.some((suffix) => file.endsWith(suffix)));
|
||||
const releaseFiles = (artifacts.length > 0 ? artifacts : files).sort();
|
||||
|
||||
const manifest = releaseFiles.map((filePath) => ({
|
||||
path: path.relative(bundleDir, filePath).replaceAll(path.sep, '/'),
|
||||
size_bytes: fs.statSync(filePath).size,
|
||||
sha256: sha256File(filePath),
|
||||
}));
|
||||
|
||||
fs.writeFileSync(
|
||||
path.join(bundleDir, 'SHA256SUMS.txt'),
|
||||
manifest.map((item) => `${item.sha256} ${item.path}`).join('\n') + (manifest.length ? '\n' : ''),
|
||||
);
|
||||
fs.writeFileSync(
|
||||
path.join(bundleDir, 'release-manifest.json'),
|
||||
`${JSON.stringify({ artifacts: manifest }, null, 2)}\n`,
|
||||
);
|
||||
|
||||
console.log(`Wrote release manifest for ${manifest.length} artifacts in ${bundleDir}`);
|
||||
if (wroteUpdaterManifest) {
|
||||
console.log(`Wrote Tauri updater manifest: ${path.join(bundleDir, 'latest.json')}`);
|
||||
}
|
||||
for (const item of manifest) {
|
||||
console.log(` - ${item.path} (${item.size_bytes} bytes)`);
|
||||
}
|
||||
Reference in New Issue
Block a user