dir-archiver 2.1.1 → 2.2.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 +85 -31
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +51 -0
- package/dist/index.d.ts +28 -0
- package/dist/index.js +320 -0
- package/package.json +24 -11
- package/cli.js +0 -31
- package/index.js +0 -154
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
[![changelog][changelog-image]][changelog-url]
|
|
3
3
|
|
|
4
4
|
# Dir Archiver
|
|
5
|
-
Compress a whole directory (including subdirectories) into a
|
|
5
|
+
Compress a whole directory (including subdirectories) into a zip file, with options to exclude specific files or directories.
|
|
6
6
|
|
|
7
7
|
# Installation
|
|
8
8
|
|
|
@@ -10,44 +10,57 @@ Compress a whole directory (including subdirectories) into a zip file, with opt
|
|
|
10
10
|
$ npm install dir-archiver
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
+
Requires Node.js >=18.
|
|
14
|
+
|
|
13
15
|
# Usage
|
|
14
16
|
|
|
15
17
|
## API
|
|
16
18
|
|
|
19
|
+
Quick start (async/await):
|
|
20
|
+
|
|
17
21
|
```javascript
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
console.error(err);
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
// Or with async/await:
|
|
45
|
-
// await archive.createZip();
|
|
22
|
+
const DirArchiver = require('dir-archiver');
|
|
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();
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Signature:
|
|
36
|
+
|
|
37
|
+
```ts
|
|
38
|
+
new DirArchiver(
|
|
39
|
+
directoryPath: string,
|
|
40
|
+
zipPath: string,
|
|
41
|
+
includeBaseDirectory?: boolean,
|
|
42
|
+
excludes?: string[],
|
|
43
|
+
followSymlinks?: boolean
|
|
44
|
+
)
|
|
46
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`.
|
|
56
|
+
|
|
57
|
+
`createZip()` returns a Promise that resolves with the zip path when the archive is finalized and rejects on failure.
|
|
58
|
+
Zip entries always use forward slashes, regardless of OS, and are added in deterministic order.
|
|
59
|
+
|
|
47
60
|
## Command Line Interface
|
|
48
61
|
|
|
49
62
|
```sh
|
|
50
|
-
Usage: dir-archiver --src <path-to-directory> --dest <path-to-file>.zip --includebasedir true|false --exclude folder-name file-name.
|
|
63
|
+
Usage: dir-archiver --src <path-to-directory> --dest <path-to-file>.zip --includebasedir true|false --exclude folder-name file-name.extension
|
|
51
64
|
|
|
52
65
|
Options:
|
|
53
66
|
--src The path of the folder to archive. [string][required]
|
|
@@ -58,9 +71,50 @@ Options:
|
|
|
58
71
|
an archive that includes this base directory.
|
|
59
72
|
If this option is set to false the archive created will
|
|
60
73
|
unzip its content to the current directory. [bool]
|
|
61
|
-
--
|
|
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]
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Inline values are supported for flags (for example, `--includebasedir=true` or `--exclude=cache`).
|
|
81
|
+
|
|
82
|
+
# CLI examples
|
|
83
|
+
|
|
84
|
+
```sh
|
|
85
|
+
# Basic
|
|
86
|
+
dir-archiver --src ./my-project --dest ./my-project.zip
|
|
87
|
+
|
|
88
|
+
# Include base directory and exclude node_modules anywhere
|
|
89
|
+
dir-archiver --src ./my-project --dest ./my-project.zip --includebasedir=true --exclude node_modules
|
|
90
|
+
|
|
91
|
+
# Exclude a specific path
|
|
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
|
|
102
|
+
|
|
103
|
+
```sh
|
|
104
|
+
$ npm test
|
|
62
105
|
```
|
|
63
106
|
|
|
107
|
+
# Development
|
|
108
|
+
|
|
109
|
+
```sh
|
|
110
|
+
$ npm install
|
|
111
|
+
$ npm run typecheck
|
|
112
|
+
$ npm run build
|
|
113
|
+
$ npm run lint
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Linting runs TypeScript typechecking and ESLint. CI runs lint and tests on Node 18/20/22 across Linux, macOS, and Windows.
|
|
117
|
+
|
|
64
118
|
|
|
65
119
|
|
|
66
120
|
[changelog-image]: https://img.shields.io/badge/changelog-md-blue.svg?style=flat-square
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
+
};
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
const index_1 = __importDefault(require("./index"));
|
|
8
|
+
const argv_flags_1 = __importDefault(require("argv-flags"));
|
|
9
|
+
const parseBooleanFlag = (flag) => {
|
|
10
|
+
const rawValue = (0, argv_flags_1.default)(flag, 'string');
|
|
11
|
+
if (typeof rawValue === 'string' && rawValue.length > 0 && !rawValue.startsWith('-')) {
|
|
12
|
+
const normalized = rawValue.toLowerCase();
|
|
13
|
+
if (normalized === 'true') {
|
|
14
|
+
return true;
|
|
15
|
+
}
|
|
16
|
+
if (normalized === 'false') {
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return (0, argv_flags_1.default)(flag, 'boolean') === true;
|
|
21
|
+
};
|
|
22
|
+
const directoryPath = (0, argv_flags_1.default)('--src', 'string');
|
|
23
|
+
const zipPath = (0, argv_flags_1.default)('--dest', 'string');
|
|
24
|
+
const includeBaseDirectory = parseBooleanFlag('--includebasedir');
|
|
25
|
+
const followSymlinks = parseBooleanFlag('--followsymlinks');
|
|
26
|
+
const excludeValues = (0, argv_flags_1.default)('--exclude', 'array');
|
|
27
|
+
const excludes = Array.isArray(excludeValues) ? excludeValues : [];
|
|
28
|
+
if (typeof directoryPath !== 'string' || typeof zipPath !== 'string') {
|
|
29
|
+
console.log(` Dir Archiver could not be executed. Some arguments are missing.
|
|
30
|
+
|
|
31
|
+
Options:
|
|
32
|
+
--src The path of the folder to archive. [string][required]
|
|
33
|
+
--dest The path of the zip file to create. [string][required]
|
|
34
|
+
--includebasedir Includes a base directory at the root of the archive.
|
|
35
|
+
For example, if the root folder of your project is named
|
|
36
|
+
"your-project", setting this option to true will create
|
|
37
|
+
an archive that includes this base directory.
|
|
38
|
+
If this option is set to false the archive created will
|
|
39
|
+
unzip its content to the current directory. [bool]
|
|
40
|
+
--followsymlinks Follow symlinks when traversing directories. [bool]
|
|
41
|
+
--exclude A list with the names of the files and folders to exclude. [array]`);
|
|
42
|
+
process.exitCode = 1;
|
|
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);
|
|
49
|
+
process.exitCode = 1;
|
|
50
|
+
});
|
|
51
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
declare class DirArchiver {
|
|
2
|
+
private excludedPaths;
|
|
3
|
+
private excludedNames;
|
|
4
|
+
private caseInsensitiveExcludes;
|
|
5
|
+
private directoryPath;
|
|
6
|
+
private zipPath;
|
|
7
|
+
private includeBaseDirectory;
|
|
8
|
+
private followSymlinks;
|
|
9
|
+
private baseDirectory;
|
|
10
|
+
private visitedDirectories;
|
|
11
|
+
/**
|
|
12
|
+
* The constructor.
|
|
13
|
+
* @param directoryPath - the path of the folder to archive.
|
|
14
|
+
* @param zipPath - The path of the zip file to create.
|
|
15
|
+
* @param includeBaseDirectory - Includes a base directory at the root of the archive. For example, if the root folder of your project is named "your-project", setting includeBaseDirectory to true will create an archive that includes this base directory. If this option is set to false the archive created will unzip its content to the current directory.
|
|
16
|
+
* @param excludes - The name of the files and foldes to exclude.
|
|
17
|
+
*/
|
|
18
|
+
constructor(directoryPath: string, zipPath: string, includeBaseDirectory?: boolean, excludes?: string[], followSymlinks?: boolean);
|
|
19
|
+
/**
|
|
20
|
+
* Recursively traverse the directory tree and append the files to the archive.
|
|
21
|
+
* @param directoryPath - The path of the directory being looped through.
|
|
22
|
+
*/
|
|
23
|
+
private traverseDirectoryTree;
|
|
24
|
+
private prettyBytes;
|
|
25
|
+
private normalizeExcludeValue;
|
|
26
|
+
createZip(): Promise<string>;
|
|
27
|
+
}
|
|
28
|
+
export = DirArchiver;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
const path = __importStar(require("path"));
|
|
39
|
+
const fs = __importStar(require("fs"));
|
|
40
|
+
const archiver_1 = __importDefault(require("archiver"));
|
|
41
|
+
class DirArchiver {
|
|
42
|
+
/**
|
|
43
|
+
* The constructor.
|
|
44
|
+
* @param directoryPath - the path of the folder to archive.
|
|
45
|
+
* @param zipPath - The path of the zip file to create.
|
|
46
|
+
* @param includeBaseDirectory - Includes a base directory at the root of the archive. For example, if the root folder of your project is named "your-project", setting includeBaseDirectory to true will create an archive that includes this base directory. If this option is set to false the archive created will unzip its content to the current directory.
|
|
47
|
+
* @param excludes - The name of the files and foldes to exclude.
|
|
48
|
+
*/
|
|
49
|
+
constructor(directoryPath, zipPath, includeBaseDirectory = false, excludes = [], followSymlinks = false) {
|
|
50
|
+
this.directoryPath = path.resolve(directoryPath);
|
|
51
|
+
this.zipPath = path.resolve(zipPath);
|
|
52
|
+
this.includeBaseDirectory = includeBaseDirectory;
|
|
53
|
+
this.followSymlinks = followSymlinks;
|
|
54
|
+
this.baseDirectory = path.basename(this.directoryPath);
|
|
55
|
+
this.visitedDirectories = new Set();
|
|
56
|
+
this.caseInsensitiveExcludes = process.platform === 'win32';
|
|
57
|
+
// Contains the excluded files and folders.
|
|
58
|
+
this.excludedPaths = new Set();
|
|
59
|
+
this.excludedNames = new Set();
|
|
60
|
+
for (const excludeRaw of excludes) {
|
|
61
|
+
if (typeof excludeRaw !== 'string') {
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
const trimmedRaw = excludeRaw.trim();
|
|
65
|
+
if (trimmedRaw.length === 0) {
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
let normalizedExclude = path.normalize(trimmedRaw.replace(/\\/g, path.sep));
|
|
69
|
+
if (normalizedExclude === '.' || normalizedExclude === path.sep) {
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
if (path.isAbsolute(normalizedExclude)) {
|
|
73
|
+
const relativeCandidate = path.relative(this.directoryPath, normalizedExclude);
|
|
74
|
+
const isInsideSource = relativeCandidate.length > 0
|
|
75
|
+
&& !relativeCandidate.startsWith('..')
|
|
76
|
+
&& !path.isAbsolute(relativeCandidate);
|
|
77
|
+
if (isInsideSource) {
|
|
78
|
+
normalizedExclude = path.normalize(relativeCandidate);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
if (normalizedExclude.length === 0) {
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
const hasSeparator = normalizedExclude.includes('/')
|
|
85
|
+
|| normalizedExclude.includes('\\')
|
|
86
|
+
|| normalizedExclude.includes(path.sep);
|
|
87
|
+
const trimmedExclude = normalizedExclude.replace(/[\\/]+$/g, '');
|
|
88
|
+
if (trimmedExclude.length === 0 || trimmedExclude === '.') {
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
const normalizedValue = this.normalizeExcludeValue(trimmedExclude);
|
|
92
|
+
this.excludedPaths.add(normalizedValue);
|
|
93
|
+
if (!hasSeparator) {
|
|
94
|
+
this.excludedNames.add(normalizedValue);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
const relativeZipPath = path.relative(this.directoryPath, this.zipPath);
|
|
98
|
+
const isZipInsideSource = relativeZipPath.length > 0
|
|
99
|
+
&& !relativeZipPath.startsWith('..')
|
|
100
|
+
&& !path.isAbsolute(relativeZipPath);
|
|
101
|
+
if (isZipInsideSource) {
|
|
102
|
+
const normalizedZipPath = path.normalize(relativeZipPath);
|
|
103
|
+
this.excludedPaths.add(this.normalizeExcludeValue(normalizedZipPath));
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Recursively traverse the directory tree and append the files to the archive.
|
|
108
|
+
* @param directoryPath - The path of the directory being looped through.
|
|
109
|
+
*/
|
|
110
|
+
traverseDirectoryTree(directoryPath, archive) {
|
|
111
|
+
const directoriesToVisit = [directoryPath];
|
|
112
|
+
while (directoriesToVisit.length > 0) {
|
|
113
|
+
const nextDirectory = directoriesToVisit.pop();
|
|
114
|
+
if (!nextDirectory) {
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
if (this.followSymlinks) {
|
|
118
|
+
try {
|
|
119
|
+
const realPath = fs.realpathSync(nextDirectory);
|
|
120
|
+
if (this.visitedDirectories.has(realPath)) {
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
this.visitedDirectories.add(realPath);
|
|
124
|
+
}
|
|
125
|
+
catch {
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
const resolvedDirectoryPath = path.resolve(nextDirectory);
|
|
130
|
+
const entries = fs.readdirSync(resolvedDirectoryPath, { withFileTypes: true });
|
|
131
|
+
entries.sort((firstEntry, secondEntry) => {
|
|
132
|
+
if (firstEntry.name < secondEntry.name) {
|
|
133
|
+
return -1;
|
|
134
|
+
}
|
|
135
|
+
if (firstEntry.name > secondEntry.name) {
|
|
136
|
+
return 1;
|
|
137
|
+
}
|
|
138
|
+
return 0;
|
|
139
|
+
});
|
|
140
|
+
for (const entry of entries) {
|
|
141
|
+
const currentPath = path.join(resolvedDirectoryPath, entry.name);
|
|
142
|
+
if (currentPath === this.zipPath) {
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
const relativePath = path.relative(this.directoryPath, currentPath);
|
|
146
|
+
const normalizedRelativePath = path.normalize(relativePath);
|
|
147
|
+
const archiveRelativePath = normalizedRelativePath.replace(/\\/g, '/');
|
|
148
|
+
const baseName = path.basename(normalizedRelativePath);
|
|
149
|
+
const normalizedPathValue = this.normalizeExcludeValue(normalizedRelativePath);
|
|
150
|
+
const normalizedNameValue = this.normalizeExcludeValue(baseName);
|
|
151
|
+
if (this.excludedPaths.has(normalizedPathValue) || this.excludedNames.has(normalizedNameValue)) {
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
if (entry.isFile()) {
|
|
155
|
+
if (this.includeBaseDirectory) {
|
|
156
|
+
archive.file(currentPath, {
|
|
157
|
+
name: path.posix.join(this.baseDirectory, archiveRelativePath)
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
else {
|
|
161
|
+
archive.file(currentPath, {
|
|
162
|
+
name: archiveRelativePath
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
else if (entry.isDirectory()) {
|
|
167
|
+
directoriesToVisit.push(currentPath);
|
|
168
|
+
}
|
|
169
|
+
else if (entry.isSymbolicLink()) {
|
|
170
|
+
if (!this.followSymlinks) {
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
let stats;
|
|
174
|
+
try {
|
|
175
|
+
stats = fs.statSync(currentPath);
|
|
176
|
+
}
|
|
177
|
+
catch {
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
if (stats.isFile()) {
|
|
181
|
+
if (this.includeBaseDirectory) {
|
|
182
|
+
archive.file(currentPath, {
|
|
183
|
+
name: path.posix.join(this.baseDirectory, archiveRelativePath)
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
else {
|
|
187
|
+
archive.file(currentPath, {
|
|
188
|
+
name: archiveRelativePath
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
else if (stats.isDirectory()) {
|
|
193
|
+
directoriesToVisit.push(currentPath);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
prettyBytes(bytes) {
|
|
200
|
+
if (bytes > 1000 && bytes < 1000000) {
|
|
201
|
+
const kiloBytes = Math.round(((bytes / 1000) + Number.EPSILON) * 100) / 100;
|
|
202
|
+
return `${kiloBytes} KB`;
|
|
203
|
+
}
|
|
204
|
+
if (bytes > 1000000 && bytes < 1000000000) {
|
|
205
|
+
const megaBytes = Math.round(((bytes / 1000000) + Number.EPSILON) * 100) / 100;
|
|
206
|
+
return `${megaBytes} MB`;
|
|
207
|
+
}
|
|
208
|
+
if (bytes > 1000000000) {
|
|
209
|
+
const gigaBytes = Math.round(((bytes / 1000000000) + Number.EPSILON) * 100) / 100;
|
|
210
|
+
return `${gigaBytes} GB`;
|
|
211
|
+
}
|
|
212
|
+
return `${bytes} bytes`;
|
|
213
|
+
}
|
|
214
|
+
normalizeExcludeValue(value) {
|
|
215
|
+
return this.caseInsensitiveExcludes ? value.toLowerCase() : value;
|
|
216
|
+
}
|
|
217
|
+
createZip() {
|
|
218
|
+
return new Promise((resolve, reject) => {
|
|
219
|
+
let output = null;
|
|
220
|
+
let archive = null;
|
|
221
|
+
let settled = false;
|
|
222
|
+
const safeResolve = (value) => {
|
|
223
|
+
if (settled) {
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
settled = true;
|
|
227
|
+
resolve(value);
|
|
228
|
+
};
|
|
229
|
+
const safeReject = (err) => {
|
|
230
|
+
if (settled) {
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
settled = true;
|
|
234
|
+
try {
|
|
235
|
+
if (archive) {
|
|
236
|
+
archive.abort();
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
catch {
|
|
240
|
+
// Ignore abort errors.
|
|
241
|
+
}
|
|
242
|
+
try {
|
|
243
|
+
if (output) {
|
|
244
|
+
output.destroy();
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
catch {
|
|
248
|
+
// Ignore destroy errors.
|
|
249
|
+
}
|
|
250
|
+
try {
|
|
251
|
+
if (fs.existsSync(this.zipPath)) {
|
|
252
|
+
fs.unlinkSync(this.zipPath);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
catch {
|
|
256
|
+
// Ignore cleanup errors.
|
|
257
|
+
}
|
|
258
|
+
const normalizedError = err instanceof Error ? err : new Error(String(err));
|
|
259
|
+
reject(normalizedError);
|
|
260
|
+
};
|
|
261
|
+
// Remove the destination zip if it exists.
|
|
262
|
+
// see : https://github.com/Ismail-elkorchi/dir-archiver/issues/5
|
|
263
|
+
try {
|
|
264
|
+
if (fs.existsSync(this.zipPath)) {
|
|
265
|
+
fs.unlinkSync(this.zipPath);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
catch (err) {
|
|
269
|
+
safeReject(err);
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
// Create a file to stream archive data to.
|
|
273
|
+
output = fs.createWriteStream(this.zipPath);
|
|
274
|
+
archive = (0, archiver_1.default)('zip', {
|
|
275
|
+
zlib: { level: 9 }
|
|
276
|
+
});
|
|
277
|
+
// Catch warnings during archiving.
|
|
278
|
+
archive.on('warning', (err) => {
|
|
279
|
+
if (err.code === 'ENOENT') {
|
|
280
|
+
// log warning
|
|
281
|
+
console.log(err);
|
|
282
|
+
}
|
|
283
|
+
else {
|
|
284
|
+
safeReject(err);
|
|
285
|
+
}
|
|
286
|
+
});
|
|
287
|
+
// Catch errors during archiving.
|
|
288
|
+
archive.on('error', (err) => {
|
|
289
|
+
safeReject(err);
|
|
290
|
+
});
|
|
291
|
+
output.on('error', (err) => {
|
|
292
|
+
safeReject(err);
|
|
293
|
+
});
|
|
294
|
+
// Listen for all archive data to be written.
|
|
295
|
+
output.on('close', () => {
|
|
296
|
+
if (settled) {
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
console.log(`Created ${this.zipPath} of ${this.prettyBytes(archive.pointer())}`);
|
|
300
|
+
safeResolve(this.zipPath);
|
|
301
|
+
});
|
|
302
|
+
// Pipe archive data to the file.
|
|
303
|
+
archive.pipe(output);
|
|
304
|
+
// Recursively traverse the directory tree and append the files to the archive.
|
|
305
|
+
this.visitedDirectories.clear();
|
|
306
|
+
try {
|
|
307
|
+
this.traverseDirectoryTree(this.directoryPath, archive);
|
|
308
|
+
}
|
|
309
|
+
catch (err) {
|
|
310
|
+
safeReject(err);
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
// Finalize the archive.
|
|
314
|
+
void Promise.resolve(archive.finalize()).catch((err) => {
|
|
315
|
+
safeReject(err);
|
|
316
|
+
});
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
module.exports = DirArchiver;
|
package/package.json
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dir-archiver",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.2.0",
|
|
4
4
|
"description": "Compress a whole directory (including subdirectories) into a zip file, with options to exclude specific files, or directories.",
|
|
5
5
|
"license": "MIT",
|
|
6
|
-
"main": "index.js",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
7
8
|
"bin": {
|
|
8
|
-
"dir-archiver": "cli.js"
|
|
9
|
+
"dir-archiver": "dist/cli.js"
|
|
9
10
|
},
|
|
10
11
|
"files": [
|
|
11
12
|
"LICENSE",
|
|
12
|
-
"
|
|
13
|
-
"cli.js"
|
|
13
|
+
"dist"
|
|
14
14
|
],
|
|
15
15
|
"keywords": [
|
|
16
16
|
"zip",
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
],
|
|
24
24
|
"repository": {
|
|
25
25
|
"type": "git",
|
|
26
|
-
"url": "https://github.com/Ismail-elkorchi/dir-archiver.git"
|
|
26
|
+
"url": "git+https://github.com/Ismail-elkorchi/dir-archiver.git"
|
|
27
27
|
},
|
|
28
28
|
"author": {
|
|
29
29
|
"name": "Ismail El Korchi",
|
|
@@ -33,15 +33,28 @@
|
|
|
33
33
|
"bugs": "https://github.com/Ismail-elkorchi/dir-archiver/issues",
|
|
34
34
|
"homepage": "https://github.com/Ismail-elkorchi/dir-archiver",
|
|
35
35
|
"scripts": {
|
|
36
|
-
"
|
|
37
|
-
"
|
|
36
|
+
"build": "tsc -p tsconfig.json",
|
|
37
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
38
|
+
"lint": "npm run typecheck && eslint . --max-warnings=0",
|
|
39
|
+
"test": "npm run build && node test/smoke.js && node test/cli.js",
|
|
40
|
+
"prepack": "npm run build"
|
|
41
|
+
},
|
|
42
|
+
"engines": {
|
|
43
|
+
"node": ">=18"
|
|
38
44
|
},
|
|
39
45
|
"dependencies": {
|
|
40
46
|
"archiver": "^7.0.1",
|
|
41
|
-
"argv-flags": "^0.
|
|
47
|
+
"argv-flags": "^0.2.1"
|
|
42
48
|
},
|
|
43
49
|
"devDependencies": {
|
|
44
|
-
"eslint": "^
|
|
45
|
-
"
|
|
50
|
+
"@eslint/js": "^9.39.2",
|
|
51
|
+
"@types/archiver": "^7.0.0",
|
|
52
|
+
"@types/node": "^20.11.0",
|
|
53
|
+
"@typescript-eslint/eslint-plugin": "^8.54.0",
|
|
54
|
+
"@typescript-eslint/parser": "^8.54.0",
|
|
55
|
+
"eslint": "^9.39.2",
|
|
56
|
+
"globals": "^17.2.0",
|
|
57
|
+
"typescript": "^5.3.3",
|
|
58
|
+
"yauzl": "^3.2.0"
|
|
46
59
|
}
|
|
47
60
|
}
|
package/cli.js
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
const DirArchiver = require( './index' );
|
|
4
|
-
const parseArgs = require( 'argv-flags' );
|
|
5
|
-
|
|
6
|
-
const directoryPath = parseArgs( '--src', 'string' );
|
|
7
|
-
const zipPath = parseArgs( '--dest', 'string' );
|
|
8
|
-
const includeBaseDirectory = parseArgs( '--includebasedir', 'boolean' );
|
|
9
|
-
const excludes = parseArgs( '--exclude', 'array' ) || [];
|
|
10
|
-
|
|
11
|
-
if ( directoryPath === false || zipPath === false ) {
|
|
12
|
-
console.log( ` Dir Archiver could not be executed. Some arguments are missing.
|
|
13
|
-
|
|
14
|
-
Options:
|
|
15
|
-
--src The path of the folder to archive. [string][required]
|
|
16
|
-
--dest The path of the zip file to create. [string][required]
|
|
17
|
-
--includebasedir Includes a base directory at the root of the archive.
|
|
18
|
-
For example, if the root folder of your project is named
|
|
19
|
-
"your-project", setting this option to true will create
|
|
20
|
-
an archive that includes this base directory.
|
|
21
|
-
If this option is set to false the archive created will
|
|
22
|
-
unzip its content to the current directory. [bool]
|
|
23
|
-
--exclude A list with the names of the files and folders to exclude. [array]` );
|
|
24
|
-
process.exit(); // eslint-disable-line n/no-process-exit
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const archive = new DirArchiver( directoryPath, zipPath, includeBaseDirectory, excludes );
|
|
28
|
-
archive.createZip().catch( ( err ) => {
|
|
29
|
-
console.error( err );
|
|
30
|
-
process.exitCode = 1;
|
|
31
|
-
} );
|
package/index.js
DELETED
|
@@ -1,154 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const path = require( 'path' );
|
|
4
|
-
const fs = require( 'fs' );
|
|
5
|
-
const archiver = require( 'archiver' );
|
|
6
|
-
|
|
7
|
-
class DirArchiver {
|
|
8
|
-
/**
|
|
9
|
-
* The constructor.
|
|
10
|
-
* @param {string} directoryPath - the path of the folder to archive.
|
|
11
|
-
* @param {string} zipPath - The path of the zip file to create.
|
|
12
|
-
* @param {Boolean} includeBaseDirectory - Includes a base directory at the root of the archive. For example, if the root folder of your project is named "your-project", setting includeBaseDirectory to true will create an archive that includes this base directory. If this option is set to false the archive created will unzip its content to the current directory.
|
|
13
|
-
* @param {array} excludes - The name of the files and foldes to exclude.
|
|
14
|
-
*/
|
|
15
|
-
constructor( directoryPath, zipPath, includeBaseDirectory, excludes ) {
|
|
16
|
-
|
|
17
|
-
// Contains the excluded files and folders.
|
|
18
|
-
const safeExcludes = Array.isArray( excludes ) ? excludes : [];
|
|
19
|
-
this.excludes = safeExcludes.map( ( element ) => {
|
|
20
|
-
return path.normalize( element );
|
|
21
|
-
} );
|
|
22
|
-
|
|
23
|
-
this.directoryPath = path.resolve( directoryPath );
|
|
24
|
-
|
|
25
|
-
this.zipPath = path.resolve( zipPath );
|
|
26
|
-
|
|
27
|
-
this.includeBaseDirectory = includeBaseDirectory;
|
|
28
|
-
|
|
29
|
-
this.baseDirectory = path.basename( this.directoryPath );
|
|
30
|
-
|
|
31
|
-
const relativeZipPath = path.relative( this.directoryPath, this.zipPath );
|
|
32
|
-
const isZipInsideSource = relativeZipPath && ! relativeZipPath.startsWith( '..' ) && ! path.isAbsolute( relativeZipPath );
|
|
33
|
-
if ( isZipInsideSource ) {
|
|
34
|
-
const normalizedZipPath = path.normalize( relativeZipPath );
|
|
35
|
-
if ( ! this.excludes.includes( normalizedZipPath ) ) {
|
|
36
|
-
this.excludes.push( normalizedZipPath );
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Recursively traverse the directory tree and append the files to the archive.
|
|
43
|
-
* @param {string} directoryPath - The path of the directory being looped through.
|
|
44
|
-
*/
|
|
45
|
-
traverseDirectoryTree( directoryPath ) {
|
|
46
|
-
const files = fs.readdirSync( directoryPath );
|
|
47
|
-
for ( const file of files ) {
|
|
48
|
-
const currentPath = path.join( path.resolve( directoryPath ), file );
|
|
49
|
-
if ( path.resolve( currentPath ) === this.zipPath ) {
|
|
50
|
-
continue;
|
|
51
|
-
}
|
|
52
|
-
const stats = fs.statSync( currentPath );
|
|
53
|
-
let relativePath = path.relative( this.directoryPath, currentPath );
|
|
54
|
-
if ( stats.isFile() && ! this.excludes.includes( relativePath ) ) {
|
|
55
|
-
if ( this.includeBaseDirectory === true ) {
|
|
56
|
-
this.archive.file( currentPath, {
|
|
57
|
-
name: path.join( this.baseDirectory, relativePath )
|
|
58
|
-
} );
|
|
59
|
-
} else {
|
|
60
|
-
this.archive.file( currentPath, {
|
|
61
|
-
name: relativePath
|
|
62
|
-
} );
|
|
63
|
-
}
|
|
64
|
-
} else if ( stats.isDirectory() && ! this.excludes.includes( relativePath ) ) {
|
|
65
|
-
this.traverseDirectoryTree( currentPath );
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
prettyBytes( bytes ) {
|
|
71
|
-
if ( bytes > 1000 && bytes < 1000000 ) {
|
|
72
|
-
return Math.round( ( ( bytes / 1000 ) + Number.EPSILON ) * 100 ) / 100 + ' KB';
|
|
73
|
-
}
|
|
74
|
-
if ( bytes > 1000000 && bytes < 1000000000 ) {
|
|
75
|
-
return Math.round( ( ( bytes / 1000000 ) + Number.EPSILON ) * 100 ) / 100 + ' MB';
|
|
76
|
-
}
|
|
77
|
-
if ( bytes > 1000000000 ) {
|
|
78
|
-
return Math.round( ( ( bytes / 1000000000 ) + Number.EPSILON ) * 100 ) / 100 + ' GB';
|
|
79
|
-
}
|
|
80
|
-
return bytes + ' bytes';
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
createZip () {
|
|
84
|
-
return new Promise( ( resolve, reject ) => {
|
|
85
|
-
let settled = false;
|
|
86
|
-
const safeResolve = ( value ) => {
|
|
87
|
-
if ( settled ) {
|
|
88
|
-
return;
|
|
89
|
-
}
|
|
90
|
-
settled = true;
|
|
91
|
-
resolve( value );
|
|
92
|
-
};
|
|
93
|
-
const safeReject = ( err ) => {
|
|
94
|
-
if ( settled ) {
|
|
95
|
-
return;
|
|
96
|
-
}
|
|
97
|
-
settled = true;
|
|
98
|
-
reject( err );
|
|
99
|
-
};
|
|
100
|
-
|
|
101
|
-
// Remove the destination zip if it exists.
|
|
102
|
-
// see : https://github.com/Ismail-elkorchi/dir-archiver/issues/5
|
|
103
|
-
try {
|
|
104
|
-
if ( fs.existsSync( this.zipPath ) ) {
|
|
105
|
-
fs.unlinkSync( this.zipPath );
|
|
106
|
-
}
|
|
107
|
-
} catch ( err ) {
|
|
108
|
-
safeReject( err );
|
|
109
|
-
return;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
// Create a file to stream archive data to.
|
|
113
|
-
this.output = fs.createWriteStream( this.zipPath );
|
|
114
|
-
this.archive = archiver( 'zip', {
|
|
115
|
-
zlib: { level: 9 }
|
|
116
|
-
} );
|
|
117
|
-
|
|
118
|
-
// Catch warnings during archiving.
|
|
119
|
-
this.archive.on( 'warning', ( err ) => {
|
|
120
|
-
if ( err.code === 'ENOENT' ) {
|
|
121
|
-
// log warning
|
|
122
|
-
console.log( err );
|
|
123
|
-
} else {
|
|
124
|
-
safeReject( err );
|
|
125
|
-
}
|
|
126
|
-
} );
|
|
127
|
-
|
|
128
|
-
// Catch errors during archiving.
|
|
129
|
-
this.archive.on( 'error', ( err ) => {
|
|
130
|
-
safeReject( err );
|
|
131
|
-
} );
|
|
132
|
-
|
|
133
|
-
this.output.on( 'error', ( err ) => {
|
|
134
|
-
safeReject( err );
|
|
135
|
-
} );
|
|
136
|
-
|
|
137
|
-
// Listen for all archive data to be written.
|
|
138
|
-
this.output.on( 'close', () => {
|
|
139
|
-
console.log( `Created ${this.zipPath} of ${this.prettyBytes( this.archive.pointer() )}` );
|
|
140
|
-
safeResolve( this.zipPath );
|
|
141
|
-
} );
|
|
142
|
-
|
|
143
|
-
// Pipe archive data to the file.
|
|
144
|
-
this.archive.pipe( this.output );
|
|
145
|
-
|
|
146
|
-
// Recursively traverse the directory tree and append the files to the archive.
|
|
147
|
-
this.traverseDirectoryTree( this.directoryPath );
|
|
148
|
-
|
|
149
|
-
// Finalize the archive.
|
|
150
|
-
this.archive.finalize();
|
|
151
|
-
} );
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
module.exports = DirArchiver;
|