dir-archiver 2.2.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +60 -97
- package/dist/cli-args.d.ts +24 -0
- package/dist/cli-args.js +204 -0
- package/dist/cli.js +131 -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 +121 -0
- package/dist/types.js +1 -0
- package/jsr.json +30 -0
- package/package.json +36 -15
package/README.md
CHANGED
|
@@ -1,125 +1,88 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
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
|
+
---
|
|
3
10
|
|
|
4
|
-
#
|
|
5
|
-
Compress a whole directory (including subdirectories) into a zip file, with options to exclude specific files or directories.
|
|
11
|
+
# dir-archiver
|
|
6
12
|
|
|
7
|
-
|
|
13
|
+
`dir-archiver` v3 is a bytefold-backed archive orchestration layer for Node.js, Deno, and Bun.
|
|
14
|
+
ESM-only. Safety profiles: `compat | strict | agent`.
|
|
8
15
|
|
|
9
|
-
|
|
10
|
-
$ npm install dir-archiver
|
|
11
|
-
```
|
|
12
|
-
|
|
13
|
-
Requires Node.js >=18.
|
|
14
|
-
|
|
15
|
-
# Usage
|
|
16
|
-
|
|
17
|
-
## API
|
|
16
|
+
## Install
|
|
18
17
|
|
|
19
|
-
|
|
18
|
+
### npm
|
|
20
19
|
|
|
21
|
-
```
|
|
22
|
-
|
|
20
|
+
```sh
|
|
21
|
+
npm install dir-archiver
|
|
22
|
+
```
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
'./my-project',
|
|
26
|
-
'./my-project.zip',
|
|
27
|
-
true,
|
|
28
|
-
['node_modules', 'dist', 'nested/secret.txt'],
|
|
29
|
-
false
|
|
30
|
-
);
|
|
24
|
+
### JSR
|
|
31
25
|
|
|
32
|
-
|
|
26
|
+
```sh
|
|
27
|
+
deno add jsr:@ismail-elkorchi/dir-archiver
|
|
33
28
|
```
|
|
34
29
|
|
|
35
|
-
|
|
30
|
+
## Quickstart (API)
|
|
36
31
|
|
|
37
32
|
```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`.
|
|
33
|
+
import { write, detect, list, extract } from 'dir-archiver';
|
|
56
34
|
|
|
57
|
-
|
|
58
|
-
|
|
35
|
+
await write('./project', './project.zip', {
|
|
36
|
+
format: 'zip',
|
|
37
|
+
includeBaseDirectory: true,
|
|
38
|
+
profile: 'strict'
|
|
39
|
+
});
|
|
59
40
|
|
|
60
|
-
|
|
41
|
+
const detected = await detect('./project.zip');
|
|
42
|
+
const listed = await list('./project.zip');
|
|
43
|
+
await extract('./project.zip', './out', { profile: 'strict' });
|
|
61
44
|
|
|
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]
|
|
45
|
+
console.log(detected.format, listed.entries.length);
|
|
78
46
|
```
|
|
79
47
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
# CLI examples
|
|
48
|
+
## Public operations
|
|
83
49
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
50
|
+
- `open(input, options)`
|
|
51
|
+
- `detect(input, options)`
|
|
52
|
+
- `list(input, options)`
|
|
53
|
+
- `audit(input, options)`
|
|
54
|
+
- `extract(input, destination, options)`
|
|
55
|
+
- `normalize(input, destination, options)`
|
|
56
|
+
- `write(source, destination, options)`
|
|
87
57
|
|
|
88
|
-
|
|
89
|
-
|
|
58
|
+
Format surface matches bytefold `ArchiveFormat` support.
|
|
59
|
+
Directory + single-file codec requests are normalized to `tar.<codec>` (`gz`, `bz2`, `xz`, `zst`, `br`).
|
|
90
60
|
|
|
91
|
-
|
|
92
|
-
dir-archiver --src ./my-project --dest ./my-project.zip --exclude nested/secret.txt
|
|
93
|
-
|
|
94
|
-
# Windows-style excludes (backslashes are normalized)
|
|
95
|
-
dir-archiver --src . --dest archive.zip --exclude .\\nested\\skip.txt
|
|
96
|
-
|
|
97
|
-
# Follow symlinks
|
|
98
|
-
dir-archiver --src ./my-project --dest ./my-project.zip --followsymlinks=true
|
|
99
|
-
```
|
|
100
|
-
|
|
101
|
-
# Testing
|
|
61
|
+
## CLI
|
|
102
62
|
|
|
103
63
|
```sh
|
|
104
|
-
|
|
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
|
|
105
70
|
```
|
|
106
71
|
|
|
107
|
-
|
|
72
|
+
Exit codes:
|
|
108
73
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
$ npm run build
|
|
113
|
-
$ npm run lint
|
|
114
|
-
```
|
|
74
|
+
- `0` success
|
|
75
|
+
- `1` operational failure
|
|
76
|
+
- `2` usage/validation failure
|
|
115
77
|
|
|
116
|
-
|
|
78
|
+
## Security model
|
|
117
79
|
|
|
80
|
+
- Archive extraction treats input as untrusted by default.
|
|
81
|
+
- Traversal/absolute paths are blocked in strict/agent profiles.
|
|
82
|
+
- See `SECURITY.md` and `docs/security-triage.md`.
|
|
118
83
|
|
|
84
|
+
## Docs
|
|
119
85
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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
|
|
86
|
+
- `docs/V3_CONTRACT.md`
|
|
87
|
+
- `CHANGELOG.md`
|
|
88
|
+
- `SECURITY.md`
|
|
@@ -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
|
+
};
|
package/dist/cli.js
CHANGED
|
@@ -1,51 +1,136 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
2
|
+
import { audit, detect, extract, list, normalize, open, write } from './index.js';
|
|
3
|
+
import { DirArchiverError } from './errors.js';
|
|
4
|
+
import { parseCliArgs } from './cli-args.js';
|
|
5
|
+
const usage = `Usage:
|
|
6
|
+
dir-archiver write --source <path> --output <archive> [--format <format>] [--include-base-directory] [--exclude <path>...]
|
|
7
|
+
dir-archiver detect --input <archive>
|
|
8
|
+
dir-archiver list --input <archive>
|
|
9
|
+
dir-archiver audit --input <archive> [--profile compat|strict|agent]
|
|
10
|
+
dir-archiver extract --input <archive> --output <directory> [--profile compat|strict|agent] [--max-entry-bytes <n>] [--max-total-extracted-bytes <n>]
|
|
11
|
+
dir-archiver normalize --input <archive> --output <archive> [--profile compat|strict|agent]
|
|
12
|
+
|
|
13
|
+
Common options:
|
|
14
|
+
--format <format> zip|tar|tgz|tar.gz|gz|bz2|tar.bz2|zst|tar.zst|br|tar.br|xz|tar.xz
|
|
15
|
+
--profile <profile> compat|strict|agent
|
|
16
|
+
--json emit machine-readable JSON
|
|
17
|
+
--allow-symlinks enable symlink extraction
|
|
18
|
+
--allow-hardlinks enable hardlink extraction (currently unsupported)
|
|
19
|
+
`;
|
|
20
|
+
const run = async () => {
|
|
21
|
+
const parsed = await parseCliArgs(process.argv.slice(2));
|
|
22
|
+
const command = parsed.command;
|
|
23
|
+
if (!parsed.ok || !command) {
|
|
24
|
+
const payload = {
|
|
25
|
+
schemaVersion: '1',
|
|
26
|
+
code: 'DIRARCHIVER_USAGE',
|
|
27
|
+
message: 'Invalid CLI arguments.',
|
|
28
|
+
issues: parsed.issues
|
|
29
|
+
};
|
|
30
|
+
if (parsed.json) {
|
|
31
|
+
console.log(JSON.stringify(payload));
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
console.error(usage);
|
|
35
|
+
for (const issue of parsed.issues) {
|
|
36
|
+
console.error(`- [${issue.code}] ${issue.message}`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return 2;
|
|
40
|
+
}
|
|
41
|
+
const commonOptions = {
|
|
42
|
+
profile: parsed.profile,
|
|
43
|
+
...(parsed.format ? { format: parsed.format } : {})
|
|
44
|
+
};
|
|
45
|
+
switch (command) {
|
|
46
|
+
case 'write': {
|
|
47
|
+
const result = await write(requireString(parsed.source, 'write requires --source/--src'), requireString(parsed.output, 'write requires --output/--dest'), {
|
|
48
|
+
...commonOptions,
|
|
49
|
+
includeBaseDirectory: parsed.includeBaseDirectory,
|
|
50
|
+
followSymlinks: parsed.followSymlinks,
|
|
51
|
+
exclude: parsed.exclude
|
|
52
|
+
});
|
|
53
|
+
outputResult(parsed.json, result);
|
|
54
|
+
return 0;
|
|
55
|
+
}
|
|
56
|
+
case 'open': {
|
|
57
|
+
const reader = await open(requireString(parsed.input, 'open requires --input'), commonOptions);
|
|
58
|
+
outputResult(parsed.json, {
|
|
59
|
+
format: reader.format,
|
|
60
|
+
detection: reader.detection
|
|
61
|
+
});
|
|
62
|
+
return 0;
|
|
63
|
+
}
|
|
64
|
+
case 'detect': {
|
|
65
|
+
const result = await detect(requireString(parsed.input, 'detect requires --input'), commonOptions);
|
|
66
|
+
outputResult(parsed.json, result);
|
|
67
|
+
return 0;
|
|
68
|
+
}
|
|
69
|
+
case 'list': {
|
|
70
|
+
const result = await list(requireString(parsed.input, 'list requires --input'), commonOptions);
|
|
71
|
+
outputResult(parsed.json, result);
|
|
72
|
+
return 0;
|
|
73
|
+
}
|
|
74
|
+
case 'audit': {
|
|
75
|
+
const result = await audit(requireString(parsed.input, 'audit requires --input'), commonOptions);
|
|
76
|
+
outputResult(parsed.json, result);
|
|
77
|
+
return 0;
|
|
78
|
+
}
|
|
79
|
+
case 'extract': {
|
|
80
|
+
const result = await extract(requireString(parsed.input, 'extract requires --input'), requireString(parsed.output, 'extract requires --output'), {
|
|
81
|
+
...commonOptions,
|
|
82
|
+
allowSymlinks: parsed.allowSymlinks,
|
|
83
|
+
allowHardlinks: parsed.allowHardlinks,
|
|
84
|
+
...(typeof parsed.maxEntryBytes === 'number' ? { maxEntryBytes: parsed.maxEntryBytes } : {}),
|
|
85
|
+
...(typeof parsed.maxTotalExtractedBytes === 'number'
|
|
86
|
+
? { maxTotalExtractedBytes: parsed.maxTotalExtractedBytes }
|
|
87
|
+
: {})
|
|
88
|
+
});
|
|
89
|
+
outputResult(parsed.json, result);
|
|
90
|
+
return 0;
|
|
91
|
+
}
|
|
92
|
+
case 'normalize': {
|
|
93
|
+
const result = await normalize(requireString(parsed.input, 'normalize requires --input'), requireString(parsed.output, 'normalize requires --output'), {
|
|
94
|
+
...commonOptions
|
|
95
|
+
});
|
|
96
|
+
outputResult(parsed.json, result);
|
|
97
|
+
return 0;
|
|
18
98
|
}
|
|
99
|
+
default:
|
|
100
|
+
break;
|
|
19
101
|
}
|
|
20
|
-
|
|
102
|
+
const unreachable = command;
|
|
103
|
+
throw new Error(`Unhandled command: ${String(unreachable)}`);
|
|
21
104
|
};
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
}
|
|
44
|
-
else {
|
|
45
|
-
const archive = new index_1.default(directoryPath, zipPath, includeBaseDirectory, excludes, followSymlinks);
|
|
46
|
-
archive.createZip().catch((err) => {
|
|
47
|
-
const normalizedError = err instanceof Error ? err : new Error(String(err));
|
|
48
|
-
console.error(normalizedError);
|
|
105
|
+
const outputResult = (asJson, payload) => {
|
|
106
|
+
if (asJson) {
|
|
107
|
+
console.log(JSON.stringify(payload));
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
console.log(payload);
|
|
111
|
+
};
|
|
112
|
+
const requireString = (value, message) => {
|
|
113
|
+
if (typeof value !== 'string' || value.length === 0) {
|
|
114
|
+
throw new DirArchiverError('DIRARCHIVER_USAGE', message);
|
|
115
|
+
}
|
|
116
|
+
return value;
|
|
117
|
+
};
|
|
118
|
+
void run()
|
|
119
|
+
.then((exitCode) => {
|
|
120
|
+
process.exitCode = exitCode;
|
|
121
|
+
})
|
|
122
|
+
.catch((error) => {
|
|
123
|
+
var _a;
|
|
124
|
+
if (error instanceof DirArchiverError) {
|
|
125
|
+
console.error(JSON.stringify(error.toJSON()));
|
|
49
126
|
process.exitCode = 1;
|
|
50
|
-
|
|
51
|
-
}
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
if (error instanceof Error) {
|
|
130
|
+
console.error((_a = error.stack) !== null && _a !== void 0 ? _a : error.message);
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
console.error(String(error));
|
|
134
|
+
}
|
|
135
|
+
process.exitCode = 1;
|
|
136
|
+
});
|
package/dist/core.d.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { ArchiveAuditReport, ArchiveReader } from '@ismail-elkorchi/bytefold';
|
|
2
|
+
import type { DetectResult, DirArchiverInput, ExtractOptions, ExtractResult, ListResult, NormalizeOptions, NormalizeResult, OpenOptions, WriteOptions, WriteResult } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Opens an archive input with bytefold runtime bindings.
|
|
5
|
+
*/
|
|
6
|
+
export declare const open: (input: DirArchiverInput, options?: OpenOptions) => Promise<ArchiveReader>;
|
|
7
|
+
/**
|
|
8
|
+
* Detects archive format and exposes bytefold detection metadata.
|
|
9
|
+
*/
|
|
10
|
+
export declare const detect: (input: DirArchiverInput, options?: OpenOptions) => Promise<DetectResult>;
|
|
11
|
+
/**
|
|
12
|
+
* Lists archive entries without extracting to disk.
|
|
13
|
+
*/
|
|
14
|
+
export declare const list: (input: DirArchiverInput, options?: OpenOptions) => Promise<ListResult>;
|
|
15
|
+
/**
|
|
16
|
+
* Runs bytefold audit checks for the selected safety profile.
|
|
17
|
+
*/
|
|
18
|
+
export declare const audit: (input: DirArchiverInput, options?: OpenOptions) => Promise<ArchiveAuditReport>;
|
|
19
|
+
/**
|
|
20
|
+
* Writes a normalized deterministic archive when supported by the format.
|
|
21
|
+
*/
|
|
22
|
+
export declare const normalize: (input: DirArchiverInput, destination: string, options?: NormalizeOptions) => Promise<NormalizeResult>;
|
|
23
|
+
/**
|
|
24
|
+
* Extracts entries to a destination directory with safety enforcement.
|
|
25
|
+
*/
|
|
26
|
+
export declare const extract: (input: DirArchiverInput, destination: string, options?: ExtractOptions) => Promise<ExtractResult>;
|
|
27
|
+
/**
|
|
28
|
+
* Writes an archive from a file or directory source.
|
|
29
|
+
*/
|
|
30
|
+
export declare const write: (source: string, destination: string, options?: WriteOptions) => Promise<WriteResult>;
|
|
31
|
+
export declare const copyStreamToFile: (source: string, destination: string) => Promise<void>;
|
|
32
|
+
export declare const pathExists: (value: string) => boolean;
|
|
33
|
+
export declare const fileSize: (value: string) => number;
|