dir-archiver 3.0.0 → 3.0.2

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/dist/errors.d.ts CHANGED
@@ -1,25 +1,69 @@
1
1
  /**
2
- * Stable dir-archiver error codes.
2
+ * Stable machine-readable error codes emitted by `dir-archiver`.
3
+ *
4
+ * - `DIRARCHIVER_INVALID_SOURCE`: input bytes or paths could not be read.
5
+ * - `DIRARCHIVER_INVALID_DESTINATION`: destination path or parent directory is
6
+ * invalid for the requested operation.
7
+ * - `DIRARCHIVER_PATH_TRAVERSAL`: strict extraction rejected a traversal or
8
+ * absolute-path entry.
9
+ * - `DIRARCHIVER_UNSUPPORTED_ENTRY`: the archive contains an entry type or
10
+ * feature that `dir-archiver` does not support safely.
11
+ * - `DIRARCHIVER_RESOURCE_LIMIT`: extraction exceeded configured byte limits.
12
+ * - `DIRARCHIVER_RUNTIME_UNSUPPORTED`: the current runtime cannot satisfy a
13
+ * required bytefold capability.
14
+ * - `DIRARCHIVER_NORMALIZE_UNSUPPORTED`: normalization is unavailable for the
15
+ * selected format/runtime pair.
16
+ * - `DIRARCHIVER_USAGE`: CLI invocation is missing required flags or uses
17
+ * unsupported values.
3
18
  */
4
19
  export type DirArchiverErrorCode = 'DIRARCHIVER_INVALID_SOURCE' | 'DIRARCHIVER_INVALID_DESTINATION' | 'DIRARCHIVER_PATH_TRAVERSAL' | 'DIRARCHIVER_UNSUPPORTED_ENTRY' | 'DIRARCHIVER_RESOURCE_LIMIT' | 'DIRARCHIVER_RUNTIME_UNSUPPORTED' | 'DIRARCHIVER_NORMALIZE_UNSUPPORTED' | 'DIRARCHIVER_USAGE';
20
+ /**
21
+ * Stable JSON payload emitted by `DirArchiverError.toJSON()`.
22
+ *
23
+ * This is the machine-readable error shape used by the CLI `--json` surface and
24
+ * by API consumers that persist `DirArchiverError` objects to logs or reports.
25
+ */
26
+ export interface DirArchiverErrorJson {
27
+ /** Schema version for the serialized error payload. */
28
+ schemaVersion: '1';
29
+ /** Stable error class name used in serialized output. */
30
+ name: 'DirArchiverError';
31
+ /** Stable machine-readable error code. */
32
+ code: DirArchiverErrorCode;
33
+ /** Human-readable summary of the failure. */
34
+ message: string;
35
+ /** Optional remediation hint when the error carries one. */
36
+ hint?: string;
37
+ /** Optional structured context for logs and diagnostics. */
38
+ context?: Record<string, unknown>;
39
+ }
5
40
  /**
6
41
  * Structured error contract for dir-archiver v3.
7
42
  */
8
43
  export declare class DirArchiverError extends Error {
44
+ /** Stable machine-readable error code. */
9
45
  readonly code: DirArchiverErrorCode;
46
+ /** Optional operator-facing hint for remediation. */
10
47
  readonly hint: string | undefined;
48
+ /** Optional structured context for logs, JSON output, or diagnostics. */
11
49
  readonly context: Record<string, unknown> | undefined;
50
+ /**
51
+ * Creates a structured error value safe for CLI and API consumers.
52
+ *
53
+ * @param code Stable machine-readable error code.
54
+ * @param message Human-readable summary of the failure.
55
+ * @param options Optional hint, structured context, and nested cause.
56
+ */
12
57
  constructor(code: DirArchiverErrorCode, message: string, options?: {
13
58
  hint?: string | undefined;
14
59
  context?: Record<string, unknown> | undefined;
15
60
  cause?: unknown;
16
61
  });
17
- toJSON(): {
18
- schemaVersion: '1';
19
- name: 'DirArchiverError';
20
- code: DirArchiverErrorCode;
21
- message: string;
22
- hint?: string;
23
- context?: Record<string, unknown>;
24
- };
62
+ /**
63
+ * Serializes the error into the stable JSON shape used by the CLI.
64
+ *
65
+ * The returned object always includes `schemaVersion`, `name`, `code`, and
66
+ * `message`. Optional `hint` and `context` keys are omitted when unset.
67
+ */
68
+ toJSON(): DirArchiverErrorJson;
25
69
  }
package/dist/errors.js CHANGED
@@ -2,6 +2,13 @@
2
2
  * Structured error contract for dir-archiver v3.
3
3
  */
4
4
  export class DirArchiverError extends Error {
5
+ /**
6
+ * Creates a structured error value safe for CLI and API consumers.
7
+ *
8
+ * @param code Stable machine-readable error code.
9
+ * @param message Human-readable summary of the failure.
10
+ * @param options Optional hint, structured context, and nested cause.
11
+ */
5
12
  constructor(code, message, options = {}) {
6
13
  super(message);
7
14
  this.name = 'DirArchiverError';
@@ -12,6 +19,12 @@ export class DirArchiverError extends Error {
12
19
  this.cause = options.cause;
13
20
  }
14
21
  }
22
+ /**
23
+ * Serializes the error into the stable JSON shape used by the CLI.
24
+ *
25
+ * The returned object always includes `schemaVersion`, `name`, `code`, and
26
+ * `message`. Optional `hint` and `context` keys are omitted when unset.
27
+ */
15
28
  toJSON() {
16
29
  return {
17
30
  schemaVersion: '1',
package/dist/index.d.ts CHANGED
@@ -1,19 +1,40 @@
1
1
  /**
2
- * dir-archiver v3 API surface.
2
+ * Deterministic directory archiving and extraction over zip, tar, and layered compression.
3
3
  *
4
- * v3 is a bytefold-backed orchestration layer that supports Node.js, Deno, and Bun.
4
+ * Supports Node.js, Deno, and Bun through one API surface backed by bytefold.
5
5
  */
6
6
  import { audit, detect, extract, list, normalize, open, write } from './core.js';
7
7
  export { audit, detect, extract, list, normalize, open, write };
8
8
  export { DirArchiverError } from './errors.js';
9
- export type { ArchiveFormat, ArchiveLimits, ArchiveProfile, CliUsageError, DetectResult, DirArchiverInput, ExtractOptions, ExtractResult, ListEntry, ListResult, NormalizeOptions, NormalizeResult, OpenOptions, SupportedCommandMap, WriteOptions, WriteResult } from './types.js';
10
- declare const api: {
11
- open: (input: import("./types.js").DirArchiverInput, options?: import("./types.js").OpenOptions) => Promise<import("@ismail-elkorchi/bytefold").ArchiveReader>;
12
- detect: (input: import("./types.js").DirArchiverInput, options?: import("./types.js").OpenOptions) => Promise<import("./types.js").DetectResult>;
13
- list: (input: import("./types.js").DirArchiverInput, options?: import("./types.js").OpenOptions) => Promise<import("./types.js").ListResult>;
14
- audit: (input: import("./types.js").DirArchiverInput, options?: import("./types.js").OpenOptions) => Promise<import("@ismail-elkorchi/bytefold").ArchiveAuditReport>;
15
- normalize: (input: import("./types.js").DirArchiverInput, destination: string, options?: import("./types.js").NormalizeOptions) => Promise<import("./types.js").NormalizeResult>;
16
- extract: (input: import("./types.js").DirArchiverInput, destination: string, options?: import("./types.js").ExtractOptions) => Promise<import("./types.js").ExtractResult>;
17
- write: (source: string, destination: string, options?: import("./types.js").WriteOptions) => Promise<import("./types.js").WriteResult>;
18
- };
9
+ export type { DirArchiverErrorCode, DirArchiverErrorJson } from './errors.js';
10
+ export type { ArchiveFormat, ArchiveLimits, ArchiveDetectionReport, ArchiveIssue, ArchiveNormalizeReport, ArchiveProfile, CliUsageError, DetectResult, DirArchiverInput, ExtractOptions, ExtractResult, ListEntry, ListResult, NormalizeOptions, NormalizeResult, OpenOptions, SupportedCommandMap, WriteOptions, WriteResult } from './types.js';
11
+ /**
12
+ * Namespace-style API contract mirrored by the default export.
13
+ *
14
+ * Consumers who prefer `import dirArchiver from "dir-archiver"` get the same
15
+ * operations and semantics as the named exports on this interface.
16
+ */
17
+ export interface DirArchiverNamespace {
18
+ /** Open an archive reader after resolving format, limits, and runtime support. */
19
+ readonly open: typeof open;
20
+ /** Detect an archive format without extracting entries. */
21
+ readonly detect: typeof detect;
22
+ /** Project archive entries into a stable listing payload. */
23
+ readonly list: typeof list;
24
+ /** Audit an archive against the requested safety profile and limits. */
25
+ readonly audit: typeof audit;
26
+ /** Rewrite an archive into its normalized deterministic representation. */
27
+ readonly normalize: typeof normalize;
28
+ /** Extract an archive to disk with explicit safety and size controls. */
29
+ readonly extract: typeof extract;
30
+ /** Write a directory or file tree into an archive. */
31
+ readonly write: typeof write;
32
+ }
33
+ /**
34
+ * Namespace-style default export for consumers who prefer
35
+ * `import dirArchiver from "dir-archiver"`.
36
+ *
37
+ * It mirrors the named exports exactly and does not add extra behavior.
38
+ */
39
+ declare const api: DirArchiverNamespace;
19
40
  export default api;
package/dist/index.js CHANGED
@@ -1,11 +1,17 @@
1
1
  /**
2
- * dir-archiver v3 API surface.
2
+ * Deterministic directory archiving and extraction over zip, tar, and layered compression.
3
3
  *
4
- * v3 is a bytefold-backed orchestration layer that supports Node.js, Deno, and Bun.
4
+ * Supports Node.js, Deno, and Bun through one API surface backed by bytefold.
5
5
  */
6
6
  import { audit, detect, extract, list, normalize, open, write } from './core.js';
7
7
  export { audit, detect, extract, list, normalize, open, write };
8
8
  export { DirArchiverError } from './errors.js';
9
+ /**
10
+ * Namespace-style default export for consumers who prefer
11
+ * `import dirArchiver from "dir-archiver"`.
12
+ *
13
+ * It mirrors the named exports exactly and does not add extra behavior.
14
+ */
9
15
  const api = {
10
16
  open,
11
17
  detect,
package/dist/types.d.ts CHANGED
@@ -1,107 +1,210 @@
1
- import type { ArchiveDetectionReport, ArchiveFormat, ArchiveIssue, ArchiveLimits, ArchiveNormalizeReport, ArchiveOpenOptions, ArchiveProfile } from '@ismail-elkorchi/bytefold';
2
- export type { ArchiveFormat, ArchiveLimits, ArchiveProfile };
1
+ import type { ArchiveFormat, ArchiveProfile } from '@ismail-elkorchi/bytefold';
2
+ export type { ArchiveFormat, ArchiveProfile, } from '@ismail-elkorchi/bytefold';
3
+ /** Resource limit configuration accepted by `open`, `audit`, and extraction flows. */
4
+ export type ArchiveLimits = Record<string, unknown>;
5
+ /** Issue shape emitted for archive read/normalize/extract failures. */
6
+ export type ArchiveIssue = Record<string, unknown>;
7
+ /** Public detection report shape aligned with runtime diagnostics payloads. */
8
+ export type ArchiveDetectionReport = Record<string, unknown>;
9
+ /** Public normalize report shape for deterministic archive rewrites. */
10
+ export type ArchiveNormalizeReport = Record<string, unknown>;
3
11
  /**
4
12
  * Accepted input shapes for archive read operations.
13
+ *
14
+ * String paths and `URL` objects are the most common inputs, but callers can
15
+ * also supply raw bytes or web streams when the archive is already in memory.
5
16
  */
6
17
  export type DirArchiverInput = string | URL | Uint8Array | ArrayBuffer | ReadableStream<Uint8Array> | Blob;
7
18
  /**
8
- * Common options forwarded to bytefold open operations.
19
+ * Common options forwarded to bytefold archive-open operations.
20
+ *
21
+ * Used by `open()`, `detect()`, `list()`, and `audit()`.
9
22
  */
10
23
  export interface OpenOptions {
11
- format?: ArchiveOpenOptions['format'] | undefined;
12
- profile?: ArchiveOpenOptions['profile'] | undefined;
13
- isStrict?: ArchiveOpenOptions['isStrict'] | undefined;
14
- limits?: ArchiveOpenOptions['limits'] | undefined;
15
- signal?: ArchiveOpenOptions['signal'] | undefined;
16
- password?: ArchiveOpenOptions['password'] | undefined;
17
- filename?: ArchiveOpenOptions['filename'] | undefined;
24
+ /**
25
+ * Explicit format override when callers already know archive type.
26
+ */
27
+ format?: ArchiveFormat | 'auto' | undefined;
28
+ /**
29
+ * Safety profile (`compat`, `strict`, `agent`) applied during reads/audits.
30
+ */
31
+ profile?: ArchiveProfile | undefined;
32
+ /**
33
+ * Extra strictness toggle forwarded to bytefold parsing.
34
+ */
35
+ isStrict?: boolean | undefined;
36
+ /**
37
+ * Parser/resource limits enforced while opening or auditing archives.
38
+ */
39
+ limits?: ArchiveLimits | undefined;
40
+ /**
41
+ * Abort signal for cancelling in-flight async operations.
42
+ */
43
+ signal?: AbortSignal | undefined;
44
+ /**
45
+ * Password used for encrypted archives when supported by the runtime.
46
+ */
47
+ password?: string | undefined;
48
+ /**
49
+ * Filename hint used for extension-based inference with non-path inputs.
50
+ */
51
+ filename?: string | undefined;
18
52
  }
19
53
  /**
20
54
  * Format detection result.
21
55
  */
22
56
  export interface DetectResult {
57
+ /** Resolved archive format after detection. */
23
58
  format: ArchiveFormat;
59
+ /** Bytefold detection metadata, if the runtime produced it. */
24
60
  detection: ArchiveDetectionReport | undefined;
25
61
  }
26
62
  /**
27
63
  * Single archive entry projection used by list responses.
28
64
  */
29
65
  export interface ListEntry {
66
+ /** Entry format as exposed by the underlying reader. */
30
67
  format: ArchiveFormat;
68
+ /** Entry path inside the archive, normalized to forward slashes. */
31
69
  name: string;
70
+ /** Entry size encoded as a string for JSON-safe transport. */
32
71
  size: string;
72
+ /** Whether the entry materializes as a directory. */
33
73
  isDirectory: boolean;
74
+ /** Whether the entry is a symbolic link. */
34
75
  isSymlink: boolean;
76
+ /** Link target when the entry is a symbolic link. */
35
77
  linkName?: string | undefined;
36
78
  }
37
79
  /**
38
80
  * Archive listing response payload.
39
81
  */
40
82
  export interface ListResult {
83
+ /** Resolved archive format after detection/open completed. */
41
84
  format: ArchiveFormat;
85
+ /** Bytefold detection metadata used to choose `format`, when available. */
42
86
  detection: ArchiveDetectionReport | undefined;
87
+ /** Projected archive entries in archive iteration order. */
43
88
  entries: ListEntry[];
44
89
  }
90
+ /**
91
+ * Options for `audit()`.
92
+ *
93
+ * Alias of `OpenOptions` for stable API typing; CLI-only flags (for example
94
+ * `--json`) are not part of this programmatic surface.
95
+ */
45
96
  export type AuditOptions = OpenOptions;
46
97
  /**
47
98
  * Normalize operation options.
48
99
  */
49
100
  export interface NormalizeOptions extends OpenOptions {
101
+ /** Request deterministic normalization when the runtime supports the knob. */
50
102
  deterministic?: boolean | undefined;
51
103
  }
52
104
  /**
53
105
  * Normalize operation result payload.
54
106
  */
55
107
  export interface NormalizeResult {
108
+ /** Source archive format that was normalized. */
56
109
  format: ArchiveFormat;
110
+ /** Detailed normalization report from bytefold. */
57
111
  report: ArchiveNormalizeReport;
58
112
  }
59
113
  /**
60
114
  * Extraction options with explicit safety limits.
115
+ *
116
+ * `extract()` defaults to `profile: 'strict'` when no profile is supplied.
61
117
  */
62
118
  export interface ExtractOptions extends OpenOptions {
119
+ /**
120
+ * If `true`, symbolic-link entries are materialized on disk; otherwise they
121
+ * are skipped and counted in `ExtractResult.skippedEntries`.
122
+ */
63
123
  allowSymlinks?: boolean | undefined;
124
+ /**
125
+ * Reserved for forward compatibility. Hard-link entries are currently
126
+ * rejected with `DIRARCHIVER_UNSUPPORTED_ENTRY` regardless of this flag.
127
+ */
64
128
  allowHardlinks?: boolean | undefined;
129
+ /**
130
+ * Maximum bytes allowed for any single extracted file entry.
131
+ */
65
132
  maxEntryBytes?: number | undefined;
133
+ /**
134
+ * Maximum cumulative bytes allowed across all extracted file entries.
135
+ */
66
136
  maxTotalExtractedBytes?: number | undefined;
67
137
  }
68
138
  /**
69
139
  * Extraction summary result.
70
140
  */
71
141
  export interface ExtractResult {
142
+ /** Source archive format that was extracted to disk. */
72
143
  format: ArchiveFormat;
144
+ /** Absolute destination directory path used for extraction. */
73
145
  destination: string;
146
+ /** Number of file entries written to disk. */
74
147
  extractedFiles: number;
148
+ /** Number of directory entries created on disk. */
75
149
  extractedDirectories: number;
150
+ /** Number of entries skipped due to policy, such as disallowed symlinks. */
76
151
  skippedEntries: number;
152
+ /** Audit issues collected before or during extraction. */
77
153
  issues: ArchiveIssue[];
78
154
  }
79
155
  /**
80
156
  * Archive writer options.
81
157
  */
82
158
  export interface WriteOptions {
159
+ /**
160
+ * Requested output format. If omitted, inferred from destination extension
161
+ * and falls back to `zip` when inference is not possible.
162
+ */
83
163
  format?: ArchiveFormat | undefined;
164
+ /**
165
+ * Includes the source directory name as a root folder in the archive when
166
+ * source is a directory.
167
+ */
84
168
  includeBaseDirectory?: boolean | undefined;
169
+ /**
170
+ * Follows symbolic links while walking directory sources for `write()`.
171
+ */
85
172
  followSymlinks?: boolean | undefined;
173
+ /**
174
+ * Glob-like exclusion patterns evaluated relative to the source root.
175
+ */
86
176
  exclude?: string[] | undefined;
177
+ /**
178
+ * Writer profile (`compat`, `strict`, `agent`) forwarded to bytefold.
179
+ */
87
180
  profile?: ArchiveProfile | undefined;
181
+ /**
182
+ * Optional writer limits passed through to bytefold operations.
183
+ */
88
184
  limits?: ArchiveLimits | undefined;
89
185
  }
90
186
  /**
91
187
  * Archive writer result payload.
92
188
  */
93
189
  export interface WriteResult {
190
+ /** Archive format emitted to the destination path. */
94
191
  format: ArchiveFormat;
192
+ /** Absolute source path that was archived. */
95
193
  source: string;
194
+ /** Absolute destination archive path that was written. */
96
195
  destination: string;
196
+ /** Number of archive entries written to the output archive. */
97
197
  entryCount: number;
198
+ /** Whether a directory source was wrapped in a tar-based single-file codec. */
98
199
  wrappedDirectoryCodec: boolean;
99
200
  }
100
201
  /**
101
202
  * Usage-error shape emitted by CLI parsing.
102
203
  */
103
204
  export interface CliUsageError {
205
+ /** Human-readable summary of the CLI validation failure. */
104
206
  message: string;
207
+ /** Individual issues returned by the command-line parser. */
105
208
  issues: readonly {
106
209
  code: string;
107
210
  message: string;
@@ -111,11 +214,18 @@ export interface CliUsageError {
111
214
  * Canonical command identifiers supported by the CLI contract.
112
215
  */
113
216
  export interface SupportedCommandMap {
217
+ /** Literal identifier for the `open` command. */
114
218
  open: 'open';
219
+ /** Literal identifier for the `detect` command. */
115
220
  detect: 'detect';
221
+ /** Literal identifier for the `list` command. */
116
222
  list: 'list';
223
+ /** Literal identifier for the `audit` command. */
117
224
  audit: 'audit';
225
+ /** Literal identifier for the `extract` command. */
118
226
  extract: 'extract';
227
+ /** Literal identifier for the `normalize` command. */
119
228
  normalize: 'normalize';
229
+ /** Literal identifier for the `write` command. */
120
230
  write: 'write';
121
231
  }
@@ -0,0 +1,5 @@
1
+ # Explanation index
2
+
3
+ Design and tradeoff notes:
4
+
5
+ - [Profile behavior and tradeoffs](profiles.md)
@@ -0,0 +1,21 @@
1
+ # Profile behavior and tradeoffs
2
+
3
+ Profiles configure safety posture for audit and extraction.
4
+
5
+ ## compat
6
+
7
+ - Minimal guardrails.
8
+ - Use only for trusted inputs or internal tooling.
9
+
10
+ ## strict
11
+
12
+ - Blocks traversal, absolute paths, and unsafe entries.
13
+ - Enforces explicit resource limits.
14
+
15
+ ## agent
16
+
17
+ - Same safety posture as strict.
18
+ - Adds additional audit assertions for automation pipelines.
19
+
20
+ Profiles are passed through to bytefold, so bytefold updates may expand the
21
+ checked conditions without changing the profile names.
@@ -0,0 +1,78 @@
1
+ # How-to: use CLI JSON output and exit codes
2
+
3
+ ## Goal
4
+ Drive `dir-archiver` from automation without scraping human-readable command
5
+ output.
6
+
7
+ ## Prereqs
8
+ - Node `>=24`
9
+ - `npm install`
10
+ - `npm run build`
11
+
12
+ ## Copy/paste
13
+ ```sh
14
+ tmpdir="$(mktemp -d)"
15
+ mkdir -p "$tmpdir/src"
16
+ printf 'hello from dir-archiver\n' > "$tmpdir/src/hello.txt"
17
+
18
+ node dist/cli.js write --source "$tmpdir/src" --output "$tmpdir/archive.zip" --json
19
+ node dist/cli.js detect --input "$tmpdir/archive.zip" --json
20
+
21
+ set +e
22
+ node dist/cli.js extract --json
23
+ usage_exit=$?
24
+ set -e
25
+
26
+ printf 'usage_exit=%s\n' "$usage_exit"
27
+ rm -rf "$tmpdir"
28
+ ```
29
+
30
+ ## What you should see
31
+ - `write` emits JSON on stdout with fields shaped like:
32
+
33
+ ```json
34
+ {
35
+ "format": "zip",
36
+ "source": "/tmp/.../src",
37
+ "destination": "/tmp/.../archive.zip",
38
+ "entryCount": 1,
39
+ "wrappedDirectoryCodec": false
40
+ }
41
+ ```
42
+
43
+ - `detect` emits JSON on stdout with `format` plus a `detection` object.
44
+ - The invalid `extract --json` invocation emits a usage payload on stdout shaped
45
+ like:
46
+
47
+ ```json
48
+ {
49
+ "schemaVersion": "1",
50
+ "code": "DIRARCHIVER_USAGE",
51
+ "message": "Invalid CLI arguments.",
52
+ "issues": [
53
+ { "code": "REQUIRED", "message": "extract requires --input." },
54
+ { "code": "REQUIRED", "message": "extract requires --output." }
55
+ ]
56
+ }
57
+ ```
58
+
59
+ - `usage_exit=2` confirms the usage-error exit code.
60
+
61
+ ## Exit codes
62
+
63
+ | Exit code | Meaning | Where to read details |
64
+ | --- | --- | --- |
65
+ | `0` | Command completed successfully. | stdout (`--json`) or normal console output |
66
+ | `1` | Runtime failure or archive-policy failure. | stderr |
67
+ | `2` | CLI usage or validation failure. | stdout with `--json`, stderr otherwise |
68
+
69
+ ## Common failure modes
70
+ - Scripts scrape prose output instead of passing `--json`.
71
+ - Exit codes `1` and `2` are collapsed into one generic failure bucket.
72
+ - stdout and stderr are merged, which corrupts JSON parsing.
73
+ - Commands are run before `npm run build`, so `dist/cli.js` is missing.
74
+
75
+ ## Related reference
76
+ - [CLI reference](../reference/cli.md)
77
+ - [Options reference](../reference/options.md)
78
+ - [Contract](../../CONTRACT.md)
@@ -0,0 +1,54 @@
1
+ # How-to: extract untrusted archives safely
2
+
3
+ ## Goal
4
+ Prevent path traversal and decompression amplification from turning extraction
5
+ into a filesystem or resource-exhaustion risk.
6
+
7
+ ## Prereqs
8
+ - Node `>=24`
9
+ - `npm install`
10
+ - `npm run build`
11
+
12
+ ## Copy/paste
13
+ Runnable example file:
14
+
15
+ ```sh
16
+ node examples/extract-untrusted.mjs
17
+ ```
18
+
19
+ Equivalent API pattern (audit first, then extract with limits):
20
+
21
+ ```ts
22
+ import { audit, extract } from "dir-archiver";
23
+
24
+ const input = "./incoming.zip";
25
+ const report = await audit(input, { profile: "agent" });
26
+ if (!report.ok) {
27
+ console.error(JSON.stringify({ ok: false, issues: report.issues }, null, 2));
28
+ process.exit(1);
29
+ }
30
+
31
+ await extract(input, "./out", {
32
+ profile: "strict",
33
+ maxEntryBytes: 64 * 1024 * 1024,
34
+ maxTotalExtractedBytes: 512 * 1024 * 1024,
35
+ });
36
+ ```
37
+
38
+ ## What you should see
39
+ - The audit step succeeds before extraction starts.
40
+ - The example intentionally sets a low extraction limit and reports
41
+ `DIRARCHIVER_RESOURCE_LIMIT`.
42
+
43
+ ## Common failure modes
44
+ - `profile: "compat"` is used for hostile input, which weakens pre-extract
45
+ safety checks.
46
+ - Limits are left unset, so decompression amplification can consume far more
47
+ disk than expected.
48
+ - Callers treat file creation as success instead of checking the returned issues
49
+ and skipped-entry counts.
50
+
51
+ ## Related reference
52
+ - [CLI reference](../reference/cli.md)
53
+ - [Options reference](../reference/options.md)
54
+ - [Contract](../../CONTRACT.md)
@@ -0,0 +1,7 @@
1
+ # How-to index
2
+
3
+ Pick the guide that matches the job:
4
+
5
+ - [Use CLI JSON output and exit codes](cli-json-and-exit-codes.md)
6
+ - [Troubleshoot common failures](troubleshoot-common-failures.md)
7
+ - [Extract untrusted archives safely](extract-untrusted.md)
@@ -0,0 +1,78 @@
1
+ # How-to: troubleshoot common failures
2
+
3
+ ## Goal
4
+ Map common `dir-archiver` failures to the likely option or input problem
5
+ without guessing from raw shell output.
6
+
7
+ ## Prereqs
8
+ - Node `>=24`
9
+ - `npm install`
10
+ - `npm run build`
11
+
12
+ ## Copy/paste
13
+ ```sh
14
+ tmpdir="$(mktemp -d)"
15
+ mkdir -p "$tmpdir/src"
16
+ printf 'hello world\n' > "$tmpdir/src/hello.txt"
17
+ node dist/cli.js write --source "$tmpdir/src" --output "$tmpdir/archive.zip" --json >/dev/null
18
+
19
+ set +e
20
+ node dist/cli.js extract \
21
+ --input "$tmpdir/archive.zip" \
22
+ --output "$tmpdir/out" \
23
+ --max-entry-bytes 4 \
24
+ --json
25
+ runtime_exit=$?
26
+
27
+ node dist/cli.js extract --json
28
+ usage_exit=$?
29
+ set -e
30
+
31
+ printf 'runtime_exit=%s\n' "$runtime_exit"
32
+ printf 'usage_exit=%s\n' "$usage_exit"
33
+
34
+ node dist/cli.js detect --input "$tmpdir/archive.zip" --json
35
+ node dist/cli.js list --input "$tmpdir/archive.zip" --json
36
+ rm -rf "$tmpdir"
37
+ ```
38
+
39
+ ## What you should see
40
+ - The first `extract` fails with a `DirArchiverError` JSON payload on stderr
41
+ shaped like:
42
+
43
+ ```json
44
+ {
45
+ "schemaVersion": "1",
46
+ "name": "DirArchiverError",
47
+ "code": "DIRARCHIVER_RESOURCE_LIMIT",
48
+ "message": "..."
49
+ }
50
+ ```
51
+
52
+ - `runtime_exit=1` confirms a runtime/archive-policy failure.
53
+ - The second `extract --json` emits a `DIRARCHIVER_USAGE` payload on stdout and
54
+ `usage_exit=2`.
55
+ - `detect` and `list` still succeed. Use them to inspect the archive before
56
+ trying a different `extract` policy or limit.
57
+
58
+ ## Quick diagnosis table
59
+
60
+ | Symptom | Likely cause | First fix |
61
+ | --- | --- | --- |
62
+ | Exit `2` with `DIRARCHIVER_USAGE` | Missing or invalid command flags | Compare the command to the [CLI reference](../reference/cli.md). |
63
+ | Exit `1` with `DIRARCHIVER_RESOURCE_LIMIT` | `maxEntryBytes` or `maxTotalExtractedBytes` is lower than the archive requires | Raise the limit or audit first to size the archive. |
64
+ | Exit `1` with `DIRARCHIVER_PATH_TRAVERSAL` or `DIRARCHIVER_UNSUPPORTED_ENTRY` | Strict/agent safety checks rejected an entry path or link | Run `audit` or `list` first and keep `compat` only for trusted input. |
65
+ | Raw `ENOENT` or a missing-path stack trace | The input or output path is wrong for the current working directory | Re-run with absolute paths or verify the file exists. |
66
+
67
+ ## Common failure modes
68
+ - Missing input files or missing output directories.
69
+ - Unsupported archive formats or encrypted inputs without the required
70
+ password/support.
71
+ - Strict extraction rejecting traversal-style or link-based entries.
72
+ - Treating an exit code alone as the diagnosis instead of reading the JSON
73
+ error `code`.
74
+
75
+ ## Related reference
76
+ - [CLI reference](../reference/cli.md)
77
+ - [Options reference](../reference/options.md)
78
+ - [Contract](../../CONTRACT.md)