mirror of
https://github.com/nimbold/Firelink.git
synced 2026-07-26 20:18:37 +00:00
158 lines
5.1 KiB
JavaScript
Executable File
158 lines
5.1 KiB
JavaScript
Executable File
#!/usr/bin/env node
|
|
import fs from 'node:fs';
|
|
import os from 'node:os';
|
|
import path from 'node:path';
|
|
import { spawnSync } from 'node:child_process';
|
|
import { fileURLToPath } from 'node:url';
|
|
import { collectRegularFiles, sha256 } from './engine-payload-integrity.js';
|
|
|
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
const repoRoot = path.resolve(__dirname, '..');
|
|
|
|
function argValue(name) {
|
|
const index = process.argv.indexOf(name);
|
|
return index >= 0 ? process.argv[index + 1] : undefined;
|
|
}
|
|
|
|
function fail(message) {
|
|
console.error(message);
|
|
process.exit(1);
|
|
}
|
|
|
|
function mustBeDirectory(directory, label) {
|
|
if (!fs.existsSync(directory) || !fs.statSync(directory).isDirectory()) {
|
|
fail(`${label} does not exist or is not a directory: ${directory}`);
|
|
}
|
|
}
|
|
|
|
function mustBeFile(file, label) {
|
|
if (!fs.existsSync(file) || !fs.statSync(file).isFile()) {
|
|
fail(`${label} does not exist or is not a file: ${file}`);
|
|
}
|
|
}
|
|
|
|
function findSingleAppImage(directory) {
|
|
mustBeDirectory(directory, 'AppImage bundle directory');
|
|
const appImages = fs
|
|
.readdirSync(directory)
|
|
.filter(name => name.endsWith('.AppImage'))
|
|
.map(name => path.join(directory, name));
|
|
|
|
if (appImages.length !== 1) {
|
|
fail(`Expected exactly one AppImage in ${directory}, found ${appImages.length}.`);
|
|
}
|
|
|
|
return appImages[0];
|
|
}
|
|
|
|
function validatePayloadManifest(root, target, label) {
|
|
const manifestPath = path.join(root, 'payload-manifest.json');
|
|
mustBeFile(manifestPath, `${label} payload manifest`);
|
|
|
|
let manifest;
|
|
try {
|
|
manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
|
|
} catch (error) {
|
|
fail(`${label} payload manifest is not valid JSON: ${error.message}`);
|
|
}
|
|
|
|
if (manifest.target !== target) {
|
|
fail(`${label} payload target mismatch: expected ${target}, got ${manifest.target}`);
|
|
}
|
|
|
|
const expectedFiles = Object.keys(manifest.files || {}).sort();
|
|
const actualFiles = collectRegularFiles(root, { ignoredNames: ['payload-manifest.json'] })
|
|
.map(file => path.relative(root, file).split(path.sep).join('/'))
|
|
.sort();
|
|
|
|
if (JSON.stringify(actualFiles) !== JSON.stringify(expectedFiles)) {
|
|
fail(`${label} payload files do not match payload-manifest.json.`);
|
|
}
|
|
|
|
for (const relative of expectedFiles) {
|
|
const file = path.join(root, relative);
|
|
if (sha256(file) !== manifest.files[relative]) {
|
|
fail(`${label} payload checksum mismatch: ${relative}`);
|
|
}
|
|
}
|
|
}
|
|
|
|
function run(command, args, options = {}) {
|
|
const result = spawnSync(command, args, {
|
|
cwd: options.cwd ?? repoRoot,
|
|
env: { ...process.env, ...options.env },
|
|
stdio: options.stdio ?? 'inherit',
|
|
encoding: options.stdio === 'pipe' ? 'utf8' : undefined,
|
|
});
|
|
|
|
if (result.error) {
|
|
fail(`Failed to run ${command}: ${result.error.message}`);
|
|
}
|
|
|
|
if (result.status !== 0) {
|
|
if (options.stdio === 'pipe') {
|
|
if (result.stdout) {
|
|
process.stdout.write(result.stdout);
|
|
}
|
|
if (result.stderr) {
|
|
process.stderr.write(result.stderr);
|
|
}
|
|
}
|
|
fail(`${command} exited with status ${result.status}`);
|
|
}
|
|
}
|
|
|
|
const target = argValue('--target') || process.env.FIRELINK_TARGET_TRIPLE || process.env.TAURI_ENV_TARGET_TRIPLE;
|
|
if (!target) {
|
|
fail('Pass --target <triple>.');
|
|
}
|
|
|
|
const bundleDirectory = path.join(repoRoot, 'src-tauri', 'target', target, 'release', 'bundle', 'appimage');
|
|
const appDir = path.resolve(argValue('--appdir') || path.join(bundleDirectory, 'Firelink.AppDir'));
|
|
const appImage = path.resolve(argValue('--appimage') || findSingleAppImage(bundleDirectory));
|
|
const appImageTool = path.resolve(argValue('--appimagetool') || process.env.APPIMAGETOOL || 'appimagetool');
|
|
const source = path.join(repoRoot, 'src-tauri', 'provisioned-engines', target);
|
|
const destination = path.join(appDir, 'usr', 'lib', 'Firelink', 'engine-dist', target);
|
|
|
|
mustBeDirectory(appDir, 'Firelink.AppDir');
|
|
mustBeDirectory(source, 'Provisioned engine payload');
|
|
mustBeFile(appImage, 'AppImage');
|
|
mustBeFile(appImageTool, 'appimagetool');
|
|
|
|
validatePayloadManifest(source, target, 'Provisioned engine');
|
|
|
|
fs.rmSync(destination, { recursive: true, force: true });
|
|
fs.mkdirSync(path.dirname(destination), { recursive: true });
|
|
fs.cpSync(source, destination, {
|
|
recursive: true,
|
|
dereference: false,
|
|
preserveTimestamps: true,
|
|
});
|
|
validatePayloadManifest(destination, target, 'AppDir engine');
|
|
|
|
fs.chmodSync(appImageTool, 0o755);
|
|
run(appImageTool, [appDir, appImage], {
|
|
env: {
|
|
APPIMAGE_EXTRACT_AND_RUN: '1',
|
|
ARCH: 'x86_64',
|
|
},
|
|
});
|
|
|
|
const extractRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'firelink-appimage-'));
|
|
try {
|
|
fs.chmodSync(appImage, 0o755);
|
|
run(appImage, ['--appimage-extract'], {
|
|
cwd: extractRoot,
|
|
env: { APPIMAGE_EXTRACT_AND_RUN: '1' },
|
|
stdio: 'pipe',
|
|
});
|
|
|
|
const extractedPayload = path.join(extractRoot, 'squashfs-root', 'usr', 'lib', 'Firelink', 'engine-dist', target);
|
|
mustBeDirectory(extractedPayload, 'Extracted AppImage engine payload');
|
|
validatePayloadManifest(extractedPayload, target, 'Extracted AppImage engine');
|
|
} finally {
|
|
fs.rmSync(extractRoot, { recursive: true, force: true });
|
|
}
|
|
|
|
console.log(`Repacked and verified AppImage engine payload for ${target}.`);
|