modern-tar 0.4.2 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -205,8 +205,11 @@ import { pipeline } from 'node:stream/promises';
205
205
  const sources: TarSource[] = [
206
206
  { type: 'file', source: './package.json', target: 'project/package.json' },
207
207
  { type: 'directory', source: './src', target: 'project/src' },
208
+
208
209
  { type: 'content', content: 'Hello World!', target: 'project/hello.txt' },
209
- { type: 'content', content: '#!/bin/bash\necho "Executable"', target: 'bin/script.sh', mode: 0o755 }
210
+ { type: 'content', content: '#!/bin/bash\necho "Executable"', target: 'bin/script.sh', mode: 0o755 },
211
+ { type: 'stream', content: createReadStream('./large-file.bin'), target: 'project/data.bin', size: 1048576 },
212
+ { type: 'stream', content: fetch('/api/data').then(r => r.body!), target: 'project/remote.json', size: 2048 }
210
213
  ];
211
214
 
212
215
  const archiveStream = packTar(sources);
@@ -1,4 +1,4 @@
1
- import { TarEntryData, TarHeader, UnpackOptions } from "../types-D-xPQp4Z.js";
1
+ import { TarEntryData, TarHeader, UnpackOptions } from "../types-Dc3p5B3s.js";
2
2
  import { Readable, Writable } from "node:stream";
3
3
  import { Stats } from "node:fs";
4
4
 
@@ -50,34 +50,51 @@ interface UnpackOptionsFS extends UnpackOptions {
50
50
  */
51
51
  concurrency?: number;
52
52
  }
53
+ /** Base interface containing common metadata properties for all source types. */
54
+ interface BaseSource {
55
+ /** Destination path for the entry inside the tar archive. */
56
+ target: string;
57
+ /** Optional modification time. Overrides filesystem values or defaults to current time. */
58
+ mtime?: Date;
59
+ /** Optional user ID. Overrides filesystem values or defaults to 0. */
60
+ uid?: number;
61
+ /** Optional group ID. Overrides filesystem values or defaults to 0. */
62
+ gid?: number;
63
+ /** Optional user name. */
64
+ uname?: string;
65
+ /** Optional group name. */
66
+ gname?: string;
67
+ /** Optional Unix file permissions for the entry (e.g., 0o644, 0o755). */
68
+ mode?: number;
69
+ }
53
70
  /** Describes a file on the local filesystem to be added to the archive. */
54
- interface FileSource {
71
+ interface FileSource extends BaseSource {
55
72
  type: "file";
56
73
  /** Path to the source file on the local filesystem. */
57
74
  source: string;
58
- /** Destination path for the file inside the tar archive. */
59
- target: string;
60
75
  }
61
76
  /** Describes a directory on the local filesystem to be added to the archive. */
62
- interface DirectorySource {
77
+ interface DirectorySource extends BaseSource {
63
78
  type: "directory";
64
79
  /** Path to the source directory on the local filesystem. */
65
80
  source: string;
66
- /** Destination path for the directory inside the tar archive. */
67
- target: string;
68
81
  }
69
- /** Describes raw content to be added to the archive. Supports all TarEntryData types including strings, buffers, streams, blobs, and null. */
70
- interface ContentSource {
82
+ /** Describes raw, buffered content to be added to the archive. */
83
+ interface ContentSource extends BaseSource {
71
84
  type: "content";
72
- /** Raw content to add. Supports string, Uint8Array, ArrayBuffer, ReadableStream, Blob, or null. */
85
+ /** Raw content to add. Supports string, Uint8Array, ArrayBuffer, Blob, or null. */
73
86
  content: TarEntryData;
74
- /** Destination path for the content inside the tar archive. */
75
- target: string;
76
- /** Optional Unix file permissions for the entry (e.g., 0o644). */
77
- mode?: number;
87
+ }
88
+ /** Describes a stream of content to be added to the archive. */
89
+ interface StreamSource extends BaseSource {
90
+ type: "stream";
91
+ /** A Readable or ReadableStream. */
92
+ content: Readable | ReadableStream;
93
+ /** The total size of the stream's content in bytes. This is required for streams. */
94
+ size: number;
78
95
  }
79
96
  /** A union of all possible source types for creating a tar archive. */
80
- type TarSource = FileSource | DirectorySource | ContentSource;
97
+ type TarSource = FileSource | DirectorySource | ContentSource | StreamSource;
81
98
  //#endregion
82
99
  //#region src/fs/pack.d.ts
83
100
  /**
package/dist/fs/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { createTarPacker, createTarUnpacker, normalizeBody, transformHeader } from "../unpacker-DBTDVhe4.js";
1
+ import { createTarPacker, createTarUnpacker, normalizeBody, transformHeader } from "../unpacker-yB6Ahxxk.js";
2
2
  import * as fs from "node:fs/promises";
3
3
  import { cpus } from "node:os";
4
4
  import * as path from "node:path";
@@ -46,7 +46,16 @@ function packTar(sources, options = {}) {
46
46
  packer.add(result.header);
47
47
  if (result.body) if (result.body instanceof Uint8Array) {
48
48
  if (result.body.length > 0) packer.write(result.body);
49
- } else {
49
+ } else if (result.body instanceof Readable || result.body instanceof ReadableStream) try {
50
+ for await (const chunk of result.body) {
51
+ if (stream.destroyed) break;
52
+ packer.write(chunk instanceof Uint8Array ? chunk : Buffer.from(chunk));
53
+ }
54
+ } catch (error) {
55
+ stream.destroy(error);
56
+ return;
57
+ }
58
+ else {
50
59
  const { handle, size } = result.body;
51
60
  try {
52
61
  let bytesLeft = size;
@@ -87,32 +96,44 @@ function packTar(sources, options = {}) {
87
96
  let jobResult = null;
88
97
  const target = job.target.replace(/\\/g, "/");
89
98
  try {
90
- if (job.type === "content") {
91
- const data = await normalizeBody(job.content);
99
+ if (job.type === "content" || job.type === "stream") {
100
+ let body$1;
101
+ let size;
102
+ if (job.type === "stream") {
103
+ if (typeof job.size !== "number" || job.size <= 0) throw new Error("StreamSource requires a positive size property.");
104
+ size = job.size;
105
+ body$1 = job.content;
106
+ } else {
107
+ const content = await normalizeBody(job.content);
108
+ size = content.length;
109
+ body$1 = content;
110
+ }
92
111
  const stat$1 = {
93
- size: data.length,
112
+ size,
94
113
  isFile: () => true,
95
114
  isDirectory: () => false,
96
115
  isSymbolicLink: () => false,
97
116
  mode: job.mode ?? 420,
98
- mtime: /* @__PURE__ */ new Date(),
99
- uid: process.getuid?.() ?? 0,
100
- gid: process.getgid?.() ?? 0
117
+ mtime: job.mtime ?? /* @__PURE__ */ new Date(),
118
+ uid: job.uid ?? 0,
119
+ gid: job.gid ?? 0
101
120
  };
102
121
  if (filter && !filter(target, stat$1)) return;
103
122
  let header$1 = {
104
123
  name: target,
105
124
  type: "file",
106
- size: stat$1.size,
125
+ size,
107
126
  mode: stat$1.mode,
108
127
  mtime: stat$1.mtime,
109
128
  uid: stat$1.uid,
110
- gid: stat$1.gid
129
+ gid: stat$1.gid,
130
+ uname: job.uname,
131
+ gname: job.gname
111
132
  };
112
133
  if (map) header$1 = map(header$1);
113
134
  jobResult = {
114
135
  header: header$1,
115
- body: data
136
+ body: body$1
116
137
  };
117
138
  return;
118
139
  }
@@ -128,10 +149,12 @@ function packTar(sources, options = {}) {
128
149
  let header = {
129
150
  name: target,
130
151
  size: 0,
131
- mode: Number(stat.mode),
132
- mtime: stat.mtime,
133
- uid: Number(stat.uid),
134
- gid: Number(stat.gid),
152
+ mode: job.mode ?? Number(stat.mode),
153
+ mtime: job.mtime ?? stat.mtime,
154
+ uid: job.uid ?? Number(stat.uid),
155
+ gid: job.gid ?? Number(stat.gid),
156
+ uname: job.uname,
157
+ gname: job.gname,
135
158
  type: "file"
136
159
  };
137
160
  let body;
@@ -46,31 +46,7 @@ interface TarHeader {
46
46
  /**
47
47
  * Union type for entry body data that can be packed into a tar archive.
48
48
  */
49
- type TarEntryData = string | Uint8Array | ArrayBuffer | ReadableStream<Uint8Array> | Blob | null | undefined;
50
- /**
51
- * Represents a complete entry to be packed into a tar archive.
52
- *
53
- * Combines header metadata with optional body data. Used as input to {@link packTar}
54
- * and the controller returned by {@link createTarPacker}.
55
- */
56
- interface TarEntry {
57
- header: TarHeader;
58
- body?: TarEntryData;
59
- }
60
- /**
61
- * Represents an entry parsed from a tar archive stream.
62
- */
63
- interface ParsedTarEntry {
64
- header: TarHeader;
65
- body: ReadableStream<Uint8Array>;
66
- }
67
- /**
68
- * Represents an extracted entry with fully buffered content.
69
- */
70
- interface ParsedTarEntryWithData {
71
- header: TarHeader;
72
- data: Uint8Array;
73
- }
49
+ type TarEntryData = string | Uint8Array | ArrayBuffer | Blob | null | undefined;
74
50
  /**
75
51
  * Configuration options for creating a tar decoder stream.
76
52
  */
@@ -105,4 +81,4 @@ interface UnpackOptions extends DecoderOptions {
105
81
  streamTimeout?: number;
106
82
  }
107
83
  //#endregion
108
- export { DecoderOptions, ParsedTarEntry, ParsedTarEntryWithData, TarEntry, TarEntryData, TarHeader, UnpackOptions };
84
+ export { DecoderOptions, TarEntryData, TarHeader, UnpackOptions };
@@ -93,8 +93,10 @@ function readOctal(view, offset, size) {
93
93
  function readNumeric(view, offset, size) {
94
94
  if (view[offset] & 128) {
95
95
  let result = 0;
96
- for (let i = 0; i < size; i++) result = result << 8 | view[offset + i];
97
- return result & ~(128 << (size - 1) * 8);
96
+ result = view[offset] & 127;
97
+ for (let i = 1; i < size; i++) result = result * 256 + view[offset + i];
98
+ if (!Number.isSafeInteger(result)) throw new Error("TAR number too large");
99
+ return result;
98
100
  }
99
101
  return readOctal(view, offset, size);
100
102
  }
@@ -126,7 +128,6 @@ async function normalizeBody(body) {
126
128
  if (typeof body === "string") return encoder.encode(body);
127
129
  if (body instanceof ArrayBuffer) return new Uint8Array(body);
128
130
  if (body instanceof Blob) return new Uint8Array(await body.arrayBuffer());
129
- if (body instanceof ReadableStream) return streamToBuffer(body);
130
131
  throw new TypeError("Unsupported content type for entry body.");
131
132
  }
132
133
 
@@ -253,6 +254,16 @@ function parseUstarHeader(block, strict) {
253
254
  if (magic === "ustar") header.prefix = readString(block, USTAR_PREFIX_OFFSET, USTAR_PREFIX_SIZE);
254
255
  return header;
255
256
  }
257
+ const PAX_MAPPING = {
258
+ path: ["name", (v) => v],
259
+ linkpath: ["linkname", (v) => v],
260
+ size: ["size", (v) => parseInt(v, 10)],
261
+ mtime: ["mtime", parseFloat],
262
+ uid: ["uid", (v) => parseInt(v, 10)],
263
+ gid: ["gid", (v) => parseInt(v, 10)],
264
+ uname: ["uname", (v) => v],
265
+ gname: ["gname", (v) => v]
266
+ };
256
267
  function parsePax(buffer) {
257
268
  const decoder$1 = new TextDecoder("utf-8");
258
269
  const overrides = {};
@@ -267,31 +278,11 @@ function parsePax(buffer) {
267
278
  const [key, value] = decoder$1.decode(buffer.subarray(spaceIndex + 1, recordEnd - 1)).split("=", 2);
268
279
  if (key && value !== void 0) {
269
280
  pax[key] = value;
270
- switch (key) {
271
- case "path":
272
- overrides.name = value;
273
- break;
274
- case "linkpath":
275
- overrides.linkname = value;
276
- break;
277
- case "size":
278
- overrides.size = parseInt(value, 10);
279
- break;
280
- case "mtime":
281
- overrides.mtime = parseFloat(value);
282
- break;
283
- case "uid":
284
- overrides.uid = parseInt(value, 10);
285
- break;
286
- case "gid":
287
- overrides.gid = parseInt(value, 10);
288
- break;
289
- case "uname":
290
- overrides.uname = value;
291
- break;
292
- case "gname":
293
- overrides.gname = value;
294
- break;
281
+ const mapping = PAX_MAPPING[key];
282
+ if (mapping) {
283
+ const [targetKey, parser] = mapping;
284
+ const parsedValue = parser(value);
285
+ if (typeof parsedValue === "string" || !Number.isNaN(parsedValue)) overrides[targetKey] = parsedValue;
295
286
  }
296
287
  }
297
288
  offset = recordEnd;
@@ -1,4 +1,4 @@
1
- import { DecoderOptions, ParsedTarEntry, ParsedTarEntryWithData, TarEntry, TarEntryData, TarHeader, UnpackOptions } from "../types-D-xPQp4Z.js";
1
+ import { DecoderOptions, TarEntryData, TarHeader, UnpackOptions } from "../types-Dc3p5B3s.js";
2
2
 
3
3
  //#region src/web/compression.d.ts
4
4
 
@@ -72,6 +72,32 @@ declare function createGzipEncoder(): ReadableWritablePair<Uint8Array, Uint8Arra
72
72
  */
73
73
  declare function createGzipDecoder(): ReadableWritablePair<Uint8Array, Uint8Array>;
74
74
  //#endregion
75
+ //#region src/web/types.d.ts
76
+ /**
77
+ * Represents a complete entry to be packed into a tar archive.
78
+ *
79
+ * Combines header metadata with optional body data. Used as input to {@link packTar}
80
+ * and the controller returned by {@link createTarPacker}.
81
+ */
82
+ interface TarEntry {
83
+ header: TarHeader;
84
+ body?: TarEntryData | ReadableStream<Uint8Array>;
85
+ }
86
+ /**
87
+ * Represents an entry parsed from a tar archive stream.
88
+ */
89
+ interface ParsedTarEntry {
90
+ header: TarHeader;
91
+ body: ReadableStream<Uint8Array>;
92
+ }
93
+ /**
94
+ * Represents an extracted entry with fully buffered content.
95
+ */
96
+ interface ParsedTarEntryWithData {
97
+ header: TarHeader;
98
+ data: Uint8Array;
99
+ }
100
+ //#endregion
75
101
  //#region src/web/helpers.d.ts
76
102
  /**
77
103
  * Packs an array of tar entries into a single `Uint8Array` buffer.
package/dist/web/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { createTarPacker as createTarPacker$1, createTarUnpacker, normalizeBody, streamToBuffer, transformHeader } from "../unpacker-DBTDVhe4.js";
1
+ import { createTarPacker as createTarPacker$1, createTarUnpacker, normalizeBody, streamToBuffer, transformHeader } from "../unpacker-yB6Ahxxk.js";
2
2
 
3
3
  //#region src/web/compression.ts
4
4
  function createGzipEncoder() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "modern-tar",
3
- "version": "0.4.2",
3
+ "version": "0.5.1",
4
4
  "description": "Zero dependency streaming tar parser and writer for JavaScript.",
5
5
  "author": "Ayuhito <hello@ayuhito.com>",
6
6
  "license": "MIT",