feat: release toolbox portal 0.1.0
This commit is contained in:
77
scripts/archive.mjs
Normal file
77
scripts/archive.mjs
Normal file
@@ -0,0 +1,77 @@
|
||||
import { createReadStream, createWriteStream } from 'node:fs';
|
||||
import { mkdir, readdir, stat } from 'node:fs/promises';
|
||||
import path from 'node:path';
|
||||
import { pipeline } from 'node:stream/promises';
|
||||
import archiver from 'archiver';
|
||||
import {
|
||||
canonicalExistingPath,
|
||||
canonicalPathsOverlap,
|
||||
canonicalProspectivePath,
|
||||
} from './path-safety.mjs';
|
||||
|
||||
const ARCHIVE_DATE = new Date('1980-01-01T00:00:00.000Z');
|
||||
|
||||
function compareCodePoints(left, right) {
|
||||
const leftPoints = Array.from(left, (value) => value.codePointAt(0));
|
||||
const rightPoints = Array.from(right, (value) => value.codePointAt(0));
|
||||
const length = Math.min(leftPoints.length, rightPoints.length);
|
||||
for (let index = 0; index < length; index += 1) {
|
||||
if (leftPoints[index] !== rightPoints[index])
|
||||
return leftPoints[index] - rightPoints[index];
|
||||
}
|
||||
return leftPoints.length - rightPoints.length;
|
||||
}
|
||||
|
||||
async function listFiles(root, relative = '') {
|
||||
const directory = path.join(root, relative);
|
||||
const entries = await readdir(directory, { withFileTypes: true });
|
||||
const files = [];
|
||||
for (const entry of entries.sort((left, right) =>
|
||||
compareCodePoints(left.name, right.name)
|
||||
)) {
|
||||
const child = path.posix.join(
|
||||
relative.split(path.sep).join('/'),
|
||||
entry.name
|
||||
);
|
||||
if (entry.isDirectory()) files.push(...(await listFiles(root, child)));
|
||||
else if (entry.isFile()) files.push(child);
|
||||
else
|
||||
throw new Error(
|
||||
`Archive input contains an unsupported file type: ${child}`
|
||||
);
|
||||
}
|
||||
return files;
|
||||
}
|
||||
|
||||
export async function createStaticArchive(inputDirectory, outputFile) {
|
||||
const source = await canonicalExistingPath(
|
||||
inputDirectory,
|
||||
'Archive input directory'
|
||||
);
|
||||
const output = await canonicalProspectivePath(outputFile);
|
||||
if (canonicalPathsOverlap(source, output))
|
||||
throw new Error('Archive output and input directory must not overlap.');
|
||||
const sourceStat = await stat(source);
|
||||
if (!sourceStat.isDirectory())
|
||||
throw new Error(`Archive input is not a directory: ${source}`);
|
||||
await mkdir(path.dirname(output), { recursive: true });
|
||||
|
||||
const archive = archiver('zip', { zlib: { level: 9 } });
|
||||
const destination = createWriteStream(output, { flags: 'wx' });
|
||||
archive.on('warning', (error) => {
|
||||
if (error.code !== 'ENOENT') destination.destroy(error);
|
||||
});
|
||||
archive.on('error', (error) => destination.destroy(error));
|
||||
|
||||
const writing = pipeline(archive, destination);
|
||||
for (const relative of await listFiles(source)) {
|
||||
archive.append(createReadStream(path.join(source, relative)), {
|
||||
name: relative,
|
||||
date: ARCHIVE_DATE,
|
||||
mode: 0o644,
|
||||
});
|
||||
}
|
||||
await archive.finalize();
|
||||
await writing;
|
||||
return output;
|
||||
}
|
||||
Reference in New Issue
Block a user