Files
eepp/projects/scripts/upload_split_artifact.cjs
T
2026-10-02 01:16:05 -03:00

108 lines
2.9 KiB
JavaScript
Executable File

#!/usr/bin/env node
'use strict';
const fs = require('fs');
const path = require('path');
const { pipeline } = require('stream/promises');
const { DefaultArtifactClient } = require('@actions/artifact');
function parseSize(value) {
const match = /^(\d+)([KMG]?)$/i.exec(value);
if (!match)
throw new Error(`Invalid size '${value}' (expected e.g. 400M)`);
const multipliers = {
'': 1,
K: 1024,
M: 1024 * 1024,
G: 1024 * 1024 * 1024,
};
return Number(match[1]) * multipliers[match[2].toUpperCase()];
}
async function writeChunk(source, destination, start, end) {
await pipeline(
fs.createReadStream(source, { start, end }),
fs.createWriteStream(destination)
);
}
async function main() {
const [
archiveArg,
artifactPrefix = 'eepp-dev-linux-x86_64',
chunkSizeArg = '400M',
retentionDaysArg = '5',
maxPartsArg = '8',
] = process.argv.slice(2);
if (!archiveArg) {
console.error(
'Usage: upload_split_artifact.cjs <archive> [artifact-prefix] [chunk-size] [retention-days] [max-parts]'
);
process.exit(2);
}
const archive = path.resolve(archiveArg);
const chunkSize = parseSize(chunkSizeArg);
const retentionDays = Number(retentionDaysArg);
const maxParts = Number(maxPartsArg);
const stat = await fs.promises.stat(archive);
if (!stat.isFile())
throw new Error(`Not a regular file: ${archive}`);
if (!Number.isInteger(retentionDays) || retentionDays < 1)
throw new Error(`Invalid retention days: ${retentionDaysArg}`);
if (!Number.isInteger(maxParts) || maxParts < 1)
throw new Error(`Invalid max parts: ${maxPartsArg}`);
const partCount = Math.ceil(stat.size / chunkSize);
if (partCount > maxParts) {
throw new Error(
`Archive requires ${partCount} artifacts at ${chunkSizeArg} per part, ` +
`but the configured maximum is ${maxParts}. Increase the chunk size or artifact budget.`
);
}
console.log(
`Uploading ${archive} (${stat.size} bytes) as ${partCount} artifact part(s) of at most ${chunkSizeArg}`
);
const artifact = new DefaultArtifactClient();
const suffixWidth = Math.max(2, String(partCount - 1).length);
for (let index = 0; index < partCount; ++index) {
const suffix = String(index).padStart(suffixWidth, '0');
const partPath = `${archive}.part-${suffix}`;
const start = index * chunkSize;
const end = Math.min(stat.size, start + chunkSize) - 1;
const artifactName = `${artifactPrefix}-part-${suffix}`;
console.log(
`Creating ${path.basename(partPath)} (${start}-${end}, ${end - start + 1} bytes)`
);
await writeChunk(archive, partPath, start, end);
try {
const { id, size } = await artifact.uploadArtifact(
artifactName,
[partPath],
path.dirname(partPath),
{
retentionDays,
compressionLevel: 0,
}
);
console.log(`Uploaded ${artifactName}: id=${id}, size=${size}`);
} finally {
await fs.promises.rm(partPath, { force: true });
}
}
}
main().catch((error) => {
console.error(error.stack || error.message || error);
process.exit(1);
});