zip-lib 1.2.3 → 1.3.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/lib/zip.js CHANGED
@@ -18,13 +18,14 @@ class Zip extends cancelable_1.Cancelable {
18
18
  super();
19
19
  this.options = options;
20
20
  this.isPipe = false;
21
+ this.isChunk = false;
21
22
  this.zipFiles = [];
22
23
  this.zipFolders = [];
23
24
  }
24
25
  /**
25
- * Adds a file from the file system at realPath into the zipfile as metadataPath.
26
+ * Adds a file from the file system at `realPath` to the zip file as `metadataPath`.
26
27
  * @param file
27
- * @param metadataPath Typically metadataPath would be calculated as path.relative(root, realPath).
28
+ * @param metadataPath Typically, `metadataPath` would be calculated as `path.relative(root, realPath)`.
28
29
  * A valid metadataPath must not start with "/" or /[A-Za-z]:\//, and must not contain "..".
29
30
  */
30
31
  addFile(file, metadataPath) {
@@ -38,9 +39,9 @@ class Zip extends cancelable_1.Cancelable {
38
39
  });
39
40
  }
40
41
  /**
41
- * Adds a folder from the file system at realPath into the zipfile as metadataPath.
42
+ * Adds a folder from the file system at `realPath` to the zip file as `metadataPath`.
42
43
  * @param folder
43
- * @param metadataPath Typically metadataPath would be calculated as path.relative(root, realPath).
44
+ * @param metadataPath Typically, `metadataPath` would be calculated as `path.relative(root, realPath)`.
44
45
  * A valid metadataPath must not start with "/" or /[A-Za-z]:\//, and must not contain "..".
45
46
  */
46
47
  addFolder(folder, metadataPath) {
@@ -49,64 +50,137 @@ class Zip extends cancelable_1.Cancelable {
49
50
  metadataPath,
50
51
  });
51
52
  }
52
- /**
53
- * Generate zip file.
54
- * @param zipFile the zip file path.
55
- */
56
53
  async archive(zipFile) {
57
- if (!zipFile) {
58
- return Promise.reject(new Error("zipPath must not be empty"));
59
- }
60
54
  const token = new cancelable_1.CancellationToken();
61
55
  this.token = token;
56
+ const zip = await this.prepareArchive(zipFile);
57
+ return await new Promise((resolve, reject) => {
58
+ let disposeCancel = () => {
59
+ // noop
60
+ };
61
+ const settle = this.createPromiseSettler((value) => {
62
+ disposeCancel();
63
+ resolve(value);
64
+ }, (error) => {
65
+ disposeCancel();
66
+ reject(error);
67
+ });
68
+ disposeCancel = token.onCancelled(() => {
69
+ this.stop(this.canceledError());
70
+ settle.reject(this.canceledError());
71
+ });
72
+ this.bindArchiveOutput(zip, zipFile, token, settle);
73
+ this.addQueuedEntries(zip, token)
74
+ .catch((error) => {
75
+ settle.reject(this.wrapError(error, token.isCancelled));
76
+ })
77
+ .finally(() => {
78
+ zip.end();
79
+ });
80
+ });
81
+ }
82
+ async prepareArchive(zipFile) {
62
83
  this.isPipe = false;
63
- await exfs.ensureFolder(path.dirname(zipFile));
84
+ this.isChunk = false;
85
+ if (zipFile) {
86
+ await exfs.ensureFolder(path.dirname(zipFile));
87
+ }
64
88
  // Re-instantiate yazl every time the archive method is called to ensure that files are not added repeatedly.
65
89
  // This will also make the Zip class reusable.
66
90
  this.yazlFile = new yazl.ZipFile();
67
- return new Promise((c, e) => {
68
- this.yazlFile.once("error", (err) => {
69
- e(this.wrapError(err, token.isCancelled));
91
+ return this.yazlFile;
92
+ }
93
+ bindArchiveOutput(zip, zipFile, token, settle) {
94
+ if (zipFile) {
95
+ void this.archiveToFile(zip, zipFile, token, settle);
96
+ return;
97
+ }
98
+ void this.archiveToBuffer(zip, token, settle);
99
+ }
100
+ async archiveToFile(zip, zipFile, token, settle) {
101
+ this.zipStream = (0, node_fs_1.createWriteStream)(zipFile);
102
+ this.isPipe = true;
103
+ const archivePromise = new Promise((resolve) => {
104
+ zip.once("error", (err) => {
105
+ settle.reject(this.wrapError(err, token.isCancelled));
106
+ resolve();
70
107
  });
71
- const zip = this.yazlFile;
72
- if (!token.isCancelled) {
73
- this.zipStream = (0, node_fs_1.createWriteStream)(zipFile);
74
- this.zipStream.once("error", (err) => {
75
- e(this.wrapError(err, token.isCancelled));
76
- });
77
- this.zipStream.once("close", () => {
78
- if (token.isCancelled) {
79
- e(this.canceledError());
80
- }
81
- else {
82
- c(void 0);
83
- }
84
- });
85
- zip.outputStream.once("error", (err) => {
86
- e(this.wrapError(err, token.isCancelled));
87
- });
88
- zip.outputStream.pipe(this.zipStream);
89
- this.isPipe = true;
90
- }
91
- const start = async () => {
92
- try {
93
- const files = this.zipFiles;
94
- for (const file of files) {
95
- const entry = await exfs.getFileEntry(file.path);
96
- await this.addEntry(zip, entry, file, token);
97
- }
98
- if (this.zipFolders.length > 0) {
99
- await this.walkDir(this.zipFolders, token);
100
- }
108
+ this.zipStream.once("error", (err) => {
109
+ settle.reject(this.wrapError(err, token.isCancelled));
110
+ resolve();
111
+ });
112
+ this.zipStream.once("close", () => {
113
+ if (token.isCancelled) {
114
+ settle.reject(this.canceledError());
101
115
  }
102
- catch (error) {
103
- e(this.wrapError(error, token.isCancelled));
116
+ else {
117
+ settle.resolve(void 0);
104
118
  }
105
- };
106
- start().finally(() => {
107
- zip.end();
119
+ resolve();
120
+ });
121
+ });
122
+ try {
123
+ zip.outputStream.pipe(this.zipStream);
124
+ await archivePromise;
125
+ }
126
+ finally {
127
+ this.isPipe = false;
128
+ }
129
+ }
130
+ async archiveToBuffer(zip, token, settle) {
131
+ const chunks = [];
132
+ this.isChunk = true;
133
+ const archivePromise = new Promise((resolve) => {
134
+ zip.once("error", (err) => {
135
+ settle.reject(this.wrapError(err, token.isCancelled));
136
+ resolve();
137
+ });
138
+ zip.outputStream.on("data", (chunk) => {
139
+ chunks.push(chunk);
140
+ });
141
+ zip.outputStream.once("end", () => {
142
+ settle.resolve(Buffer.concat(chunks));
143
+ resolve();
144
+ });
145
+ zip.outputStream.once("error", (err) => {
146
+ settle.reject(this.wrapError(err, token.isCancelled));
147
+ resolve();
108
148
  });
109
149
  });
150
+ try {
151
+ await archivePromise;
152
+ }
153
+ finally {
154
+ this.isChunk = false;
155
+ }
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.stopPipe(this.canceledError());
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((c, e) => {
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.stopPipe(wrappedError);
155
- e(wrappedError);
232
+ this.stop(wrappedError);
233
+ reject(wrappedError);
156
234
  });
157
235
  fileStream.once("close", () => {
158
- c();
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,24 +260,28 @@ 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(this.yazlFile, entry, { path: entry.path, metadataPath }, token);
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
- this.yazlFile.addEmptyDirectory(folder.metadataPath);
270
+ zip.addEmptyDirectory(folder.metadataPath);
192
271
  }
193
272
  }
194
273
  }
195
274
  }
196
- stopPipe(err) {
275
+ stop(err) {
197
276
  if (this.isPipe) {
198
277
  this.yazlFile.outputStream.unpipe(this.zipStream);
199
278
  this.zipStream.destroy(err);
200
279
  this.isPipe = false;
201
280
  }
281
+ if (this.isChunk) {
282
+ this.yazlFile.outputStream.destroy(err);
283
+ this.isChunk = false;
284
+ }
202
285
  }
203
286
  followSymlink() {
204
287
  let followSymlink = false;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "zip-lib",
3
- "version": "1.2.3",
4
- "description": "zip and unzip library for node",
3
+ "version": "1.3.0",
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.7",
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",