mirror of
https://github.com/nimbold/Firelink.git
synced 2026-09-09 09:16:57 +00:00
fix(deps): refresh packages and harden engine staging
- update npm, Cargo, Deno, and FFmpeg lockfiles - refresh verified cross-platform engine payload metadata - serialize staging, verification, and Tauri packaging across concurrent runs - recover interrupted lock records and cover crash and ownership races
This commit is contained in:
@@ -46,6 +46,8 @@ target/
|
||||
src-tauri/target/
|
||||
src-tauri/gen/
|
||||
src-tauri/engine-dist/
|
||||
src-tauri/engine-dist.lock
|
||||
src-tauri/engine-dist.lock.*
|
||||
src-tauri/provisioned-engines/
|
||||
|
||||
# Locally provisioned native engines
|
||||
|
||||
+12
-12
@@ -8,14 +8,14 @@
|
||||
"sha256": "30b4c14aafab6082becff7881e41b76df46dc43ea7633479410a91e29da492bf"
|
||||
},
|
||||
"deno": {
|
||||
"version": "2.9.5",
|
||||
"url": "https://github.com/denoland/deno/releases/download/v2.9.5/deno-x86_64-pc-windows-msvc.zip",
|
||||
"sha256": "171efab55ac6b9881fd53ee4c20f8bf3bb1340ffc618483746909014db12216a"
|
||||
"version": "2.9.6",
|
||||
"url": "https://github.com/denoland/deno/releases/download/v2.9.6/deno-x86_64-pc-windows-msvc.zip",
|
||||
"sha256": "15e5300b0ba3c3695a7621d90160a746ec9e710228cee639afa9d580f6e3cd11"
|
||||
},
|
||||
"ffmpeg": {
|
||||
"version": "8.1.2-46-g139afe709a",
|
||||
"url": "https://github.com/BtbN/FFmpeg-Builds/releases/download/autobuild-2026-08-26-13-06/ffmpeg-n8.1.2-46-g139afe709a-win64-gpl-8.1.zip",
|
||||
"sha256": "f966bc2e843bcd680dedd6d1a2c0c895bab859a402c6dd107cbe72a796dfebcf"
|
||||
"version": "8.1.2-50-g1a748fe2cd",
|
||||
"url": "https://github.com/BtbN/FFmpeg-Builds/releases/download/autobuild-2026-08-29-13-12/ffmpeg-n8.1.2-50-g1a748fe2cd-win64-gpl-8.1.zip",
|
||||
"sha256": "3b56a6e46944cbe87692a4a72b29d95e527cbbd9925fc7beb07bb63d578cd5ae"
|
||||
},
|
||||
"aria2c": {
|
||||
"version": "1.37.0",
|
||||
@@ -30,14 +30,14 @@
|
||||
"sha256": "32e72032766bef9199d99d15beb69fd52e46df8f8b06f0d8745db59e04d339e9"
|
||||
},
|
||||
"deno": {
|
||||
"version": "2.9.5",
|
||||
"url": "https://github.com/denoland/deno/releases/download/v2.9.5/deno-x86_64-unknown-linux-gnu.zip",
|
||||
"sha256": "8b010a3b1a4a0188a67cdb8a7a27348b2a501af78aec7fc74f2ace167368d530"
|
||||
"version": "2.9.6",
|
||||
"url": "https://github.com/denoland/deno/releases/download/v2.9.6/deno-x86_64-unknown-linux-gnu.zip",
|
||||
"sha256": "394f07f4da2bebe6ce6f1e7ce0fa16429b29b08c35e3fac3fe25972676dff4b2"
|
||||
},
|
||||
"ffmpeg": {
|
||||
"version": "8.1.2-46-g139afe709a",
|
||||
"url": "https://github.com/BtbN/FFmpeg-Builds/releases/download/autobuild-2026-08-26-13-06/ffmpeg-n8.1.2-46-g139afe709a-linux64-gpl-8.1.tar.xz",
|
||||
"sha256": "0814f4491c2673ea505be8fb65a76c2bfabaa5aad8f33d49b1c5b87a2262e8c5"
|
||||
"version": "8.1.2-50-g1a748fe2cd",
|
||||
"url": "https://github.com/BtbN/FFmpeg-Builds/releases/download/autobuild-2026-08-29-13-12/ffmpeg-n8.1.2-50-g1a748fe2cd-linux64-gpl-8.1.tar.xz",
|
||||
"sha256": "be243f2520d6e7e92c82b015ed72a3f5b57c7970801c0d51f031457122653c76"
|
||||
},
|
||||
"aria2c": {
|
||||
"version": "1.37.0",
|
||||
|
||||
+2
-2
@@ -23,10 +23,10 @@
|
||||
"sha256": "734e6b72a0c2d0d5e089b5a0094be74fa058c15158f6b0689207a01fedafd8f5"
|
||||
},
|
||||
"deno": {
|
||||
"version": "2.9.5",
|
||||
"version": "2.9.6",
|
||||
"source": "https://github.com/denoland/deno",
|
||||
"build": "official aarch64-apple-darwin executable",
|
||||
"sha256": "b5bd08edab254d42d7b05aa5b6cb4c9b8d4dede4975aff76951ce2cce18866fa"
|
||||
"sha256": "b3ac3bd206e48c26026cadd80c1367e96c149f9c66130952382a642b09fa8a71"
|
||||
}
|
||||
},
|
||||
"runtimeTrees": {
|
||||
|
||||
Generated
+8
-8
@@ -25,7 +25,7 @@
|
||||
"@tauri-apps/plugin-notification": "^2.3.3",
|
||||
"@tauri-apps/plugin-opener": "^2",
|
||||
"i18next": "^26.4.0",
|
||||
"lucide-react": "^1.34.0",
|
||||
"lucide-react": "^1.37.0",
|
||||
"react": "^19.2.8",
|
||||
"react-dom": "^19.2.8",
|
||||
"react-i18next": "^17.0.12",
|
||||
@@ -35,7 +35,7 @@
|
||||
"@tauri-apps/cli": "^2.11.4",
|
||||
"@types/react": "^19.2.18",
|
||||
"@types/react-dom": "^19.2.5",
|
||||
"@vitejs/plugin-react": "^6.1.0",
|
||||
"@vitejs/plugin-react": "^6.1.1",
|
||||
"autoprefixer": "^10.5.4",
|
||||
"postcss": "^8.5.26",
|
||||
"tailwindcss": "^4.3.3",
|
||||
@@ -1416,9 +1416,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@vitejs/plugin-react": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.1.0.tgz",
|
||||
"integrity": "sha512-qd2BzUBehkov86WFhg0JkEFEYyCLG9uPCe6qWTY/kRlss9OvJrOF2UbIWT7p+8IzZHkEu0DNGHc4HSv+JdDLsw==",
|
||||
"version": "6.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.1.1.tgz",
|
||||
"integrity": "sha512-yxLaQV9gkhS8ezJqCM6+ndU7mDY6gqAg75NQ+0IjwEI8IYOmQCgkRwHKVSfWXW076DsqMo0Dk+0FK1U+M5RgFw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -2122,9 +2122,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/lucide-react": {
|
||||
"version": "1.34.0",
|
||||
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.34.0.tgz",
|
||||
"integrity": "sha512-vnjGJNI7Htk5+oWW8gXGuaLgwgAb0T6/iZbBrp9JCfRFwdNWZ0YTm3eyxjOLgwN6r8iyAf3UA70zNmBRBNv7yg==",
|
||||
"version": "1.37.0",
|
||||
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.37.0.tgz",
|
||||
"integrity": "sha512-LPsB4rD1TD6wZu1djKOf9vUnS1jTNaHbolXebXDgiTdb6jeA1agIJhJsIybCmjKmQClcOaal1o1OaiYahEftyQ==",
|
||||
"license": "ISC",
|
||||
"peerDependencies": {
|
||||
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
|
||||
+3
-3
@@ -40,7 +40,7 @@
|
||||
"test:torrent:rpc": "cd src-tauri && cargo test --test torrent_rpc -- --nocapture",
|
||||
"verify:macos-signing": "node scripts/verify-macos-signing.js",
|
||||
"preview": "vite preview",
|
||||
"tauri": "tauri",
|
||||
"tauri": "node scripts/tauri-command.js",
|
||||
"test": "vitest"
|
||||
},
|
||||
"dependencies": {
|
||||
@@ -60,7 +60,7 @@
|
||||
"@tauri-apps/plugin-notification": "^2.3.3",
|
||||
"@tauri-apps/plugin-opener": "^2",
|
||||
"i18next": "^26.4.0",
|
||||
"lucide-react": "^1.34.0",
|
||||
"lucide-react": "^1.37.0",
|
||||
"react": "^19.2.8",
|
||||
"react-dom": "^19.2.8",
|
||||
"react-i18next": "^17.0.12",
|
||||
@@ -70,7 +70,7 @@
|
||||
"@tauri-apps/cli": "^2.11.4",
|
||||
"@types/react": "^19.2.18",
|
||||
"@types/react-dom": "^19.2.5",
|
||||
"@vitejs/plugin-react": "^6.1.0",
|
||||
"@vitejs/plugin-react": "^6.1.1",
|
||||
"autoprefixer": "^10.5.4",
|
||||
"postcss": "^8.5.26",
|
||||
"tailwindcss": "^4.3.3",
|
||||
|
||||
@@ -0,0 +1,187 @@
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
|
||||
const DEFAULT_TIMEOUT_MS = 60_000;
|
||||
const RETRY_DELAYS_MS = [25, 50, 100, 250, 500];
|
||||
const RETRYABLE_RENAME_ERRORS = new Set(['EACCES', 'EBUSY', 'EPERM']);
|
||||
|
||||
function sleep(milliseconds) {
|
||||
return new Promise(resolve => setTimeout(resolve, milliseconds));
|
||||
}
|
||||
|
||||
function isProcessAlive(pid) {
|
||||
if (!Number.isSafeInteger(pid) || pid <= 0) return false;
|
||||
try {
|
||||
process.kill(pid, 0);
|
||||
return true;
|
||||
} catch (error) {
|
||||
return error?.code === 'EPERM';
|
||||
}
|
||||
}
|
||||
|
||||
function readLock(lockPath) {
|
||||
let contents;
|
||||
try {
|
||||
contents = fs.readFileSync(lockPath, 'utf8');
|
||||
} catch (error) {
|
||||
if (error?.code === 'ENOENT') return null;
|
||||
throw error;
|
||||
}
|
||||
|
||||
try {
|
||||
const owner = JSON.parse(contents);
|
||||
if (
|
||||
!Number.isSafeInteger(owner?.pid)
|
||||
|| owner.pid <= 0
|
||||
|| typeof owner.token !== 'string'
|
||||
|| owner.token.length === 0
|
||||
) {
|
||||
return { malformed: true };
|
||||
}
|
||||
return { owner };
|
||||
} catch {
|
||||
return { malformed: true };
|
||||
}
|
||||
}
|
||||
|
||||
async function removeStaleLock(lockPath) {
|
||||
const quarantinePath = `${lockPath}.stale-${process.pid}-${process.hrtime.bigint()}`;
|
||||
for (let attempt = 0; ; attempt += 1) {
|
||||
try {
|
||||
// Rename is the compare-and-remove operation: another waiter cannot
|
||||
// delete a newly acquired lock after this path has changed owners.
|
||||
fs.renameSync(lockPath, quarantinePath);
|
||||
break;
|
||||
} catch (error) {
|
||||
if (error?.code === 'ENOENT' || error?.code === 'EEXIST') return false;
|
||||
if (!RETRYABLE_RENAME_ERRORS.has(error?.code) || attempt >= RETRY_DELAYS_MS.length) {
|
||||
return false;
|
||||
}
|
||||
await sleep(RETRY_DELAYS_MS[attempt]);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
fs.unlinkSync(quarantinePath);
|
||||
} catch (error) {
|
||||
if (error?.code !== 'ENOENT') throw error;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function releaseLock(lockPath, token) {
|
||||
const currentLock = readLock(lockPath);
|
||||
if (!currentLock) return;
|
||||
if (currentLock.malformed) {
|
||||
throw new Error(`Cannot release staging lock with an invalid owner record: ${lockPath}`);
|
||||
}
|
||||
const owner = currentLock.owner;
|
||||
if (owner.token !== token || owner.pid !== process.pid) {
|
||||
throw new Error(`Staging lock ownership changed before release: ${lockPath}`);
|
||||
}
|
||||
try {
|
||||
fs.unlinkSync(lockPath);
|
||||
} catch (error) {
|
||||
if (error?.code !== 'ENOENT') throw error;
|
||||
}
|
||||
}
|
||||
|
||||
function createLock(lockPath, owner) {
|
||||
const temporaryPath = `${lockPath}.owner-${owner.token}`;
|
||||
let temporaryCreated = false;
|
||||
try {
|
||||
// Write the complete record away from the contested path, then publish it
|
||||
// with a hard link. Unlike rename, link never replaces an existing lock.
|
||||
fs.writeFileSync(temporaryPath, `${JSON.stringify(owner)}\n`, {
|
||||
encoding: 'utf8',
|
||||
flag: 'wx',
|
||||
mode: 0o600,
|
||||
});
|
||||
temporaryCreated = true;
|
||||
fs.linkSync(temporaryPath, lockPath);
|
||||
} finally {
|
||||
if (temporaryCreated) {
|
||||
try {
|
||||
fs.unlinkSync(temporaryPath);
|
||||
} catch (error) {
|
||||
if (error?.code !== 'ENOENT') throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function readExclusiveFileLockOwner(lockPath) {
|
||||
const currentLock = readLock(path.resolve(lockPath));
|
||||
if (!currentLock?.owner) {
|
||||
throw new Error(`Staging lock owner record is unavailable: ${path.resolve(lockPath)}`);
|
||||
}
|
||||
return currentLock.owner;
|
||||
}
|
||||
|
||||
export function assertExclusiveFileLockHeld(lockPath, expectedOwner) {
|
||||
if (
|
||||
!Number.isSafeInteger(expectedOwner?.pid)
|
||||
|| expectedOwner.pid <= 0
|
||||
|| typeof expectedOwner.token !== 'string'
|
||||
|| expectedOwner.token.length === 0
|
||||
) {
|
||||
throw new Error(`Invalid inherited staging lock owner: ${path.resolve(lockPath)}`);
|
||||
}
|
||||
|
||||
const owner = readExclusiveFileLockOwner(lockPath);
|
||||
if (
|
||||
owner.pid !== expectedOwner.pid
|
||||
|| owner.token !== expectedOwner.token
|
||||
|| !isProcessAlive(owner.pid)
|
||||
) {
|
||||
throw new Error(`Inherited staging lock is no longer held: ${path.resolve(lockPath)}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes operations that replace the shared engine-dist directory.
|
||||
* Returns an idempotent release function after the caller owns the lock.
|
||||
*/
|
||||
export async function acquireExclusiveFileLock(lockPath, options = {}) {
|
||||
const resolvedPath = path.resolve(lockPath);
|
||||
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
||||
const startedAt = Date.now();
|
||||
const token = `${process.pid}-${process.hrtime.bigint()}`;
|
||||
const owner = {
|
||||
pid: process.pid,
|
||||
token,
|
||||
startedAt: new Date().toISOString(),
|
||||
};
|
||||
|
||||
for (let attempt = 0; ; attempt += 1) {
|
||||
try {
|
||||
createLock(resolvedPath, owner);
|
||||
|
||||
let released = false;
|
||||
return () => {
|
||||
if (released) return;
|
||||
releaseLock(resolvedPath, token);
|
||||
released = true;
|
||||
};
|
||||
} catch (error) {
|
||||
if (error?.code !== 'EEXIST') throw error;
|
||||
|
||||
const currentLock = readLock(resolvedPath);
|
||||
if (currentLock?.malformed) {
|
||||
if (await removeStaleLock(resolvedPath)) continue;
|
||||
} else if (currentLock?.owner && !isProcessAlive(currentLock.owner.pid)) {
|
||||
if (await removeStaleLock(resolvedPath)) continue;
|
||||
}
|
||||
|
||||
if (Date.now() - startedAt >= timeoutMs) {
|
||||
const ownerDescription = currentLock?.owner?.pid
|
||||
? ` owned by PID ${currentLock.owner.pid}`
|
||||
: ' with an unreadable owner record';
|
||||
throw new Error(`Timed out waiting for staging lock ${resolvedPath}${ownerDescription}`);
|
||||
}
|
||||
|
||||
const delay = RETRY_DELAYS_MS[Math.min(attempt, RETRY_DELAYS_MS.length - 1)];
|
||||
await sleep(delay);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,186 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import { once } from 'node:events';
|
||||
import { spawn } from 'node:child_process';
|
||||
import fs from 'node:fs';
|
||||
import os from 'node:os';
|
||||
import path from 'node:path';
|
||||
import test from 'node:test';
|
||||
import { pathToFileURL } from 'node:url';
|
||||
import {
|
||||
acquireExclusiveFileLock,
|
||||
assertExclusiveFileLockHeld,
|
||||
readExclusiveFileLockOwner,
|
||||
} from './engine-staging-lock.js';
|
||||
|
||||
const childOutputStates = new WeakMap();
|
||||
|
||||
function temporaryLockPath() {
|
||||
const directory = fs.mkdtempSync(path.join(os.tmpdir(), 'firelink-stage-lock-'));
|
||||
return path.join(directory, 'engine-dist.lock');
|
||||
}
|
||||
|
||||
function waitForOutput(child, expected) {
|
||||
const state = childOutputStates.get(child);
|
||||
assert.ok(state, 'child output tracking must be installed before waiting');
|
||||
if (state.output.includes(expected)) return Promise.resolve(state.output);
|
||||
if (state.error) return Promise.reject(state.error);
|
||||
if (state.exit) {
|
||||
return Promise.reject(new Error(
|
||||
`lock child exited before '${expected}' (code=${state.exit.code}, signal=${state.exit.signal})`,
|
||||
));
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
state.waiters.push({ expected, resolve, reject });
|
||||
});
|
||||
}
|
||||
|
||||
function trackChildOutput(child) {
|
||||
const state = { error: null, exit: null, output: '', waiters: [] };
|
||||
childOutputStates.set(child, state);
|
||||
child.stdout.setEncoding('utf8');
|
||||
child.stdout.on('data', chunk => {
|
||||
state.output += chunk;
|
||||
for (const waiter of state.waiters.splice(0)) {
|
||||
if (state.output.includes(waiter.expected)) {
|
||||
waiter.resolve(state.output);
|
||||
} else {
|
||||
state.waiters.push(waiter);
|
||||
}
|
||||
}
|
||||
});
|
||||
child.once('error', error => {
|
||||
state.error = error;
|
||||
for (const waiter of state.waiters.splice(0)) waiter.reject(error);
|
||||
});
|
||||
child.once('exit', (code, signal) => {
|
||||
state.exit = { code, signal };
|
||||
const error = new Error(`lock child exited before a requested output (code=${code}, signal=${signal})`);
|
||||
for (const waiter of state.waiters.splice(0)) waiter.reject(error);
|
||||
});
|
||||
}
|
||||
|
||||
function spawnLockChild(lockPath) {
|
||||
const moduleUrl = pathToFileURL(path.resolve('scripts/engine-staging-lock.js')).href;
|
||||
const source = `
|
||||
import { acquireExclusiveFileLock } from ${JSON.stringify(moduleUrl)};
|
||||
process.stdout.write('started\\n');
|
||||
const release = await acquireExclusiveFileLock(process.argv[1], { timeoutMs: 5_000 });
|
||||
process.stdout.write('acquired\\n');
|
||||
process.stdin.once('data', () => {
|
||||
release();
|
||||
process.exit(0);
|
||||
});
|
||||
process.stdin.resume();
|
||||
`;
|
||||
const child = spawn(process.execPath, ['--input-type=module', '-e', source, lockPath], {
|
||||
stdio: ['pipe', 'pipe', 'pipe'],
|
||||
});
|
||||
trackChildOutput(child);
|
||||
return child;
|
||||
}
|
||||
|
||||
test('exclusive staging lock waits for a live owner and releases idempotently', async () => {
|
||||
const lockPath = temporaryLockPath();
|
||||
const releaseFirst = await acquireExclusiveFileLock(lockPath, { timeoutMs: 1_000 });
|
||||
const secondAcquisition = acquireExclusiveFileLock(lockPath, {
|
||||
timeoutMs: 1_000,
|
||||
});
|
||||
|
||||
assert.equal(fs.existsSync(lockPath), true);
|
||||
releaseFirst();
|
||||
releaseFirst();
|
||||
|
||||
const releaseSecond = await secondAcquisition;
|
||||
assert.equal(fs.existsSync(lockPath), true);
|
||||
releaseSecond();
|
||||
assert.equal(fs.existsSync(lockPath), false);
|
||||
});
|
||||
|
||||
test('exclusive staging lock recovers empty and malformed legacy records', async () => {
|
||||
for (const contents of ['', '{not-json']) {
|
||||
const lockPath = temporaryLockPath();
|
||||
fs.writeFileSync(lockPath, contents);
|
||||
|
||||
const release = await acquireExclusiveFileLock(lockPath, { timeoutMs: 1_000 });
|
||||
const owner = JSON.parse(fs.readFileSync(lockPath, 'utf8'));
|
||||
assert.equal(owner.pid, process.pid);
|
||||
release();
|
||||
assert.equal(fs.existsSync(lockPath), false);
|
||||
}
|
||||
});
|
||||
|
||||
test('exclusive staging lock serializes separate processes', async () => {
|
||||
const lockPath = temporaryLockPath();
|
||||
const first = spawnLockChild(lockPath);
|
||||
let second;
|
||||
try {
|
||||
await waitForOutput(first, 'acquired\n');
|
||||
second = spawnLockChild(lockPath);
|
||||
await waitForOutput(second, 'started\n');
|
||||
|
||||
const owner = JSON.parse(fs.readFileSync(lockPath, 'utf8'));
|
||||
assert.equal(owner.pid, first.pid);
|
||||
first.stdin.write('\n');
|
||||
await once(first, 'exit');
|
||||
await waitForOutput(second, 'acquired\n');
|
||||
second.stdin.write('\n');
|
||||
await once(second, 'exit');
|
||||
} finally {
|
||||
for (const child of [first, second]) {
|
||||
if (child && child.exitCode === null && child.signalCode === null) child.kill();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test('exclusive staging lock recovers after an owner is force-killed', async () => {
|
||||
const lockPath = temporaryLockPath();
|
||||
const owner = spawnLockChild(lockPath);
|
||||
try {
|
||||
await waitForOutput(owner, 'acquired\n');
|
||||
assert.equal(owner.kill('SIGKILL'), true);
|
||||
await once(owner, 'exit');
|
||||
|
||||
const release = await acquireExclusiveFileLock(lockPath, { timeoutMs: 1_000 });
|
||||
const replacement = JSON.parse(fs.readFileSync(lockPath, 'utf8'));
|
||||
assert.equal(replacement.pid, process.pid);
|
||||
release();
|
||||
assert.equal(fs.existsSync(lockPath), false);
|
||||
} finally {
|
||||
if (owner.exitCode === null && owner.signalCode === null) owner.kill();
|
||||
}
|
||||
});
|
||||
|
||||
test('exclusive staging lock recovers a lock owned by a dead process', async () => {
|
||||
const lockPath = temporaryLockPath();
|
||||
fs.writeFileSync(lockPath, JSON.stringify({
|
||||
pid: 999_999_999,
|
||||
token: 'dead-owner',
|
||||
}));
|
||||
|
||||
const release = await acquireExclusiveFileLock(lockPath, { timeoutMs: 1_000 });
|
||||
const owner = JSON.parse(fs.readFileSync(lockPath, 'utf8'));
|
||||
assert.equal(owner.pid, process.pid);
|
||||
assert.notEqual(owner.token, 'dead-owner');
|
||||
release();
|
||||
assert.equal(fs.existsSync(lockPath), false);
|
||||
});
|
||||
|
||||
test('staging lock release fails closed when ownership changes', async () => {
|
||||
const lockPath = temporaryLockPath();
|
||||
const release = await acquireExclusiveFileLock(lockPath, { timeoutMs: 1_000 });
|
||||
fs.writeFileSync(lockPath, JSON.stringify({ pid: process.pid, token: 'other-owner' }));
|
||||
|
||||
assert.throws(() => release(), /ownership changed/);
|
||||
fs.unlinkSync(lockPath);
|
||||
});
|
||||
|
||||
test('inherited staging lock validation rejects a released lease', async () => {
|
||||
const lockPath = temporaryLockPath();
|
||||
const release = await acquireExclusiveFileLock(lockPath, { timeoutMs: 1_000 });
|
||||
const owner = readExclusiveFileLockOwner(lockPath);
|
||||
assert.doesNotThrow(() => assertExclusiveFileLockHeld(lockPath, owner));
|
||||
release();
|
||||
|
||||
assert.throws(() => assertExclusiveFileLockHeld(lockPath, owner), /owner record is unavailable/);
|
||||
});
|
||||
+43
-20
@@ -4,6 +4,10 @@ import os from 'node:os';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { collectRegularFiles, sha256, treeDigest } from './engine-payload-integrity.js';
|
||||
import {
|
||||
acquireExclusiveFileLock,
|
||||
assertExclusiveFileLockHeld,
|
||||
} from './engine-staging-lock.js';
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
const repoRoot = path.resolve(__dirname, '..');
|
||||
@@ -104,29 +108,48 @@ if (targetLock) {
|
||||
}
|
||||
|
||||
const destination = path.join(outputRoot, target);
|
||||
fs.rmSync(outputRoot, { recursive: true, force: true });
|
||||
fs.mkdirSync(destination, { recursive: true });
|
||||
|
||||
for (const name of expectedNames) {
|
||||
fs.copyFileSync(path.join(source, name), path.join(destination, name));
|
||||
if (!isWindowsTarget) {
|
||||
fs.chmodSync(path.join(destination, name), 0o755);
|
||||
}
|
||||
const stagingLockPath = `${outputRoot}.lock`;
|
||||
const inheritedLockPid = process.env.FIRELINK_ENGINE_STAGING_LOCK_PID;
|
||||
const inheritedLockToken = process.env.FIRELINK_ENGINE_STAGING_LOCK_TOKEN;
|
||||
const inheritedStagingLock = inheritedLockPid !== undefined || inheritedLockToken !== undefined;
|
||||
if (inheritedStagingLock) {
|
||||
assertExclusiveFileLockHeld(stagingLockPath, {
|
||||
pid: Number(inheritedLockPid),
|
||||
token: inheritedLockToken,
|
||||
});
|
||||
}
|
||||
const releaseStageLock = inheritedStagingLock
|
||||
? null
|
||||
: await acquireExclusiveFileLock(stagingLockPath);
|
||||
try {
|
||||
// Tauri packages the shared engine-dist root. Keep exactly one target in it;
|
||||
// the lock serializes this replacement when local stage commands overlap.
|
||||
fs.rmSync(outputRoot, { recursive: true, force: true });
|
||||
fs.mkdirSync(destination, { recursive: true });
|
||||
|
||||
for (const runtimeDir of ['_internal', 'aria2-libs']) {
|
||||
const sourceDir = path.join(source, runtimeDir);
|
||||
if (fs.existsSync(sourceDir)) {
|
||||
fs.cpSync(sourceDir, path.join(destination, runtimeDir), {
|
||||
recursive: true,
|
||||
dereference: false,
|
||||
preserveTimestamps: true,
|
||||
});
|
||||
for (const name of expectedNames) {
|
||||
fs.copyFileSync(path.join(source, name), path.join(destination, name));
|
||||
if (!isWindowsTarget) {
|
||||
fs.chmodSync(path.join(destination, name), 0o755);
|
||||
}
|
||||
}
|
||||
}
|
||||
const payloadManifest = path.join(source, 'payload-manifest.json');
|
||||
if (fs.existsSync(payloadManifest)) {
|
||||
fs.copyFileSync(payloadManifest, path.join(destination, 'payload-manifest.json'));
|
||||
|
||||
for (const runtimeDir of ['_internal', 'aria2-libs']) {
|
||||
const sourceDir = path.join(source, runtimeDir);
|
||||
if (fs.existsSync(sourceDir)) {
|
||||
fs.cpSync(sourceDir, path.join(destination, runtimeDir), {
|
||||
recursive: true,
|
||||
dereference: false,
|
||||
preserveTimestamps: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
const payloadManifest = path.join(source, 'payload-manifest.json');
|
||||
if (fs.existsSync(payloadManifest)) {
|
||||
fs.copyFileSync(payloadManifest, path.join(destination, 'payload-manifest.json'));
|
||||
}
|
||||
} finally {
|
||||
releaseStageLock?.();
|
||||
}
|
||||
|
||||
console.log(`Staged Firelink engines for ${target} from ${source}`);
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
#!/usr/bin/env node
|
||||
import path from 'node:path';
|
||||
import { spawn } from 'node:child_process';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import {
|
||||
acquireExclusiveFileLock,
|
||||
readExclusiveFileLockOwner,
|
||||
} from './engine-staging-lock.js';
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
const repoRoot = path.resolve(__dirname, '..');
|
||||
const tauriCli = path.join(repoRoot, 'node_modules', '@tauri-apps', 'cli', 'tauri.js');
|
||||
const stagingLockPath = path.join(repoRoot, 'src-tauri', 'engine-dist.lock');
|
||||
const ENGINE_TREE_COMMANDS = new Set(['dev', 'build', 'bundle']);
|
||||
|
||||
export function commandUsesEngineTree(args) {
|
||||
return args.some(argument => ENGINE_TREE_COMMANDS.has(argument));
|
||||
}
|
||||
|
||||
function signalExitCode(signal) {
|
||||
return {
|
||||
SIGHUP: 129,
|
||||
SIGINT: 130,
|
||||
SIGTERM: 143,
|
||||
}[signal] ?? 1;
|
||||
}
|
||||
|
||||
const args = process.argv.slice(2);
|
||||
const holdsEngineLock = commandUsesEngineTree(args);
|
||||
let releaseEngineLock;
|
||||
let child;
|
||||
|
||||
function handleSignal(signal) {
|
||||
if (child && child.exitCode === null && child.signalCode === null) {
|
||||
try {
|
||||
child.kill(signal);
|
||||
} catch (error) {
|
||||
if (error?.code !== 'ESRCH') {
|
||||
console.error(`[WARN] Could not terminate Tauri: ${error.message}`);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// No child has started yet, so there is no packaging work to preserve. If
|
||||
// lock acquisition was interrupted after ownership was granted, release it
|
||||
// before exiting; otherwise exit promptly instead of waiting out the lock.
|
||||
try {
|
||||
releaseEngineLock?.();
|
||||
} catch (error) {
|
||||
console.error(`[WARN] Could not release the engine staging lock: ${error.message}`);
|
||||
}
|
||||
process.exit(signalExitCode(signal));
|
||||
}
|
||||
|
||||
async function run() {
|
||||
const handlers = new Map(['SIGHUP', 'SIGINT', 'SIGTERM'].map(signal => [
|
||||
signal,
|
||||
() => handleSignal(signal),
|
||||
]));
|
||||
for (const [signal, handler] of handlers) process.once(signal, handler);
|
||||
|
||||
try {
|
||||
const env = { ...process.env };
|
||||
if (holdsEngineLock) {
|
||||
releaseEngineLock = await acquireExclusiveFileLock(stagingLockPath);
|
||||
const owner = readExclusiveFileLockOwner(stagingLockPath);
|
||||
// beforeBuildCommand and beforeDevCommand run as child processes. The
|
||||
// wrapper keeps the lock until Tauri has finished consuming resources;
|
||||
// nested staging/verifying must therefore use this held lease instead
|
||||
// of trying to acquire the same path again.
|
||||
env.FIRELINK_ENGINE_STAGING_LOCK_PID = String(owner.pid);
|
||||
env.FIRELINK_ENGINE_STAGING_LOCK_TOKEN = owner.token;
|
||||
}
|
||||
|
||||
child = spawn(process.execPath, [tauriCli, ...args], {
|
||||
cwd: repoRoot,
|
||||
env,
|
||||
stdio: 'inherit',
|
||||
windowsHide: true,
|
||||
});
|
||||
|
||||
const result = await new Promise((resolve, reject) => {
|
||||
child.once('error', reject);
|
||||
child.once('close', (code, signal) => resolve({ code, signal }));
|
||||
});
|
||||
|
||||
if (result.signal) {
|
||||
process.exitCode = signalExitCode(result.signal);
|
||||
} else {
|
||||
process.exitCode = result.code ?? 1;
|
||||
}
|
||||
} finally {
|
||||
for (const [signal, handler] of handlers) process.removeListener(signal, handler);
|
||||
releaseEngineLock?.();
|
||||
}
|
||||
}
|
||||
|
||||
const isMain = process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url);
|
||||
|
||||
if (isMain) {
|
||||
run().catch(error => {
|
||||
console.error(`[FAIL] Tauri command failed: ${error.message}`);
|
||||
process.exitCode = 1;
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import { commandUsesEngineTree } from './tauri-command.js';
|
||||
|
||||
test('Tauri engine-consuming commands hold the shared staging lease', () => {
|
||||
assert.equal(commandUsesEngineTree(['dev']), true);
|
||||
assert.equal(commandUsesEngineTree(['build', '--target', 'x86_64-unknown-linux-gnu']), true);
|
||||
assert.equal(commandUsesEngineTree(['bundle', '--bundles', 'appimage']), true);
|
||||
assert.equal(commandUsesEngineTree(['info']), false);
|
||||
assert.equal(commandUsesEngineTree(['--help']), false);
|
||||
});
|
||||
@@ -5,6 +5,10 @@ import os from 'node:os';
|
||||
import net from 'node:net';
|
||||
import { execFileSync, spawn } from 'node:child_process';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import {
|
||||
acquireExclusiveFileLock,
|
||||
assertExclusiveFileLockHeld,
|
||||
} from './engine-staging-lock.js';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
@@ -89,6 +93,20 @@ const binariesDir = configuredRoot
|
||||
? path.resolve(configuredRoot)
|
||||
: path.join(scriptsDir, '..', 'src-tauri', 'binaries');
|
||||
const requiredEngines = ['yt-dlp', 'aria2c', 'ffmpeg', 'deno'];
|
||||
const stagedVerification = process.argv.includes('--staged');
|
||||
const stagingLockPath = path.join(scriptsDir, '..', 'src-tauri', 'engine-dist.lock');
|
||||
const inheritedLockPid = process.env.FIRELINK_ENGINE_STAGING_LOCK_PID;
|
||||
const inheritedLockToken = process.env.FIRELINK_ENGINE_STAGING_LOCK_TOKEN;
|
||||
const inheritedStagingLock = inheritedLockPid !== undefined || inheritedLockToken !== undefined;
|
||||
if (stagedVerification && inheritedStagingLock) {
|
||||
assertExclusiveFileLockHeld(stagingLockPath, {
|
||||
pid: Number(inheritedLockPid),
|
||||
token: inheritedLockToken,
|
||||
});
|
||||
}
|
||||
const releaseStagingLock = stagedVerification && !inheritedStagingLock
|
||||
? await acquireExclusiveFileLock(stagingLockPath)
|
||||
: null;
|
||||
|
||||
const FORBIDDEN_OTOOL_PATHS = ['/opt/homebrew', '/usr/local/Cellar'];
|
||||
const FORBIDDEN_STDERR = [
|
||||
@@ -171,6 +189,7 @@ for (const eng of requiredEngines) {
|
||||
|
||||
if (exitCode !== 0) {
|
||||
console.error('\nAborting: missing required sidecars.');
|
||||
releaseStagingLock?.();
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
@@ -583,6 +602,7 @@ if (canExecuteTarget) {
|
||||
|
||||
// ───── Result ─────
|
||||
console.log('');
|
||||
releaseStagingLock?.();
|
||||
if (exitCode !== 0) {
|
||||
console.error(`[FAIL] ${exitCode} engine verification check(s) failed.`);
|
||||
process.exit(1);
|
||||
|
||||
Generated
+75
-62
@@ -10,13 +10,13 @@ checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
|
||||
|
||||
[[package]]
|
||||
name = "aes"
|
||||
version = "0.8.4"
|
||||
version = "0.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0"
|
||||
checksum = "35f0f96ce78e38c3dc6d8948aa8163d06385be74000f3c7a95bf1eef35d3ea32"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cipher",
|
||||
"cpufeatures 0.2.17",
|
||||
"cpubits",
|
||||
"cpufeatures 0.3.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -396,11 +396,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "block-padding"
|
||||
version = "0.3.3"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93"
|
||||
checksum = "710f1dd022ef4e93f8a438b4ba958de7f64308434fa6a87104481645cc30068b"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
"hybrid-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -557,9 +557,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cbc"
|
||||
version = "0.1.2"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6"
|
||||
checksum = "ce2dc9ee5f88d11e0beb842c88b33c8a5cf0d1329c4b19494af42b07dbfe8896"
|
||||
dependencies = [
|
||||
"cipher",
|
||||
]
|
||||
@@ -609,12 +609,12 @@ checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
||||
|
||||
[[package]]
|
||||
name = "chacha20"
|
||||
version = "0.10.1"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d524456ba66e72eb8b115ff89e01e497f8e6d11d78b70b1aa13c0fbd97540a81"
|
||||
checksum = "65c35e4b699c7e15ccbe7ee35c005e4fc0a278d22238a2857e6ce2dadeda1b06"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures 0.3.0",
|
||||
"cpufeatures 0.3.1",
|
||||
"rand_core 0.10.1",
|
||||
]
|
||||
|
||||
@@ -634,11 +634,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cipher"
|
||||
version = "0.4.4"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
|
||||
checksum = "e8cf2a2c93cd704877c0858356ed03480ff301ee950b43f1cbe4573b088bfa6c"
|
||||
dependencies = [
|
||||
"crypto-common 0.1.7",
|
||||
"crypto-common 0.2.2",
|
||||
"inout",
|
||||
]
|
||||
|
||||
@@ -752,6 +752,12 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cpubits"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "15b85f9c39137c3a891689859392b1bd49812121d0d61c9caf00d46ed5ce06ae"
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.2.17"
|
||||
@@ -763,9 +769,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.3.0"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201"
|
||||
checksum = "5ca28b0ae3115b884660db4118d803791fd6756b6e88f39c0f3f7859060d7566"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
@@ -1053,7 +1059,6 @@ checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
|
||||
dependencies = [
|
||||
"block-buffer 0.10.4",
|
||||
"crypto-common 0.1.7",
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1403,7 +1408,7 @@ dependencies = [
|
||||
"base64 0.22.1",
|
||||
"chrono",
|
||||
"futures-util",
|
||||
"hmac 0.13.0",
|
||||
"hmac",
|
||||
"keepawake",
|
||||
"keyring-core",
|
||||
"log 0.4.34",
|
||||
@@ -1453,12 +1458,13 @@ checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99"
|
||||
|
||||
[[package]]
|
||||
name = "flate2"
|
||||
version = "1.1.9"
|
||||
version = "1.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c"
|
||||
checksum = "6e634e2e0ebac1ee034020da1ca582e17ffe4e0f5e985823721e168928136dcb"
|
||||
dependencies = [
|
||||
"crc32fast",
|
||||
"miniz_oxide",
|
||||
"miniz_oxide 0.9.1",
|
||||
"zlib-rs",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2001,20 +2007,11 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
||||
|
||||
[[package]]
|
||||
name = "hkdf"
|
||||
version = "0.12.4"
|
||||
version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7"
|
||||
checksum = "4aaa26c720c68b866f2c96ef5c1264b3e6f473fe5d4ce61cd44bbe913e553018"
|
||||
dependencies = [
|
||||
"hmac 0.12.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hmac"
|
||||
version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
|
||||
dependencies = [
|
||||
"digest 0.10.7",
|
||||
"hmac",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2092,9 +2089,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "hyper"
|
||||
version = "1.11.0"
|
||||
version = "1.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d22053281f852e11534f5198498373cbb59295120a20771d90f7ed1897490a72"
|
||||
checksum = "27b501faa50e7a26c3d3560ca625132f4078a17771f4810baf70475ae48cbe43"
|
||||
dependencies = [
|
||||
"atomic-waker",
|
||||
"bytes",
|
||||
@@ -2320,9 +2317,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.14.0"
|
||||
version = "2.14.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"
|
||||
checksum = "07aa2048142242915a31d35844fb311e0e53fcca590c3a0a40dcf1b841fa09eb"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown 0.17.1",
|
||||
@@ -2341,12 +2338,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "inout"
|
||||
version = "0.1.4"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01"
|
||||
checksum = "4250ce6452e92010fdf7268ccc5d14faa80bb12fc741938534c58f16804e03c7"
|
||||
dependencies = [
|
||||
"block-padding",
|
||||
"generic-array",
|
||||
"hybrid-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2677,9 +2674,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "libredox"
|
||||
version = "0.1.20"
|
||||
version = "0.1.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "28d0a00925a9f930d679b6789b721e3a7f9ed110f41b86d2497caa780c3a070a"
|
||||
checksum = "d7955dfc218a8afb29dfeffd540e3a6e96baeb94fe7138228dd7cc6937fbbf96"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
@@ -2811,6 +2808,16 @@ dependencies = [
|
||||
"simd-adler32",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b63fbc4a50860e98e7b2aa7804ded1db5cbc3aff9193adaff57a6931bf7c4b4c"
|
||||
dependencies = [
|
||||
"adler2",
|
||||
"simd-adler32",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mio"
|
||||
version = "1.2.2"
|
||||
@@ -3403,7 +3410,7 @@ checksum = "8701b58ea97060d5e5b155d383a69952a60943f0e6dfe30b04c287beb0b27455"
|
||||
dependencies = [
|
||||
"fixedbitset",
|
||||
"hashbrown 0.15.5",
|
||||
"indexmap 2.14.0",
|
||||
"indexmap 2.14.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3489,7 +3496,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7da1d65da6dd5d1e44199ac0f58712d241c0f439f80adea8924d832384087f85"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"indexmap 2.14.0",
|
||||
"indexmap 2.14.1",
|
||||
"quick-xml",
|
||||
"serde",
|
||||
"time",
|
||||
@@ -3505,7 +3512,7 @@ dependencies = [
|
||||
"crc32fast",
|
||||
"fdeflate",
|
||||
"flate2",
|
||||
"miniz_oxide",
|
||||
"miniz_oxide 0.8.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3518,7 +3525,7 @@ dependencies = [
|
||||
"crc32fast",
|
||||
"fdeflate",
|
||||
"flate2",
|
||||
"miniz_oxide",
|
||||
"miniz_oxide 0.8.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4135,20 +4142,20 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
||||
|
||||
[[package]]
|
||||
name = "secret-service"
|
||||
version = "5.1.0"
|
||||
version = "5.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a62d7f86047af0077255a29494136b9aaaf697c76ff70b8e49cded4e2623c14"
|
||||
checksum = "5107b24b91445dd2aa449a258a1807b63240942157292354dc5bfdbeb8bc6db8"
|
||||
dependencies = [
|
||||
"aes",
|
||||
"cbc",
|
||||
"futures-util",
|
||||
"generic-array",
|
||||
"getrandom 0.2.17",
|
||||
"getrandom 0.4.3",
|
||||
"hkdf",
|
||||
"hybrid-array",
|
||||
"num",
|
||||
"once_cell",
|
||||
"serde",
|
||||
"sha2 0.10.9",
|
||||
"sha2 0.11.0",
|
||||
"zbus",
|
||||
]
|
||||
|
||||
@@ -4333,7 +4340,7 @@ dependencies = [
|
||||
"chrono",
|
||||
"hex",
|
||||
"indexmap 1.9.3",
|
||||
"indexmap 2.14.0",
|
||||
"indexmap 2.14.1",
|
||||
"jiff",
|
||||
"schemars 0.9.0",
|
||||
"schemars 1.2.2",
|
||||
@@ -4404,7 +4411,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aacc4cc499359472b4abe1bf11d0b12e688af9a805fa5e3016f9a386dc2d0214"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures 0.3.0",
|
||||
"cpufeatures 0.3.1",
|
||||
"digest 0.11.3",
|
||||
]
|
||||
|
||||
@@ -4426,7 +4433,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures 0.3.0",
|
||||
"cpufeatures 0.3.1",
|
||||
"digest 0.11.3",
|
||||
]
|
||||
|
||||
@@ -5459,7 +5466,7 @@ version = "0.9.12+spec-1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863"
|
||||
dependencies = [
|
||||
"indexmap 2.14.0",
|
||||
"indexmap 2.14.1",
|
||||
"serde_core",
|
||||
"serde_spanned 1.1.1",
|
||||
"toml_datetime 0.7.5+spec-1.1.0",
|
||||
@@ -5474,7 +5481,7 @@ version = "1.1.4+spec-1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3aace63f4bbcdfc2c965b059de67119c89c4017a70d633be6c104910f67056f5"
|
||||
dependencies = [
|
||||
"indexmap 2.14.0",
|
||||
"indexmap 2.14.1",
|
||||
"serde_core",
|
||||
"serde_spanned 1.1.1",
|
||||
"toml_datetime 1.1.1+spec-1.1.0",
|
||||
@@ -5516,7 +5523,7 @@ version = "0.19.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421"
|
||||
dependencies = [
|
||||
"indexmap 2.14.0",
|
||||
"indexmap 2.14.1",
|
||||
"toml_datetime 0.6.3",
|
||||
"winnow 0.5.40",
|
||||
]
|
||||
@@ -5527,7 +5534,7 @@ version = "0.20.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338"
|
||||
dependencies = [
|
||||
"indexmap 2.14.0",
|
||||
"indexmap 2.14.1",
|
||||
"serde",
|
||||
"serde_spanned 0.6.9",
|
||||
"toml_datetime 0.6.3",
|
||||
@@ -5540,7 +5547,7 @@ version = "0.25.13+spec-1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6975367e4d2ef766d86af01ffad14b622fecc8d4357a998fbc4deb6e9bacaf9b"
|
||||
dependencies = [
|
||||
"indexmap 2.14.0",
|
||||
"indexmap 2.14.1",
|
||||
"toml_datetime 1.1.1+spec-1.1.0",
|
||||
"toml_parser",
|
||||
"winnow 1.0.4",
|
||||
@@ -5894,9 +5901,9 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "1.25.0"
|
||||
version = "1.26.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f053576934f05a761a402421fbbe3d425d9366f75f978806a037b3ca481abecc"
|
||||
checksum = "b5772d71c9be8a8a6ac2117d949c5b224c1b72241bb611d9a3012edcf8af7812"
|
||||
dependencies = [
|
||||
"getrandom 0.4.3",
|
||||
"js-sys",
|
||||
@@ -7132,6 +7139,12 @@ dependencies = [
|
||||
"syn 3.0.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zlib-rs"
|
||||
version = "0.6.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34b31d188d9d685a4f9c7b46d6e36631b07058d2cfe190267adce54dc230bf12"
|
||||
|
||||
[[package]]
|
||||
name = "zmij"
|
||||
version = "1.0.23"
|
||||
|
||||
Binary file not shown.
Reference in New Issue
Block a user