zip-lib 1.2.3 → 1.3.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/README.md +296 -205
- package/lib/fs.d.ts +1 -0
- package/lib/fs.js +74 -61
- package/lib/index.d.ts +21 -2
- package/lib/index.js +27 -24
- package/lib/unzip.d.ts +22 -10
- package/lib/unzip.js +207 -135
- package/lib/zip.d.ts +25 -13
- package/lib/zip.js +154 -66
- package/package.json +3 -3
package/lib/zip.js
CHANGED
|
@@ -17,14 +17,14 @@ class Zip extends cancelable_1.Cancelable {
|
|
|
17
17
|
constructor(options) {
|
|
18
18
|
super();
|
|
19
19
|
this.options = options;
|
|
20
|
-
this.
|
|
20
|
+
this.activeArchive = null;
|
|
21
21
|
this.zipFiles = [];
|
|
22
22
|
this.zipFolders = [];
|
|
23
23
|
}
|
|
24
24
|
/**
|
|
25
|
-
* Adds a file from the file system at realPath
|
|
25
|
+
* Adds a file from the file system at `realPath` to the zip file as `metadataPath`.
|
|
26
26
|
* @param file
|
|
27
|
-
* @param metadataPath Typically metadataPath would be calculated as path.relative(root, realPath)
|
|
27
|
+
* @param metadataPath Typically, `metadataPath` would be calculated as `path.relative(root, realPath)`.
|
|
28
28
|
* A valid metadataPath must not start with "/" or /[A-Za-z]:\//, and must not contain "..".
|
|
29
29
|
*/
|
|
30
30
|
addFile(file, metadataPath) {
|
|
@@ -38,9 +38,9 @@ class Zip extends cancelable_1.Cancelable {
|
|
|
38
38
|
});
|
|
39
39
|
}
|
|
40
40
|
/**
|
|
41
|
-
* Adds a folder from the file system at realPath
|
|
41
|
+
* Adds a folder from the file system at `realPath` to the zip file as `metadataPath`.
|
|
42
42
|
* @param folder
|
|
43
|
-
* @param metadataPath Typically metadataPath would be calculated as path.relative(root, realPath)
|
|
43
|
+
* @param metadataPath Typically, `metadataPath` would be calculated as `path.relative(root, realPath)`.
|
|
44
44
|
* A valid metadataPath must not start with "/" or /[A-Za-z]:\//, and must not contain "..".
|
|
45
45
|
*/
|
|
46
46
|
addFolder(folder, metadataPath) {
|
|
@@ -49,64 +49,138 @@ class Zip extends cancelable_1.Cancelable {
|
|
|
49
49
|
metadataPath,
|
|
50
50
|
});
|
|
51
51
|
}
|
|
52
|
-
/**
|
|
53
|
-
* Generate zip file.
|
|
54
|
-
* @param zipFile the zip file path.
|
|
55
|
-
*/
|
|
56
52
|
async archive(zipFile) {
|
|
57
|
-
if (!zipFile) {
|
|
58
|
-
return Promise.reject(new Error("zipPath must not be empty"));
|
|
59
|
-
}
|
|
60
53
|
const token = new cancelable_1.CancellationToken();
|
|
61
54
|
this.token = token;
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
55
|
+
const zip = await this.prepareArchive(zipFile);
|
|
56
|
+
const activeArchive = {
|
|
57
|
+
zip,
|
|
58
|
+
mode: zipFile ? "file" : "buffer",
|
|
59
|
+
};
|
|
60
|
+
this.activeArchive = activeArchive;
|
|
61
|
+
try {
|
|
62
|
+
return await new Promise((resolve, reject) => {
|
|
63
|
+
let disposeCancel = () => {
|
|
64
|
+
// noop
|
|
65
|
+
};
|
|
66
|
+
const settle = this.createPromiseSettler((value) => {
|
|
67
|
+
disposeCancel();
|
|
68
|
+
resolve(value);
|
|
69
|
+
}, (error) => {
|
|
70
|
+
disposeCancel();
|
|
71
|
+
reject(error);
|
|
76
72
|
});
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
}
|
|
81
|
-
else {
|
|
82
|
-
c(void 0);
|
|
83
|
-
}
|
|
73
|
+
disposeCancel = token.onCancelled(() => {
|
|
74
|
+
this.stop(this.canceledError(), activeArchive);
|
|
75
|
+
settle.reject(this.canceledError());
|
|
84
76
|
});
|
|
85
|
-
|
|
86
|
-
|
|
77
|
+
this.bindArchiveOutput(zip, zipFile, token, settle, activeArchive);
|
|
78
|
+
this.addQueuedEntries(zip, token)
|
|
79
|
+
.catch((error) => {
|
|
80
|
+
settle.reject(this.wrapError(error, token.isCancelled));
|
|
81
|
+
})
|
|
82
|
+
.finally(() => {
|
|
83
|
+
zip.end();
|
|
87
84
|
});
|
|
88
|
-
|
|
89
|
-
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
finally {
|
|
88
|
+
if (this.token === token) {
|
|
89
|
+
this.token = null;
|
|
90
90
|
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
91
|
+
if (this.activeArchive === activeArchive) {
|
|
92
|
+
this.activeArchive = null;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
async prepareArchive(zipFile) {
|
|
97
|
+
if (zipFile) {
|
|
98
|
+
await exfs.ensureFolder(path.dirname(zipFile));
|
|
99
|
+
}
|
|
100
|
+
// Re-instantiate yazl every time the archive method is called to ensure that files are not added repeatedly.
|
|
101
|
+
// This will also make the Zip class reusable.
|
|
102
|
+
return new yazl.ZipFile();
|
|
103
|
+
}
|
|
104
|
+
bindArchiveOutput(zip, zipFile, token, settle, activeArchive) {
|
|
105
|
+
if (zipFile) {
|
|
106
|
+
void this.archiveToFile(zip, zipFile, token, settle, activeArchive);
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
void this.archiveToBuffer(zip, token, settle);
|
|
110
|
+
}
|
|
111
|
+
async archiveToFile(zip, zipFile, token, settle, activeArchive) {
|
|
112
|
+
const zipStream = (0, node_fs_1.createWriteStream)(zipFile);
|
|
113
|
+
activeArchive.zipStream = zipStream;
|
|
114
|
+
const archivePromise = new Promise((resolve) => {
|
|
115
|
+
zip.once("error", (err) => {
|
|
116
|
+
settle.reject(this.wrapError(err, token.isCancelled));
|
|
117
|
+
resolve();
|
|
118
|
+
});
|
|
119
|
+
zipStream.once("error", (err) => {
|
|
120
|
+
settle.reject(this.wrapError(err, token.isCancelled));
|
|
121
|
+
resolve();
|
|
122
|
+
});
|
|
123
|
+
zipStream.once("close", () => {
|
|
124
|
+
if (token.isCancelled) {
|
|
125
|
+
settle.reject(this.canceledError());
|
|
101
126
|
}
|
|
102
|
-
|
|
103
|
-
|
|
127
|
+
else {
|
|
128
|
+
settle.resolve(void 0);
|
|
104
129
|
}
|
|
105
|
-
|
|
106
|
-
start().finally(() => {
|
|
107
|
-
zip.end();
|
|
130
|
+
resolve();
|
|
108
131
|
});
|
|
109
132
|
});
|
|
133
|
+
zip.outputStream.pipe(zipStream);
|
|
134
|
+
await archivePromise;
|
|
135
|
+
}
|
|
136
|
+
async archiveToBuffer(zip, token, settle) {
|
|
137
|
+
const chunks = [];
|
|
138
|
+
const archivePromise = new Promise((resolve) => {
|
|
139
|
+
zip.once("error", (err) => {
|
|
140
|
+
settle.reject(this.wrapError(err, token.isCancelled));
|
|
141
|
+
resolve();
|
|
142
|
+
});
|
|
143
|
+
zip.outputStream.on("data", (chunk) => {
|
|
144
|
+
chunks.push(chunk);
|
|
145
|
+
});
|
|
146
|
+
zip.outputStream.once("end", () => {
|
|
147
|
+
settle.resolve(Buffer.concat(chunks));
|
|
148
|
+
resolve();
|
|
149
|
+
});
|
|
150
|
+
zip.outputStream.once("error", (err) => {
|
|
151
|
+
settle.reject(this.wrapError(err, token.isCancelled));
|
|
152
|
+
resolve();
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
await archivePromise;
|
|
156
|
+
}
|
|
157
|
+
createPromiseSettler(resolve, reject) {
|
|
158
|
+
let settled = false;
|
|
159
|
+
return {
|
|
160
|
+
resolve: (value) => {
|
|
161
|
+
if (settled) {
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
settled = true;
|
|
165
|
+
resolve(value);
|
|
166
|
+
},
|
|
167
|
+
reject: (error) => {
|
|
168
|
+
if (settled) {
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
settled = true;
|
|
172
|
+
reject(error);
|
|
173
|
+
},
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
async addQueuedEntries(zip, token) {
|
|
177
|
+
for (const file of this.zipFiles) {
|
|
178
|
+
const entry = await exfs.getFileEntry(file.path);
|
|
179
|
+
await this.addEntry(zip, entry, file, token);
|
|
180
|
+
}
|
|
181
|
+
if (this.zipFolders.length > 0) {
|
|
182
|
+
await this.walkDir(zip, this.zipFolders, token);
|
|
183
|
+
}
|
|
110
184
|
}
|
|
111
185
|
/**
|
|
112
186
|
* Cancel compression.
|
|
@@ -117,14 +191,14 @@ class Zip extends cancelable_1.Cancelable {
|
|
|
117
191
|
this.token.cancel();
|
|
118
192
|
this.token = null;
|
|
119
193
|
}
|
|
120
|
-
this.
|
|
194
|
+
this.stop(this.canceledError());
|
|
121
195
|
}
|
|
122
196
|
async addEntry(zip, entry, file, token) {
|
|
123
197
|
if (entry.isSymbolicLink) {
|
|
124
198
|
if (this.followSymlink()) {
|
|
125
199
|
if (entry.type === "dir") {
|
|
126
200
|
const realPath = await exfs.realpath(file.path);
|
|
127
|
-
await this.walkDir([{ path: realPath, metadataPath: file.metadataPath }], token);
|
|
201
|
+
await this.walkDir(zip, [{ path: realPath, metadataPath: file.metadataPath }], token);
|
|
128
202
|
}
|
|
129
203
|
else {
|
|
130
204
|
zip.addFile(file.path, file.metadataPath, this.getYazlOption());
|
|
@@ -147,15 +221,20 @@ class Zip extends cancelable_1.Cancelable {
|
|
|
147
221
|
}
|
|
148
222
|
}
|
|
149
223
|
addFileStream(zip, file, metadataPath, token) {
|
|
150
|
-
return new Promise((
|
|
224
|
+
return new Promise((resolve, reject) => {
|
|
151
225
|
const fileStream = (0, node_fs_1.createReadStream)(file.path);
|
|
226
|
+
const disposeCancel = token.onCancelled(() => {
|
|
227
|
+
fileStream.destroy(this.canceledError());
|
|
228
|
+
});
|
|
152
229
|
fileStream.once("error", (err) => {
|
|
230
|
+
disposeCancel();
|
|
153
231
|
const wrappedError = this.wrapError(err, token.isCancelled);
|
|
154
|
-
this.
|
|
155
|
-
|
|
232
|
+
this.stop(wrappedError);
|
|
233
|
+
reject(wrappedError);
|
|
156
234
|
});
|
|
157
235
|
fileStream.once("close", () => {
|
|
158
|
-
|
|
236
|
+
disposeCancel();
|
|
237
|
+
resolve();
|
|
159
238
|
});
|
|
160
239
|
// If the file attribute is known, add the entry using `addReadStream`,
|
|
161
240
|
// this can reduce the number of calls to the `fs.stat` method.
|
|
@@ -166,7 +245,7 @@ class Zip extends cancelable_1.Cancelable {
|
|
|
166
245
|
const linkTarget = await fs.readlink(file.path);
|
|
167
246
|
zip.addBuffer(Buffer.from(linkTarget), metadataPath, Object.assign(Object.assign({}, this.getYazlOption()), { mtime: file.mtime, mode: file.mode }));
|
|
168
247
|
}
|
|
169
|
-
async walkDir(folders, token) {
|
|
248
|
+
async walkDir(zip, folders, token) {
|
|
170
249
|
for (const folder of folders) {
|
|
171
250
|
if (token.isCancelled) {
|
|
172
251
|
return;
|
|
@@ -181,23 +260,32 @@ class Zip extends cancelable_1.Cancelable {
|
|
|
181
260
|
const metadataPath = folder.metadataPath
|
|
182
261
|
? path.join(folder.metadataPath, relativePath)
|
|
183
262
|
: relativePath;
|
|
184
|
-
await this.addEntry(
|
|
263
|
+
await this.addEntry(zip, entry, { path: entry.path, metadataPath }, token);
|
|
185
264
|
}
|
|
186
265
|
}
|
|
187
266
|
else {
|
|
188
267
|
// If the folder is empty and the metadataPath has a value,
|
|
189
268
|
// an empty folder should be created based on the metadataPath
|
|
190
269
|
if (folder.metadataPath) {
|
|
191
|
-
|
|
270
|
+
zip.addEmptyDirectory(folder.metadataPath);
|
|
192
271
|
}
|
|
193
272
|
}
|
|
194
273
|
}
|
|
195
274
|
}
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
275
|
+
stop(err, activeArchive) {
|
|
276
|
+
const archive = activeArchive !== null && activeArchive !== void 0 ? activeArchive : this.activeArchive;
|
|
277
|
+
if (!archive) {
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
if (archive.mode === "file" && archive.zipStream) {
|
|
281
|
+
archive.zip.outputStream.unpipe(archive.zipStream);
|
|
282
|
+
archive.zipStream.destroy(err);
|
|
283
|
+
}
|
|
284
|
+
if (archive.mode === "buffer") {
|
|
285
|
+
archive.zip.outputStream.destroy(err);
|
|
286
|
+
}
|
|
287
|
+
if (!activeArchive || this.activeArchive === activeArchive) {
|
|
288
|
+
this.activeArchive = null;
|
|
201
289
|
}
|
|
202
290
|
}
|
|
203
291
|
followSymlink() {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zip-lib",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "zip and unzip library for
|
|
3
|
+
"version": "1.3.1",
|
|
4
|
+
"description": "A zip and unzip library for Node.js.",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"types": "./lib/index.d.ts",
|
|
7
7
|
"scripts": {
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"yazl": "^3.3.1"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
|
-
"@biomejs/biome": "^2.4.
|
|
37
|
+
"@biomejs/biome": "^2.4.8",
|
|
38
38
|
"@types/node": "^20.19.37",
|
|
39
39
|
"@types/yauzl": "^2.10.3",
|
|
40
40
|
"@types/yazl": "^3.3.0",
|