dir-archiver 3.0.1 → 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/CHANGELOG.md +6 -0
- package/README.md +7 -17
- package/dist/core.d.ts +76 -7
- package/dist/core.js +76 -7
- package/dist/errors.d.ts +53 -9
- package/dist/errors.js +13 -0
- package/dist/index.d.ts +33 -12
- package/dist/index.js +8 -2
- package/dist/types.d.ts +54 -9
- package/docs/how-to/cli-json-and-exit-codes.md +78 -0
- package/docs/how-to/extract-untrusted.md +19 -22
- package/docs/how-to/index.md +3 -3
- package/docs/how-to/troubleshoot-common-failures.md +78 -0
- package/docs/index.md +6 -7
- package/docs/maintainers/ci-release-artifact.md +39 -0
- package/docs/maintainers/ci-usage.md +41 -0
- package/docs/reference/contract.md +13 -0
- package/docs/reference/index.md +1 -1
- package/docs/tutorial/bundle-a-plugin.md +29 -16
- package/docs/tutorial/first-archive-flow.md +38 -19
- package/jsr.json +1 -1
- package/package.json +17 -12
- package/docs/how-to/ci-release-artifact.md +0 -32
- package/docs/how-to/ci-usage.md +0 -34
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,12 @@
|
|
|
4
4
|
|
|
5
5
|
* No entries yet.
|
|
6
6
|
|
|
7
|
+
### 3.0.2 (June 19, 2026)
|
|
8
|
+
|
|
9
|
+
* Fix JSR documentation checks for current and pinned Deno doc output.
|
|
10
|
+
* Update `argv-flags` to 1.0.5.
|
|
11
|
+
* Bump GitHub Actions dependencies in CI and release workflows.
|
|
12
|
+
|
|
7
13
|
### 3.0.1 (March 3, 2026)
|
|
8
14
|
|
|
9
15
|
* Rework README/docs information architecture for fast first-use onboarding.
|
package/README.md
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# dir-archiver
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Deterministic directory archiving and extraction over zip, tar, and layered compression.
|
|
4
|
+
|
|
5
|
+
Supports Node.js, Deno, and Bun.
|
|
4
6
|
|
|
5
7
|
## What it is
|
|
6
8
|
|
|
@@ -29,12 +31,6 @@ await extract("./project.zip", "./out", { profile: "strict" });
|
|
|
29
31
|
console.log(detected.format);
|
|
30
32
|
```
|
|
31
33
|
|
|
32
|
-
## Options reference
|
|
33
|
-
|
|
34
|
-
- [Options reference](https://github.com/Ismail-elkorchi/dir-archiver/blob/main/docs/reference/options.md)
|
|
35
|
-
- [CLI reference](https://github.com/Ismail-elkorchi/dir-archiver/blob/main/docs/reference/cli.md)
|
|
36
|
-
- [10-minute tutorial: bundle a plugin directory](https://github.com/Ismail-elkorchi/dir-archiver/blob/main/docs/tutorial/bundle-a-plugin.md)
|
|
37
|
-
|
|
38
34
|
## When not to use
|
|
39
35
|
|
|
40
36
|
- You only need a low-level parser for a single format.
|
|
@@ -51,19 +47,13 @@ console.log(detected.format);
|
|
|
51
47
|
|
|
52
48
|
- Module system: ESM-only.
|
|
53
49
|
- Runtimes: Node `>=24`, current Deno, current Bun.
|
|
54
|
-
- CLI and API contracts are documented in
|
|
50
|
+
- CLI and API contracts are documented in [Contract](https://github.com/Ismail-elkorchi/dir-archiver/blob/main/CONTRACT.md).
|
|
55
51
|
|
|
56
|
-
##
|
|
52
|
+
## Documentation
|
|
57
53
|
|
|
58
54
|
- [Docs index](https://github.com/Ismail-elkorchi/dir-archiver/blob/main/docs/index.md)
|
|
59
|
-
-
|
|
60
|
-
|
|
61
|
-
- [Contract](https://github.com/Ismail-elkorchi/dir-archiver/blob/main/CONTRACT.md)
|
|
62
|
-
- [Security policy](https://github.com/Ismail-elkorchi/dir-archiver/blob/main/SECURITY.md)
|
|
63
|
-
- How-to:
|
|
64
|
-
- [How-to index](https://github.com/Ismail-elkorchi/dir-archiver/blob/main/docs/how-to/index.md)
|
|
65
|
-
- [Contributing](https://github.com/Ismail-elkorchi/dir-archiver/blob/main/CONTRIBUTING.md)
|
|
66
|
-
- Explanation: [explanation index](https://github.com/Ismail-elkorchi/dir-archiver/blob/main/docs/explanation/index.md)
|
|
55
|
+
- [Tutorial: bundle a plugin directory](https://github.com/Ismail-elkorchi/dir-archiver/blob/main/docs/tutorial/bundle-a-plugin.md)
|
|
56
|
+
- [Reference: CLI](https://github.com/Ismail-elkorchi/dir-archiver/blob/main/docs/reference/cli.md)
|
|
67
57
|
|
|
68
58
|
## Verification
|
|
69
59
|
|
package/dist/core.d.ts
CHANGED
|
@@ -1,31 +1,100 @@
|
|
|
1
1
|
import type { ArchiveAuditReport, ArchiveReader } from '@ismail-elkorchi/bytefold';
|
|
2
2
|
import type { DetectResult, DirArchiverInput, ExtractOptions, ExtractResult, ListResult, NormalizeOptions, NormalizeResult, OpenOptions, WriteOptions, WriteResult } from './types.js';
|
|
3
3
|
/**
|
|
4
|
-
* Opens an archive
|
|
4
|
+
* Opens an archive and returns the live bytefold reader.
|
|
5
|
+
*
|
|
6
|
+
* Use this when you need direct access to low-level reader capabilities such
|
|
7
|
+
* as `entries()`, `audit()`, or normalization support checks. The returned
|
|
8
|
+
* reader is not auto-disposed, so callers should close or dispose it when the
|
|
9
|
+
* active runtime exposes a cleanup hook.
|
|
10
|
+
*
|
|
11
|
+
* @param input Archive bytes, path, URL, stream, or blob to open.
|
|
12
|
+
* @param options Format hints, safety profile, limits, and cancellation
|
|
13
|
+
* signal forwarded to bytefold.
|
|
14
|
+
* @returns A live `ArchiveReader` for advanced inspection flows.
|
|
5
15
|
*/
|
|
6
16
|
export declare const open: (input: DirArchiverInput, options?: OpenOptions) => Promise<ArchiveReader>;
|
|
7
17
|
/**
|
|
8
|
-
* Detects archive format
|
|
18
|
+
* Detects an archive format without extracting or listing its contents.
|
|
19
|
+
*
|
|
20
|
+
* This is the lightest-weight way to confirm the container and compression
|
|
21
|
+
* layers before choosing a follow-up operation.
|
|
22
|
+
*
|
|
23
|
+
* @param input Archive bytes, path, URL, stream, or blob to inspect.
|
|
24
|
+
* @param options Format hints and parse controls applied during detection.
|
|
25
|
+
* @returns The resolved archive format and any bytefold detection metadata.
|
|
9
26
|
*/
|
|
10
27
|
export declare const detect: (input: DirArchiverInput, options?: OpenOptions) => Promise<DetectResult>;
|
|
11
28
|
/**
|
|
12
|
-
* Lists archive entries without extracting to disk.
|
|
29
|
+
* Lists archive entries without extracting anything to disk.
|
|
30
|
+
*
|
|
31
|
+
* Each entry is projected into a JSON-safe summary so CLI and API callers can
|
|
32
|
+
* inspect paths, sizes, and link metadata before deciding to extract.
|
|
33
|
+
*
|
|
34
|
+
* @param input Archive bytes, path, URL, stream, or blob to inspect.
|
|
35
|
+
* @param options Format hints and parse controls applied while reading the
|
|
36
|
+
* archive directory.
|
|
37
|
+
* @returns Archive metadata plus the entry summaries visible to callers.
|
|
13
38
|
*/
|
|
14
39
|
export declare const list: (input: DirArchiverInput, options?: OpenOptions) => Promise<ListResult>;
|
|
15
40
|
/**
|
|
16
|
-
*
|
|
41
|
+
* Audits an archive against the selected bytefold safety profile.
|
|
42
|
+
*
|
|
43
|
+
* Use this before extraction when you need a report of unsafe paths, link
|
|
44
|
+
* entries, or format-specific concerns without writing files to disk.
|
|
45
|
+
*
|
|
46
|
+
* @param input Archive bytes, path, URL, stream, or blob to audit.
|
|
47
|
+
* @param options Safety profile, limits, and format hints used during the
|
|
48
|
+
* audit pass.
|
|
49
|
+
* @returns The bytefold audit report for the requested profile.
|
|
17
50
|
*/
|
|
18
51
|
export declare const audit: (input: DirArchiverInput, options?: OpenOptions) => Promise<ArchiveAuditReport>;
|
|
19
52
|
/**
|
|
20
|
-
*
|
|
53
|
+
* Rewrites an archive into its normalized deterministic representation.
|
|
54
|
+
*
|
|
55
|
+
* Normalization is available only when the opened archive reader exposes
|
|
56
|
+
* bytefold normalization support. Unsupported formats throw
|
|
57
|
+
* `DIRARCHIVER_NORMALIZE_UNSUPPORTED`.
|
|
58
|
+
*
|
|
59
|
+
* @param input Archive bytes, path, URL, stream, or blob to normalize.
|
|
60
|
+
* @param destination Output archive path that will receive the normalized
|
|
61
|
+
* bytes.
|
|
62
|
+
* @param options Format hints and normalization controls applied during the
|
|
63
|
+
* read and write pass.
|
|
64
|
+
* @returns The source format and bytefold normalization report.
|
|
65
|
+
* @throws {DirArchiverError} When the selected archive format cannot be
|
|
66
|
+
* normalized by the active runtime.
|
|
21
67
|
*/
|
|
22
68
|
export declare const normalize: (input: DirArchiverInput, destination: string, options?: NormalizeOptions) => Promise<NormalizeResult>;
|
|
23
69
|
/**
|
|
24
|
-
* Extracts
|
|
70
|
+
* Extracts an archive into a destination directory with safety enforcement.
|
|
71
|
+
*
|
|
72
|
+
* `extract()` defaults to `profile: 'strict'`. Under `strict` or `agent`, the
|
|
73
|
+
* archive is audited before bytes are written to disk and unsafe entries raise
|
|
74
|
+
* a `DirArchiverError` instead of being silently materialized.
|
|
75
|
+
*
|
|
76
|
+
* @param input Archive bytes, path, URL, stream, or blob to extract.
|
|
77
|
+
* @param destination Directory that will receive extracted files.
|
|
78
|
+
* @param options Extraction policy, safety profile, and resource limits.
|
|
79
|
+
* @returns A summary of what was extracted, skipped, and flagged.
|
|
80
|
+
* @throws {DirArchiverError} When audit checks fail, resource limits are
|
|
81
|
+
* exceeded, or unsupported entry types are encountered.
|
|
25
82
|
*/
|
|
26
83
|
export declare const extract: (input: DirArchiverInput, destination: string, options?: ExtractOptions) => Promise<ExtractResult>;
|
|
27
84
|
/**
|
|
28
|
-
* Writes an archive from a file or directory source.
|
|
85
|
+
* Writes an archive from a file or directory source path.
|
|
86
|
+
*
|
|
87
|
+
* Directory sources are traversed deterministically. When callers request a
|
|
88
|
+
* single-file compression codec such as `gz` for a directory source,
|
|
89
|
+
* `dir-archiver` wraps the directory in the corresponding tar-based container
|
|
90
|
+
* (`tar.gz`, `tar.zst`, and so on).
|
|
91
|
+
*
|
|
92
|
+
* @param source File or directory path to archive.
|
|
93
|
+
* @param destination Output archive path.
|
|
94
|
+
* @param options Format selection, traversal rules, and exclusion controls.
|
|
95
|
+
* @returns A summary of the emitted archive format and entry count.
|
|
96
|
+
* @throws {DirArchiverError} When the requested output format is unsupported by
|
|
97
|
+
* the active bytefold writer.
|
|
29
98
|
*/
|
|
30
99
|
export declare const write: (source: string, destination: string, options?: WriteOptions) => Promise<WriteResult>;
|
|
31
100
|
export declare const copyStreamToFile: (source: string, destination: string) => Promise<void>;
|
package/dist/core.js
CHANGED
|
@@ -12,14 +12,31 @@ const DIRECTORY_TO_SINGLE_FILE_CODEC = {
|
|
|
12
12
|
};
|
|
13
13
|
const writeUnsupportedFormats = new Set(['tar.bz2', 'bz2', 'tar.xz', 'xz']);
|
|
14
14
|
/**
|
|
15
|
-
* Opens an archive
|
|
15
|
+
* Opens an archive and returns the live bytefold reader.
|
|
16
|
+
*
|
|
17
|
+
* Use this when you need direct access to low-level reader capabilities such
|
|
18
|
+
* as `entries()`, `audit()`, or normalization support checks. The returned
|
|
19
|
+
* reader is not auto-disposed, so callers should close or dispose it when the
|
|
20
|
+
* active runtime exposes a cleanup hook.
|
|
21
|
+
*
|
|
22
|
+
* @param input Archive bytes, path, URL, stream, or blob to open.
|
|
23
|
+
* @param options Format hints, safety profile, limits, and cancellation
|
|
24
|
+
* signal forwarded to bytefold.
|
|
25
|
+
* @returns A live `ArchiveReader` for advanced inspection flows.
|
|
16
26
|
*/
|
|
17
27
|
export const open = async (input, options = {}) => {
|
|
18
28
|
const runtime = await loadRuntimeBindings();
|
|
19
29
|
return runtime.openArchive(input, toArchiveOpenOptions(options));
|
|
20
30
|
};
|
|
21
31
|
/**
|
|
22
|
-
* Detects archive format
|
|
32
|
+
* Detects an archive format without extracting or listing its contents.
|
|
33
|
+
*
|
|
34
|
+
* This is the lightest-weight way to confirm the container and compression
|
|
35
|
+
* layers before choosing a follow-up operation.
|
|
36
|
+
*
|
|
37
|
+
* @param input Archive bytes, path, URL, stream, or blob to inspect.
|
|
38
|
+
* @param options Format hints and parse controls applied during detection.
|
|
39
|
+
* @returns The resolved archive format and any bytefold detection metadata.
|
|
23
40
|
*/
|
|
24
41
|
export const detect = async (input, options = {}) => {
|
|
25
42
|
const reader = await open(input, options);
|
|
@@ -34,7 +51,15 @@ export const detect = async (input, options = {}) => {
|
|
|
34
51
|
}
|
|
35
52
|
};
|
|
36
53
|
/**
|
|
37
|
-
* Lists archive entries without extracting to disk.
|
|
54
|
+
* Lists archive entries without extracting anything to disk.
|
|
55
|
+
*
|
|
56
|
+
* Each entry is projected into a JSON-safe summary so CLI and API callers can
|
|
57
|
+
* inspect paths, sizes, and link metadata before deciding to extract.
|
|
58
|
+
*
|
|
59
|
+
* @param input Archive bytes, path, URL, stream, or blob to inspect.
|
|
60
|
+
* @param options Format hints and parse controls applied while reading the
|
|
61
|
+
* archive directory.
|
|
62
|
+
* @returns Archive metadata plus the entry summaries visible to callers.
|
|
38
63
|
*/
|
|
39
64
|
export const list = async (input, options = {}) => {
|
|
40
65
|
const reader = await open(input, options);
|
|
@@ -61,7 +86,15 @@ export const list = async (input, options = {}) => {
|
|
|
61
86
|
}
|
|
62
87
|
};
|
|
63
88
|
/**
|
|
64
|
-
*
|
|
89
|
+
* Audits an archive against the selected bytefold safety profile.
|
|
90
|
+
*
|
|
91
|
+
* Use this before extraction when you need a report of unsafe paths, link
|
|
92
|
+
* entries, or format-specific concerns without writing files to disk.
|
|
93
|
+
*
|
|
94
|
+
* @param input Archive bytes, path, URL, stream, or blob to audit.
|
|
95
|
+
* @param options Safety profile, limits, and format hints used during the
|
|
96
|
+
* audit pass.
|
|
97
|
+
* @returns The bytefold audit report for the requested profile.
|
|
65
98
|
*/
|
|
66
99
|
export const audit = async (input, options = {}) => {
|
|
67
100
|
const reader = await open(input, options);
|
|
@@ -73,7 +106,20 @@ export const audit = async (input, options = {}) => {
|
|
|
73
106
|
}
|
|
74
107
|
};
|
|
75
108
|
/**
|
|
76
|
-
*
|
|
109
|
+
* Rewrites an archive into its normalized deterministic representation.
|
|
110
|
+
*
|
|
111
|
+
* Normalization is available only when the opened archive reader exposes
|
|
112
|
+
* bytefold normalization support. Unsupported formats throw
|
|
113
|
+
* `DIRARCHIVER_NORMALIZE_UNSUPPORTED`.
|
|
114
|
+
*
|
|
115
|
+
* @param input Archive bytes, path, URL, stream, or blob to normalize.
|
|
116
|
+
* @param destination Output archive path that will receive the normalized
|
|
117
|
+
* bytes.
|
|
118
|
+
* @param options Format hints and normalization controls applied during the
|
|
119
|
+
* read and write pass.
|
|
120
|
+
* @returns The source format and bytefold normalization report.
|
|
121
|
+
* @throws {DirArchiverError} When the selected archive format cannot be
|
|
122
|
+
* normalized by the active runtime.
|
|
77
123
|
*/
|
|
78
124
|
export const normalize = async (input, destination, options = {}) => {
|
|
79
125
|
const reader = await open(input, options);
|
|
@@ -95,7 +141,18 @@ export const normalize = async (input, destination, options = {}) => {
|
|
|
95
141
|
}
|
|
96
142
|
};
|
|
97
143
|
/**
|
|
98
|
-
* Extracts
|
|
144
|
+
* Extracts an archive into a destination directory with safety enforcement.
|
|
145
|
+
*
|
|
146
|
+
* `extract()` defaults to `profile: 'strict'`. Under `strict` or `agent`, the
|
|
147
|
+
* archive is audited before bytes are written to disk and unsafe entries raise
|
|
148
|
+
* a `DirArchiverError` instead of being silently materialized.
|
|
149
|
+
*
|
|
150
|
+
* @param input Archive bytes, path, URL, stream, or blob to extract.
|
|
151
|
+
* @param destination Directory that will receive extracted files.
|
|
152
|
+
* @param options Extraction policy, safety profile, and resource limits.
|
|
153
|
+
* @returns A summary of what was extracted, skipped, and flagged.
|
|
154
|
+
* @throws {DirArchiverError} When audit checks fail, resource limits are
|
|
155
|
+
* exceeded, or unsupported entry types are encountered.
|
|
99
156
|
*/
|
|
100
157
|
export const extract = async (input, destination, options = {}) => {
|
|
101
158
|
var _a;
|
|
@@ -180,7 +237,19 @@ export const extract = async (input, destination, options = {}) => {
|
|
|
180
237
|
}
|
|
181
238
|
};
|
|
182
239
|
/**
|
|
183
|
-
* Writes an archive from a file or directory source.
|
|
240
|
+
* Writes an archive from a file or directory source path.
|
|
241
|
+
*
|
|
242
|
+
* Directory sources are traversed deterministically. When callers request a
|
|
243
|
+
* single-file compression codec such as `gz` for a directory source,
|
|
244
|
+
* `dir-archiver` wraps the directory in the corresponding tar-based container
|
|
245
|
+
* (`tar.gz`, `tar.zst`, and so on).
|
|
246
|
+
*
|
|
247
|
+
* @param source File or directory path to archive.
|
|
248
|
+
* @param destination Output archive path.
|
|
249
|
+
* @param options Format selection, traversal rules, and exclusion controls.
|
|
250
|
+
* @returns A summary of the emitted archive format and entry count.
|
|
251
|
+
* @throws {DirArchiverError} When the requested output format is unsupported by
|
|
252
|
+
* the active bytefold writer.
|
|
184
253
|
*/
|
|
185
254
|
export const write = async (source, destination, options = {}) => {
|
|
186
255
|
var _a, _b;
|
package/dist/errors.d.ts
CHANGED
|
@@ -1,25 +1,69 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Stable
|
|
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
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
-
*
|
|
2
|
+
* Deterministic directory archiving and extraction over zip, tar, and layered compression.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
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 {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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
|
-
*
|
|
2
|
+
* Deterministic directory archiving and extraction over zip, tar, and layered compression.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
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,7 +1,18 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
export type { ArchiveFormat,
|
|
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
|
/**
|
|
@@ -13,56 +24,67 @@ export interface OpenOptions {
|
|
|
13
24
|
/**
|
|
14
25
|
* Explicit format override when callers already know archive type.
|
|
15
26
|
*/
|
|
16
|
-
format?:
|
|
27
|
+
format?: ArchiveFormat | 'auto' | undefined;
|
|
17
28
|
/**
|
|
18
29
|
* Safety profile (`compat`, `strict`, `agent`) applied during reads/audits.
|
|
19
30
|
*/
|
|
20
|
-
profile?:
|
|
31
|
+
profile?: ArchiveProfile | undefined;
|
|
21
32
|
/**
|
|
22
33
|
* Extra strictness toggle forwarded to bytefold parsing.
|
|
23
34
|
*/
|
|
24
|
-
isStrict?:
|
|
35
|
+
isStrict?: boolean | undefined;
|
|
25
36
|
/**
|
|
26
37
|
* Parser/resource limits enforced while opening or auditing archives.
|
|
27
38
|
*/
|
|
28
|
-
limits?:
|
|
39
|
+
limits?: ArchiveLimits | undefined;
|
|
29
40
|
/**
|
|
30
41
|
* Abort signal for cancelling in-flight async operations.
|
|
31
42
|
*/
|
|
32
|
-
signal?:
|
|
43
|
+
signal?: AbortSignal | undefined;
|
|
33
44
|
/**
|
|
34
45
|
* Password used for encrypted archives when supported by the runtime.
|
|
35
46
|
*/
|
|
36
|
-
password?:
|
|
47
|
+
password?: string | undefined;
|
|
37
48
|
/**
|
|
38
49
|
* Filename hint used for extension-based inference with non-path inputs.
|
|
39
50
|
*/
|
|
40
|
-
filename?:
|
|
51
|
+
filename?: string | undefined;
|
|
41
52
|
}
|
|
42
53
|
/**
|
|
43
54
|
* Format detection result.
|
|
44
55
|
*/
|
|
45
56
|
export interface DetectResult {
|
|
57
|
+
/** Resolved archive format after detection. */
|
|
46
58
|
format: ArchiveFormat;
|
|
59
|
+
/** Bytefold detection metadata, if the runtime produced it. */
|
|
47
60
|
detection: ArchiveDetectionReport | undefined;
|
|
48
61
|
}
|
|
49
62
|
/**
|
|
50
63
|
* Single archive entry projection used by list responses.
|
|
51
64
|
*/
|
|
52
65
|
export interface ListEntry {
|
|
66
|
+
/** Entry format as exposed by the underlying reader. */
|
|
53
67
|
format: ArchiveFormat;
|
|
68
|
+
/** Entry path inside the archive, normalized to forward slashes. */
|
|
54
69
|
name: string;
|
|
70
|
+
/** Entry size encoded as a string for JSON-safe transport. */
|
|
55
71
|
size: string;
|
|
72
|
+
/** Whether the entry materializes as a directory. */
|
|
56
73
|
isDirectory: boolean;
|
|
74
|
+
/** Whether the entry is a symbolic link. */
|
|
57
75
|
isSymlink: boolean;
|
|
76
|
+
/** Link target when the entry is a symbolic link. */
|
|
58
77
|
linkName?: string | undefined;
|
|
59
78
|
}
|
|
60
79
|
/**
|
|
61
80
|
* Archive listing response payload.
|
|
62
81
|
*/
|
|
63
82
|
export interface ListResult {
|
|
83
|
+
/** Resolved archive format after detection/open completed. */
|
|
64
84
|
format: ArchiveFormat;
|
|
85
|
+
/** Bytefold detection metadata used to choose `format`, when available. */
|
|
65
86
|
detection: ArchiveDetectionReport | undefined;
|
|
87
|
+
/** Projected archive entries in archive iteration order. */
|
|
66
88
|
entries: ListEntry[];
|
|
67
89
|
}
|
|
68
90
|
/**
|
|
@@ -76,13 +98,16 @@ export type AuditOptions = OpenOptions;
|
|
|
76
98
|
* Normalize operation options.
|
|
77
99
|
*/
|
|
78
100
|
export interface NormalizeOptions extends OpenOptions {
|
|
101
|
+
/** Request deterministic normalization when the runtime supports the knob. */
|
|
79
102
|
deterministic?: boolean | undefined;
|
|
80
103
|
}
|
|
81
104
|
/**
|
|
82
105
|
* Normalize operation result payload.
|
|
83
106
|
*/
|
|
84
107
|
export interface NormalizeResult {
|
|
108
|
+
/** Source archive format that was normalized. */
|
|
85
109
|
format: ArchiveFormat;
|
|
110
|
+
/** Detailed normalization report from bytefold. */
|
|
86
111
|
report: ArchiveNormalizeReport;
|
|
87
112
|
}
|
|
88
113
|
/**
|
|
@@ -114,11 +139,17 @@ export interface ExtractOptions extends OpenOptions {
|
|
|
114
139
|
* Extraction summary result.
|
|
115
140
|
*/
|
|
116
141
|
export interface ExtractResult {
|
|
142
|
+
/** Source archive format that was extracted to disk. */
|
|
117
143
|
format: ArchiveFormat;
|
|
144
|
+
/** Absolute destination directory path used for extraction. */
|
|
118
145
|
destination: string;
|
|
146
|
+
/** Number of file entries written to disk. */
|
|
119
147
|
extractedFiles: number;
|
|
148
|
+
/** Number of directory entries created on disk. */
|
|
120
149
|
extractedDirectories: number;
|
|
150
|
+
/** Number of entries skipped due to policy, such as disallowed symlinks. */
|
|
121
151
|
skippedEntries: number;
|
|
152
|
+
/** Audit issues collected before or during extraction. */
|
|
122
153
|
issues: ArchiveIssue[];
|
|
123
154
|
}
|
|
124
155
|
/**
|
|
@@ -156,17 +187,24 @@ export interface WriteOptions {
|
|
|
156
187
|
* Archive writer result payload.
|
|
157
188
|
*/
|
|
158
189
|
export interface WriteResult {
|
|
190
|
+
/** Archive format emitted to the destination path. */
|
|
159
191
|
format: ArchiveFormat;
|
|
192
|
+
/** Absolute source path that was archived. */
|
|
160
193
|
source: string;
|
|
194
|
+
/** Absolute destination archive path that was written. */
|
|
161
195
|
destination: string;
|
|
196
|
+
/** Number of archive entries written to the output archive. */
|
|
162
197
|
entryCount: number;
|
|
198
|
+
/** Whether a directory source was wrapped in a tar-based single-file codec. */
|
|
163
199
|
wrappedDirectoryCodec: boolean;
|
|
164
200
|
}
|
|
165
201
|
/**
|
|
166
202
|
* Usage-error shape emitted by CLI parsing.
|
|
167
203
|
*/
|
|
168
204
|
export interface CliUsageError {
|
|
205
|
+
/** Human-readable summary of the CLI validation failure. */
|
|
169
206
|
message: string;
|
|
207
|
+
/** Individual issues returned by the command-line parser. */
|
|
170
208
|
issues: readonly {
|
|
171
209
|
code: string;
|
|
172
210
|
message: string;
|
|
@@ -176,11 +214,18 @@ export interface CliUsageError {
|
|
|
176
214
|
* Canonical command identifiers supported by the CLI contract.
|
|
177
215
|
*/
|
|
178
216
|
export interface SupportedCommandMap {
|
|
217
|
+
/** Literal identifier for the `open` command. */
|
|
179
218
|
open: 'open';
|
|
219
|
+
/** Literal identifier for the `detect` command. */
|
|
180
220
|
detect: 'detect';
|
|
221
|
+
/** Literal identifier for the `list` command. */
|
|
181
222
|
list: 'list';
|
|
223
|
+
/** Literal identifier for the `audit` command. */
|
|
182
224
|
audit: 'audit';
|
|
225
|
+
/** Literal identifier for the `extract` command. */
|
|
183
226
|
extract: 'extract';
|
|
227
|
+
/** Literal identifier for the `normalize` command. */
|
|
184
228
|
normalize: 'normalize';
|
|
229
|
+
/** Literal identifier for the `write` command. */
|
|
185
230
|
write: 'write';
|
|
186
231
|
}
|
|
@@ -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)
|
|
@@ -10,7 +10,13 @@ into a filesystem or resource-exhaustion risk.
|
|
|
10
10
|
- `npm run build`
|
|
11
11
|
|
|
12
12
|
## Copy/paste
|
|
13
|
-
|
|
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):
|
|
14
20
|
|
|
15
21
|
```ts
|
|
16
22
|
import { audit, extract } from "dir-archiver";
|
|
@@ -29,29 +35,20 @@ await extract(input, "./out", {
|
|
|
29
35
|
});
|
|
30
36
|
```
|
|
31
37
|
|
|
32
|
-
Runnable example file:
|
|
33
|
-
|
|
34
|
-
```sh
|
|
35
|
-
node examples/extract-untrusted.mjs
|
|
36
|
-
```
|
|
37
|
-
|
|
38
38
|
## What you should see
|
|
39
39
|
- The audit step succeeds before extraction starts.
|
|
40
40
|
- The example intentionally sets a low extraction limit and reports
|
|
41
41
|
`DIRARCHIVER_RESOURCE_LIMIT`.
|
|
42
42
|
|
|
43
|
-
##
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
> - `allowSymlinks` defaults to `false`.
|
|
56
|
-
> - `allowHardlinks` currently remains unsupported and triggers
|
|
57
|
-
> `DIRARCHIVER_UNSUPPORTED_ENTRY`.
|
|
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)
|
package/docs/how-to/index.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# How-to index
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Pick the guide that matches the job:
|
|
4
4
|
|
|
5
|
-
- [
|
|
5
|
+
- [Use CLI JSON output and exit codes](cli-json-and-exit-codes.md)
|
|
6
|
+
- [Troubleshoot common failures](troubleshoot-common-failures.md)
|
|
6
7
|
- [Extract untrusted archives safely](extract-untrusted.md)
|
|
7
|
-
- [Use in CI pipelines](ci-usage.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)
|
package/docs/index.md
CHANGED
|
@@ -2,22 +2,21 @@
|
|
|
2
2
|
|
|
3
3
|
Use this map to pick the right doc quickly.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Tutorial
|
|
6
6
|
- [Tutorial: bundle a plugin directory](tutorial/bundle-a-plugin.md)
|
|
7
7
|
- [Tutorial: first archive flow](tutorial/first-archive-flow.md)
|
|
8
8
|
|
|
9
|
-
## How-to
|
|
10
|
-
- [
|
|
11
|
-
- [
|
|
9
|
+
## How-to
|
|
10
|
+
- [Use CLI JSON output and exit codes](how-to/cli-json-and-exit-codes.md)
|
|
11
|
+
- [Troubleshoot common failures](how-to/troubleshoot-common-failures.md)
|
|
12
12
|
- [Extract untrusted archives safely](how-to/extract-untrusted.md)
|
|
13
|
-
- [
|
|
13
|
+
- [How-to index](how-to/index.md)
|
|
14
14
|
|
|
15
15
|
## Reference
|
|
16
|
-
- [Contract](../CONTRACT.md)
|
|
17
16
|
- [CLI reference](reference/cli.md)
|
|
18
17
|
- [Options reference](reference/options.md)
|
|
18
|
+
- [Contract](reference/contract.md)
|
|
19
19
|
- [Reference index](reference/index.md)
|
|
20
|
-
- [Security policy](../SECURITY.md)
|
|
21
20
|
|
|
22
21
|
## Explanation
|
|
23
22
|
- [Explanation index](explanation/index.md)
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Maintainer how-to: create a CI release artifact
|
|
2
|
+
|
|
3
|
+
## Goal
|
|
4
|
+
Produce a release ZIP in CI and emit a machine-readable JSON summary.
|
|
5
|
+
|
|
6
|
+
## Prereqs
|
|
7
|
+
- Node `>=24`
|
|
8
|
+
- `npm install`
|
|
9
|
+
- `npm run build`
|
|
10
|
+
|
|
11
|
+
## Copy/paste
|
|
12
|
+
Minimal CI shell snippet:
|
|
13
|
+
|
|
14
|
+
```sh
|
|
15
|
+
node examples/ci-release-artifact.mjs
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Equivalent CLI flow:
|
|
19
|
+
|
|
20
|
+
```sh
|
|
21
|
+
node dist/cli.js write --source ./dist --output ./release.zip --include-base-directory --json
|
|
22
|
+
node dist/cli.js detect --input ./release.zip --json
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## What you should see
|
|
26
|
+
- JSON output containing `artifact`, `format`, and `entryCount`.
|
|
27
|
+
- `format` is `zip` when the destination extension is `.zip`.
|
|
28
|
+
|
|
29
|
+
## Common failure modes
|
|
30
|
+
- `--json` is omitted, so CI jobs have to scrape human-readable output.
|
|
31
|
+
- The destination extension does not match the intended format, so inference
|
|
32
|
+
chooses the wrong archive type.
|
|
33
|
+
- `--include-base-directory` is skipped and extracted files do not land under a
|
|
34
|
+
stable root folder.
|
|
35
|
+
|
|
36
|
+
## Related reference
|
|
37
|
+
- [CLI reference](../reference/cli.md)
|
|
38
|
+
- [Options reference](../reference/options.md)
|
|
39
|
+
- [Contract](../reference/contract.md)
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Maintainer how-to: use dir-archiver in CI pipelines
|
|
2
|
+
|
|
3
|
+
## Goal
|
|
4
|
+
Normalize incoming archives and gate releases with deterministic audit results.
|
|
5
|
+
|
|
6
|
+
## Prereqs
|
|
7
|
+
- `dir-archiver` available in CI
|
|
8
|
+
- Input archive path from build pipeline
|
|
9
|
+
|
|
10
|
+
## Copy/paste
|
|
11
|
+
Normalize:
|
|
12
|
+
|
|
13
|
+
```sh
|
|
14
|
+
node dist/cli.js normalize \
|
|
15
|
+
--input ./incoming.zip \
|
|
16
|
+
--output ./normalized.zip \
|
|
17
|
+
--profile strict \
|
|
18
|
+
--json
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Audit gate:
|
|
22
|
+
|
|
23
|
+
```sh
|
|
24
|
+
node dist/cli.js audit --input ./incoming.zip --profile agent --json
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## What you should see
|
|
28
|
+
- Normalize emits JSON report with deterministic summary fields.
|
|
29
|
+
- Audit exits with code `0` when safe and `1` when operational risk is detected.
|
|
30
|
+
|
|
31
|
+
## Common failure modes
|
|
32
|
+
- Exit code `2` is treated like an archive-safety failure instead of a CLI
|
|
33
|
+
usage mistake.
|
|
34
|
+
- Pipelines skip `audit` and extract or normalize untrusted input blindly.
|
|
35
|
+
- Jobs do not persist the JSON output, so later stages cannot inspect the exact
|
|
36
|
+
audit or normalize report.
|
|
37
|
+
|
|
38
|
+
## Related reference
|
|
39
|
+
- [CLI reference](../reference/cli.md)
|
|
40
|
+
- [Options reference](../reference/options.md)
|
|
41
|
+
- [Contract](../reference/contract.md)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Contract
|
|
2
|
+
|
|
3
|
+
Goal: point API and CLI users to the stable behavior contract that backs the public surface.
|
|
4
|
+
|
|
5
|
+
The full contract lives at the repo root so it can ship with npm and JSR artifacts alongside the package entrypoints.
|
|
6
|
+
|
|
7
|
+
Use the root contract for:
|
|
8
|
+
- CLI JSON output shape expectations
|
|
9
|
+
- exit-code meanings
|
|
10
|
+
- stability expectations for named commands and result payloads
|
|
11
|
+
|
|
12
|
+
Primary contract:
|
|
13
|
+
- [CONTRACT.md](../../CONTRACT.md)
|
package/docs/reference/index.md
CHANGED
|
@@ -10,18 +10,24 @@ development files.
|
|
|
10
10
|
- `npm run build`
|
|
11
11
|
|
|
12
12
|
## Copy/paste
|
|
13
|
-
CLI-style command:
|
|
14
|
-
|
|
15
13
|
```sh
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
14
|
+
tmpdir="$(mktemp -d)"
|
|
15
|
+
mkdir -p "$tmpdir/plugin/src" "$tmpdir/plugin/node_modules/demo"
|
|
16
|
+
printf 'export const pluginName = \"demo\";\n' > "$tmpdir/plugin/src/index.js"
|
|
17
|
+
printf '{\"name\":\"demo\"}\n' > "$tmpdir/plugin/package.json"
|
|
18
|
+
printf 'ignore me\n' > "$tmpdir/plugin/package-lock.json"
|
|
19
|
+
printf 'ignore me\n' > "$tmpdir/plugin/node_modules/demo/index.js"
|
|
20
|
+
|
|
21
|
+
node dist/cli.js write \
|
|
22
|
+
--source "$tmpdir/plugin" \
|
|
23
|
+
--output "$tmpdir/bundle.zip" \
|
|
24
|
+
--include-base-directory \
|
|
21
25
|
--exclude node_modules \
|
|
22
26
|
--exclude package-lock.json \
|
|
23
|
-
--exclude package.json \
|
|
24
27
|
--json
|
|
28
|
+
|
|
29
|
+
node dist/cli.js list --input "$tmpdir/bundle.zip" --json
|
|
30
|
+
rm -rf "$tmpdir"
|
|
25
31
|
```
|
|
26
32
|
|
|
27
33
|
Runnable example file:
|
|
@@ -32,11 +38,18 @@ node examples/bundle-a-plugin.mjs
|
|
|
32
38
|
|
|
33
39
|
## What you should see
|
|
34
40
|
- A ZIP file is created.
|
|
35
|
-
-
|
|
36
|
-
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
41
|
+
- `write` reports `format: "zip"` and a stable `entryCount`.
|
|
42
|
+
- `list` shows entries rooted under the plugin directory and does not include
|
|
43
|
+
`node_modules` or `package-lock.json`.
|
|
44
|
+
|
|
45
|
+
## Common failure modes
|
|
46
|
+
- The output extension is missing, so format inference falls back to the wrong
|
|
47
|
+
archive type.
|
|
48
|
+
- `includeBaseDirectory` is omitted and extracted files scatter directly into
|
|
49
|
+
the destination root.
|
|
50
|
+
- Exclude patterns miss local build artifacts, which leaks development files
|
|
51
|
+
into the distributable archive.
|
|
52
|
+
|
|
53
|
+
## Related reference
|
|
54
|
+
- [CLI reference](../reference/cli.md)
|
|
55
|
+
- [Options reference](../reference/options.md)
|
|
@@ -9,25 +9,44 @@ Write an archive, detect its format, and extract it with strict safety defaults.
|
|
|
9
9
|
- `npm run build`
|
|
10
10
|
|
|
11
11
|
## Copy/paste
|
|
12
|
-
```
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
12
|
+
```sh
|
|
13
|
+
tmpdir="$(mktemp -d)"
|
|
14
|
+
mkdir -p "$tmpdir/project"
|
|
15
|
+
printf 'hello from tutorial\n' > "$tmpdir/project/hello.txt"
|
|
16
|
+
|
|
17
|
+
node dist/cli.js write \
|
|
18
|
+
--source "$tmpdir/project" \
|
|
19
|
+
--output "$tmpdir/project.zip" \
|
|
20
|
+
--include-base-directory \
|
|
21
|
+
--json
|
|
22
|
+
|
|
23
|
+
node dist/cli.js detect --input "$tmpdir/project.zip" --json
|
|
24
|
+
|
|
25
|
+
node dist/cli.js extract \
|
|
26
|
+
--input "$tmpdir/project.zip" \
|
|
27
|
+
--output "$tmpdir/out" \
|
|
28
|
+
--profile strict \
|
|
29
|
+
--json
|
|
30
|
+
|
|
31
|
+
find "$tmpdir/out" -maxdepth 3 -type f | sort
|
|
32
|
+
rm -rf "$tmpdir"
|
|
24
33
|
```
|
|
25
34
|
|
|
26
35
|
## What you should see
|
|
27
|
-
- `
|
|
28
|
-
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
36
|
+
- `write` reports `format: "zip"` and `wrappedDirectoryCodec: false`.
|
|
37
|
+
- `detect` reports `format: "zip"` plus a `detection` object.
|
|
38
|
+
- `extract` reports at least one extracted file and one extracted directory.
|
|
39
|
+
- `find` prints a path ending in `/project/hello.txt`.
|
|
40
|
+
|
|
41
|
+
## Common failure modes
|
|
42
|
+
- `--include-base-directory` is omitted and the extracted files land directly
|
|
43
|
+
in the destination root.
|
|
44
|
+
- The output archive extension is missing or mismatched, so format inference is
|
|
45
|
+
not what you expected.
|
|
46
|
+
- Extraction is run with `compat` on untrusted input, which weakens path and
|
|
47
|
+
entry checks.
|
|
48
|
+
|
|
49
|
+
## Related reference
|
|
50
|
+
- [CLI reference](../reference/cli.md)
|
|
51
|
+
- [Options reference](../reference/options.md)
|
|
52
|
+
- [Contract](../../CONTRACT.md)
|
package/jsr.json
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dir-archiver",
|
|
3
|
-
"version": "3.0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "3.0.2",
|
|
4
|
+
"description": "Deterministic directory archiving and extraction over zip, tar, and layered compression.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "dist/index.js",
|
|
@@ -56,10 +56,15 @@
|
|
|
56
56
|
"examples:run": "node ./examples/run-all.mjs",
|
|
57
57
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
58
58
|
"lint": "npm run typecheck && eslint . --max-warnings=0",
|
|
59
|
+
"docs:lint:jsr": "deno doc --lint --sloppy-imports src/index.ts",
|
|
60
|
+
"docs:test:jsr": "node ./scripts/jsr-docs-quality.mjs",
|
|
61
|
+
"docs:quality:jsr": "npm run docs:lint:jsr && npm run docs:test:jsr",
|
|
62
|
+
"release:notes:dry-run": "node ./scripts/release-notes.mjs --dry-run",
|
|
63
|
+
"changelog:update:dry-run": "node ./scripts/changelog-update.mjs --dry-run",
|
|
59
64
|
"test": "npm run build && node --test test/api-snapshot.test.mjs test/operations.test.mjs test/cli.test.mjs test/cli-docs-drift.test.mjs test/matrix-node.test.mjs",
|
|
60
|
-
"check:fast": "npm run lint && npm run test && npm run examples:run",
|
|
65
|
+
"check:fast": "npm run lint && npm run docs:quality:jsr && npm run test && npm run examples:run",
|
|
61
66
|
"test:security": "npm run build && node --test test/security.test.mjs",
|
|
62
|
-
"check": "node ./scripts/verify-runtime-versions.mjs && node ./scripts/workflow-policy-check.mjs && node ./scripts/esm-only-guard.mjs && node ./scripts/docs-policy-check.mjs && npm run lint && npm run test && npm run examples:run && npm run test:security && npm run test:runtimes",
|
|
67
|
+
"check": "node ./scripts/verify-runtime-versions.mjs && node ./scripts/workflow-policy-check.mjs && node ./scripts/esm-only-guard.mjs && node ./scripts/docs-policy-check.mjs && npm run lint && npm run docs:quality:jsr && npm run test && npm run examples:run && npm run test:security && npm run test:runtimes",
|
|
63
68
|
"deps:fresh": "node ./scripts/direct-runtime-deps-freshness.mjs",
|
|
64
69
|
"test:deno": "npm run build && deno run --allow-read --allow-write --allow-env --allow-sys test/deno-smoke.mjs",
|
|
65
70
|
"test:bun": "npm run build && bun test/bun-smoke.mjs",
|
|
@@ -77,16 +82,16 @@
|
|
|
77
82
|
},
|
|
78
83
|
"dependencies": {
|
|
79
84
|
"@ismail-elkorchi/bytefold": "^0.8.1",
|
|
80
|
-
"argv-flags": "^1.0.
|
|
85
|
+
"argv-flags": "^1.0.5"
|
|
81
86
|
},
|
|
82
87
|
"devDependencies": {
|
|
83
88
|
"@eslint/js": "^10.0.1",
|
|
84
|
-
"@types/node": "^25.3
|
|
85
|
-
"@typescript-eslint/eslint-plugin": "^8.
|
|
86
|
-
"@typescript-eslint/parser": "^8.
|
|
87
|
-
"eslint": "^10.0
|
|
88
|
-
"globals": "^17.
|
|
89
|
-
"typescript": "^
|
|
90
|
-
"yauzl": "^3.
|
|
89
|
+
"@types/node": "^25.9.3",
|
|
90
|
+
"@typescript-eslint/eslint-plugin": "^8.61.1",
|
|
91
|
+
"@typescript-eslint/parser": "^8.61.1",
|
|
92
|
+
"eslint": "^10.5.0",
|
|
93
|
+
"globals": "^17.6.0",
|
|
94
|
+
"typescript": "^6.0.3",
|
|
95
|
+
"yauzl": "^3.4.0"
|
|
91
96
|
}
|
|
92
97
|
}
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
# How-to: create a CI release artifact
|
|
2
|
-
|
|
3
|
-
## Goal
|
|
4
|
-
Produce a release ZIP in CI and emit a machine-readable JSON summary.
|
|
5
|
-
|
|
6
|
-
## Prereqs
|
|
7
|
-
- Node `>=24`
|
|
8
|
-
- `npm install`
|
|
9
|
-
- `npm run build`
|
|
10
|
-
|
|
11
|
-
## Copy/paste
|
|
12
|
-
Minimal CI shell snippet:
|
|
13
|
-
|
|
14
|
-
```sh
|
|
15
|
-
node examples/ci-release-artifact.mjs
|
|
16
|
-
```
|
|
17
|
-
|
|
18
|
-
Equivalent CLI flow:
|
|
19
|
-
|
|
20
|
-
```sh
|
|
21
|
-
dir-archiver write --source ./dist --output ./release.zip --include-base-directory --json
|
|
22
|
-
dir-archiver detect --input ./release.zip --json
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
## What you should see
|
|
26
|
-
- JSON output containing `artifact`, `format`, and `entryCount`.
|
|
27
|
-
- `format` is `zip` when the destination extension is `.zip`.
|
|
28
|
-
|
|
29
|
-
## Safety notes
|
|
30
|
-
> [!NOTE]
|
|
31
|
-
> Keep `--json` enabled in CI so build steps can parse deterministic fields
|
|
32
|
-
> instead of scraping human-readable text.
|
package/docs/how-to/ci-usage.md
DELETED
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
# How-to: use dir-archiver in CI pipelines
|
|
2
|
-
|
|
3
|
-
## Goal
|
|
4
|
-
Normalize incoming archives and gate releases with deterministic audit results.
|
|
5
|
-
|
|
6
|
-
## Prereqs
|
|
7
|
-
- `dir-archiver` available in CI
|
|
8
|
-
- Input archive path from build pipeline
|
|
9
|
-
|
|
10
|
-
## Copy/paste
|
|
11
|
-
Normalize:
|
|
12
|
-
|
|
13
|
-
```sh
|
|
14
|
-
dir-archiver normalize \
|
|
15
|
-
--input ./incoming.zip \
|
|
16
|
-
--output ./normalized.zip \
|
|
17
|
-
--profile strict \
|
|
18
|
-
--json
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
Audit gate:
|
|
22
|
-
|
|
23
|
-
```sh
|
|
24
|
-
dir-archiver audit --input ./incoming.zip --profile agent --json
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
## What you should see
|
|
28
|
-
- Normalize emits JSON report with deterministic summary fields.
|
|
29
|
-
- Audit exits with code `0` when safe and `1` when operational risk is detected.
|
|
30
|
-
|
|
31
|
-
## Safety notes
|
|
32
|
-
> [!NOTE]
|
|
33
|
-
> Exit code `2` indicates CLI usage mistakes (missing/invalid flags), not
|
|
34
|
-
> archive safety problems.
|