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

21
node_modules/modern-tar/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2025 Ayuhito
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

277
node_modules/modern-tar/README.md generated vendored Normal file
View File

@@ -0,0 +1,277 @@
# 🗄️ modern-tar
Zero-dependency, cross-platform, streaming tar archive library for every JavaScript runtime. Built with the browser-native Web Streams API for performance and memory efficiency.
## Features
- 🚀 **Streaming Architecture** - Supports large archives without loading everything into memory.
- 📋 **Standards Compliant** - Full USTAR format support with PAX extensions. Compatible with GNU tar, BSD tar, and other standard implementations.
- 🗜️ **Compression** - Includes helpers for gzip compression/decompression.
- 📝 **TypeScript First** - Full type safety with detailed TypeDoc documentation.
-**Zero Dependencies** - No external dependencies, minimal bundle size.
- 🌐 **Cross-Platform** - Works in browsers, Node.js, Cloudflare Workers, and other JavaScript runtimes.
- 📁 **Node.js Integration** - Additional high-level APIs for directory packing and extraction.
## Installation
```sh
npm install modern-tar
```
## Usage
This package provides two entry points:
- `modern-tar`: The core, cross-platform streaming API (works everywhere).
- `modern-tar/fs`: High-level filesystem utilities for Node.js.
### Core Usage
These APIs use the Web Streams API and can be used in any modern JavaScript environment.
#### Simple
```typescript
import { packTar, unpackTar } from 'modern-tar';
// Pack entries into a tar buffer
const entries = [
{ header: { name: "file.txt", size: 5 }, body: "hello" },
{ header: { name: "dir/", type: "directory", size: 0 } },
{ header: { name: "dir/nested.txt", size: 3 }, body: new Uint8Array([97, 98, 99]) } // "abc"
];
// Accepts string, Uint8Array, Blob, ReadableStream<Uint8Array> and more...
const tarBuffer = await packTar(entries);
// Unpack tar buffer into entries
const entries = await unpackTar(tarBuffer);
for (const entry of entries) {
console.log(`File: ${entry.header.name}`);
const content = new TextDecoder().decode(entry.data);
console.log(`Content: ${content}`);
}
```
#### Streaming
```typescript
import { createTarPacker, createTarDecoder } from 'modern-tar';
// Create a tar packer
const { readable, controller } = createTarPacker();
// Add entries dynamically
const fileStream = controller.add({
name: "dynamic.txt",
size: 5,
type: "file"
});
// Write content to the stream
const writer = fileStream.getWriter();
await writer.write(new TextEncoder().encode("hello"));
await writer.close();
// When done adding entries, finalize the archive
controller.finalize();
// Pipe the archive right into a decoder
const decodedStream = readable.pipeThrough(createTarDecoder());
for await (const entry of decodedStream) {
console.log(`Decoded: ${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);
}
}
```
#### Compression/Decompression (gzip)
```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' }
});
```
```typescript
import { createGzipDecoder, createTarDecoder, unpackTar } 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}`);
}
// Or 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
}
```
### Node.js Filesystem Usage
These APIs use Node.js streams when interacting with the local filesystem.
#### Simple
```typescript
import { packTar, unpackTar } from 'modern-tar/fs';
import { createWriteStream, createReadStream } from 'node:fs';
import { pipeline } from 'node:stream/promises';
// Pack a directory into a tar file
const tarStream = packTar('./my/project');
const fileStream = createWriteStream('./project.tar');
await pipeline(tarStream, fileStream);
// Extract a tar file to a directory
const tarReadStream = createReadStream('./project.tar', {
highWaterMark: 256 * 1024 // 256 KB for optimal performance
});
const extractStream = unpackTar('./output/directory');
await pipeline(tarReadStream, extractStream);
```
#### Filtering and Transformation
```typescript
import { packTar, unpackTar } from 'modern-tar/fs';
import { createReadStream } from 'node:fs';
import { pipeline } from 'node:stream/promises';
// Pack with filtering
const packStream = packTar('./my/project', {
filter: (filePath, stats) => !filePath.includes('node_modules'),
map: (header) => ({ ...header, mode: 0o644 }), // Set all files to 644
dereference: true // Follow symlinks instead of archiving them
});
// Unpack with advanced options
const sourceStream = createReadStream('./archive.tar', {
highWaterMark: 256 * 1024 // 256 KB for optimal performance
});
const extractStream = unpackTar('./output', {
// Core options
strip: 1, // Remove first directory level
filter: (header) => header.name.endsWith('.js'), // Only extract JS files
map: (header) => ({ ...header, name: header.name.toLowerCase() }), // Transform names
// Filesystem-specific options
fmode: 0o644, // Override file permissions
dmode: 0o755, // Override directory permissions
maxDepth: 50, // Limit extraction depth for security (default: 1024)
concurrency: 8 // Limit concurrent filesystem operations (default: CPU cores)
});
await pipeline(sourceStream, extractStream);
```
#### Archive Creation
```typescript
import { packTar, type TarSource } from 'modern-tar/fs';
import { createWriteStream } from 'node:fs';
import { pipeline } from 'node:stream/promises';
// Pack multiple sources
const sources: TarSource[] = [
{ 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' },
{ type: 'content', content: '#!/bin/bash\necho "Executable"', target: 'bin/script.sh', mode: 0o755 },
{ type: 'stream', content: createReadStream('./large-file.bin'), target: 'project/data.bin', size: 1048576 },
{ type: 'stream', content: fetch('/api/data').then(r => r.body!), target: 'project/remote.json', size: 2048 }
];
const archiveStream = packTar(sources);
await pipeline(archiveStream, createWriteStream('project.tar'));
```
#### Compression/Decompression (gzip)
```typescript
import { packTar, unpackTar } from 'modern-tar/fs';
import { createWriteStream, createReadStream } from 'node:fs';
import { createGzip, createGunzip } from 'node:zlib';
import { pipeline } from 'node:stream/promises';
// Pack directory and compress to .tar.gz
const tarStream = packTar('./my/project');
await pipeline(tarStream, createGzip(), createWriteStream('./project.tar.gz'));
// Decompress and extract .tar.gz
const gzipStream = createReadStream('./project.tar.gz', {
highWaterMark: 256 * 1024 // 256 KB for optimal performance
});
await pipeline(gzipStream, createGunzip(), unpackTar('./output'));
```
## API Reference
See the [API Reference](./REFERENCE.md).
# Benchmarks
Current benchmarks indicate we're much faster than other popular tar libraries for small file archives (packing and unpacking). On the other hand, larger files hit an I/O bottleneck resulting in similar performance between libraries.
See the [Results](./benchmarks/README.md).
## Compatibility
The core library uses the [Web Streams API](https://caniuse.com/streams) and requires:
- **Node.js**: 18.0+
- **Browsers**: Modern browsers with Web Streams support
- Chrome 71+
- Firefox 102+
- Safari 14.1+
- Edge 79+
## Acknowledgements
- [`tar-stream`](https://github.com/mafintosh/tar-stream) and [`tar-fs`](https://github.com/mafintosh/tar-fs) - For the inspiration and test fixtures.
## License
MIT

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 };

65
node_modules/modern-tar/package.json generated vendored Normal file
View File

@@ -0,0 +1,65 @@
{
"name": "modern-tar",
"version": "0.7.6",
"description": "Zero dependency streaming tar parser and writer for JavaScript.",
"author": "Ayuhito <hello@ayuhito.com>",
"license": "MIT",
"type": "module",
"sideEffects": false,
"main": "./dist/web/index.js",
"module": "./dist/web/index.js",
"types": "./dist/web/index.d.ts",
"exports": {
"./package.json": "./package.json",
".": "./dist/web/index.js",
"./fs": "./dist/fs/index.js"
},
"typesVersions": {
"*": {
"fs": [
"dist/fs/index.d.ts"
],
"*": [
"dist/web/index.d.ts"
]
}
},
"devDependencies": {
"@biomejs/biome": "2.4.7",
"@types/node": "^25.5.0",
"@vitest/browser-playwright": "4.1.0",
"@vitest/coverage-v8": "4.1.0",
"miniflare": "^4.20260312.0",
"tsdown": "^0.21.3",
"typescript": "^5.9.3",
"vitest": "4.1.0"
},
"scripts": {
"build": "tsdown",
"dev": "tsdown --watch",
"test": "vitest",
"test:workers": "tsdown && vitest --config vitest.workers.config.ts --run",
"coverage": "vitest run --coverage",
"check": "biome check --write",
"typecheck": "tsc --noEmit",
"test:browser": "vitest --config=vitest.browser.config.ts --browser"
},
"files": [
"dist",
"README.md"
],
"homepage": "https://github.com/ayuhito/modern-tar",
"bugs": {
"url": "https://github.com/ayuhito/modern-tar/issues"
},
"repository": {
"type": "git",
"url": "git+https://github.com/ayuhito/modern-tar.git"
},
"publishConfig": {
"access": "public"
},
"engines": {
"node": ">=18.0.0"
}
}