dir-archiver 2.2.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 +48 -99
- package/SECURITY.md +23 -0
- package/SUPPORT.md +16 -0
- package/dist/cli-args.d.ts +24 -0
- package/dist/cli-args.js +204 -0
- package/dist/cli.js +132 -46
- package/dist/core.d.ts +33 -0
- package/dist/core.js +549 -0
- package/dist/errors.d.ts +25 -0
- package/dist/errors.js +25 -0
- package/dist/index.d.ts +19 -28
- package/dist/index.js +17 -319
- package/dist/runtime/bun.d.ts +2 -0
- package/dist/runtime/bun.js +17 -0
- package/dist/runtime/deno.d.ts +2 -0
- package/dist/runtime/deno.js +17 -0
- package/dist/runtime/index.d.ts +2 -0
- package/dist/runtime/index.js +33 -0
- package/dist/runtime/node.d.ts +2 -0
- package/dist/runtime/node.js +17 -0
- package/dist/runtime/types.d.ts +11 -0
- package/dist/runtime/types.js +1 -0
- package/dist/types.d.ts +186 -0
- package/dist/types.js +1 -0
- 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 +35 -0
- package/package.json +52 -20
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,125 +1,74 @@
|
|
|
1
|
-
|
|
2
|
-
[![changelog][changelog-image]][changelog-url]
|
|
1
|
+
# dir-archiver
|
|
3
2
|
|
|
4
|
-
|
|
5
|
-
Compress a whole directory (including subdirectories) into a zip file, with options to exclude specific files or directories.
|
|
3
|
+
Archive orchestration for detect/list/audit/extract/normalize/write flows across Node, Deno, and Bun.
|
|
6
4
|
|
|
7
|
-
|
|
5
|
+
## What it is
|
|
8
6
|
|
|
9
|
-
|
|
10
|
-
$ npm install dir-archiver
|
|
11
|
-
```
|
|
12
|
-
|
|
13
|
-
Requires Node.js >=18.
|
|
14
|
-
|
|
15
|
-
# Usage
|
|
16
|
-
|
|
17
|
-
## API
|
|
7
|
+
`dir-archiver` provides one API surface for archive operations with explicit safety profiles and stable error codes.
|
|
18
8
|
|
|
19
|
-
|
|
9
|
+
## Install
|
|
20
10
|
|
|
21
|
-
```
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
const archive = new DirArchiver(
|
|
25
|
-
'./my-project',
|
|
26
|
-
'./my-project.zip',
|
|
27
|
-
true,
|
|
28
|
-
['node_modules', 'dist', 'nested/secret.txt'],
|
|
29
|
-
false
|
|
30
|
-
);
|
|
31
|
-
|
|
32
|
-
await archive.createZip();
|
|
11
|
+
```sh
|
|
12
|
+
npm install dir-archiver
|
|
13
|
+
deno add jsr:@ismail-elkorchi/dir-archiver
|
|
33
14
|
```
|
|
34
15
|
|
|
35
|
-
|
|
16
|
+
## Quickstart
|
|
36
17
|
|
|
37
18
|
```ts
|
|
38
|
-
|
|
39
|
-
directoryPath: string,
|
|
40
|
-
zipPath: string,
|
|
41
|
-
includeBaseDirectory?: boolean,
|
|
42
|
-
excludes?: string[],
|
|
43
|
-
followSymlinks?: boolean
|
|
44
|
-
)
|
|
45
|
-
```
|
|
46
|
-
|
|
47
|
-
Parameters:
|
|
48
|
-
- `directoryPath`: Root folder to archive (must exist).
|
|
49
|
-
- `zipPath`: Destination zip file path (parent directory must exist).
|
|
50
|
-
- `includeBaseDirectory`: When true, the archive contains the source folder as the top-level directory.
|
|
51
|
-
- `excludes`: Names or relative paths to skip. Names without path separators match anywhere; use a relative path
|
|
52
|
-
(for example, `nested/file.txt`) to target a specific entry. Trailing slashes can target directories (for example, `cache/`).
|
|
53
|
-
Windows-style backslashes are accepted and normalized. Absolute paths inside the source tree are accepted and converted
|
|
54
|
-
to relative excludes. Matching is case-insensitive on Windows.
|
|
55
|
-
- `followSymlinks`: Follow symlinks when traversing directories. Default: `false`.
|
|
19
|
+
import { write, detect, extract } from "dir-archiver";
|
|
56
20
|
|
|
57
|
-
|
|
58
|
-
|
|
21
|
+
await write("./project", "./project.zip", {
|
|
22
|
+
format: "zip",
|
|
23
|
+
includeBaseDirectory: true,
|
|
24
|
+
});
|
|
59
25
|
|
|
60
|
-
|
|
26
|
+
const detected = await detect("./project.zip");
|
|
27
|
+
await extract("./project.zip", "./out", { profile: "strict" });
|
|
61
28
|
|
|
62
|
-
|
|
63
|
-
Usage: dir-archiver --src <path-to-directory> --dest <path-to-file>.zip --includebasedir true|false --exclude folder-name file-name.extension
|
|
64
|
-
|
|
65
|
-
Options:
|
|
66
|
-
--src The path of the folder to archive. [string][required]
|
|
67
|
-
--dest The path of the zip file to create. [string][required]
|
|
68
|
-
--includebasedir Includes a base directory at the root of the archive.
|
|
69
|
-
For example, if the root folder of your project is named
|
|
70
|
-
"your-project", setting this option to true will create
|
|
71
|
-
an archive that includes this base directory.
|
|
72
|
-
If this option is set to false the archive created will
|
|
73
|
-
unzip its content to the current directory. [bool]
|
|
74
|
-
--followsymlinks Follow symlinks when traversing directories. [bool]
|
|
75
|
-
--exclude A list with the names of the files and folders to exclude. Names without
|
|
76
|
-
path separators match anywhere; use a relative path to target a specific
|
|
77
|
-
entry. Windows-style backslashes are accepted and normalized. [array]
|
|
29
|
+
console.log(detected.format);
|
|
78
30
|
```
|
|
79
31
|
|
|
80
|
-
|
|
32
|
+
## Options reference
|
|
81
33
|
|
|
82
|
-
|
|
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)
|
|
83
37
|
|
|
84
|
-
|
|
85
|
-
# Basic
|
|
86
|
-
dir-archiver --src ./my-project --dest ./my-project.zip
|
|
38
|
+
## When not to use
|
|
87
39
|
|
|
88
|
-
|
|
89
|
-
|
|
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.
|
|
90
43
|
|
|
91
|
-
|
|
92
|
-
dir-archiver --src ./my-project --dest ./my-project.zip --exclude nested/secret.txt
|
|
44
|
+
## When to use
|
|
93
45
|
|
|
94
|
-
|
|
95
|
-
|
|
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.
|
|
96
49
|
|
|
97
|
-
|
|
98
|
-
dir-archiver --src ./my-project --dest ./my-project.zip --followsymlinks=true
|
|
99
|
-
```
|
|
50
|
+
## Compatibility
|
|
100
51
|
|
|
101
|
-
|
|
52
|
+
- Module system: ESM-only.
|
|
53
|
+
- Runtimes: Node `>=24`, current Deno, current Bun.
|
|
54
|
+
- CLI and API contracts are documented in `CONTRACT.md`.
|
|
102
55
|
|
|
103
|
-
|
|
104
|
-
$ npm test
|
|
105
|
-
```
|
|
56
|
+
## Links
|
|
106
57
|
|
|
107
|
-
|
|
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)
|
|
67
|
+
|
|
68
|
+
## Verification
|
|
108
69
|
|
|
109
70
|
```sh
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
$ npm run lint
|
|
71
|
+
npm run examples:run
|
|
72
|
+
npm run check:fast
|
|
73
|
+
npm run check
|
|
114
74
|
```
|
|
115
|
-
|
|
116
|
-
Linting runs TypeScript typechecking and ESLint. CI runs lint and tests on Node 18/20/22 across Linux, macOS, and Windows.
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
[changelog-image]: https://img.shields.io/badge/changelog-md-blue.svg?style=flat-square
|
|
121
|
-
[changelog-url]: CHANGELOG.md
|
|
122
|
-
[license-image]: https://img.shields.io/npm/l/dir-archiver.svg?style=flat-square
|
|
123
|
-
[license-url]: LICENSE
|
|
124
|
-
[npm-image]: https://img.shields.io/npm/v/dir-archiver.svg?style=flat-square
|
|
125
|
-
[npm-url]: https://www.npmjs.com/package/dir-archiver
|
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.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { ArchiveFormat, ArchiveProfile } from './types.js';
|
|
2
|
+
export type CliCommand = 'open' | 'detect' | 'list' | 'audit' | 'extract' | 'normalize' | 'write';
|
|
3
|
+
export interface ParsedCliArgs {
|
|
4
|
+
ok: boolean;
|
|
5
|
+
issues: {
|
|
6
|
+
code: string;
|
|
7
|
+
message: string;
|
|
8
|
+
}[];
|
|
9
|
+
command: CliCommand | undefined;
|
|
10
|
+
input: string | undefined;
|
|
11
|
+
source: string | undefined;
|
|
12
|
+
output: string | undefined;
|
|
13
|
+
format: ArchiveFormat | undefined;
|
|
14
|
+
profile: ArchiveProfile;
|
|
15
|
+
json: boolean;
|
|
16
|
+
includeBaseDirectory: boolean;
|
|
17
|
+
followSymlinks: boolean;
|
|
18
|
+
exclude: string[];
|
|
19
|
+
allowSymlinks: boolean;
|
|
20
|
+
allowHardlinks: boolean;
|
|
21
|
+
maxEntryBytes: number | undefined;
|
|
22
|
+
maxTotalExtractedBytes: number | undefined;
|
|
23
|
+
}
|
|
24
|
+
export declare const parseCliArgs: (argv: readonly string[]) => Promise<ParsedCliArgs>;
|
package/dist/cli-args.js
ADDED
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
const SUPPORTED_COMMANDS = new Set(['open', 'detect', 'list', 'audit', 'extract', 'normalize', 'write']);
|
|
2
|
+
const SUPPORTED_FORMATS = new Set([
|
|
3
|
+
'zip',
|
|
4
|
+
'tar',
|
|
5
|
+
'tgz',
|
|
6
|
+
'tar.gz',
|
|
7
|
+
'gz',
|
|
8
|
+
'bz2',
|
|
9
|
+
'tar.bz2',
|
|
10
|
+
'zst',
|
|
11
|
+
'tar.zst',
|
|
12
|
+
'br',
|
|
13
|
+
'tar.br',
|
|
14
|
+
'xz',
|
|
15
|
+
'tar.xz'
|
|
16
|
+
]);
|
|
17
|
+
const SUPPORTED_PROFILES = new Set(['compat', 'strict', 'agent']);
|
|
18
|
+
const CLI_SCHEMA = {
|
|
19
|
+
source: {
|
|
20
|
+
type: 'string',
|
|
21
|
+
flags: ['--source', '--src']
|
|
22
|
+
},
|
|
23
|
+
input: {
|
|
24
|
+
type: 'string',
|
|
25
|
+
flags: ['--input', '-i']
|
|
26
|
+
},
|
|
27
|
+
output: {
|
|
28
|
+
type: 'string',
|
|
29
|
+
flags: ['--output', '--dest', '-o']
|
|
30
|
+
},
|
|
31
|
+
format: {
|
|
32
|
+
type: 'string',
|
|
33
|
+
flags: ['--format']
|
|
34
|
+
},
|
|
35
|
+
profile: {
|
|
36
|
+
type: 'string',
|
|
37
|
+
flags: ['--profile'],
|
|
38
|
+
default: 'strict'
|
|
39
|
+
},
|
|
40
|
+
json: {
|
|
41
|
+
type: 'boolean',
|
|
42
|
+
flags: ['--json'],
|
|
43
|
+
default: false
|
|
44
|
+
},
|
|
45
|
+
includeBaseDirectory: {
|
|
46
|
+
type: 'boolean',
|
|
47
|
+
flags: ['--include-base-directory', '--includebasedir'],
|
|
48
|
+
default: false
|
|
49
|
+
},
|
|
50
|
+
followSymlinks: {
|
|
51
|
+
type: 'boolean',
|
|
52
|
+
flags: ['--follow-symlinks', '--followsymlinks'],
|
|
53
|
+
default: false
|
|
54
|
+
},
|
|
55
|
+
exclude: {
|
|
56
|
+
type: 'array',
|
|
57
|
+
flags: ['--exclude'],
|
|
58
|
+
default: []
|
|
59
|
+
},
|
|
60
|
+
allowSymlinks: {
|
|
61
|
+
type: 'boolean',
|
|
62
|
+
flags: ['--allow-symlinks'],
|
|
63
|
+
default: false
|
|
64
|
+
},
|
|
65
|
+
allowHardlinks: {
|
|
66
|
+
type: 'boolean',
|
|
67
|
+
flags: ['--allow-hardlinks'],
|
|
68
|
+
default: false
|
|
69
|
+
},
|
|
70
|
+
maxEntryBytes: {
|
|
71
|
+
type: 'number',
|
|
72
|
+
flags: ['--max-entry-bytes']
|
|
73
|
+
},
|
|
74
|
+
maxTotalExtractedBytes: {
|
|
75
|
+
type: 'number',
|
|
76
|
+
flags: ['--max-total-extracted-bytes']
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
let parseArgsPromise;
|
|
80
|
+
const loadParseArgs = () => {
|
|
81
|
+
parseArgsPromise !== null && parseArgsPromise !== void 0 ? parseArgsPromise : (parseArgsPromise = import('argv-flags').then((moduleExports) => moduleExports.default));
|
|
82
|
+
return parseArgsPromise;
|
|
83
|
+
};
|
|
84
|
+
export const parseCliArgs = async (argv) => {
|
|
85
|
+
const parseArgs = await loadParseArgs();
|
|
86
|
+
const parsed = parseArgs(CLI_SCHEMA, {
|
|
87
|
+
argv: [...argv]
|
|
88
|
+
});
|
|
89
|
+
const issues = parsed.issues.map((issue) => ({
|
|
90
|
+
code: issue.code,
|
|
91
|
+
message: issue.message
|
|
92
|
+
}));
|
|
93
|
+
const values = parsed.values;
|
|
94
|
+
const commandToken = parsed.rest[0];
|
|
95
|
+
const command = resolveCommand(commandToken, values, issues);
|
|
96
|
+
const profile = resolveProfile(values['profile'], issues);
|
|
97
|
+
const format = resolveFormat(values['format'], issues);
|
|
98
|
+
const source = toOptionalString(values['source']);
|
|
99
|
+
const input = toOptionalString(values['input']);
|
|
100
|
+
const output = toOptionalString(values['output']);
|
|
101
|
+
validateCommandRequirements(command, { source, input, output }, issues);
|
|
102
|
+
return {
|
|
103
|
+
ok: issues.length === 0,
|
|
104
|
+
issues,
|
|
105
|
+
command,
|
|
106
|
+
source,
|
|
107
|
+
input,
|
|
108
|
+
output,
|
|
109
|
+
format,
|
|
110
|
+
profile,
|
|
111
|
+
json: values['json'] === true,
|
|
112
|
+
includeBaseDirectory: values['includeBaseDirectory'] === true,
|
|
113
|
+
followSymlinks: values['followSymlinks'] === true,
|
|
114
|
+
exclude: toStringArray(values['exclude']),
|
|
115
|
+
allowSymlinks: values['allowSymlinks'] === true,
|
|
116
|
+
allowHardlinks: values['allowHardlinks'] === true,
|
|
117
|
+
maxEntryBytes: toOptionalNumber(values['maxEntryBytes']),
|
|
118
|
+
maxTotalExtractedBytes: toOptionalNumber(values['maxTotalExtractedBytes'])
|
|
119
|
+
};
|
|
120
|
+
};
|
|
121
|
+
const resolveCommand = (commandToken, values, issues) => {
|
|
122
|
+
if (typeof commandToken === 'string' && SUPPORTED_COMMANDS.has(commandToken)) {
|
|
123
|
+
return commandToken;
|
|
124
|
+
}
|
|
125
|
+
if (typeof commandToken === 'string' && commandToken.length > 0) {
|
|
126
|
+
issues.push({
|
|
127
|
+
code: 'USAGE',
|
|
128
|
+
message: `Unknown command "${commandToken}".`
|
|
129
|
+
});
|
|
130
|
+
return undefined;
|
|
131
|
+
}
|
|
132
|
+
const hasSource = typeof values['source'] === 'string';
|
|
133
|
+
const hasOutput = typeof values['output'] === 'string';
|
|
134
|
+
if (hasSource && hasOutput) {
|
|
135
|
+
return 'write';
|
|
136
|
+
}
|
|
137
|
+
issues.push({
|
|
138
|
+
code: 'USAGE',
|
|
139
|
+
message: 'Missing command.'
|
|
140
|
+
});
|
|
141
|
+
return undefined;
|
|
142
|
+
};
|
|
143
|
+
const resolveProfile = (value, issues) => {
|
|
144
|
+
const normalized = typeof value === 'string' ? value : 'strict';
|
|
145
|
+
if (!SUPPORTED_PROFILES.has(normalized)) {
|
|
146
|
+
issues.push({
|
|
147
|
+
code: 'INVALID_VALUE',
|
|
148
|
+
message: `Unsupported profile "${String(value)}".`
|
|
149
|
+
});
|
|
150
|
+
return 'strict';
|
|
151
|
+
}
|
|
152
|
+
return normalized;
|
|
153
|
+
};
|
|
154
|
+
const resolveFormat = (value, issues) => {
|
|
155
|
+
if (typeof value !== 'string') {
|
|
156
|
+
return undefined;
|
|
157
|
+
}
|
|
158
|
+
if (!SUPPORTED_FORMATS.has(value)) {
|
|
159
|
+
issues.push({
|
|
160
|
+
code: 'INVALID_VALUE',
|
|
161
|
+
message: `Unsupported format "${value}".`
|
|
162
|
+
});
|
|
163
|
+
return undefined;
|
|
164
|
+
}
|
|
165
|
+
return value;
|
|
166
|
+
};
|
|
167
|
+
const validateCommandRequirements = (command, values, issues) => {
|
|
168
|
+
if (!command) {
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
if (command === 'write') {
|
|
172
|
+
if (!values.source) {
|
|
173
|
+
issues.push({ code: 'REQUIRED', message: 'write requires --source/--src.' });
|
|
174
|
+
}
|
|
175
|
+
if (!values.output) {
|
|
176
|
+
issues.push({ code: 'REQUIRED', message: 'write requires --output/--dest.' });
|
|
177
|
+
}
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
if (command === 'extract' || command === 'normalize') {
|
|
181
|
+
if (!values.input) {
|
|
182
|
+
issues.push({ code: 'REQUIRED', message: `${command} requires --input.` });
|
|
183
|
+
}
|
|
184
|
+
if (!values.output) {
|
|
185
|
+
issues.push({ code: 'REQUIRED', message: `${command} requires --output.` });
|
|
186
|
+
}
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
if (!values.input) {
|
|
190
|
+
issues.push({ code: 'REQUIRED', message: `${command} requires --input.` });
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
const toOptionalString = (value) => {
|
|
194
|
+
return typeof value === 'string' ? value : undefined;
|
|
195
|
+
};
|
|
196
|
+
const toStringArray = (value) => {
|
|
197
|
+
if (!Array.isArray(value)) {
|
|
198
|
+
return [];
|
|
199
|
+
}
|
|
200
|
+
return value.filter((item) => typeof item === 'string');
|
|
201
|
+
};
|
|
202
|
+
const toOptionalNumber = (value) => {
|
|
203
|
+
return typeof value === 'number' && Number.isFinite(value) ? value : undefined;
|
|
204
|
+
};
|