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/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.isPipe = false;
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 into the zipfile as metadataPath.
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 into the zipfile as metadataPath.
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
- this.isPipe = false;
63
- await exfs.ensureFolder(path.dirname(zipFile));
64
- // Re-instantiate yazl every time the archive method is called to ensure that files are not added repeatedly.
65
- // This will also make the Zip class reusable.
66
- this.yazlFile = new yazl.ZipFile();
67
- return new Promise((c, e) => {
68
- this.yazlFile.once("error", (err) => {
69
- e(this.wrapError(err, token.isCancelled));
70
- });
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));
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
- this.zipStream.once("close", () => {
78
- if (token.isCancelled) {
79
- e(this.canceledError());
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
- zip.outputStream.once("error", (err) => {
86
- e(this.wrapError(err, token.isCancelled));
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
- zip.outputStream.pipe(this.zipStream);
89
- this.isPipe = true;
85
+ });
86
+ }
87
+ finally {
88
+ if (this.token === token) {
89
+ this.token = null;
90
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
- }
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
- catch (error) {
103
- e(this.wrapError(error, token.isCancelled));
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.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,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(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) {
197
- if (this.isPipe) {
198
- this.yazlFile.outputStream.unpipe(this.zipStream);
199
- this.zipStream.destroy(err);
200
- this.isPipe = false;
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.2.3",
4
- "description": "zip and unzip library for node",
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.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",