chore: 更新学生档案数据

This commit is contained in:
chengzi
2026-07-05 20:33:53 +08:00
parent b0574cc700
commit c49ace5ca1
2698 changed files with 489801 additions and 61355 deletions

175
node_modules/modern-tar/dist/fs/index.d.ts generated vendored Normal file
View File

@@ -0,0 +1,175 @@
import { i as UnpackOptions, n as TarEntryData, r as TarHeader } from "../types-D19dF2SE.js";
import { Readable, Writable } from "node:stream";
import { Stats } from "node:fs";
//#region src/fs/types.d.ts
/**
* Filesystem-specific configuration options for packing directories into tar archives.
*
* These options are specific to Node.js filesystem operations and use Node.js-specific
* types like `Stats` for file system metadata.
*/
interface PackOptionsFS {
/** Follow symlinks instead of storing them as symlinks (default: false) */
dereference?: boolean;
/** Filter function to include/exclude files (return false to exclude) */
filter?: (path: string, stat: Stats) => boolean;
/** Transform function to modify tar headers before packing */
map?: (header: TarHeader) => TarHeader;
/** Base directory for symlink security validation, when `dereference` is set to true. */
baseDir?: string;
/**
* Maximum number of concurrent filesystem operations during packing.
* @default os.cpus().length || 8
*/
concurrency?: number;
}
/**
* Filesystem-specific configuration options for extracting tar archives to the filesystem.
*
* Extends the core {@link UnpackOptions} with Node.js filesystem-specific settings
* for controlling file permissions and other filesystem behaviors.
*/
interface UnpackOptionsFS extends UnpackOptions {
/** Default mode for created directories (e.g., 0o755). If not specified, uses mode from tar header or system default */
dmode?: number;
/** Default mode for created files (e.g., 0o644). If not specified, uses mode from tar header or system default */
fmode?: number;
/**
* The maximum depth of paths to extract. Prevents Denial of Service (DoS) attacks
* from malicious archives with deeply nested directories.
*
* Set to `Infinity` to disable depth checking (not recommended for untrusted archives).
* @default 1024
*/
maxDepth?: number;
/**
* Maximum number of concurrent filesystem operations during extraction.
* @default os.cpus().length || 8
*/
concurrency?: number;
}
/** Base interface containing common metadata properties for all source types. */
interface BaseSource {
/** Destination path for the entry inside the tar archive. */
target: string;
/** Optional modification time. Overrides filesystem values or defaults to current time. */
mtime?: Date;
/** Optional user ID. Overrides filesystem values or defaults to 0. */
uid?: number;
/** Optional group ID. Overrides filesystem values or defaults to 0. */
gid?: number;
/** Optional user name. */
uname?: string;
/** Optional group name. */
gname?: string;
/** Optional Unix file permissions for the entry (e.g., 0o644, 0o755). */
mode?: number;
}
/** Describes a file on the local filesystem to be added to the archive. */
interface FileSource extends BaseSource {
type: "file";
/** Path to the source file on the local filesystem. */
source: string;
}
/** Describes a directory on the local filesystem to be added to the archive. */
interface DirectorySource extends BaseSource {
type: "directory";
/** Path to the source directory on the local filesystem. */
source: string;
}
/** Describes raw, buffered content to be added to the archive. */
interface ContentSource extends BaseSource {
type: "content";
/** Raw content to add. Supports string, Uint8Array, ArrayBuffer, Blob, or null. */
content: TarEntryData;
}
/** Describes a stream of content to be added to the archive. */
interface StreamSource extends BaseSource {
type: "stream";
/** A Readable or ReadableStream. */
content: Readable | ReadableStream;
/** The total size of the stream's content in bytes. This is required for streams. */
size: number;
}
/** A union of all possible source types for creating a tar archive. */
type TarSource = FileSource | DirectorySource | ContentSource | StreamSource;
//#endregion
//#region src/fs/pack.d.ts
/**
* @deprecated Use `packTar` instead. This function is now an alias for `packTar`.
*/
declare const packTarSources: typeof packTar;
/**
* Pack a directory or multiple sources into a Node.js `Readable` stream containing
* tar archive bytes. Can pack either a single directory or an array of sources
* (files, directories, or raw content).
*
* @param sources - Either a directory path string or an array of {@link TarSource} objects.
* @param options - Optional packing configuration using {@link PackOptionsFS}.
* @returns Node.js [`Readable`](https://nodejs.org/api/stream.html#class-streamreadable) stream of tar archive bytes
*
* @example
* ```typescript
* import { packTar } from 'modern-tar/fs';
* import { createWriteStream } from 'node:fs';
* import { pipeline } from 'node:stream/promises';
*
* // Basic directory packing
* const tarStream = packTar('/home/user/project');
* await pipeline(tarStream, createWriteStream('project.tar'));
*
* // Pack multiple sources
* const sources = [
* { type: 'file', source: './package.json', target: 'project/package.json' },
* { type: 'directory', source: './src', target: 'project/src' },
* { type: 'content', content: 'hello world', target: 'project/hello.txt' }
* ];
* const archiveStream = packTar(sources);
* await pipeline(archiveStream, createWriteStream('project.tar'));
*
* // With filtering and transformation
* const filteredStream = packTar('/my/project', {
* filter: (path, stats) => !path.includes('node_modules'),
* map: (header) => ({ ...header, uname: 'builder' }),
* dereference: true // Follow symlinks
* });
* ```
*/
declare function packTar(sources: TarSource[] | string, options?: PackOptionsFS): Readable;
//#endregion
//#region src/fs/unpack.d.ts
/**
* Extract a tar archive to a directory.
*
* Returns a Node.js [`Writable`](https://nodejs.org/api/stream.html#class-streamwritable)
* stream to pipe tar archive bytes into. Files, directories, symlinks, and hardlinks
* are written to the filesystem with correct permissions and timestamps.
*
* @param directoryPath - Path to directory where files will be extracted
* @param options - Optional extraction configuration
* @returns Node.js [`Writable`](https://nodejs.org/api/stream.html#class-streamwritable) stream to pipe tar archive bytes into
*
* @example
* ```typescript
* import { unpackTar } from 'modern-tar/fs';
* import { createReadStream } from 'node:fs';
* import { pipeline } from 'node:stream/promises';
*
* // Basic extraction
* const tarStream = createReadStream('project.tar');
* const extractStream = unpackTar('/output/directory');
* await pipeline(tarStream, extractStream);
*
* // Extract with path manipulation and filtering
* const advancedStream = unpackTar('/output', {
* strip: 1, // Remove first path component
* filter: (header) => header.type === 'file' && header.name.endsWith('.js'),
* map: (header) => ({ ...header, mode: 0o644 })
* });
* await pipeline(createReadStream('archive.tar'), advancedStream);
* ```
*/
declare function unpackTar(directoryPath: string, options?: UnpackOptionsFS): Writable;
//#endregion
export { type ContentSource, type DirectorySource, type FileSource, type PackOptionsFS, type TarSource, type UnpackOptionsFS, packTar, packTarSources, unpackTar };

746
node_modules/modern-tar/dist/fs/index.js generated vendored Normal file
View File

@@ -0,0 +1,746 @@
import { a as normalizeBody, c as LINK, l as SYMLINK, n as createTarPacker, o as DIRECTORY, r as transformHeader, s as FILE, t as createUnpacker } from "../unpacker-CPCEF5CT.js";
import * as fs$1 from "node:fs/promises";
import { cpus } from "node:os";
import * as path from "node:path";
import { Readable, Writable } from "node:stream";
import * as fs from "node:fs";
//#region src/fs/cache.ts
const createCache = () => {
const m = /* @__PURE__ */ new Map();
return {
get(k) {
const v = m.get(k);
if (m.delete(k)) m.set(k, v);
return v;
},
set(k, v) {
if (m.set(k, v).size > 1e4) m.delete(m.keys().next().value);
}
};
};
//#endregion
//#region src/fs/path.ts
const unicodeCache = createCache();
const normalizeUnicode = (s) => {
for (let i = 0; i < s.length; i++) if (s.charCodeAt(i) >= 128) {
const cached = unicodeCache.get(s);
if (cached !== void 0) return cached;
const normalized = s.normalize("NFD");
unicodeCache.set(s, normalized);
return normalized;
}
return s;
};
function validateBounds(targetPath, destDir, errorMessage) {
const target = normalizeUnicode(path.resolve(targetPath));
const dest = path.resolve(destDir);
if (target !== dest && !target.startsWith(dest + path.sep)) throw new Error(errorMessage);
}
const win32Reserved = {
":": "",
"<": "",
">": "",
"|": "",
"?": "",
"*": "",
"\"": ""
};
function normalizeName(name) {
const path = name.replace(/\\/g, "/");
if (path.split("/").includes("..") || /^[a-zA-Z]:\.\./.test(path)) throw new Error(`${name} points outside extraction directory`);
let relative = path;
if (/^[a-zA-Z]:/.test(relative)) relative = relative.replace(/^[a-zA-Z]:[/\\]?/, "");
else if (relative.startsWith("/")) relative = relative.replace(/^\/+/, "");
if (process.platform === "win32") return relative.replace(/[<>:"|?*]/g, (char) => win32Reserved[char]);
return relative;
}
const normalizeHeaderName = (s) => normalizeUnicode(normalizeName(s.replace(/\/+$/, "")));
//#endregion
//#region src/fs/pack.ts
const packTarSources = packTar;
function packTar(sources, options = {}) {
const stream = new Readable({ read() {} });
(async () => {
const packer = createTarPacker((chunk) => stream.push(Buffer.from(chunk)), stream.destroy.bind(stream), () => stream.push(null));
const { dereference = false, filter, map, baseDir, concurrency = cpus().length || 8 } = options;
const isDir = typeof sources === "string";
const directoryPath = isDir ? path.resolve(sources) : null;
const jobs = isDir ? (await fs$1.readdir(directoryPath, { withFileTypes: true })).map((entry) => ({
type: entry.isDirectory() ? DIRECTORY : FILE,
source: path.join(directoryPath, entry.name),
target: entry.name
})) : sources;
const results = /* @__PURE__ */ new Map();
const resolvers = /* @__PURE__ */ new Map();
const seenInodes = /* @__PURE__ */ new Map();
let jobIndex = 0;
let writeIndex = 0;
let activeWorkers = 0;
let allJobsQueued = false;
const writer = async () => {
const readBufferSmall = Buffer.alloc(64 * 1024);
let readBufferLarge = null;
while (true) {
if (stream.destroyed) return;
if (allJobsQueued && writeIndex >= jobs.length) break;
if (!results.has(writeIndex)) {
await new Promise((resolve) => resolvers.set(writeIndex, resolve));
continue;
}
const result = results.get(writeIndex);
results.delete(writeIndex);
resolvers.delete(writeIndex);
if (!result) {
writeIndex++;
continue;
}
packer.add(result.header);
if (result.body) if (result.body instanceof Uint8Array) {
if (result.body.length > 0) packer.write(result.body);
} else if (result.body instanceof Readable || result.body instanceof ReadableStream) try {
for await (const chunk of result.body) {
if (stream.destroyed) break;
packer.write(chunk instanceof Uint8Array ? chunk : Buffer.from(chunk));
}
} catch (error) {
stream.destroy(error);
return;
}
else {
const { handle, size } = result.body;
const readBuffer = size > 1048576 ? readBufferLarge ??= Buffer.alloc(512 * 1024) : readBufferSmall;
try {
let bytesLeft = size;
while (bytesLeft > 0 && !stream.destroyed) {
const toRead = Math.min(bytesLeft, readBuffer.length);
const { bytesRead } = await handle.read(readBuffer, 0, toRead, null);
if (bytesRead === 0) break;
packer.write(readBuffer.subarray(0, bytesRead));
bytesLeft -= bytesRead;
}
} catch (error) {
stream.destroy(error);
return;
} finally {
await handle.close();
}
}
packer.endEntry();
writeIndex++;
}
};
const controller = () => {
if (stream.destroyed || allJobsQueued) return;
while (activeWorkers < concurrency && jobIndex < jobs.length) {
activeWorkers++;
const currentIndex = jobIndex++;
processJob(jobs[currentIndex], currentIndex).catch(stream.destroy.bind(stream)).finally(() => {
activeWorkers--;
controller();
});
}
if (activeWorkers === 0 && jobIndex >= jobs.length) {
allJobsQueued = true;
resolvers.get(writeIndex)?.();
}
};
const processJob = async (job, index) => {
let jobResult = null;
const target = normalizeName(job.target);
try {
if (job.type === "content" || job.type === "stream") {
let body;
let size;
const isDir = target.endsWith("/");
if (job.type === "stream") {
if (!isDir && job.size <= 0 || isDir && job.size !== 0) throw new Error(isDir ? "Streams for directories must have size 0." : "Streams require a positive size.");
size = job.size;
body = job.content;
} else {
const content = await normalizeBody(job.content);
size = content.length;
body = content;
}
const stat = {
size: isDir ? 0 : size,
isFile: () => !isDir,
isDirectory: () => isDir,
isSymbolicLink: () => false,
mode: job.mode,
mtime: job.mtime ?? /* @__PURE__ */ new Date(),
uid: job.uid ?? 0,
gid: job.gid ?? 0
};
if (filter && !filter(target, stat)) return;
let header = {
name: target,
type: isDir ? DIRECTORY : FILE,
size: isDir ? 0 : size,
mode: stat.mode,
mtime: stat.mtime,
uid: stat.uid,
gid: stat.gid,
uname: job.uname,
gname: job.gname
};
if (map) header = map(header);
jobResult = {
header,
body: isDir ? void 0 : body
};
return;
}
let stat = await fs$1.lstat(job.source, { bigint: true });
if (dereference && stat.isSymbolicLink()) {
const linkTarget = await fs$1.readlink(job.source);
const resolved = path.resolve(path.dirname(job.source), linkTarget);
const resolvedBase = baseDir ?? directoryPath ?? process.cwd();
if (!resolved.startsWith(resolvedBase + path.sep) && resolved !== resolvedBase) return;
stat = await fs$1.stat(job.source, { bigint: true });
}
if (filter && !filter(job.source, stat)) return;
let header = {
name: target,
size: 0,
mode: job.mode ?? Number(stat.mode),
mtime: job.mtime ?? stat.mtime,
uid: job.uid ?? Number(stat.uid),
gid: job.gid ?? Number(stat.gid),
uname: job.uname,
gname: job.gname,
type: FILE
};
let body;
if (stat.isDirectory()) {
header.type = DIRECTORY;
header.name = target.endsWith("/") ? target : `${target}/`;
try {
for (const d of await fs$1.readdir(job.source, { withFileTypes: true })) jobs.push({
type: d.isDirectory() ? DIRECTORY : FILE,
source: path.join(job.source, d.name),
target: `${header.name}${d.name}`
});
} catch {}
} else if (stat.isSymbolicLink()) {
header.type = SYMLINK;
header.linkname = await fs$1.readlink(job.source);
} else if (stat.isFile()) {
header.size = Number(stat.size);
if (stat.nlink > 1 && seenInodes.has(stat.ino)) {
header.type = LINK;
header.linkname = seenInodes.get(stat.ino);
header.size = 0;
} else {
if (stat.nlink > 1) seenInodes.set(stat.ino, target);
if (header.size > 0) if (header.size < 32 * 1024) body = await fs$1.readFile(job.source);
else body = {
handle: await fs$1.open(job.source, "r"),
size: header.size
};
}
} else return;
if (map) header = map(header);
jobResult = {
header,
body
};
} finally {
results.set(index, jobResult);
resolvers.get(index)?.();
}
};
controller();
await writer();
if (!stream.destroyed) packer.finalize();
})().catch((error) => stream.destroy(error));
return stream;
}
//#endregion
//#region src/fs/concurrency.ts
const createOperationQueue = (concurrency) => {
let active = 0;
const tasks = [];
let head = 0;
let idle = null;
let resolveIdle = null;
const ensureIdle = () => idle ??= new Promise((resolve) => resolveIdle = resolve);
const flush = () => {
while (active < concurrency && head < tasks.length) {
const task = tasks[head++];
active++;
task().finally(() => {
active--;
flush();
});
}
if (head === tasks.length) {
tasks.length = 0;
head = 0;
if (active === 0 && resolveIdle) {
resolveIdle();
idle = null;
resolveIdle = null;
}
}
};
return {
add(op) {
const wasIdle = active === 0 && head === tasks.length;
return new Promise((resolve, reject) => {
tasks.push(() => Promise.resolve().then(op).then(resolve, reject));
if (wasIdle) ensureIdle();
flush();
});
},
onIdle() {
return active === 0 && head === tasks.length ? Promise.resolve() : ensureIdle();
}
};
};
//#endregion
//#region src/fs/file-sink.ts
const BATCH_BYTES = 256 * 1024;
const OPEN_FLAGS = fs.constants.O_WRONLY | fs.constants.O_CREAT | fs.constants.O_TRUNC | (fs.constants.O_NOFOLLOW ?? 0);
const STATE_UNOPENED = 0;
const STATE_OPENING = 1;
const STATE_OPEN = 2;
const STATE_CLOSED = 3;
const STATE_FAILED = 4;
const DRAINED_PROMISE = Promise.resolve();
function createFileSink(path, { mode = 438, mtime } = {}) {
let state = STATE_UNOPENED;
let flushing = false;
let fd = null;
let queue = [];
let spare = [];
let bytes = 0;
let storedError = null;
let endPromise = null;
let endResolve = null;
let endReject = null;
const waitResolves = [];
const waitRejects = [];
const settleWaiters = () => {
if (waitResolves.length === 0) return;
for (let i = 0; i < waitResolves.length; i++) waitResolves[i]();
waitResolves.length = 0;
waitRejects.length = 0;
};
const failWaiters = (error) => {
if (waitRejects.length === 0) return;
for (let i = 0; i < waitRejects.length; i++) waitRejects[i](error);
waitRejects.length = 0;
waitResolves.length = 0;
};
const resetBuffers = () => {
bytes = 0;
queue.length = 0;
spare.length = 0;
};
const finish = () => {
state = STATE_CLOSED;
endResolve?.();
settleWaiters();
};
const swapQueues = () => {
const current = queue;
queue = spare;
spare = current;
queue.length = 0;
return current;
};
const fail = (error) => {
if (storedError) return;
storedError = error;
state = STATE_FAILED;
resetBuffers();
flushing = false;
const fdToClose = fd;
fd = null;
if (fdToClose !== null) fs.ftruncate(fdToClose, 0, () => fs.close(fdToClose));
endReject?.(error);
failWaiters(error);
};
const close = () => {
if (fd === null) {
finish();
return;
}
const fdToClose = fd;
fd = null;
if (mtime) fs.futimes(fdToClose, mtime, mtime, (err) => {
if (err) return fail(err);
fs.close(fdToClose, (closeErr) => {
if (closeErr) fail(closeErr);
else finish();
});
});
else fs.close(fdToClose, (err) => {
if (err) fail(err);
else finish();
});
};
const flush = () => {
if (flushing || queue.length === 0 || state !== STATE_OPEN) return;
flushing = true;
const bufs = swapQueues();
const onDone = (err, written = 0) => {
if (err) return fail(err);
flushing = false;
bytes -= written;
spare.length = 0;
if (bytes < BATCH_BYTES) settleWaiters();
if (queue.length > 0) flush();
else if (endResolve) close();
};
if (bufs.length === 1) {
const buf = bufs[0];
fs.write(fd, buf, 0, buf.length, null, onDone);
} else fs.writev(fd, bufs, onDone);
};
const open = () => {
if (state !== STATE_UNOPENED) return;
state = STATE_OPENING;
fs.open(path, OPEN_FLAGS, mode, (err, openFd) => {
if (err) return fail(err);
if (state === STATE_CLOSED || state === STATE_FAILED) {
fs.close(openFd);
return;
}
fd = openFd;
state = STATE_OPEN;
if (endResolve) if (queue.length > 0) flush();
else close();
else if (bytes >= BATCH_BYTES && !flushing) flush();
else settleWaiters();
});
};
const write = (chunk) => {
if (storedError || state >= STATE_CLOSED || endResolve) return false;
if (state !== STATE_OPEN && state !== STATE_OPENING) open();
const buf = Buffer.isBuffer(chunk) ? chunk : chunk instanceof Uint8Array ? Buffer.from(chunk.buffer, chunk.byteOffset, chunk.byteLength) : Buffer.from(chunk);
if (buf.length === 0) return bytes < BATCH_BYTES;
queue.push(buf);
bytes += buf.length;
if (state === STATE_OPEN && !flushing && bytes >= BATCH_BYTES) flush();
return bytes < BATCH_BYTES;
};
const waitDrain = () => {
if (bytes < BATCH_BYTES || state !== STATE_OPEN) return DRAINED_PROMISE;
return new Promise((resolve, reject) => {
waitResolves.push(resolve);
waitRejects.push(reject);
});
};
const end = () => {
if (state >= STATE_CLOSED) return DRAINED_PROMISE;
if (storedError) return Promise.reject(storedError);
if (endPromise) return endPromise;
endPromise = new Promise((resolve, reject) => {
endResolve = resolve;
endReject = reject;
if (state !== STATE_OPEN && state !== STATE_OPENING) open();
else if (state === STATE_OPEN && !flushing) if (queue.length > 0) flush();
else close();
});
return endPromise;
};
const destroy = (error) => {
if (error) {
fail(error);
return;
}
if (state >= STATE_CLOSED || storedError) return;
resetBuffers();
flushing = false;
if (fd !== null) {
const fdToClose = fd;
fd = null;
fs.close(fdToClose);
}
finish();
};
return {
write,
end,
destroy,
waitDrain
};
}
//#endregion
//#region src/fs/path-cache.ts
const ENOENT = "ENOENT";
const createPathCache = (destDirPath, options) => {
const { maxDepth = 1024, dmode } = options;
const dirPromises = createCache();
const pathConflicts = /* @__PURE__ */ new Map();
const deferredLinks = [];
const realDirCache = createCache();
const initializeDestDir = async (destDirPath) => {
const symbolic = normalizeUnicode(path.resolve(destDirPath));
try {
await fs$1.mkdir(symbolic, { recursive: true });
} catch (err) {
if (err.code === ENOENT) {
const parentDir = path.dirname(symbolic);
if (parentDir === symbolic) throw err;
await fs$1.mkdir(parentDir, { recursive: true });
await fs$1.mkdir(symbolic, { recursive: true });
} else throw err;
}
try {
return {
symbolic,
real: await fs$1.realpath(symbolic)
};
} catch (err) {
if (err.code === ENOENT) return {
symbolic,
real: symbolic
};
throw err;
}
};
const destDirPromise = initializeDestDir(destDirPath);
destDirPromise.catch(() => {});
const getRealDir = async (dirPath, errorMessage) => {
const destDir = await destDirPromise;
if (dirPath === destDir.symbolic) {
validateBounds(destDir.real, destDir.real, errorMessage);
return destDir.real;
}
let promise = realDirCache.get(dirPath);
if (!promise) {
promise = fs$1.realpath(dirPath).then((realPath) => {
validateBounds(realPath, destDir.real, errorMessage);
return realPath;
});
realDirCache.set(dirPath, promise);
}
const realDir = await promise;
validateBounds(realDir, destDir.real, errorMessage);
return realDir;
};
const prepareDirectory = async (dirPath, mode) => {
let promise = dirPromises.get(dirPath);
if (promise) return promise;
promise = (async () => {
if (dirPath === (await destDirPromise).symbolic) return;
await prepareDirectory(path.dirname(dirPath));
try {
const stat = await fs$1.lstat(dirPath);
if (stat.isDirectory()) return;
if (stat.isSymbolicLink()) try {
const realPath = await getRealDir(dirPath, `Symlink "${dirPath}" points outside the extraction directory.`);
if ((await fs$1.stat(realPath)).isDirectory()) return;
} catch (err) {
if (err.code === ENOENT) throw new Error(`Symlink "${dirPath}" points outside the extraction directory.`);
throw err;
}
throw new Error(`"${dirPath}" is not a valid directory component.`);
} catch (err) {
if (err.code === ENOENT) {
await fs$1.mkdir(dirPath, { mode: mode ?? options.dmode });
return;
}
throw err;
}
})();
dirPromises.set(dirPath, promise);
return promise;
};
return {
async ready() {
await destDirPromise;
},
async preparePath(header) {
const { name, linkname, type, mode, mtime } = header;
const normalizedName = normalizeHeaderName(name);
const destDir = await destDirPromise;
const outPath = path.join(destDir.symbolic, normalizedName);
validateBounds(outPath, destDir.symbolic, `Entry "${name}" points outside the extraction directory.`);
if (maxDepth !== Infinity) {
let depth = 1;
for (const char of normalizedName) if (char === "/" && ++depth > maxDepth) throw new Error("Tar exceeds max specified depth.");
}
const prevOp = pathConflicts.get(normalizedName);
if (prevOp) {
if (prevOp === "directory" && type !== "directory" || prevOp !== "directory" && type === "directory") throw new Error(`Path conflict ${type} over existing ${prevOp} at "${name}"`);
return;
}
const parentDir = path.dirname(outPath);
switch (type) {
case DIRECTORY: {
pathConflicts.set(normalizedName, DIRECTORY);
const safeMode = mode ? mode & 511 : void 0;
await prepareDirectory(outPath, dmode ?? safeMode);
if (mtime) await fs$1.lutimes(outPath, mtime, mtime).catch(() => {});
return;
}
case FILE:
pathConflicts.set(normalizedName, FILE);
await prepareDirectory(parentDir);
return outPath;
case SYMLINK:
pathConflicts.set(normalizedName, SYMLINK);
if (!linkname) return;
await prepareDirectory(parentDir);
validateBounds(path.resolve(parentDir, linkname), destDir.symbolic, `Symlink "${linkname}" points outside the extraction directory.`);
await fs$1.symlink(linkname, outPath);
if (mtime) await fs$1.lutimes(outPath, mtime, mtime).catch(() => {});
return;
case LINK: {
pathConflicts.set(normalizedName, LINK);
if (!linkname) return;
const normalizedLink = normalizeUnicode(linkname);
if (path.isAbsolute(normalizedLink)) throw new Error(`Hardlink "${linkname}" points outside the extraction directory.`);
const linkTarget = path.join(destDir.symbolic, normalizedLink);
validateBounds(linkTarget, destDir.symbolic, `Hardlink "${linkname}" points outside the extraction directory.`);
await prepareDirectory(path.dirname(linkTarget));
const realTargetParent = await getRealDir(path.dirname(linkTarget), `Hardlink "${linkname}" points outside the extraction directory.`);
validateBounds(path.join(realTargetParent, path.basename(linkTarget)), destDir.real, `Hardlink "${linkname}" points outside the extraction directory.`);
if (linkTarget !== outPath) {
await prepareDirectory(parentDir);
deferredLinks.push({
linkTarget,
outPath
});
}
return;
}
default: return;
}
},
async applyLinks() {
for (const { linkTarget, outPath } of deferredLinks) try {
await fs$1.link(linkTarget, outPath);
} catch (err) {
if (err.code === ENOENT) throw new Error(`Hardlink target "${linkTarget}" does not exist for link at "${outPath}".`);
throw err;
}
}
};
};
//#endregion
//#region src/fs/unpack.ts
function unpackTar(directoryPath, options = {}) {
const unpacker = createUnpacker(options);
const opQueue = createOperationQueue(options.concurrency || cpus().length || 8);
const pathCache = createPathCache(directoryPath, options);
let currentFileStream = null;
let currentWriteCallback = null;
let queuedError = null;
const onQueuedError = (err) => {
queuedError ??= err;
if (!writable.destroyed) writable.destroy(err);
};
const writable = new Writable({
async write(chunk, _, cb) {
try {
unpacker.write(chunk);
if (unpacker.isEntryActive()) {
if (currentFileStream && currentWriteCallback) {
let needsDrain = false;
const writeCallback = currentWriteCallback;
while (!unpacker.isBodyComplete()) {
needsDrain = false;
if (unpacker.streamBody(writeCallback) === 0) if (needsDrain) await currentFileStream.waitDrain();
else {
cb();
return;
}
}
while (!unpacker.skipPadding()) {
cb();
return;
}
const streamToClose = currentFileStream;
if (streamToClose) opQueue.add(() => streamToClose.end()).catch(onQueuedError);
currentFileStream = null;
currentWriteCallback = null;
} else if (!unpacker.skipEntry()) {
cb();
return;
}
}
while (true) {
const header = unpacker.readHeader();
if (header === void 0 || header === null) {
cb();
return;
}
const transformedHeader = transformHeader(header, options);
if (!transformedHeader) {
if (!unpacker.skipEntry()) {
cb();
return;
}
continue;
}
const outPath = await opQueue.add(() => pathCache.preparePath(transformedHeader));
if (outPath) {
const safeMode = transformedHeader.mode ? transformedHeader.mode & 511 : void 0;
const fileStream = createFileSink(outPath, {
mode: options.fmode ?? safeMode,
mtime: transformedHeader.mtime ?? void 0
});
let needsDrain = false;
const writeCallback = (chunk) => {
const writeOk = fileStream.write(chunk);
if (!writeOk) needsDrain = true;
return writeOk;
};
while (!unpacker.isBodyComplete()) {
needsDrain = false;
if (unpacker.streamBody(writeCallback) === 0) if (needsDrain) await fileStream.waitDrain();
else {
currentFileStream = fileStream;
currentWriteCallback = writeCallback;
cb();
return;
}
}
while (!unpacker.skipPadding()) {
currentFileStream = fileStream;
currentWriteCallback = writeCallback;
cb();
return;
}
opQueue.add(() => fileStream.end()).catch(onQueuedError);
} else if (!unpacker.skipEntry()) {
cb();
return;
}
}
} catch (err) {
cb(err);
}
},
async final(cb) {
try {
unpacker.end();
unpacker.validateEOF();
await pathCache.ready();
await opQueue.onIdle();
if (queuedError) throw queuedError;
await pathCache.applyLinks();
cb();
} catch (err) {
cb(err);
}
},
destroy(error, callback) {
(async () => {
if (currentFileStream) {
currentFileStream.destroy(error ?? void 0);
currentFileStream = null;
currentWriteCallback = null;
}
await opQueue.onIdle();
})().then(() => callback(error ?? null), (e) => callback(error ?? (e instanceof Error ? e : /* @__PURE__ */ new Error("Stream destroyed"))));
}
});
return writable;
}
//#endregion
export { packTar, packTarSources, unpackTar };

77
node_modules/modern-tar/dist/types-D19dF2SE.d.ts generated vendored Normal file
View File

@@ -0,0 +1,77 @@
//#region src/tar/constants.d.ts
/** Type flag constants for file types. */
declare const TYPEFLAG: {
readonly file: "0";
readonly link: "1";
readonly symlink: "2";
readonly "character-device": "3";
readonly "block-device": "4";
readonly directory: "5";
readonly fifo: "6";
readonly "pax-header": "x";
readonly "pax-global-header": "g";
readonly "gnu-long-name": "L";
readonly "gnu-long-link-name": "K";
};
//#endregion
//#region src/tar/types.d.ts
/**
* Header information for a tar entry in USTAR format.
*/
interface TarHeader {
/** Entry name/path. Can be up to 255 characters with USTAR prefix extension. */
name: string;
/** Size of the entry data in bytes. Should be 0 for directories, symlinks, and hardlinks. */
size: number;
/** Modification time as a `Date` object. Defaults to current time if not specified. */
mtime?: Date;
/** Unix file permissions as an octal number (e.g., 0o644 for rw-r--r--). Defaults to 0o644 for files and 0o755 for directories. */
mode?: number;
/** Entry type. Defaults to "file" if not specified. */
type?: keyof typeof TYPEFLAG;
/** User ID of the entry owner. */
uid?: number;
/** Group ID of the entry owner. */
gid?: number;
/** User name of the entry owner. */
uname?: string;
/** Group name of the entry owner. */
gname?: string;
/** Target path for symlinks and hard links. */
linkname?: string;
/** PAX extended attributes as key-value pairs. */
pax?: Record<string, string>;
}
/**
* Union type for entry body data that can be packed into a tar archive.
*/
type TarEntryData = string | Uint8Array | ArrayBuffer | Blob | null | undefined;
/**
* Configuration options for creating a tar decoder stream.
*/
interface DecoderOptions {
/**
* Enable strict validation of the tar archive.
* When true, the decoder will throw errors for data corruption issues:
* - Invalid checksums (indicates header corruption)
* - Invalid USTAR magic string (format violation)
* @default false
*/
strict?: boolean;
}
/**
* Platform-neutral configuration options for extracting tar archives.
*
* These options work with any tar extraction implementation and are not tied
* to specific platforms like Node.js filesystem APIs.
*/
interface UnpackOptions extends DecoderOptions {
/** Number of leading path components to strip from entry names (e.g., strip: 1 removes first directory) */
strip?: number;
/** Filter function to include/exclude entries (return false to skip) */
filter?: (header: TarHeader) => boolean;
/** Transform function to modify tar headers before extraction */
map?: (header: TarHeader) => TarHeader;
}
//#endregion
export { UnpackOptions as i, TarEntryData as n, TarHeader as r, DecoderOptions as t };

636
node_modules/modern-tar/dist/unpacker-CPCEF5CT.js generated vendored Normal file
View File

@@ -0,0 +1,636 @@
const FILE = "file";
const LINK = "link";
const SYMLINK = "symlink";
const DIRECTORY = "directory";
const TYPEFLAG = {
file: "0",
link: "1",
symlink: "2",
"character-device": "3",
"block-device": "4",
directory: "5",
fifo: "6",
"pax-header": "x",
"pax-global-header": "g",
"gnu-long-name": "L",
"gnu-long-link-name": "K"
};
const FLAGTYPE = {
"0": FILE,
"1": LINK,
"2": SYMLINK,
"3": "character-device",
"4": "block-device",
"5": DIRECTORY,
"6": "fifo",
x: "pax-header",
g: "pax-global-header",
L: "gnu-long-name",
K: "gnu-long-link-name"
};
const ZERO_BLOCK = new Uint8Array(512);
const EMPTY = new Uint8Array(0);
//#endregion
//#region src/tar/encoding.ts
const encoder = new TextEncoder();
const decoder = new TextDecoder();
function writeString(view, offset, size, value) {
if (value) encoder.encodeInto(value, view.subarray(offset, offset + size));
}
function writeOctal(view, offset, size, value) {
if (value === void 0) return;
const octalString = value.toString(8).padStart(size - 1, "0");
encoder.encodeInto(octalString, view.subarray(offset, offset + size - 1));
}
function readString(view, offset, size) {
const end = view.indexOf(0, offset);
const sliceEnd = end === -1 || end > offset + size ? offset + size : end;
return decoder.decode(view.subarray(offset, sliceEnd));
}
function readOctal(view, offset, size) {
let value = 0;
const end = offset + size;
for (let i = offset; i < end; i++) {
const charCode = view[i];
if (charCode === 0) break;
if (charCode === 32) continue;
value = value * 8 + (charCode - 48);
}
return value;
}
function readNumeric(view, offset, size) {
if (view[offset] & 128) {
let result = 0;
result = view[offset] & 127;
for (let i = 1; i < size; i++) result = result * 256 + view[offset + i];
if (!Number.isSafeInteger(result)) throw new Error("TAR number too large");
return result;
}
return readOctal(view, offset, size);
}
//#endregion
//#region src/tar/body.ts
const isBodyless = (header) => header.type === "directory" || header.type === "symlink" || header.type === "link" || header.type === "character-device" || header.type === "block-device" || header.type === "fifo";
async function normalizeBody(body) {
if (body === null || body === void 0) return EMPTY;
if (body instanceof Uint8Array) return body;
if (typeof body === "string") return encoder.encode(body);
if (body instanceof ArrayBuffer) return new Uint8Array(body);
if (body instanceof Blob) return new Uint8Array(await body.arrayBuffer());
throw new TypeError("Unsupported content type for entry body.");
}
//#endregion
//#region src/tar/options.ts
const stripPath = (p, n) => {
const parts = p.split("/").filter(Boolean);
return n >= parts.length ? "" : parts.slice(n).join("/");
};
function transformHeader(header, options) {
const { strip, filter, map } = options;
if (!strip && !filter && !map) return header;
const h = { ...header };
if (strip && strip > 0) {
const newName = stripPath(h.name, strip);
if (!newName) return null;
h.name = h.type === "directory" && !newName.endsWith("/") ? `${newName}/` : newName;
if (h.linkname) {
const isAbsolute = h.linkname.startsWith("/");
if (isAbsolute || h.type === "link") {
const stripped = stripPath(h.linkname, strip);
h.linkname = isAbsolute ? `/${stripped}` || "/" : stripped;
}
}
}
if (filter?.(h) === false) return null;
const result = map ? map(h) : h;
if (result && (!result.name || !result.name.trim() || result.name === "." || result.name === "/")) return null;
return result;
}
//#endregion
//#region src/tar/checksum.ts
const CHECKSUM_SPACE = 32;
const ASCII_ZERO = 48;
function validateChecksum(block) {
const stored = readOctal(block, 148, 8);
let sum = 0;
for (let i = 0; i < block.length; i++) if (i >= 148 && i < 156) sum += CHECKSUM_SPACE;
else sum += block[i];
return stored === sum;
}
function writeChecksum(block) {
block.fill(CHECKSUM_SPACE, 148, 156);
let checksum = 0;
for (const byte of block) checksum += byte;
for (let i = 153; i >= 148; i--) {
block[i] = (checksum & 7) + ASCII_ZERO;
checksum >>= 3;
}
block[154] = 0;
block[155] = CHECKSUM_SPACE;
}
//#endregion
//#region src/tar/pax.ts
const USTAR_SPLIT_MAX_SIZE = 256;
function generatePax(header) {
const paxRecords = {};
if (encoder.encode(header.name).length > 100) {
if (findUstarSplit(header.name) === null) paxRecords.path = header.name;
}
if (header.linkname && encoder.encode(header.linkname).length > 100) paxRecords.linkpath = header.linkname;
if (header.uname && encoder.encode(header.uname).length > 32) paxRecords.uname = header.uname;
if (header.gname && encoder.encode(header.gname).length > 32) paxRecords.gname = header.gname;
if (header.uid != null && header.uid > 2097151) paxRecords.uid = String(header.uid);
if (header.gid != null && header.gid > 2097151) paxRecords.gid = String(header.gid);
if (header.size != null && header.size > 8589934591) paxRecords.size = String(header.size);
if (header.pax) Object.assign(paxRecords, header.pax);
const paxEntries = Object.entries(paxRecords);
if (paxEntries.length === 0) return null;
const paxBody = encoder.encode(paxEntries.map(([key, value]) => {
const record = `${key}=${value}\n`;
const partLength = encoder.encode(record).length + 1;
let totalLength = partLength + String(partLength).length;
totalLength = partLength + String(totalLength).length;
return `${totalLength} ${record}`;
}).join(""));
return {
paxHeader: createTarHeader({
name: decoder.decode(encoder.encode(`PaxHeader/${header.name}`).slice(0, 100)),
size: paxBody.length,
type: "pax-header",
mode: 420,
mtime: header.mtime,
uname: header.uname,
gname: header.gname,
uid: header.uid,
gid: header.gid
}),
paxBody
};
}
function findUstarSplit(path) {
const totalPathBytes = encoder.encode(path).length;
if (totalPathBytes <= 100 || totalPathBytes > USTAR_SPLIT_MAX_SIZE) return null;
for (let i = path.length - 1; i > 0; i--) {
if (path[i] !== "/") continue;
const prefix = path.slice(0, i);
const name = path.slice(i + 1);
if (encoder.encode(prefix).length <= 155 && encoder.encode(name).length <= 100) return {
prefix,
name
};
}
return null;
}
//#endregion
//#region src/tar/header.ts
function createTarHeader(header) {
const view = new Uint8Array(512);
const size = isBodyless(header) ? 0 : header.size ?? 0;
let name = header.name;
let prefix = "";
if (!header.pax?.path) {
const split = findUstarSplit(name);
if (split) {
name = split.name;
prefix = split.prefix;
}
}
writeString(view, 0, 100, name);
writeOctal(view, 100, 8, header.mode ?? (header.type === "directory" ? 493 : 420));
writeOctal(view, 108, 8, header.uid ?? 0);
writeOctal(view, 116, 8, header.gid ?? 0);
writeOctal(view, 124, 12, size);
writeOctal(view, 136, 12, Math.floor((header.mtime?.getTime() ?? Date.now()) / 1e3));
writeString(view, 156, 1, TYPEFLAG[header.type ?? "file"]);
writeString(view, 157, 100, header.linkname);
writeString(view, 257, 6, "ustar\0");
writeString(view, 263, 2, "00");
writeString(view, 265, 32, header.uname);
writeString(view, 297, 32, header.gname);
writeString(view, 345, 155, prefix);
writeChecksum(view);
return view;
}
function parseUstarHeader(block, strict) {
if (strict && !validateChecksum(block)) throw new Error("Invalid tar header checksum.");
const typeflag = readString(block, 156, 1);
const header = {
name: readString(block, 0, 100),
mode: readOctal(block, 100, 8),
uid: readNumeric(block, 108, 8),
gid: readNumeric(block, 116, 8),
size: readNumeric(block, 124, 12),
mtime: /* @__PURE__ */ new Date(readNumeric(block, 136, 12) * 1e3),
type: FLAGTYPE[typeflag] || "file",
linkname: readString(block, 157, 100)
};
const magic = readString(block, 257, 6);
if (isBodyless(header)) header.size = 0;
if (magic.trim() === "ustar") {
header.uname = readString(block, 265, 32);
header.gname = readString(block, 297, 32);
}
if (magic === "ustar") header.prefix = readString(block, 345, 155);
return header;
}
const PAX_MAPPING = {
path: ["name", (v) => v],
linkpath: ["linkname", (v) => v],
size: ["size", (v) => parseInt(v, 10)],
mtime: ["mtime", parseFloat],
uid: ["uid", (v) => parseInt(v, 10)],
gid: ["gid", (v) => parseInt(v, 10)],
uname: ["uname", (v) => v],
gname: ["gname", (v) => v]
};
function parsePax(buffer) {
const decoder = new TextDecoder("utf-8");
const overrides = Object.create(null);
const pax = Object.create(null);
let offset = 0;
while (offset < buffer.length) {
const spaceIndex = buffer.indexOf(32, offset);
if (spaceIndex === -1) break;
const length = parseInt(decoder.decode(buffer.subarray(offset, spaceIndex)), 10);
if (Number.isNaN(length) || length === 0) break;
const recordEnd = offset + length;
const [key, value] = decoder.decode(buffer.subarray(spaceIndex + 1, recordEnd - 1)).split("=", 2);
if (key && value !== void 0) {
pax[key] = value;
if (Object.hasOwn(PAX_MAPPING, key)) {
const [targetKey, parser] = PAX_MAPPING[key];
const parsedValue = parser(value);
if (typeof parsedValue === "string" || !Number.isNaN(parsedValue)) overrides[targetKey] = parsedValue;
}
}
offset = recordEnd;
}
if (Object.keys(pax).length > 0) overrides.pax = pax;
return overrides;
}
function applyOverrides(header, overrides) {
if (overrides.name !== void 0) header.name = overrides.name;
if (overrides.linkname !== void 0) header.linkname = overrides.linkname;
if (overrides.size !== void 0) header.size = overrides.size;
if (overrides.mtime !== void 0) header.mtime = /* @__PURE__ */ new Date(overrides.mtime * 1e3);
if (overrides.uid !== void 0) header.uid = overrides.uid;
if (overrides.gid !== void 0) header.gid = overrides.gid;
if (overrides.uname !== void 0) header.uname = overrides.uname;
if (overrides.gname !== void 0) header.gname = overrides.gname;
if (overrides.pax) header.pax = Object.assign({}, header.pax ?? {}, overrides.pax);
}
function getMetaParser(type) {
switch (type) {
case "pax-global-header":
case "pax-header": return parsePax;
case "gnu-long-name": return (data) => ({ name: readString(data, 0, data.length) });
case "gnu-long-link-name": return (data) => ({ linkname: readString(data, 0, data.length) });
default: return;
}
}
function getHeaderBlocks(header) {
const base = createTarHeader(header);
const pax = generatePax(header);
if (!pax) return [base];
const paxPadding = -pax.paxBody.length & 511;
const paddingBlocks = paxPadding > 0 ? [ZERO_BLOCK.subarray(0, paxPadding)] : [];
return [
pax.paxHeader,
pax.paxBody,
...paddingBlocks,
base
];
}
//#endregion
//#region src/tar/packer.ts
const EOF_BUFFER = new Uint8Array(512 * 2);
function createTarPacker(onData, onError, onFinalize) {
let currentHeader = null;
let bytesWritten = 0;
let finalized = false;
const fail = (message) => {
const error = new Error(message);
onError(error);
throw error;
};
return {
add(header) {
if (finalized) fail("No new tar entries after finalize.");
if (currentHeader !== null) fail("Previous entry must be completed before adding a new one");
const size = isBodyless(header) ? 0 : header.size;
if (!Number.isSafeInteger(size) || size < 0) fail("Invalid tar entry size.");
try {
const headerBlocks = getHeaderBlocks({
...header,
size
});
for (const block of headerBlocks) onData(block);
currentHeader = {
...header,
size
};
bytesWritten = 0;
} catch (error) {
onError(error);
}
},
write(chunk) {
if (!currentHeader) fail("No active tar entry.");
if (finalized) fail("Cannot write data after finalize.");
const newTotal = bytesWritten + chunk.length;
if (newTotal > currentHeader.size) fail(`"${currentHeader.name}" exceeds given size of ${currentHeader.size} bytes.`);
try {
bytesWritten = newTotal;
onData(chunk);
} catch (error) {
onError(error);
}
},
endEntry() {
if (!currentHeader) fail("No active entry to end.");
if (finalized) fail("Cannot end entry after finalize.");
try {
if (bytesWritten !== currentHeader.size) fail(`Size mismatch for "${currentHeader.name}".`);
const paddingSize = -currentHeader.size & 511;
if (paddingSize > 0) onData(new Uint8Array(paddingSize));
currentHeader = null;
bytesWritten = 0;
} catch (error) {
onError(error);
throw error;
}
},
finalize() {
if (finalized) fail("Archive has already been finalized");
if (currentHeader !== null) fail("Cannot finalize while an entry is still active");
try {
onData(EOF_BUFFER);
finalized = true;
if (onFinalize) onFinalize();
} catch (error) {
onError(error);
}
}
};
}
//#endregion
//#region src/tar/chunk-queue.ts
const INITIAL_CAPACITY = 256;
function createChunkQueue() {
let chunks = new Array(INITIAL_CAPACITY);
let capacityMask = chunks.length - 1;
let head = 0;
let tail = 0;
let totalAvailable = 0;
const consumeFromHead = (count) => {
const chunk = chunks[head];
if (count === chunk.length) {
chunks[head] = EMPTY;
head = head + 1 & capacityMask;
} else chunks[head] = chunk.subarray(count);
totalAvailable -= count;
if (totalAvailable === 0 && chunks.length > INITIAL_CAPACITY) {
chunks = new Array(INITIAL_CAPACITY);
capacityMask = INITIAL_CAPACITY - 1;
head = 0;
tail = 0;
}
};
function pull(bytes, callback) {
if (callback) {
let fed = 0;
let remaining = Math.min(bytes, totalAvailable);
while (remaining > 0) {
const chunk = chunks[head];
const toFeed = Math.min(remaining, chunk.length);
const segment = toFeed === chunk.length ? chunk : chunk.subarray(0, toFeed);
consumeFromHead(toFeed);
remaining -= toFeed;
fed += toFeed;
if (!callback(segment)) break;
}
return fed;
}
if (totalAvailable < bytes) return null;
if (bytes === 0) return EMPTY;
const firstChunk = chunks[head];
if (firstChunk.length >= bytes) {
const view = firstChunk.length === bytes ? firstChunk : firstChunk.subarray(0, bytes);
consumeFromHead(bytes);
return view;
}
const result = new Uint8Array(bytes);
let copied = 0;
let remaining = bytes;
while (remaining > 0) {
const chunk = chunks[head];
const toCopy = Math.min(remaining, chunk.length);
result.set(toCopy === chunk.length ? chunk : chunk.subarray(0, toCopy), copied);
copied += toCopy;
remaining -= toCopy;
consumeFromHead(toCopy);
}
return result;
}
return {
push: (chunk) => {
if (chunk.length === 0) return;
let nextTail = tail + 1 & capacityMask;
if (nextTail === head) {
const oldLen = chunks.length;
const newLen = oldLen * 2;
const newChunks = new Array(newLen);
const count = tail - head + oldLen & oldLen - 1;
if (head < tail) for (let i = 0; i < count; i++) newChunks[i] = chunks[head + i];
else if (count > 0) {
const firstPart = oldLen - head;
for (let i = 0; i < firstPart; i++) newChunks[i] = chunks[head + i];
for (let i = 0; i < tail; i++) newChunks[firstPart + i] = chunks[i];
}
chunks = newChunks;
capacityMask = newLen - 1;
head = 0;
tail = count;
nextTail = tail + 1 & capacityMask;
}
chunks[tail] = chunk;
tail = nextTail;
totalAvailable += chunk.length;
},
available: () => totalAvailable,
peek: (bytes) => {
if (totalAvailable < bytes) return null;
if (bytes === 0) return EMPTY;
const firstChunk = chunks[head];
if (firstChunk.length >= bytes) return firstChunk.length === bytes ? firstChunk : firstChunk.subarray(0, bytes);
const result = new Uint8Array(bytes);
let copied = 0;
let index = head;
while (copied < bytes) {
const chunk = chunks[index];
const toCopy = Math.min(bytes - copied, chunk.length);
if (toCopy === chunk.length) result.set(chunk, copied);
else result.set(chunk.subarray(0, toCopy), copied);
copied += toCopy;
index = index + 1 & capacityMask;
}
return result;
},
discard: (bytes) => {
if (bytes > totalAvailable) throw new Error("Too many bytes consumed");
if (bytes === 0) return;
let remaining = bytes;
while (remaining > 0) {
const chunk = chunks[head];
const toConsume = Math.min(remaining, chunk.length);
consumeFromHead(toConsume);
remaining -= toConsume;
}
},
pull
};
}
//#endregion
//#region src/tar/unpacker.ts
const STATE_HEADER = 0;
const STATE_BODY = 1;
const truncateErr = /* @__PURE__ */ new Error("Tar archive is truncated.");
function createUnpacker(options = {}) {
const strict = options.strict ?? false;
const { available, peek, push, discard, pull } = createChunkQueue();
let state = STATE_HEADER;
let ended = false;
let done = false;
let eof = false;
let currentEntry = null;
const paxGlobals = {};
let nextEntryOverrides = {};
const unpacker = {
isEntryActive: () => state === STATE_BODY,
isBodyComplete: () => !currentEntry || currentEntry.remaining === 0,
canFinish: () => !currentEntry || available() >= currentEntry.remaining + currentEntry.padding,
write(chunk) {
if (ended) throw new Error("Archive already ended.");
push(chunk);
},
end() {
ended = true;
},
readHeader() {
if (state !== STATE_HEADER) throw new Error("Cannot read header while an entry is active");
if (done) return void 0;
while (!done) {
if (available() < 512) {
if (ended) {
if (available() > 0 && strict) throw truncateErr;
done = true;
return;
}
return null;
}
const headerBlock = peek(512);
if (isZeroBlock(headerBlock)) {
if (available() < 512 * 2) {
if (ended) {
if (strict) throw truncateErr;
done = true;
return;
}
return null;
}
if (isZeroBlock(peek(512 * 2).subarray(512))) {
discard(512 * 2);
done = true;
eof = true;
return;
}
if (strict) throw new Error("Invalid tar header.");
discard(512);
continue;
}
let internalHeader;
try {
internalHeader = parseUstarHeader(headerBlock, strict);
} catch (err) {
if (strict) throw err;
discard(512);
continue;
}
const metaParser = getMetaParser(internalHeader.type);
if (metaParser) {
const paddedSize = internalHeader.size + (-internalHeader.size & 511);
if (available() < 512 + paddedSize) {
if (ended && strict) throw truncateErr;
return null;
}
discard(512);
const overrides = metaParser(pull(paddedSize).subarray(0, internalHeader.size));
const target = internalHeader.type === "pax-global-header" ? paxGlobals : nextEntryOverrides;
for (const key in overrides) target[key] = overrides[key];
continue;
}
discard(512);
const header = internalHeader;
if (internalHeader.prefix) header.name = `${internalHeader.prefix}/${header.name}`;
applyOverrides(header, paxGlobals);
applyOverrides(header, nextEntryOverrides);
if (header.name.endsWith("/") && header.type === "file") header.type = DIRECTORY;
nextEntryOverrides = {};
currentEntry = {
header,
remaining: header.size,
padding: -header.size & 511
};
state = STATE_BODY;
return header;
}
},
streamBody(callback) {
if (state !== STATE_BODY || !currentEntry || currentEntry.remaining === 0) return 0;
const bytesToFeed = Math.min(currentEntry.remaining, available());
if (bytesToFeed === 0) return 0;
const fed = pull(bytesToFeed, callback);
currentEntry.remaining -= fed;
return fed;
},
skipPadding() {
if (state !== STATE_BODY || !currentEntry) return true;
if (currentEntry.remaining > 0) throw new Error("Body not fully consumed");
if (available() < currentEntry.padding) return false;
discard(currentEntry.padding);
currentEntry = null;
state = STATE_HEADER;
return true;
},
skipEntry() {
if (state !== STATE_BODY || !currentEntry) return true;
const toDiscard = Math.min(currentEntry.remaining, available());
if (toDiscard > 0) {
discard(toDiscard);
currentEntry.remaining -= toDiscard;
}
if (currentEntry.remaining > 0) return false;
return unpacker.skipPadding();
},
validateEOF() {
if (strict) {
if (!eof) throw truncateErr;
if (available() > 0) {
if (pull(available()).some((byte) => byte !== 0)) throw new Error("Invalid EOF.");
}
}
}
};
return unpacker;
}
function isZeroBlock(block) {
if (block.byteOffset % 8 === 0) {
const view = new BigUint64Array(block.buffer, block.byteOffset, block.length / 8);
for (let i = 0; i < view.length; i++) if (view[i] !== 0n) return false;
return true;
}
for (let i = 0; i < block.length; i++) if (block[i] !== 0) return false;
return true;
}
//#endregion
export { normalizeBody as a, LINK as c, isBodyless as i, SYMLINK as l, createTarPacker as n, DIRECTORY as o, transformHeader as r, FILE as s, createUnpacker as t };

329
node_modules/modern-tar/dist/web/index.d.ts generated vendored Normal file
View File

@@ -0,0 +1,329 @@
import { i as UnpackOptions, n as TarEntryData, r as TarHeader, t as DecoderOptions } from "../types-D19dF2SE.js";
//#region src/web/compression.d.ts
/**
* Creates a gzip compression stream that is compatible with Uint8Array streams.
*
* @returns A {@link ReadableWritablePair} configured for gzip compression.
* @example
* ```typescript
* import { createGzipEncoder, createTarPacker } from 'modern-tar';
*
* // Create and compress a tar archive
* const { readable, controller } = createTarPacker();
* const compressedStream = readable.pipeThrough(createGzipEncoder());
*
* // Add entries...
* const fileStream = controller.add({ name: "file.txt", size: 5, type: "file" });
* const writer = fileStream.getWriter();
* await writer.write(new TextEncoder().encode("hello"));
* await writer.close();
* controller.finalize();
*
* // Upload compressed .tar.gz
* await fetch('/api/upload', {
* method: 'POST',
* body: compressedStream,
* headers: { 'Content-Type': 'application/gzip' }
* });
* ```
*/
declare function createGzipEncoder(): ReadableWritablePair<Uint8Array, Uint8Array>;
/**
* Creates a gzip decompression stream that is compatible with Uint8Array streams.
*
* @returns A {@link ReadableWritablePair} configured for gzip decompression.
* @example
* ```typescript
* import { createGzipDecoder, createTarDecoder } from 'modern-tar';
*
* // Download and process a .tar.gz file
* const response = await fetch('https://api.example.com/archive.tar.gz');
* if (!response.body) throw new Error('No response body');
*
* // Buffer entire archive
* const entries = await unpackTar(response.body.pipeThrough(createGzipDecoder()));
*
* for (const entry of entries) {
* console.log(`Extracted: ${entry.header.name}`);
* const content = new TextDecoder().decode(entry.data);
* console.log(`Content: ${content}`);
* }
* ```
* @example
* ```typescript
* import { createGzipDecoder, createTarDecoder } from 'modern-tar';
*
* // Download and process a .tar.gz file
* const response = await fetch('https://api.example.com/archive.tar.gz');
* if (!response.body) throw new Error('No response body');
*
* // Chain decompression and tar parsing using streams
* const entries = response.body
* .pipeThrough(createGzipDecoder())
* .pipeThrough(createTarDecoder());
*
* for await (const entry of entries) {
* console.log(`Extracted: ${entry.header.name}`);
* // Process entry.body ReadableStream as needed
* }
* ```
*/
declare function createGzipDecoder(): ReadableWritablePair<Uint8Array, Uint8Array>;
//#endregion
//#region src/web/types.d.ts
/**
* Represents a complete entry to be packed into a tar archive.
*
* Combines header metadata with optional body data. Used as input to {@link packTar}
* and the controller returned by {@link createTarPacker}.
*/
interface TarEntry {
header: TarHeader;
body?: TarEntryData | ReadableStream<Uint8Array>;
}
/**
* Represents an entry parsed from a tar archive stream.
*/
interface ParsedTarEntry {
header: TarHeader;
body: ReadableStream<Uint8Array>;
}
/**
* Represents an extracted entry with fully buffered content.
*
* For bodyless entries (directories, symlinks, hardlinks), `data` will be `undefined`.
* For files (including empty files), `data` will be a `Uint8Array`.
*/
interface ParsedTarEntryWithData {
header: TarHeader;
data?: Uint8Array;
}
//#endregion
//#region src/web/helpers.d.ts
/**
* Packs an array of tar entries into a single `Uint8Array` buffer.
*
* For streaming scenarios or large archives, use {@link createTarPacker} instead.
*
* @param entries - Array of tar entries with headers and optional bodies
* @returns A `Promise` that resolves to the complete tar archive as a Uint8Array
* @example
* ```typescript
* import { packTar } from 'modern-tar';
*
* const entries = [
* {
* header: { name: "hello.txt", size: 5, type: "file" },
* body: "hello"
* },
* {
* header: { name: "data.json", size: 13, type: "file" },
* body: new Uint8Array([123, 34, 116, 101, 115, 116, 34, 58, 116, 114, 117, 101, 125]) // {"test":true}
* },
* {
* header: { name: "folder/", type: "directory", size: 0 }
* }
* ];
*
* const tarBuffer = await packTar(entries);
*
* // Save to file or upload
* await fetch('/api/upload', {
* method: 'POST',
* body: tarBuffer,
* headers: { 'Content-Type': 'application/x-tar' }
* });
* ```
*/
declare function packTar(entries: (TarEntry | ParsedTarEntryWithData)[]): Promise<Uint8Array>;
/**
* Extracts all entries and their data from a complete tar archive buffer.
*
* For streaming scenarios or large archives, use {@link createTarDecoder} instead.
*
* @param archive - The complete tar archive as `ArrayBuffer` or `Uint8Array`
* @param options - Optional extraction configuration
* @returns A `Promise` that resolves to an array of entries with buffered data
* @example
* ```typescript
* import { unpackTar } from 'modern-tar';
*
* // From a file upload or fetch
* const response = await fetch('/api/archive.tar');
* const tarBuffer = await response.arrayBuffer();
*
* const entries = await unpackTar(tarBuffer);
* for (const entry of entries) {
* if (entry.data) {
* console.log(`File: ${entry.header.name}, Size: ${entry.data.length} bytes`);
* const content = new TextDecoder().decode(entry.data);
* console.log(`Content: ${content}`);
* } else {
* console.log(`${entry.header.type}: ${entry.header.name}`);
* }
* }
* ```
* @example
* ```typescript
* // From a Uint8Array with options
* const tarData = new Uint8Array([...]); // your tar data
* const entries = await unpackTar(tarData, {
* strip: 1,
* filter: (header) => header.name.endsWith('.txt'),
* map: (header) => ({ ...header, name: header.name.toLowerCase() })
* });
*
* // Process filtered files
* for (const file of entries) {
* if (file.data) {
* console.log(new TextDecoder().decode(file.data));
* }
* }
* ```
*/
declare function unpackTar(archive: ArrayBuffer | Uint8Array | ReadableStream<Uint8Array>, options?: UnpackOptions): Promise<ParsedTarEntryWithData[]>;
//#endregion
//#region src/web/pack.d.ts
/**
* Controls a streaming tar packing process.
*
* Provides methods to add entries to a tar archive and finalize the stream.
* This is the advanced API for streaming tar creation, allowing you to dynamically
* add entries and write their content as a [`ReadableStream`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream).
*/
interface TarPackController {
/**
* Add an entry to the tar archive.
*
* After adding the entry, you must write exactly `header.size` bytes of data
* to the returned [`WritableStream`](https://developer.mozilla.org/en-US/docs/Web/API/WritableStream)
* and then close it. For entries that do not have a body (e.g., directories),
* the size property should be set to 0 and the stream should be closed immediately.
*
* @param header - The tar header for the entry. The `size` property must be accurate
* @returns A [`WritableStream`](https://developer.mozilla.org/en-US/docs/Web/API/WritableStream) for writing the entry's body data
*
* @example
* ```typescript
* // Add a text file
* const fileStream = controller.add({
* name: "file.txt",
* size: 11,
* type: "file"
* });
*
* const writer = fileStream.getWriter();
* await writer.write(new TextEncoder().encode("hello world"));
* await writer.close();
*
* // Add a directory
* const dirStream = controller.add({
* name: "folder/",
* type: "directory",
* size: 0
* });
* await dirStream.close(); // Directories have no content
* ```
*/
add(header: TarHeader): WritableStream<Uint8Array>;
/**
* Finalize the archive.
*
* Must be called after all entries have been added.
* This writes the end-of-archive marker and closes the readable stream.
*/
finalize(): void;
/**
* Abort the packing process with an error.
*
* @param err - The error that caused the abort
*/
error(err: unknown): void;
}
/**
* Create a streaming tar packer.
*
* Provides a controller-based API for creating tar archives, suitable for scenarios where entries are
* generated dynamically. The returned [`ReadableStream`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream)
* outputs tar archive bytes as entries are added.
*
* @returns Object containing the readable stream and controller
* @returns readable - [`ReadableStream`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream) that outputs the tar archive bytes
* @returns controller - {@link TarPackController} for adding entries and finalizing
*
* @example
* ```typescript
* import { createTarPacker } from 'modern-tar';
*
* const { readable, controller } = createTarPacker();
*
* // Add entries dynamically
* const fileStream = controller.add({
* name: "dynamic.txt",
* size: 5,
* type: "file"
* });
*
* const writer = fileStream.getWriter();
* await writer.write(new TextEncoder().encode("hello"));
* await writer.close();
*
* // Add multiple entries
* const jsonStream = controller.add({
* name: "data.json",
* size: 13,
* type: "file"
* });
* const jsonWriter = jsonStream.getWriter();
* await jsonWriter.write(new TextEncoder().encode('{"test":true}'));
* await jsonWriter.close();
*
* // Finalize the archive
* controller.finalize();
*
* // Use the readable stream
* const response = new Response(readable);
* const buffer = await response.arrayBuffer();
* ```
*/
declare function createTarPacker(): {
readable: ReadableStream<Uint8Array>;
controller: TarPackController;
};
//#endregion
//#region src/web/unpack.d.ts
/**
* Create a transform stream that parses tar bytes into entries.
*
* @param options - Optional configuration for the decoder using {@link DecoderOptions}.
* @returns `TransformStream` that converts tar archive bytes to {@link ParsedTarEntry} objects.
* @example
* ```typescript
* import { createTarDecoder } from 'modern-tar';
*
* const decoder = createTarDecoder({ strict: true });
* const entriesStream = tarStream.pipeThrough(decoder);
*
* for await (const entry of entriesStream) {
* console.log(`Entry: ${entry.header.name}`);
*
* const shouldSkip = entry.header.name.endsWith('.md');
* if (shouldSkip) {
* // You MUST drain the body with cancel() to proceed to the next entry or read it fully,
* // otherwise the stream will stall.
* await entry.body.cancel();
* continue;
* }
*
* const reader = entry.body.getReader();
* while (true) {
* const { done, value } = await reader.read();
* if (done) break;
* processChunk(value);
* }
* }
*/
declare function createTarDecoder(options?: DecoderOptions): TransformStream<Uint8Array, ParsedTarEntry>;
//#endregion
export { type DecoderOptions, type ParsedTarEntry, type ParsedTarEntryWithData, type TarEntry, type TarEntryData, type TarHeader, type TarPackController, type UnpackOptions, createGzipDecoder, createGzipEncoder, createTarDecoder, createTarPacker, packTar, unpackTar };

281
node_modules/modern-tar/dist/web/index.js generated vendored Normal file
View File

@@ -0,0 +1,281 @@
import { a as normalizeBody, i as isBodyless, n as createTarPacker$1, r as transformHeader, t as createUnpacker } from "../unpacker-CPCEF5CT.js";
//#region src/web/compression.ts
function createGzipEncoder() {
return new CompressionStream("gzip");
}
function createGzipDecoder() {
return new DecompressionStream("gzip");
}
//#endregion
//#region src/web/pack.ts
function createTarPacker() {
let streamController;
let packer;
return {
readable: new ReadableStream({ start(controller) {
streamController = controller;
packer = createTarPacker$1(controller.enqueue.bind(controller), controller.error.bind(controller), controller.close.bind(controller));
} }),
controller: {
add(header) {
const bodyless = isBodyless(header);
packer.add(header);
if (bodyless) packer.endEntry();
return new WritableStream({
write(chunk) {
packer.write(chunk);
},
close() {
if (!bodyless) packer.endEntry();
},
abort(reason) {
streamController.error(reason);
}
});
},
finalize() {
packer.finalize();
},
error(err) {
streamController.error(err);
}
}
};
}
//#endregion
//#region src/web/stream-utils.ts
async function streamToBuffer(stream) {
const chunks = [];
const reader = stream.getReader();
let totalLength = 0;
try {
while (true) {
const { done, value } = await reader.read();
if (done) break;
chunks.push(value);
totalLength += value.length;
}
const result = new Uint8Array(totalLength);
let offset = 0;
for (const chunk of chunks) {
result.set(chunk, offset);
offset += chunk.length;
}
return result;
} finally {
reader.releaseLock();
}
}
const drain = (stream) => stream.pipeTo(new WritableStream());
//#endregion
//#region src/web/unpack.ts
function createTarDecoder(options = {}) {
const unpacker = createUnpacker(options);
const strict = options.strict ?? false;
let controller = null;
let bodyController = null;
let pumping = false;
let eofReached = false;
let sourceEnded = false;
let closed = false;
const closeBody = () => {
try {
bodyController?.close();
} catch {}
bodyController = null;
};
const fail = (reason) => {
if (closed) return;
closed = true;
try {
bodyController?.error(reason);
} catch {}
bodyController = null;
try {
controller.error(reason);
} catch {}
controller = null;
};
const finish = () => {
if (closed) return;
closed = true;
closeBody();
try {
controller.close();
} catch {}
controller = null;
};
const truncateOrFinish = () => {
if (strict) throw new Error("Tar archive is truncated.");
finish();
};
const pump = () => {
if (pumping || closed || !controller) return;
pumping = true;
try {
while (true) {
if (eofReached) {
if (sourceEnded) {
unpacker.validateEOF();
finish();
}
break;
}
if (unpacker.isEntryActive()) {
if (sourceEnded && !unpacker.canFinish()) {
truncateOrFinish();
break;
}
if (bodyController) {
if ((bodyController.desiredSize ?? 1) <= 0) break;
if (unpacker.streamBody((c) => (bodyController.enqueue(c), (bodyController.desiredSize ?? 1) > 0)) === 0 && !unpacker.isBodyComplete()) {
if (sourceEnded) truncateOrFinish();
break;
}
} else if (!unpacker.skipEntry()) {
if (sourceEnded) truncateOrFinish();
break;
}
if (unpacker.isBodyComplete()) {
closeBody();
if (!unpacker.skipPadding()) {
if (sourceEnded) truncateOrFinish();
break;
}
}
} else {
if ((controller.desiredSize ?? 0) < 0) break;
const header = unpacker.readHeader();
if (header === null) {
if (sourceEnded) finish();
break;
}
if (header === void 0) {
if (sourceEnded) {
unpacker.validateEOF();
finish();
break;
}
eofReached = true;
break;
}
controller.enqueue({
header,
body: new ReadableStream({
start(c) {
if (header.size === 0) c.close();
else bodyController = c;
},
pull: pump,
cancel() {
bodyController = null;
pump();
}
})
});
}
}
} catch (error) {
fail(error);
throw error;
} finally {
pumping = false;
}
};
return {
readable: new ReadableStream({
start(c) {
controller = c;
},
pull: pump,
cancel(reason) {
if (reason !== void 0) fail(reason);
else finish();
}
}, { highWaterMark: 2 }),
writable: new WritableStream({
write(chunk) {
try {
if (eofReached && strict && chunk.some((byte) => byte !== 0)) throw new Error("Invalid EOF.");
unpacker.write(chunk);
pump();
} catch (error) {
fail(error);
throw error;
}
},
close() {
try {
sourceEnded = true;
unpacker.end();
pump();
} catch (error) {
fail(error);
throw error;
}
},
abort(reason) {
fail(reason);
}
})
};
}
//#endregion
//#region src/web/helpers.ts
async function packTar(entries) {
const { readable, controller } = createTarPacker();
await (async () => {
for (const entry of entries) {
const entryStream = controller.add(entry.header);
const body = "body" in entry ? entry.body : entry.data;
if (!body) {
await entryStream.close();
continue;
}
if (body instanceof ReadableStream) await body.pipeTo(entryStream);
else if (body instanceof Blob) await body.stream().pipeTo(entryStream);
else try {
const chunk = await normalizeBody(body);
if (chunk.length > 0) {
const writer = entryStream.getWriter();
await writer.write(chunk);
await writer.close();
} else await entryStream.close();
} catch {
throw new TypeError(`Unsupported content type for entry "${entry.header.name}".`);
}
}
})().then(() => controller.finalize()).catch((err) => controller.error(err));
return new Uint8Array(await streamToBuffer(readable));
}
async function unpackTar(archive, options = {}) {
const sourceStream = archive instanceof ReadableStream ? archive : new ReadableStream({ start(controller) {
controller.enqueue(archive instanceof Uint8Array ? archive : new Uint8Array(archive));
controller.close();
} });
const results = [];
const entryStream = sourceStream.pipeThrough(createTarDecoder(options));
for await (const entry of entryStream) {
let processedHeader;
try {
processedHeader = transformHeader(entry.header, options);
} catch (error) {
await entry.body.cancel();
throw error;
}
if (processedHeader === null) {
await drain(entry.body);
continue;
}
if (isBodyless(processedHeader)) {
await drain(entry.body);
results.push({ header: processedHeader });
} else results.push({
header: processedHeader,
data: await streamToBuffer(entry.body)
});
}
return results;
}
//#endregion
export { createGzipDecoder, createGzipEncoder, createTarDecoder, createTarPacker, packTar, unpackTar };