feat: publish av-tools 0.1.0

This commit is contained in:
2026-07-24 14:58:03 +02:00
commit fcc537b024
254 changed files with 63270 additions and 0 deletions

View File

@@ -0,0 +1,150 @@
import { createHash } from 'node:crypto';
import { access, lstat, readFile, writeFile } from 'node:fs/promises';
import path from 'node:path';
import { fileURLToPath, pathToFileURL } from 'node:url';
const repositoryRoot = path.resolve(
path.dirname(fileURLToPath(import.meta.url)),
'..'
);
export function sha256(data) {
return createHash('sha256').update(data).digest('hex');
}
export async function sha256File(file) {
return sha256(await readFile(file));
}
async function requireRegularFile(file, label) {
const details = await lstat(file);
if (details.isSymbolicLink() || !details.isFile()) {
throw new Error(`${label} must be a regular file: ${file}`);
}
}
export function checksumLine(digest, archiveName) {
if (!/^[a-f0-9]{64}$/.test(digest)) {
throw new Error('SHA-256 digest must be 64 lowercase hexadecimal digits.');
}
if (
typeof archiveName !== 'string' ||
archiveName.length === 0 ||
archiveName.includes('/') ||
archiveName.includes('\\') ||
archiveName.includes('\n') ||
archiveName.includes('\r')
) {
throw new Error('Checksum archive name must be one safe basename.');
}
return `${digest} ${archiveName}\n`;
}
export function parseChecksumLine(contents) {
const match = /^([a-f0-9]{64}) ([^/\\\r\n]+)\n?$/.exec(contents);
if (!match) {
throw new Error(
'Checksum sidecar must contain `<lowercase-sha256> <archive-basename>`.'
);
}
return { digest: match[1], archiveName: match[2] };
}
export async function verifyChecksum(archivePath, sidecarPath) {
await requireRegularFile(archivePath, 'Release archive');
await requireRegularFile(sidecarPath, 'Checksum sidecar');
const expected = parseChecksumLine(await readFile(sidecarPath, 'utf8'));
if (expected.archiveName !== path.basename(archivePath)) {
throw new Error(
`Checksum names ${expected.archiveName}, not ${path.basename(archivePath)}.`
);
}
const actual = await sha256File(archivePath);
if (actual !== expected.digest) {
throw new Error(
`Checksum mismatch for ${path.basename(archivePath)}: expected ${expected.digest}, received ${actual}.`
);
}
return actual;
}
export async function writeChecksum(archivePath, options = {}) {
const sidecarPath = options.sidecarPath ?? `${archivePath}.sha256`;
await requireRegularFile(archivePath, 'Release archive');
if (options.force && (await exists(sidecarPath))) {
await requireRegularFile(sidecarPath, 'Existing checksum sidecar');
}
const digest = await sha256File(archivePath);
await writeFile(
sidecarPath,
checksumLine(digest, path.basename(archivePath)),
{
encoding: 'utf8',
flag: options.force ? 'w' : 'wx',
}
);
return { digest, sidecarPath };
}
async function exists(file) {
try {
await access(file);
return true;
} catch (error) {
if (error && typeof error === 'object' && error.code === 'ENOENT') {
return false;
}
throw error;
}
}
async function main() {
const packageJson = JSON.parse(
await readFile(path.join(repositoryRoot, 'package.json'), 'utf8')
);
const defaultArchive = path.join(
repositoryRoot,
'release',
`av-tools-${packageJson.version}.zip`
);
const argumentsList = process.argv.slice(2);
const write = argumentsList.includes('--write');
const force = argumentsList.includes('--force');
const positional = argumentsList.filter(
(argument) => argument !== '--write' && argument !== '--force'
);
if (positional.length > 1) {
throw new Error(
'Usage: node scripts/checksum-release.mjs [archive.zip] [--write] [--force]'
);
}
const archivePath = path.resolve(
repositoryRoot,
positional[0] ?? defaultArchive
);
const sidecarPath = `${archivePath}.sha256`;
if (write) {
const result = await writeChecksum(archivePath, { force, sidecarPath });
console.log(
`Wrote ${path.relative(repositoryRoot, result.sidecarPath)} (${result.digest})`
);
return;
}
if (!(await exists(sidecarPath))) {
throw new Error(
`${path.relative(repositoryRoot, sidecarPath)} is missing; pass --write to create it.`
);
}
const digest = await verifyChecksum(archivePath, sidecarPath);
console.log(
`Verified ${path.relative(repositoryRoot, archivePath)} (${digest})`
);
}
if (
process.argv[1] &&
import.meta.url === pathToFileURL(path.resolve(process.argv[1])).href
) {
await main();
}