dir-archiver 3.0.0 → 3.0.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/CHANGELOG.md +94 -0
- package/CONTRACT.md +94 -0
- package/CONTRIBUTING.md +33 -0
- package/README.md +45 -59
- package/SECURITY.md +23 -0
- package/SUPPORT.md +16 -0
- package/dist/cli.js +1 -0
- package/dist/types.d.ts +66 -1
- package/docs/explanation/index.md +5 -0
- package/docs/explanation/profiles.md +21 -0
- package/docs/how-to/ci-release-artifact.md +32 -0
- package/docs/how-to/ci-usage.md +34 -0
- package/docs/how-to/extract-untrusted.md +57 -0
- package/docs/how-to/index.md +7 -0
- package/docs/index.md +24 -0
- package/docs/reference/cli.md +277 -0
- package/docs/reference/index.md +7 -0
- package/docs/reference/options.md +57 -0
- package/docs/tutorial/bundle-a-plugin.md +42 -0
- package/docs/tutorial/first-archive-flow.md +33 -0
- package/jsr.json +10 -5
- package/package.json +21 -10
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# Changes to Dir Archiver
|
|
2
|
+
|
|
3
|
+
### Unreleased
|
|
4
|
+
|
|
5
|
+
* No entries yet.
|
|
6
|
+
|
|
7
|
+
### 3.0.1 (March 3, 2026)
|
|
8
|
+
|
|
9
|
+
* Rework README/docs information architecture for fast first-use onboarding.
|
|
10
|
+
* Expand `docs/reference/cli.md` as the canonical CLI command/flag/output reference.
|
|
11
|
+
* Add runnable offline examples and wire `npm run examples:run` into check flows.
|
|
12
|
+
* Add example doc blocks (Goal/Prereqs/Run/Expected output/Safety notes) for each example file.
|
|
13
|
+
* Keep archive runtime behavior and CLI semantics unchanged.
|
|
14
|
+
|
|
15
|
+
### 3.0.0 (February 28, 2026)
|
|
16
|
+
|
|
17
|
+
* Rewrite dir-archiver as a bytefold-backed orchestration layer over `open`, `detect`, `list`, `audit`, `extract`, `normalize`, and `write`.
|
|
18
|
+
* Expand format support to the full bytefold `ArchiveFormat` surface (zip/tar/layered codecs).
|
|
19
|
+
* Add runtime adapters for Node.js, Deno, and Bun with unified API behavior.
|
|
20
|
+
* Add strict/agent extraction security enforcement and stable `DirArchiverError.code` contracts.
|
|
21
|
+
* Add CLI v3 contract with schema-driven parsing via `argv-flags`, machine JSON mode, and explicit exit code semantics.
|
|
22
|
+
* Add capability-derived runtime matrix tests, determinism fingerprints, and adversarial security fixtures.
|
|
23
|
+
* Add downstream compatibility gates against released dependencies and `main` branches.
|
|
24
|
+
* Enforce ESM-only, workflow-policy, docs-policy, and runtime-policy gates via `npm run check`.
|
|
25
|
+
* Align dependency source to released `@ismail-elkorchi/bytefold@^0.7.2` (remove local/git-sha dependency path).
|
|
26
|
+
* Bump runtime floor to Node.js >=24 and keep Deno/Bun support through dedicated runtime checks.
|
|
27
|
+
|
|
28
|
+
### 2.2.0 (January 28, 2026)
|
|
29
|
+
|
|
30
|
+
* Migrate source to TypeScript with strict compiler options.
|
|
31
|
+
* Build compiled output into `dist/` with declaration files.
|
|
32
|
+
* Add strict ESLint configuration alongside TypeScript typechecking.
|
|
33
|
+
* Require Node.js >=18.
|
|
34
|
+
* CLI exits with a non-zero status when required arguments are missing.
|
|
35
|
+
* Add a CLI smoke test.
|
|
36
|
+
* Skip symlinks by default and add an option to follow them.
|
|
37
|
+
* Exclude entries by name anywhere unless a relative path is specified.
|
|
38
|
+
* Normalize exclude path separators so Windows-style paths work cross-platform.
|
|
39
|
+
* Accept absolute exclude paths when they resolve inside the source tree.
|
|
40
|
+
* Make exclude matching case-insensitive on Windows.
|
|
41
|
+
* Normalize archive entry paths to forward slashes for cross-platform zip compatibility.
|
|
42
|
+
* Traverse entries in deterministic order.
|
|
43
|
+
* Honor explicit true/false values for CLI boolean flags.
|
|
44
|
+
* Improve error handling for archive creation failures.
|
|
45
|
+
* Bump argv-flags to 0.2.1 (inline flag values).
|
|
46
|
+
|
|
47
|
+
### 2.1.2 (January 28, 2026)
|
|
48
|
+
|
|
49
|
+
* Add a smoke test suite and npm test script.
|
|
50
|
+
* Add CI to run linting and tests.
|
|
51
|
+
* Declare Node.js >=14 in package metadata.
|
|
52
|
+
|
|
53
|
+
### 2.1.1 (January 28, 2026)
|
|
54
|
+
|
|
55
|
+
* Avoid archiving the destination zip when it lives inside the source directory ([#5](https://github.com/Ismail-elkorchi/dir-archiver/issues/5)).
|
|
56
|
+
* `createZip()` now returns a Promise that resolves when the archive is closed ([#13](https://github.com/Ismail-elkorchi/dir-archiver/issues/13)).
|
|
57
|
+
* Bump archiver to 7.0.1 to address dependency deprecation warnings ([#14](https://github.com/Ismail-elkorchi/dir-archiver/issues/14)).
|
|
58
|
+
|
|
59
|
+
### 2.1.0 (October 25, 2022)
|
|
60
|
+
|
|
61
|
+
* Add ESLint ([#12](https://github.com/Ismail-elkorchi/dir-archiver/pull/12)).
|
|
62
|
+
* Several enhancements to better support Microsoft Windows ([Diff](https://github.com/Ismail-elkorchi/dir-archiver/compare/2.0.0...v2.1.0)).
|
|
63
|
+
|
|
64
|
+
### 2.0.0 (September 20, 2022)
|
|
65
|
+
|
|
66
|
+
* Bump archiver from 5.2.0 to 5.3.1 ([42c30b7](https://github.com/Ismail-elkorchi/dir-archiver/commit/42c30b7a3b7fa0b3101e21559f1774f45d2f06ce)).
|
|
67
|
+
* Add an option to include the base directory in the archive root ([#11](https://github.com/Ismail-elkorchi/dir-archiver/pull/11)).
|
|
68
|
+
|
|
69
|
+
### 1.2.0 (February 28, 2021)
|
|
70
|
+
|
|
71
|
+
* Bump archiver from 4.0.2 to 5.2.0 ([b84c347](https://github.com/Ismail-elkorchi/dir-archiver/commit/b84c34731617c57b7c439f15910fcc8fa00747b2)).
|
|
72
|
+
* Make exclude paths relative to run directory ([#4](https://github.com/Ismail-elkorchi/dir-archiver/pull/4)).
|
|
73
|
+
* Remove the destination zip if it exists already ([#7](https://github.com/Ismail-elkorchi/dir-archiver/pull/7)).
|
|
74
|
+
|
|
75
|
+
### 1.1.2 (July 21, 2020)
|
|
76
|
+
|
|
77
|
+
* Bump lodash from 4.17.15 to 4.17.19.
|
|
78
|
+
* Bump archiver from 4.0.1 to 4.0.2.
|
|
79
|
+
|
|
80
|
+
### 1.1.1 (May 14, 2020)
|
|
81
|
+
|
|
82
|
+
* CLI : prevent execution if the required arguments are missing.
|
|
83
|
+
|
|
84
|
+
### 1.1.0 (May 13, 2020)
|
|
85
|
+
|
|
86
|
+
* Add cli script.
|
|
87
|
+
|
|
88
|
+
### 1.0.1 (May 12, 2020)
|
|
89
|
+
|
|
90
|
+
* Fix the installation instructions.
|
|
91
|
+
|
|
92
|
+
### 1.0.0 (May 12, 2020)
|
|
93
|
+
|
|
94
|
+
* initial release.
|
package/CONTRACT.md
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# dir-archiver contract
|
|
2
|
+
|
|
3
|
+
This document defines the current public behavior contract for `dir-archiver`.
|
|
4
|
+
|
|
5
|
+
## Runtime support
|
|
6
|
+
|
|
7
|
+
- Node.js LTS (minimum aligned with bytefold `engines.node`).
|
|
8
|
+
- Latest stable Deno.
|
|
9
|
+
- Latest stable Bun.
|
|
10
|
+
|
|
11
|
+
## Public operations
|
|
12
|
+
|
|
13
|
+
- `open(input, options)`
|
|
14
|
+
- `detect(input, options)`
|
|
15
|
+
- `list(input, options)`
|
|
16
|
+
- `audit(input, options)`
|
|
17
|
+
- `extract(input, destination, options)`
|
|
18
|
+
- `normalize(input, destination, options)`
|
|
19
|
+
- `write(source, destination, options)`
|
|
20
|
+
|
|
21
|
+
Profiles are passed through to bytefold for read/audit/extract/normalize flows (`compat | strict | agent`).
|
|
22
|
+
`WriteOptions.profile` and `WriteOptions.limits` are currently reserved and not forwarded by `write()`.
|
|
23
|
+
|
|
24
|
+
Detailed option fields and examples are maintained in
|
|
25
|
+
`docs/reference/options.md`.
|
|
26
|
+
|
|
27
|
+
## Format surface
|
|
28
|
+
|
|
29
|
+
The package accepts the full bytefold `ArchiveFormat` union:
|
|
30
|
+
|
|
31
|
+
`zip`, `tar`, `tgz`, `tar.gz`, `gz`, `bz2`, `tar.bz2`, `zst`, `tar.zst`, `br`, `tar.br`, `xz`, `tar.xz`.
|
|
32
|
+
|
|
33
|
+
### Directory input with single-file codec
|
|
34
|
+
|
|
35
|
+
When `write()` receives a directory source and the requested format is a single-file codec:
|
|
36
|
+
|
|
37
|
+
- `gz` -> `tar.gz`
|
|
38
|
+
- `bz2` -> `tar.bz2`
|
|
39
|
+
- `xz` -> `tar.xz`
|
|
40
|
+
- `zst` -> `tar.zst`
|
|
41
|
+
- `br` -> `tar.br`
|
|
42
|
+
|
|
43
|
+
This conversion is deterministic and reported via `WriteResult.wrappedDirectoryCodec`.
|
|
44
|
+
|
|
45
|
+
## Determinism rules
|
|
46
|
+
|
|
47
|
+
- Directory traversal order is lexicographic and stable.
|
|
48
|
+
- Archive entry paths are normalized to `/`.
|
|
49
|
+
- `normalize()` defaults to deterministic mode (`isDeterministic: true`).
|
|
50
|
+
|
|
51
|
+
## Resource limits
|
|
52
|
+
|
|
53
|
+
Extraction limits are explicit options:
|
|
54
|
+
|
|
55
|
+
- `maxEntryBytes`
|
|
56
|
+
- `maxTotalExtractedBytes`
|
|
57
|
+
|
|
58
|
+
Budget overruns fail with stable code `DIRARCHIVER_RESOURCE_LIMIT`.
|
|
59
|
+
|
|
60
|
+
## Security model
|
|
61
|
+
|
|
62
|
+
- Extraction treats archive entries as untrusted.
|
|
63
|
+
- Absolute paths, drive-prefixed paths, and traversal (`..`) are rejected with `DIRARCHIVER_PATH_TRAVERSAL`.
|
|
64
|
+
- Strict/agent extraction runs an audit gate before writing files.
|
|
65
|
+
- Hard links are rejected with `DIRARCHIVER_UNSUPPORTED_ENTRY`.
|
|
66
|
+
- Symlinks are skipped unless explicitly enabled.
|
|
67
|
+
|
|
68
|
+
## Error code stability
|
|
69
|
+
|
|
70
|
+
Consumers rely on `DirArchiverError.code`, not message text.
|
|
71
|
+
Current stable codes:
|
|
72
|
+
|
|
73
|
+
- `DIRARCHIVER_INVALID_SOURCE`
|
|
74
|
+
- `DIRARCHIVER_INVALID_DESTINATION`
|
|
75
|
+
- `DIRARCHIVER_PATH_TRAVERSAL`
|
|
76
|
+
- `DIRARCHIVER_UNSUPPORTED_ENTRY`
|
|
77
|
+
- `DIRARCHIVER_RESOURCE_LIMIT`
|
|
78
|
+
- `DIRARCHIVER_RUNTIME_UNSUPPORTED`
|
|
79
|
+
- `DIRARCHIVER_NORMALIZE_UNSUPPORTED`
|
|
80
|
+
- `DIRARCHIVER_USAGE`
|
|
81
|
+
|
|
82
|
+
## CLI exit codes
|
|
83
|
+
|
|
84
|
+
- `0`: success
|
|
85
|
+
- `1`: operational failure
|
|
86
|
+
- `2`: usage/validation failure
|
|
87
|
+
|
|
88
|
+
Canonical command/flag documentation lives in `docs/reference/cli.md`.
|
|
89
|
+
|
|
90
|
+
## API surface snapshot oracle
|
|
91
|
+
|
|
92
|
+
`test/api-snapshot.test.mjs` compares module exports with
|
|
93
|
+
`test/fixtures/api-surface.v3.json`.
|
|
94
|
+
Any intentional API change updates both this contract and the snapshot.
|
package/CONTRIBUTING.md
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
## Development setup
|
|
4
|
+
|
|
5
|
+
```sh
|
|
6
|
+
npm ci
|
|
7
|
+
npm run check:fast
|
|
8
|
+
npm run check
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Verification commands
|
|
12
|
+
|
|
13
|
+
- `npm run check:fast`: lint + Node test suite (no runtime matrix).
|
|
14
|
+
- `npm run check`: full repository gate (policy checks, lint, tests, security tests, runtime matrix).
|
|
15
|
+
|
|
16
|
+
## Change requirements
|
|
17
|
+
|
|
18
|
+
- Keep ESM-only packaging.
|
|
19
|
+
- Preserve Node + Deno + Bun compatibility.
|
|
20
|
+
- Add/update tests for behavior changes.
|
|
21
|
+
- Update `CONTRACT.md` for API or guarantee changes.
|
|
22
|
+
|
|
23
|
+
## Runtime dependency freshness policy
|
|
24
|
+
|
|
25
|
+
- Keep direct runtime dependencies in `package.json` and `package-lock.json` on the latest published stable versions before every release.
|
|
26
|
+
- Validate with `npm run deps:fresh` (this gate runs in the release workflow).
|
|
27
|
+
- If a dependency is stale, update it in a dedicated PR and run `npm run check`.
|
|
28
|
+
|
|
29
|
+
## Pull request checklist
|
|
30
|
+
|
|
31
|
+
- [ ] `npm run check` passes locally
|
|
32
|
+
- [ ] docs updated for user-facing changes
|
|
33
|
+
- [ ] changelog entry added for release-relevant updates
|
package/README.md
CHANGED
|
@@ -1,88 +1,74 @@
|
|
|
1
|
-
---
|
|
2
|
-
role: overview
|
|
3
|
-
audience: users
|
|
4
|
-
source_of_truth: README.md
|
|
5
|
-
update_triggers:
|
|
6
|
-
- public API changes
|
|
7
|
-
- CLI contract changes
|
|
8
|
-
- runtime support changes
|
|
9
|
-
---
|
|
10
|
-
|
|
11
1
|
# dir-archiver
|
|
12
2
|
|
|
13
|
-
|
|
14
|
-
ESM-only. Safety profiles: `compat | strict | agent`.
|
|
3
|
+
Archive orchestration for detect/list/audit/extract/normalize/write flows across Node, Deno, and Bun.
|
|
15
4
|
|
|
16
|
-
##
|
|
5
|
+
## What it is
|
|
17
6
|
|
|
18
|
-
|
|
7
|
+
`dir-archiver` provides one API surface for archive operations with explicit safety profiles and stable error codes.
|
|
19
8
|
|
|
20
|
-
|
|
21
|
-
npm install dir-archiver
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
### JSR
|
|
9
|
+
## Install
|
|
25
10
|
|
|
26
11
|
```sh
|
|
12
|
+
npm install dir-archiver
|
|
27
13
|
deno add jsr:@ismail-elkorchi/dir-archiver
|
|
28
14
|
```
|
|
29
15
|
|
|
30
|
-
## Quickstart
|
|
16
|
+
## Quickstart
|
|
31
17
|
|
|
32
18
|
```ts
|
|
33
|
-
import { write, detect,
|
|
19
|
+
import { write, detect, extract } from "dir-archiver";
|
|
34
20
|
|
|
35
|
-
await write(
|
|
36
|
-
format:
|
|
21
|
+
await write("./project", "./project.zip", {
|
|
22
|
+
format: "zip",
|
|
37
23
|
includeBaseDirectory: true,
|
|
38
|
-
profile: 'strict'
|
|
39
24
|
});
|
|
40
25
|
|
|
41
|
-
const detected = await detect(
|
|
42
|
-
|
|
43
|
-
await extract('./project.zip', './out', { profile: 'strict' });
|
|
26
|
+
const detected = await detect("./project.zip");
|
|
27
|
+
await extract("./project.zip", "./out", { profile: "strict" });
|
|
44
28
|
|
|
45
|
-
console.log(detected.format
|
|
29
|
+
console.log(detected.format);
|
|
46
30
|
```
|
|
47
31
|
|
|
48
|
-
##
|
|
32
|
+
## Options reference
|
|
49
33
|
|
|
50
|
-
-
|
|
51
|
-
-
|
|
52
|
-
-
|
|
53
|
-
- `audit(input, options)`
|
|
54
|
-
- `extract(input, destination, options)`
|
|
55
|
-
- `normalize(input, destination, options)`
|
|
56
|
-
- `write(source, destination, options)`
|
|
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)
|
|
57
37
|
|
|
58
|
-
|
|
59
|
-
Directory + single-file codec requests are normalized to `tar.<codec>` (`gz`, `bz2`, `xz`, `zst`, `br`).
|
|
38
|
+
## When not to use
|
|
60
39
|
|
|
61
|
-
|
|
40
|
+
- You only need a low-level parser for a single format.
|
|
41
|
+
- You target CommonJS-only environments or Node < 24.
|
|
42
|
+
- You need interactive archive browsing UI features.
|
|
62
43
|
|
|
63
|
-
|
|
64
|
-
dir-archiver write --source ./project --output ./project.zip --format zip --json
|
|
65
|
-
dir-archiver detect --input ./project.zip --json
|
|
66
|
-
dir-archiver list --input ./project.zip --json
|
|
67
|
-
dir-archiver audit --input ./project.zip --profile agent --json
|
|
68
|
-
dir-archiver extract --input ./project.zip --output ./out --profile strict --json
|
|
69
|
-
dir-archiver normalize --input ./project.zip --output ./normalized.zip --json
|
|
70
|
-
```
|
|
44
|
+
## When to use
|
|
71
45
|
|
|
72
|
-
|
|
46
|
+
- You need one API for detect, list, audit, extract, normalize, and write.
|
|
47
|
+
- You want deterministic normalization for CI pipelines.
|
|
48
|
+
- You need safety profiles for untrusted inputs.
|
|
73
49
|
|
|
74
|
-
|
|
75
|
-
- `1` operational failure
|
|
76
|
-
- `2` usage/validation failure
|
|
50
|
+
## Compatibility
|
|
77
51
|
|
|
78
|
-
|
|
52
|
+
- Module system: ESM-only.
|
|
53
|
+
- Runtimes: Node `>=24`, current Deno, current Bun.
|
|
54
|
+
- CLI and API contracts are documented in `CONTRACT.md`.
|
|
79
55
|
|
|
80
|
-
|
|
81
|
-
- Traversal/absolute paths are blocked in strict/agent profiles.
|
|
82
|
-
- See `SECURITY.md` and `docs/security-triage.md`.
|
|
56
|
+
## Links
|
|
83
57
|
|
|
84
|
-
|
|
58
|
+
- [Docs index](https://github.com/Ismail-elkorchi/dir-archiver/blob/main/docs/index.md)
|
|
59
|
+
- Reference:
|
|
60
|
+
- [Reference index](https://github.com/Ismail-elkorchi/dir-archiver/blob/main/docs/reference/index.md)
|
|
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)
|
|
85
67
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
68
|
+
## Verification
|
|
69
|
+
|
|
70
|
+
```sh
|
|
71
|
+
npm run examples:run
|
|
72
|
+
npm run check:fast
|
|
73
|
+
npm run check
|
|
74
|
+
```
|
package/SECURITY.md
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Security policy
|
|
2
|
+
|
|
3
|
+
## Threat model
|
|
4
|
+
|
|
5
|
+
- Archive inputs are untrusted by default.
|
|
6
|
+
- Common risks include path traversal, symlink abuse, hard-link abuse, and resource exhaustion.
|
|
7
|
+
- Runtime/feature mismatches are treated as explicit typed failures.
|
|
8
|
+
|
|
9
|
+
## Safe usage guidance
|
|
10
|
+
|
|
11
|
+
- Prefer `strict` or `agent` profiles for extraction and audit.
|
|
12
|
+
- Audit untrusted archives before extraction (`audit` + `assertSafe` paths).
|
|
13
|
+
- Set resource limits (`maxEntryBytes`, `maxTotalExtractedBytes`) for hostile inputs.
|
|
14
|
+
|
|
15
|
+
## Reporting vulnerabilities
|
|
16
|
+
|
|
17
|
+
Report security issues through GitHub Security Advisories for this repository.
|
|
18
|
+
|
|
19
|
+
## Disclosure workflow
|
|
20
|
+
|
|
21
|
+
1. Reproduce and classify impact.
|
|
22
|
+
2. Patch with tests.
|
|
23
|
+
3. Publish release notes and remediation guidance.
|
package/SUPPORT.md
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Support
|
|
2
|
+
|
|
3
|
+
## Usage questions
|
|
4
|
+
|
|
5
|
+
Open a GitHub issue and include:
|
|
6
|
+
- runtime (`node`, `deno`, `bun`)
|
|
7
|
+
- package version
|
|
8
|
+
- minimal reproducible example
|
|
9
|
+
|
|
10
|
+
## Bug reports
|
|
11
|
+
|
|
12
|
+
Use GitHub issues with expected vs actual behavior and sample command/input.
|
|
13
|
+
|
|
14
|
+
## Security reports
|
|
15
|
+
|
|
16
|
+
Follow `SECURITY.md` for private disclosure.
|
package/dist/cli.js
CHANGED
|
@@ -4,6 +4,7 @@ import { DirArchiverError } from './errors.js';
|
|
|
4
4
|
import { parseCliArgs } from './cli-args.js';
|
|
5
5
|
const usage = `Usage:
|
|
6
6
|
dir-archiver write --source <path> --output <archive> [--format <format>] [--include-base-directory] [--exclude <path>...]
|
|
7
|
+
dir-archiver open --input <archive> [--profile compat|strict|agent]
|
|
7
8
|
dir-archiver detect --input <archive>
|
|
8
9
|
dir-archiver list --input <archive>
|
|
9
10
|
dir-archiver audit --input <archive> [--profile compat|strict|agent]
|
package/dist/types.d.ts
CHANGED
|
@@ -5,15 +5,38 @@ export type { ArchiveFormat, ArchiveLimits, ArchiveProfile };
|
|
|
5
5
|
*/
|
|
6
6
|
export type DirArchiverInput = string | URL | Uint8Array | ArrayBuffer | ReadableStream<Uint8Array> | Blob;
|
|
7
7
|
/**
|
|
8
|
-
* Common options forwarded to bytefold open operations.
|
|
8
|
+
* Common options forwarded to bytefold archive-open operations.
|
|
9
|
+
*
|
|
10
|
+
* Used by `open()`, `detect()`, `list()`, and `audit()`.
|
|
9
11
|
*/
|
|
10
12
|
export interface OpenOptions {
|
|
13
|
+
/**
|
|
14
|
+
* Explicit format override when callers already know archive type.
|
|
15
|
+
*/
|
|
11
16
|
format?: ArchiveOpenOptions['format'] | undefined;
|
|
17
|
+
/**
|
|
18
|
+
* Safety profile (`compat`, `strict`, `agent`) applied during reads/audits.
|
|
19
|
+
*/
|
|
12
20
|
profile?: ArchiveOpenOptions['profile'] | undefined;
|
|
21
|
+
/**
|
|
22
|
+
* Extra strictness toggle forwarded to bytefold parsing.
|
|
23
|
+
*/
|
|
13
24
|
isStrict?: ArchiveOpenOptions['isStrict'] | undefined;
|
|
25
|
+
/**
|
|
26
|
+
* Parser/resource limits enforced while opening or auditing archives.
|
|
27
|
+
*/
|
|
14
28
|
limits?: ArchiveOpenOptions['limits'] | undefined;
|
|
29
|
+
/**
|
|
30
|
+
* Abort signal for cancelling in-flight async operations.
|
|
31
|
+
*/
|
|
15
32
|
signal?: ArchiveOpenOptions['signal'] | undefined;
|
|
33
|
+
/**
|
|
34
|
+
* Password used for encrypted archives when supported by the runtime.
|
|
35
|
+
*/
|
|
16
36
|
password?: ArchiveOpenOptions['password'] | undefined;
|
|
37
|
+
/**
|
|
38
|
+
* Filename hint used for extension-based inference with non-path inputs.
|
|
39
|
+
*/
|
|
17
40
|
filename?: ArchiveOpenOptions['filename'] | undefined;
|
|
18
41
|
}
|
|
19
42
|
/**
|
|
@@ -42,6 +65,12 @@ export interface ListResult {
|
|
|
42
65
|
detection: ArchiveDetectionReport | undefined;
|
|
43
66
|
entries: ListEntry[];
|
|
44
67
|
}
|
|
68
|
+
/**
|
|
69
|
+
* Options for `audit()`.
|
|
70
|
+
*
|
|
71
|
+
* Alias of `OpenOptions` for stable API typing; CLI-only flags (for example
|
|
72
|
+
* `--json`) are not part of this programmatic surface.
|
|
73
|
+
*/
|
|
45
74
|
export type AuditOptions = OpenOptions;
|
|
46
75
|
/**
|
|
47
76
|
* Normalize operation options.
|
|
@@ -58,11 +87,27 @@ export interface NormalizeResult {
|
|
|
58
87
|
}
|
|
59
88
|
/**
|
|
60
89
|
* Extraction options with explicit safety limits.
|
|
90
|
+
*
|
|
91
|
+
* `extract()` defaults to `profile: 'strict'` when no profile is supplied.
|
|
61
92
|
*/
|
|
62
93
|
export interface ExtractOptions extends OpenOptions {
|
|
94
|
+
/**
|
|
95
|
+
* If `true`, symbolic-link entries are materialized on disk; otherwise they
|
|
96
|
+
* are skipped and counted in `ExtractResult.skippedEntries`.
|
|
97
|
+
*/
|
|
63
98
|
allowSymlinks?: boolean | undefined;
|
|
99
|
+
/**
|
|
100
|
+
* Reserved for forward compatibility. Hard-link entries are currently
|
|
101
|
+
* rejected with `DIRARCHIVER_UNSUPPORTED_ENTRY` regardless of this flag.
|
|
102
|
+
*/
|
|
64
103
|
allowHardlinks?: boolean | undefined;
|
|
104
|
+
/**
|
|
105
|
+
* Maximum bytes allowed for any single extracted file entry.
|
|
106
|
+
*/
|
|
65
107
|
maxEntryBytes?: number | undefined;
|
|
108
|
+
/**
|
|
109
|
+
* Maximum cumulative bytes allowed across all extracted file entries.
|
|
110
|
+
*/
|
|
66
111
|
maxTotalExtractedBytes?: number | undefined;
|
|
67
112
|
}
|
|
68
113
|
/**
|
|
@@ -80,11 +125,31 @@ export interface ExtractResult {
|
|
|
80
125
|
* Archive writer options.
|
|
81
126
|
*/
|
|
82
127
|
export interface WriteOptions {
|
|
128
|
+
/**
|
|
129
|
+
* Requested output format. If omitted, inferred from destination extension
|
|
130
|
+
* and falls back to `zip` when inference is not possible.
|
|
131
|
+
*/
|
|
83
132
|
format?: ArchiveFormat | undefined;
|
|
133
|
+
/**
|
|
134
|
+
* Includes the source directory name as a root folder in the archive when
|
|
135
|
+
* source is a directory.
|
|
136
|
+
*/
|
|
84
137
|
includeBaseDirectory?: boolean | undefined;
|
|
138
|
+
/**
|
|
139
|
+
* Follows symbolic links while walking directory sources for `write()`.
|
|
140
|
+
*/
|
|
85
141
|
followSymlinks?: boolean | undefined;
|
|
142
|
+
/**
|
|
143
|
+
* Glob-like exclusion patterns evaluated relative to the source root.
|
|
144
|
+
*/
|
|
86
145
|
exclude?: string[] | undefined;
|
|
146
|
+
/**
|
|
147
|
+
* Writer profile (`compat`, `strict`, `agent`) forwarded to bytefold.
|
|
148
|
+
*/
|
|
87
149
|
profile?: ArchiveProfile | undefined;
|
|
150
|
+
/**
|
|
151
|
+
* Optional writer limits passed through to bytefold operations.
|
|
152
|
+
*/
|
|
88
153
|
limits?: ArchiveLimits | undefined;
|
|
89
154
|
}
|
|
90
155
|
/**
|
|
@@ -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,32 @@
|
|
|
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.
|
|
@@ -0,0 +1,34 @@
|
|
|
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.
|
|
@@ -0,0 +1,57 @@
|
|
|
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
|
+
Recommended pattern (audit first, then extract with limits):
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import { audit, extract } from "dir-archiver";
|
|
17
|
+
|
|
18
|
+
const input = "./incoming.zip";
|
|
19
|
+
const report = await audit(input, { profile: "agent" });
|
|
20
|
+
if (!report.ok) {
|
|
21
|
+
console.error(JSON.stringify({ ok: false, issues: report.issues }, null, 2));
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
await extract(input, "./out", {
|
|
26
|
+
profile: "strict",
|
|
27
|
+
maxEntryBytes: 64 * 1024 * 1024,
|
|
28
|
+
maxTotalExtractedBytes: 512 * 1024 * 1024,
|
|
29
|
+
});
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Runnable example file:
|
|
33
|
+
|
|
34
|
+
```sh
|
|
35
|
+
node examples/extract-untrusted.mjs
|
|
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
|
+
## Safety notes
|
|
44
|
+
> [!CAUTION]
|
|
45
|
+
> Never extract untrusted archives without limits. Attackers can use deeply
|
|
46
|
+
> nested or highly compressed entries to trigger large disk writes
|
|
47
|
+
> (`CWE-409`-style decompression amplification).
|
|
48
|
+
>
|
|
49
|
+
> [!CAUTION]
|
|
50
|
+
> Keep `profile: "strict"` or `"agent"` for untrusted input. These profiles
|
|
51
|
+
> reject traversal-style paths and unsafe entry classes during extraction.
|
|
52
|
+
>
|
|
53
|
+
> [!WARNING]
|
|
54
|
+
> Symlink and hardlink handling changes the risk envelope:
|
|
55
|
+
> - `allowSymlinks` defaults to `false`.
|
|
56
|
+
> - `allowHardlinks` currently remains unsupported and triggers
|
|
57
|
+
> `DIRARCHIVER_UNSUPPORTED_ENTRY`.
|
package/docs/index.md
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Documentation
|
|
2
|
+
|
|
3
|
+
Use this map to pick the right doc quickly.
|
|
4
|
+
|
|
5
|
+
## Start here
|
|
6
|
+
- [Tutorial: bundle a plugin directory](tutorial/bundle-a-plugin.md)
|
|
7
|
+
- [Tutorial: first archive flow](tutorial/first-archive-flow.md)
|
|
8
|
+
|
|
9
|
+
## How-to guides
|
|
10
|
+
- [How-to index](how-to/index.md)
|
|
11
|
+
- [Create CI release artifacts](how-to/ci-release-artifact.md)
|
|
12
|
+
- [Extract untrusted archives safely](how-to/extract-untrusted.md)
|
|
13
|
+
- [Use in CI pipelines](how-to/ci-usage.md)
|
|
14
|
+
|
|
15
|
+
## Reference
|
|
16
|
+
- [Contract](../CONTRACT.md)
|
|
17
|
+
- [CLI reference](reference/cli.md)
|
|
18
|
+
- [Options reference](reference/options.md)
|
|
19
|
+
- [Reference index](reference/index.md)
|
|
20
|
+
- [Security policy](../SECURITY.md)
|
|
21
|
+
|
|
22
|
+
## Explanation
|
|
23
|
+
- [Explanation index](explanation/index.md)
|
|
24
|
+
- [Profile behavior and tradeoffs](explanation/profiles.md)
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
# CLI reference
|
|
2
|
+
|
|
3
|
+
## Goal
|
|
4
|
+
Provide one canonical reference for `dir-archiver` CLI commands, flags, JSON
|
|
5
|
+
outputs, and exit-code behavior.
|
|
6
|
+
|
|
7
|
+
## Prereqs
|
|
8
|
+
- Build CLI once: `npm run build`
|
|
9
|
+
- Binary path in this repo: `dist/cli.js`
|
|
10
|
+
|
|
11
|
+
## Copy/paste
|
|
12
|
+
Show usage:
|
|
13
|
+
|
|
14
|
+
```sh
|
|
15
|
+
node dist/cli.js
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Run examples:
|
|
19
|
+
|
|
20
|
+
```sh
|
|
21
|
+
node examples/run-all.mjs
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## What you should see
|
|
25
|
+
- Exit code `2` with usage diagnostics for invalid invocations.
|
|
26
|
+
- Exit code `0` for successful commands.
|
|
27
|
+
- JSON payloads on stdout when `--json` is set.
|
|
28
|
+
|
|
29
|
+
## Safety notes
|
|
30
|
+
> [!NOTE]
|
|
31
|
+
> For automation, always pass `--json` and consume stable codes/fields instead
|
|
32
|
+
> of parsing human-readable output.
|
|
33
|
+
|
|
34
|
+
## Common flags
|
|
35
|
+
|
|
36
|
+
| Flag | Alias | Default | Notes |
|
|
37
|
+
| --- | --- | --- | --- |
|
|
38
|
+
| `--source` | `--src` | command-specific | Input directory/file for `write`. |
|
|
39
|
+
| `--input` | `-i` | command-specific | Archive path for `open`, `detect`, `list`, `audit`, `extract`, `normalize`. |
|
|
40
|
+
| `--output` | `--dest`, `-o` | command-specific | Destination path for `write`, `extract`, `normalize`. |
|
|
41
|
+
| `--format` | - | inferred/auto | Values: `zip`, `tar`, `tgz`, `tar.gz`, `gz`, `bz2`, `tar.bz2`, `zst`, `tar.zst`, `br`, `tar.br`, `xz`, `tar.xz`. |
|
|
42
|
+
| `--profile` | - | `strict` | Values: `compat`, `strict`, `agent`. |
|
|
43
|
+
| `--json` | - | `false` | Emit machine-readable JSON on stdout. |
|
|
44
|
+
| `--include-base-directory` | `--includebasedir` | `false` | Wrap archive content under the source directory name in `write`. |
|
|
45
|
+
| `--follow-symlinks` | `--followsymlinks` | `false` | Follow symlink targets when archiving source input. |
|
|
46
|
+
| `--exclude` | - | `[]` | Exclude path globs in `write`. Repeatable. |
|
|
47
|
+
| `--allow-symlinks` | - | `false` | Permit symlink extraction targets. |
|
|
48
|
+
| `--allow-hardlinks` | - | `false` | Permit hardlink extraction targets. |
|
|
49
|
+
| `--max-entry-bytes` | - | unset | Maximum bytes per extracted entry. |
|
|
50
|
+
| `--max-total-extracted-bytes` | - | unset | Maximum aggregate extracted bytes per run. |
|
|
51
|
+
|
|
52
|
+
## Commands
|
|
53
|
+
|
|
54
|
+
### `write`
|
|
55
|
+
|
|
56
|
+
Synopsis:
|
|
57
|
+
|
|
58
|
+
```sh
|
|
59
|
+
dir-archiver write --source <path> --src <path> --output <archive> --dest <archive> [--format <format>] [--include-base-directory|--includebasedir] [--follow-symlinks|--followsymlinks] [--exclude <path>...]
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Example:
|
|
63
|
+
|
|
64
|
+
```sh
|
|
65
|
+
dir-archiver write --source ./plugin --output ./bundle.zip --include-base-directory --exclude .git --json
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
JSON output shape:
|
|
69
|
+
|
|
70
|
+
```json
|
|
71
|
+
{
|
|
72
|
+
"format": "zip",
|
|
73
|
+
"source": "./plugin",
|
|
74
|
+
"destination": "./bundle.zip",
|
|
75
|
+
"entryCount": 3,
|
|
76
|
+
"wrappedDirectoryCodec": false
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### `open`
|
|
81
|
+
|
|
82
|
+
Synopsis:
|
|
83
|
+
|
|
84
|
+
```sh
|
|
85
|
+
dir-archiver open --input <archive> [--format <format>] [--profile <profile>] [--json]
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Example:
|
|
89
|
+
|
|
90
|
+
```sh
|
|
91
|
+
dir-archiver open --input ./bundle.zip --json
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
JSON output shape:
|
|
95
|
+
|
|
96
|
+
```json
|
|
97
|
+
{
|
|
98
|
+
"format": "zip",
|
|
99
|
+
"detection": {
|
|
100
|
+
"schemaVersion": "1",
|
|
101
|
+
"inputKind": "file",
|
|
102
|
+
"detected": {
|
|
103
|
+
"container": "zip",
|
|
104
|
+
"compression": "none",
|
|
105
|
+
"layers": ["zip"]
|
|
106
|
+
},
|
|
107
|
+
"confidence": "high",
|
|
108
|
+
"notes": ["Format inferred from magic bytes"]
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### `detect`
|
|
114
|
+
|
|
115
|
+
Synopsis:
|
|
116
|
+
|
|
117
|
+
```sh
|
|
118
|
+
dir-archiver detect --input <archive> [--json]
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Example:
|
|
122
|
+
|
|
123
|
+
```sh
|
|
124
|
+
dir-archiver detect --input ./bundle.zip --json
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
JSON output shape:
|
|
128
|
+
|
|
129
|
+
```json
|
|
130
|
+
{
|
|
131
|
+
"format": "zip",
|
|
132
|
+
"detection": {
|
|
133
|
+
"schemaVersion": "1",
|
|
134
|
+
"inputKind": "file",
|
|
135
|
+
"detected": {
|
|
136
|
+
"container": "zip",
|
|
137
|
+
"compression": "none",
|
|
138
|
+
"layers": ["zip"]
|
|
139
|
+
},
|
|
140
|
+
"confidence": "high",
|
|
141
|
+
"notes": ["Format inferred from magic bytes"]
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### `list`
|
|
147
|
+
|
|
148
|
+
Synopsis:
|
|
149
|
+
|
|
150
|
+
```sh
|
|
151
|
+
dir-archiver list --input <archive> [--json]
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
Example:
|
|
155
|
+
|
|
156
|
+
```sh
|
|
157
|
+
dir-archiver list --input ./bundle.zip --json
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
JSON output shape:
|
|
161
|
+
|
|
162
|
+
```json
|
|
163
|
+
{
|
|
164
|
+
"format": "zip",
|
|
165
|
+
"detection": { "...": "same shape as detect/open" },
|
|
166
|
+
"entries": [
|
|
167
|
+
{
|
|
168
|
+
"format": "zip",
|
|
169
|
+
"name": "plugin/index.js",
|
|
170
|
+
"size": "42",
|
|
171
|
+
"isDirectory": false,
|
|
172
|
+
"isSymlink": false
|
|
173
|
+
}
|
|
174
|
+
]
|
|
175
|
+
}
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### `audit`
|
|
179
|
+
|
|
180
|
+
Synopsis:
|
|
181
|
+
|
|
182
|
+
```sh
|
|
183
|
+
dir-archiver audit --input <archive> [--profile <profile>] [--json]
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
Example:
|
|
187
|
+
|
|
188
|
+
```sh
|
|
189
|
+
dir-archiver audit --input ./bundle.zip --profile agent --json
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
JSON output shape:
|
|
193
|
+
|
|
194
|
+
```json
|
|
195
|
+
{
|
|
196
|
+
"schemaVersion": "1",
|
|
197
|
+
"ok": true,
|
|
198
|
+
"summary": {
|
|
199
|
+
"entries": 3,
|
|
200
|
+
"warnings": 0,
|
|
201
|
+
"errors": 0
|
|
202
|
+
},
|
|
203
|
+
"issues": []
|
|
204
|
+
}
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
### `extract`
|
|
208
|
+
|
|
209
|
+
Synopsis:
|
|
210
|
+
|
|
211
|
+
```sh
|
|
212
|
+
dir-archiver extract --input <archive> --output <directory> [--profile <profile>] [--allow-symlinks] [--allow-hardlinks] [--max-entry-bytes <n>] [--max-total-extracted-bytes <n>] [--json]
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
Example:
|
|
216
|
+
|
|
217
|
+
```sh
|
|
218
|
+
dir-archiver extract --input ./bundle.zip --output ./out --profile strict --max-total-extracted-bytes 536870912 --json
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
JSON output shape:
|
|
222
|
+
|
|
223
|
+
```json
|
|
224
|
+
{
|
|
225
|
+
"format": "zip",
|
|
226
|
+
"destination": "./out",
|
|
227
|
+
"extractedFiles": 2,
|
|
228
|
+
"extractedDirectories": 1,
|
|
229
|
+
"skippedEntries": 0,
|
|
230
|
+
"issues": []
|
|
231
|
+
}
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
### `normalize`
|
|
235
|
+
|
|
236
|
+
Synopsis:
|
|
237
|
+
|
|
238
|
+
```sh
|
|
239
|
+
dir-archiver normalize --input <archive> --output <archive> [--profile <profile>] [--json]
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
Example:
|
|
243
|
+
|
|
244
|
+
```sh
|
|
245
|
+
dir-archiver normalize --input ./incoming.zip --output ./normalized.zip --profile strict --json
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
JSON output shape:
|
|
249
|
+
|
|
250
|
+
```json
|
|
251
|
+
{
|
|
252
|
+
"format": "zip",
|
|
253
|
+
"report": {
|
|
254
|
+
"schemaVersion": "1",
|
|
255
|
+
"ok": true,
|
|
256
|
+
"summary": {
|
|
257
|
+
"entries": 10,
|
|
258
|
+
"outputEntries": 10,
|
|
259
|
+
"droppedEntries": 0,
|
|
260
|
+
"renamedEntries": 0,
|
|
261
|
+
"warnings": 0,
|
|
262
|
+
"errors": 0
|
|
263
|
+
},
|
|
264
|
+
"issues": []
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
## Exit codes and stderr/stdout contract
|
|
270
|
+
|
|
271
|
+
- Exit `0`: success.
|
|
272
|
+
- Exit `1`: operational failure (typed `DirArchiverError` JSON on stderr).
|
|
273
|
+
- Exit `2`: usage/validation failure (`DIRARCHIVER_USAGE` with `issues`).
|
|
274
|
+
|
|
275
|
+
For machine consumers:
|
|
276
|
+
- stdout is reserved for success payloads (especially with `--json`).
|
|
277
|
+
- stderr is reserved for diagnostics/failures.
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# Options reference
|
|
2
|
+
|
|
3
|
+
This page is the single reference for public option fields in `dir-archiver`.
|
|
4
|
+
For CLI commands and flags, see [CLI reference](cli.md).
|
|
5
|
+
|
|
6
|
+
## `OpenOptions`
|
|
7
|
+
|
|
8
|
+
Used by `open`, `detect`, `list`, and `audit`.
|
|
9
|
+
|
|
10
|
+
| Field | Type | Default | Notes |
|
|
11
|
+
| --- | --- | --- | --- |
|
|
12
|
+
| `format` | `ArchiveFormat` | auto-detect | Force a format when detection by bytes/filename is ambiguous. |
|
|
13
|
+
| `profile` | `compat \| strict \| agent` | bytefold default | Safety profile passed to archive readers/audit. |
|
|
14
|
+
| `isStrict` | `boolean` | profile-driven | Explicit strict-mode override. |
|
|
15
|
+
| `limits` | `ArchiveLimits` | none | Resource ceilings for parse/audit operations. |
|
|
16
|
+
| `signal` | `AbortSignal` | none | Cancels long-running operations. |
|
|
17
|
+
| `password` | `string` | none | Password for encrypted archives where supported. |
|
|
18
|
+
| `filename` | `string` | none | Filename hint for extension-based detection. |
|
|
19
|
+
|
|
20
|
+
## `ExtractOptions`
|
|
21
|
+
|
|
22
|
+
`ExtractOptions` extends `OpenOptions`.
|
|
23
|
+
|
|
24
|
+
| Field | Type | Default | Notes |
|
|
25
|
+
| --- | --- | --- | --- |
|
|
26
|
+
| `profile` | `compat \| strict \| agent` | `strict` | `extract` defaults to strict safety posture. |
|
|
27
|
+
| `allowSymlinks` | `boolean` | `false` | When `false`, symlink entries are skipped. |
|
|
28
|
+
| `allowHardlinks` | `boolean` | `false` | Hard-link entries are currently rejected with `DIRARCHIVER_UNSUPPORTED_ENTRY`. |
|
|
29
|
+
| `maxEntryBytes` | `number` | none | Maximum bytes for one extracted file entry. |
|
|
30
|
+
| `maxTotalExtractedBytes` | `number` | none | Maximum cumulative bytes written during extraction. |
|
|
31
|
+
|
|
32
|
+
## `WriteOptions`
|
|
33
|
+
|
|
34
|
+
| Field | Type | Default | Notes |
|
|
35
|
+
| --- | --- | --- | --- |
|
|
36
|
+
| `format` | `ArchiveFormat` | inferred from destination extension, fallback `zip` | Also wraps directory + single-file codec requests (`gz` -> `tar.gz`, etc.). |
|
|
37
|
+
| `includeBaseDirectory` | `boolean` | `false` | Include source directory name as archive root path prefix. |
|
|
38
|
+
| `followSymlinks` | `boolean` | `false` | Follow symlinks while traversing source directories. |
|
|
39
|
+
| `exclude` | `string[]` | `[]` | Exact basename or relative-path matches to skip while traversing the source root (not glob patterns). |
|
|
40
|
+
| `profile` | `ArchiveProfile` | none | Present in API type; currently reserved and not forwarded by `write()`. |
|
|
41
|
+
| `limits` | `ArchiveLimits` | none | Present in API type; currently reserved and not forwarded by `write()`. |
|
|
42
|
+
|
|
43
|
+
## Examples
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
import { extract, write } from "dir-archiver";
|
|
47
|
+
|
|
48
|
+
await extract("./archive.zip", "./out", {
|
|
49
|
+
profile: "strict",
|
|
50
|
+
maxTotalExtractedBytes: 512 * 1024 * 1024,
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
await write("./project", "./project.zip", {
|
|
54
|
+
includeBaseDirectory: true,
|
|
55
|
+
exclude: ["tmp", "nested/skip.txt"],
|
|
56
|
+
});
|
|
57
|
+
```
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Tutorial: bundle a plugin directory
|
|
2
|
+
|
|
3
|
+
## Goal
|
|
4
|
+
Create a distributable ZIP that keeps a stable root folder and excludes local
|
|
5
|
+
development files.
|
|
6
|
+
|
|
7
|
+
## Prereqs
|
|
8
|
+
- Node `>=24`
|
|
9
|
+
- `npm install`
|
|
10
|
+
- `npm run build`
|
|
11
|
+
|
|
12
|
+
## Copy/paste
|
|
13
|
+
CLI-style command:
|
|
14
|
+
|
|
15
|
+
```sh
|
|
16
|
+
dir-archiver write \
|
|
17
|
+
--includebasedir \
|
|
18
|
+
--src . \
|
|
19
|
+
--dest ../bundle.zip \
|
|
20
|
+
--exclude .git \
|
|
21
|
+
--exclude node_modules \
|
|
22
|
+
--exclude package-lock.json \
|
|
23
|
+
--exclude package.json \
|
|
24
|
+
--json
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Runnable example file:
|
|
28
|
+
|
|
29
|
+
```sh
|
|
30
|
+
node examples/bundle-a-plugin.mjs
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## What you should see
|
|
34
|
+
- A ZIP file is created.
|
|
35
|
+
- JSON output reports `format: "zip"`.
|
|
36
|
+
- Excluded paths (`.git`, `node_modules`, lock/package manifests) are omitted.
|
|
37
|
+
|
|
38
|
+
## Safety notes
|
|
39
|
+
> [!NOTE]
|
|
40
|
+
> `--includebasedir` preserves one top-level folder in the archive. This keeps
|
|
41
|
+
> extraction deterministic and prevents files from scattering into whichever
|
|
42
|
+
> directory the user extracts into.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Tutorial: first archive flow
|
|
2
|
+
|
|
3
|
+
## Goal
|
|
4
|
+
Write an archive, detect its format, and extract it with strict safety defaults.
|
|
5
|
+
|
|
6
|
+
## Prereqs
|
|
7
|
+
- Node `>=24`
|
|
8
|
+
- `npm install`
|
|
9
|
+
- `npm run build`
|
|
10
|
+
|
|
11
|
+
## Copy/paste
|
|
12
|
+
```ts
|
|
13
|
+
import { write, detect, extract } from "dir-archiver";
|
|
14
|
+
|
|
15
|
+
await write("./project", "./project.zip", {
|
|
16
|
+
format: "zip",
|
|
17
|
+
includeBaseDirectory: true,
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
const detected = await detect("./project.zip");
|
|
21
|
+
await extract("./project.zip", "./out", { profile: "strict" });
|
|
22
|
+
|
|
23
|
+
console.log(detected.format);
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## What you should see
|
|
27
|
+
- `detected.format` prints `zip`.
|
|
28
|
+
- `./out` contains the extracted files.
|
|
29
|
+
|
|
30
|
+
## Safety notes
|
|
31
|
+
> [!NOTE]
|
|
32
|
+
> Use `profile: "strict"` for extraction unless you have a documented reason to
|
|
33
|
+
> weaken constraints.
|
package/jsr.json
CHANGED
|
@@ -1,21 +1,26 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ismail-elkorchi/dir-archiver",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.1",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": "./src/index.ts"
|
|
7
7
|
},
|
|
8
8
|
"imports": {
|
|
9
|
-
"@ismail-elkorchi/bytefold": "jsr:@ismail-elkorchi/bytefold@^0.
|
|
10
|
-
"@ismail-elkorchi/bytefold/node": "jsr:@ismail-elkorchi/bytefold/node@^0.
|
|
11
|
-
"@ismail-elkorchi/bytefold/deno": "jsr:@ismail-elkorchi/bytefold/deno@^0.
|
|
12
|
-
"@ismail-elkorchi/bytefold/bun": "jsr:@ismail-elkorchi/bytefold/bun@^0.
|
|
9
|
+
"@ismail-elkorchi/bytefold": "jsr:@ismail-elkorchi/bytefold@^0.8.1",
|
|
10
|
+
"@ismail-elkorchi/bytefold/node": "jsr:@ismail-elkorchi/bytefold/node@^0.8.1",
|
|
11
|
+
"@ismail-elkorchi/bytefold/deno": "jsr:@ismail-elkorchi/bytefold/deno@^0.8.1",
|
|
12
|
+
"@ismail-elkorchi/bytefold/bun": "jsr:@ismail-elkorchi/bytefold/bun@^0.8.1"
|
|
13
13
|
},
|
|
14
14
|
"publish": {
|
|
15
15
|
"include": [
|
|
16
16
|
"LICENSE",
|
|
17
17
|
"README.md",
|
|
18
18
|
"CHANGELOG.md",
|
|
19
|
+
"CONTRACT.md",
|
|
20
|
+
"SECURITY.md",
|
|
21
|
+
"CONTRIBUTING.md",
|
|
22
|
+
"SUPPORT.md",
|
|
23
|
+
"docs/**",
|
|
19
24
|
"src/**",
|
|
20
25
|
"jsr.json",
|
|
21
26
|
"package.json"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dir-archiver",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.1",
|
|
4
4
|
"description": "Bytefold-backed archive orchestration for directories/files across Node.js, Deno, and Bun.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -17,6 +17,13 @@
|
|
|
17
17
|
},
|
|
18
18
|
"files": [
|
|
19
19
|
"LICENSE",
|
|
20
|
+
"README.md",
|
|
21
|
+
"CHANGELOG.md",
|
|
22
|
+
"CONTRACT.md",
|
|
23
|
+
"SECURITY.md",
|
|
24
|
+
"CONTRIBUTING.md",
|
|
25
|
+
"SUPPORT.md",
|
|
26
|
+
"docs",
|
|
20
27
|
"dist",
|
|
21
28
|
"jsr.json"
|
|
22
29
|
],
|
|
@@ -46,16 +53,20 @@
|
|
|
46
53
|
"homepage": "https://github.com/Ismail-elkorchi/dir-archiver",
|
|
47
54
|
"scripts": {
|
|
48
55
|
"build": "tsc -p tsconfig.json",
|
|
56
|
+
"examples:run": "node ./examples/run-all.mjs",
|
|
49
57
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
50
58
|
"lint": "npm run typecheck && eslint . --max-warnings=0",
|
|
51
|
-
"test": "npm run build && node --test test/api-snapshot.test.mjs test/operations.test.mjs test/cli.test.mjs test/matrix-node.test.mjs",
|
|
59
|
+
"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",
|
|
52
61
|
"test:security": "npm run build && node --test test/security.test.mjs",
|
|
53
|
-
"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 test:security && npm run test:runtimes",
|
|
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",
|
|
63
|
+
"deps:fresh": "node ./scripts/direct-runtime-deps-freshness.mjs",
|
|
54
64
|
"test:deno": "npm run build && deno run --allow-read --allow-write --allow-env --allow-sys test/deno-smoke.mjs",
|
|
55
65
|
"test:bun": "npm run build && bun test/bun-smoke.mjs",
|
|
56
66
|
"test:runtimes": "npm run test:deno && npm run test:bun && npm run test:runtimes:fingerprints",
|
|
57
67
|
"test:runtimes:fingerprints": "npm run build && node ./scripts/compare-runtime-fingerprints.mjs",
|
|
58
68
|
"jsr:score": "node ./scripts/jsr-score-gate.mjs",
|
|
69
|
+
"release:audit": "node ./scripts/release-audit.mjs",
|
|
59
70
|
"test:downstream:released": "npm run test",
|
|
60
71
|
"test:downstream:main": "npm install --no-save github:Ismail-elkorchi/bytefold#main github:Ismail-elkorchi/argv-flags#main && npm run test",
|
|
61
72
|
"jsr:dry": "deno publish --dry-run --allow-dirty --sloppy-imports",
|
|
@@ -65,16 +76,16 @@
|
|
|
65
76
|
"node": ">=24"
|
|
66
77
|
},
|
|
67
78
|
"dependencies": {
|
|
68
|
-
"@ismail-elkorchi/bytefold": "^0.
|
|
69
|
-
"argv-flags": "^1.0.
|
|
79
|
+
"@ismail-elkorchi/bytefold": "^0.8.1",
|
|
80
|
+
"argv-flags": "^1.0.4"
|
|
70
81
|
},
|
|
71
82
|
"devDependencies": {
|
|
72
|
-
"@eslint/js": "^
|
|
73
|
-
"@types/node": "^
|
|
74
|
-
"@typescript-eslint/eslint-plugin": "^8.
|
|
83
|
+
"@eslint/js": "^10.0.1",
|
|
84
|
+
"@types/node": "^25.3.2",
|
|
85
|
+
"@typescript-eslint/eslint-plugin": "^8.56.1",
|
|
75
86
|
"@typescript-eslint/parser": "^8.54.0",
|
|
76
|
-
"eslint": "^
|
|
77
|
-
"globals": "^17.
|
|
87
|
+
"eslint": "^10.0.2",
|
|
88
|
+
"globals": "^17.3.0",
|
|
78
89
|
"typescript": "^5.3.3",
|
|
79
90
|
"yauzl": "^3.2.0"
|
|
80
91
|
}
|