@fgv/ts-extras 5.0.0-14 → 5.0.0-16

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.
@@ -523,9 +523,9 @@ export { ZipFileTree }
523
523
  */
524
524
  declare class ZipFileTreeAccessors implements FileTree.IFileTreeAccessors {
525
525
  /**
526
- * The AdmZip instance containing the archive.
526
+ * The unzipped file data.
527
527
  */
528
- private readonly _zip;
528
+ private readonly _files;
529
529
  /**
530
530
  * Optional prefix to prepend to paths.
531
531
  */
@@ -536,17 +536,24 @@ declare class ZipFileTreeAccessors implements FileTree.IFileTreeAccessors {
536
536
  private readonly _itemCache;
537
537
  /**
538
538
  * Constructor for ZipFileTreeAccessors.
539
- * @param zip - The AdmZip instance.
539
+ * @param files - The unzipped file data from fflate.
540
540
  * @param prefix - Optional prefix to prepend to paths.
541
541
  */
542
542
  private constructor();
543
543
  /**
544
- * Creates a new ZipFileTreeAccessors instance from a ZIP file buffer.
544
+ * Creates a new ZipFileTreeAccessors instance from a ZIP file buffer (synchronous).
545
545
  * @param zipBuffer - The ZIP file as an ArrayBuffer or Uint8Array.
546
546
  * @param prefix - Optional prefix to prepend to paths.
547
547
  * @returns Result containing the ZipFileTreeAccessors instance.
548
548
  */
549
549
  static fromBuffer(zipBuffer: ArrayBuffer | Uint8Array, prefix?: string): Result<ZipFileTreeAccessors>;
550
+ /**
551
+ * Creates a new ZipFileTreeAccessors instance from a ZIP file buffer (asynchronous).
552
+ * @param zipBuffer - The ZIP file as an ArrayBuffer or Uint8Array.
553
+ * @param prefix - Optional prefix to prepend to paths.
554
+ * @returns Promise containing Result with the ZipFileTreeAccessors instance.
555
+ */
556
+ static fromBufferAsync(zipBuffer: ArrayBuffer | Uint8Array, prefix?: string): Promise<Result<ZipFileTreeAccessors>>;
550
557
  /**
551
558
  * Creates a new ZipFileTreeAccessors instance from a File object (browser environment).
552
559
  * @param file - The File object containing ZIP data.
@@ -87,9 +87,9 @@ export declare class ZipDirectoryItem implements FileTree.IFileTreeDirectoryItem
87
87
  */
88
88
  export declare class ZipFileTreeAccessors implements FileTree.IFileTreeAccessors {
89
89
  /**
90
- * The AdmZip instance containing the archive.
90
+ * The unzipped file data.
91
91
  */
92
- private readonly _zip;
92
+ private readonly _files;
93
93
  /**
94
94
  * Optional prefix to prepend to paths.
95
95
  */
@@ -100,17 +100,24 @@ export declare class ZipFileTreeAccessors implements FileTree.IFileTreeAccessors
100
100
  private readonly _itemCache;
101
101
  /**
102
102
  * Constructor for ZipFileTreeAccessors.
103
- * @param zip - The AdmZip instance.
103
+ * @param files - The unzipped file data from fflate.
104
104
  * @param prefix - Optional prefix to prepend to paths.
105
105
  */
106
106
  private constructor();
107
107
  /**
108
- * Creates a new ZipFileTreeAccessors instance from a ZIP file buffer.
108
+ * Creates a new ZipFileTreeAccessors instance from a ZIP file buffer (synchronous).
109
109
  * @param zipBuffer - The ZIP file as an ArrayBuffer or Uint8Array.
110
110
  * @param prefix - Optional prefix to prepend to paths.
111
111
  * @returns Result containing the ZipFileTreeAccessors instance.
112
112
  */
113
113
  static fromBuffer(zipBuffer: ArrayBuffer | Uint8Array, prefix?: string): Result<ZipFileTreeAccessors>;
114
+ /**
115
+ * Creates a new ZipFileTreeAccessors instance from a ZIP file buffer (asynchronous).
116
+ * @param zipBuffer - The ZIP file as an ArrayBuffer or Uint8Array.
117
+ * @param prefix - Optional prefix to prepend to paths.
118
+ * @returns Promise containing Result with the ZipFileTreeAccessors instance.
119
+ */
120
+ static fromBufferAsync(zipBuffer: ArrayBuffer | Uint8Array, prefix?: string): Promise<Result<ZipFileTreeAccessors>>;
114
121
  /**
115
122
  * Creates a new ZipFileTreeAccessors instance from a File object (browser environment).
116
123
  * @param file - The File object containing ZIP data.
@@ -20,12 +20,9 @@
20
20
  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
21
  * SOFTWARE.
22
22
  */
23
- var __importDefault = (this && this.__importDefault) || function (mod) {
24
- return (mod && mod.__esModule) ? mod : { "default": mod };
25
- };
26
23
  Object.defineProperty(exports, "__esModule", { value: true });
27
24
  exports.ZipFileTreeAccessors = exports.ZipDirectoryItem = exports.ZipFileItem = void 0;
28
- const adm_zip_1 = __importDefault(require("adm-zip"));
25
+ const fflate_1 = require("fflate");
29
26
  const ts_utils_1 = require("@fgv/ts-utils");
30
27
  /**
31
28
  * Implementation of `FileTree.IFileTreeFileItem` for files in a ZIP archive.
@@ -114,35 +111,62 @@ exports.ZipDirectoryItem = ZipDirectoryItem;
114
111
  class ZipFileTreeAccessors {
115
112
  /**
116
113
  * Constructor for ZipFileTreeAccessors.
117
- * @param zip - The AdmZip instance.
114
+ * @param files - The unzipped file data from fflate.
118
115
  * @param prefix - Optional prefix to prepend to paths.
119
116
  */
120
- constructor(zip, prefix) {
117
+ constructor(files, prefix) {
121
118
  /**
122
119
  * Cache of all items in the ZIP for efficient lookups.
123
120
  */
124
121
  this._itemCache = new Map();
125
- this._zip = zip;
122
+ this._files = files;
126
123
  this._prefix = prefix || '';
127
124
  this._buildItemCache();
128
125
  }
129
126
  /**
130
- * Creates a new ZipFileTreeAccessors instance from a ZIP file buffer.
127
+ * Creates a new ZipFileTreeAccessors instance from a ZIP file buffer (synchronous).
131
128
  * @param zipBuffer - The ZIP file as an ArrayBuffer or Uint8Array.
132
129
  * @param prefix - Optional prefix to prepend to paths.
133
130
  * @returns Result containing the ZipFileTreeAccessors instance.
134
131
  */
135
132
  static fromBuffer(zipBuffer, prefix) {
136
133
  try {
137
- const buffer = Buffer.from(zipBuffer);
138
- const zip = new adm_zip_1.default(buffer);
139
- return (0, ts_utils_1.succeed)(new ZipFileTreeAccessors(zip, prefix));
134
+ /* c8 ignore next 1 - defense in depth */
135
+ const uint8Buffer = zipBuffer instanceof Uint8Array ? zipBuffer : new Uint8Array(zipBuffer);
136
+ const files = (0, fflate_1.unzipSync)(uint8Buffer);
137
+ return (0, ts_utils_1.succeed)(new ZipFileTreeAccessors(files, prefix));
140
138
  }
141
139
  catch (error) {
142
- /* c8 ignore next 1 - defensive coding: AdmZip always throws Error objects in practice */
140
+ /* c8 ignore next 1 - defensive coding: fflate always throws Error objects in practice */
143
141
  return (0, ts_utils_1.fail)(`Failed to load ZIP archive: ${error instanceof Error ? error.message : String(error)}`);
144
142
  }
145
143
  }
144
+ /**
145
+ * Creates a new ZipFileTreeAccessors instance from a ZIP file buffer (asynchronous).
146
+ * @param zipBuffer - The ZIP file as an ArrayBuffer or Uint8Array.
147
+ * @param prefix - Optional prefix to prepend to paths.
148
+ * @returns Promise containing Result with the ZipFileTreeAccessors instance.
149
+ */
150
+ static async fromBufferAsync(zipBuffer, prefix) {
151
+ return new Promise((resolve) => {
152
+ try {
153
+ /* c8 ignore next 1 - defense in depth */
154
+ const uint8Buffer = zipBuffer instanceof Uint8Array ? zipBuffer : new Uint8Array(zipBuffer);
155
+ (0, fflate_1.unzip)(uint8Buffer, (err, files) => {
156
+ if (err) {
157
+ resolve((0, ts_utils_1.fail)(`Failed to load ZIP archive: ${err.message}`));
158
+ }
159
+ else {
160
+ resolve((0, ts_utils_1.succeed)(new ZipFileTreeAccessors(files, prefix)));
161
+ }
162
+ });
163
+ }
164
+ catch (error) {
165
+ /* c8 ignore next 5 - defensive coding: fflate always throws Error objects in practice */
166
+ resolve((0, ts_utils_1.fail)(`Failed to load ZIP archive: ${error instanceof Error ? error.message : String(error)}`));
167
+ }
168
+ });
169
+ }
146
170
  /**
147
171
  * Creates a new ZipFileTreeAccessors instance from a File object (browser environment).
148
172
  * @param file - The File object containing ZIP data.
@@ -152,7 +176,7 @@ class ZipFileTreeAccessors {
152
176
  static async fromFile(file, prefix) {
153
177
  try {
154
178
  const arrayBuffer = await file.arrayBuffer();
155
- return ZipFileTreeAccessors.fromBuffer(new Uint8Array(arrayBuffer), prefix);
179
+ return await ZipFileTreeAccessors.fromBufferAsync(new Uint8Array(arrayBuffer), prefix);
156
180
  }
157
181
  catch (error) {
158
182
  return (0, ts_utils_1.fail)(`Failed to read file: ${error instanceof Error ? error.message : String(error)}`);
@@ -163,40 +187,38 @@ class ZipFileTreeAccessors {
163
187
  */
164
188
  _buildItemCache() {
165
189
  const directories = new Set();
166
- const entries = this._zip.getEntries();
167
- // First pass: collect all directories from file paths
168
- entries.forEach((entry) => {
169
- if (!entry.isDirectory) {
170
- // Extract directory path from file path
171
- const pathParts = entry.entryName.split('/');
172
- for (let i = 1; i < pathParts.length; i++) {
173
- const dirPath = pathParts.slice(0, i).join('/');
190
+ // Process all files and collect directory paths
191
+ for (const [relativePath, fileData] of Object.entries(this._files)) {
192
+ // Skip directories in fflate output (they have null data)
193
+ /* c8 ignore next 5 - handles explicit directory entries from external ZIP tools (fflate doesn't create these) */
194
+ if (fileData === null || fileData === undefined) {
195
+ const dirPath = relativePath.replace(/\/$/, '');
196
+ if (dirPath) {
174
197
  directories.add(dirPath);
175
198
  }
176
199
  }
177
200
  else {
178
- // Also add explicit directories
179
- const dirPath = entry.entryName.replace(/\/$/, ''); // Remove trailing slash
180
- if (dirPath) {
181
- directories.add(dirPath);
201
+ // Extract directory paths from file paths
202
+ const pathParts = relativePath.split('/');
203
+ for (let i = 1; i < pathParts.length; i++) {
204
+ const dirPath = pathParts.slice(0, i).join('/');
205
+ if (dirPath) {
206
+ directories.add(dirPath);
207
+ }
182
208
  }
209
+ // Add the file item with its contents
210
+ const absolutePath = this.resolveAbsolutePath(relativePath);
211
+ const contents = new TextDecoder().decode(fileData);
212
+ const item = new ZipFileItem(relativePath, contents, this);
213
+ this._itemCache.set(absolutePath, item);
183
214
  }
184
- });
215
+ }
185
216
  // Add directory items to cache
186
217
  directories.forEach((dirPath) => {
187
218
  const absolutePath = this.resolveAbsolutePath(dirPath);
188
219
  const item = new ZipDirectoryItem(dirPath, this);
189
220
  this._itemCache.set(absolutePath, item);
190
221
  });
191
- // Add file items to cache
192
- entries.forEach((entry) => {
193
- if (!entry.isDirectory) {
194
- const absolutePath = this.resolveAbsolutePath(entry.entryName);
195
- const contents = entry.getData().toString('utf8');
196
- const item = new ZipFileItem(entry.entryName, contents, this);
197
- this._itemCache.set(absolutePath, item);
198
- }
199
- });
200
222
  }
201
223
  /**
202
224
  * Resolves paths to an absolute path.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fgv/ts-extras",
3
- "version": "5.0.0-14",
3
+ "version": "5.0.0-16",
4
4
  "description": "Assorted Typescript Utilities",
5
5
  "main": "lib/index.js",
6
6
  "types": "dist/ts-extras.d.ts",
@@ -45,18 +45,17 @@
45
45
  "@types/heft-jest": "1.0.6",
46
46
  "@rushstack/heft-jest-plugin": "~0.16.11",
47
47
  "eslint-plugin-tsdoc": "~0.4.0",
48
- "@fgv/ts-utils-jest": "5.0.0-14",
49
- "@fgv/ts-utils": "5.0.0-14"
48
+ "@fgv/ts-utils-jest": "5.0.0-16",
49
+ "@fgv/ts-utils": "5.0.0-16"
50
50
  },
51
51
  "dependencies": {
52
52
  "luxon": "^3.7.1",
53
53
  "mustache": "^4.2.0",
54
54
  "papaparse": "^5.4.1",
55
- "@types/adm-zip": "~0.5.7",
56
- "adm-zip": "~0.5.16"
55
+ "fflate": "~0.8.2"
57
56
  },
58
57
  "peerDependencies": {
59
- "@fgv/ts-utils": "5.0.0-14"
58
+ "@fgv/ts-utils": "5.0.0-16"
60
59
  },
61
60
  "scripts": {
62
61
  "build": "heft test --clean",
@@ -20,7 +20,7 @@
20
20
  * SOFTWARE.
21
21
  */
22
22
 
23
- import AdmZip from 'adm-zip';
23
+ import { unzipSync, unzip, Unzipped } from 'fflate';
24
24
  import { Result, succeed, fail, captureResult, Converter, Validator, FileTree } from '@fgv/ts-utils';
25
25
 
26
26
  /**
@@ -163,9 +163,9 @@ export class ZipDirectoryItem implements FileTree.IFileTreeDirectoryItem {
163
163
  */
164
164
  export class ZipFileTreeAccessors implements FileTree.IFileTreeAccessors {
165
165
  /**
166
- * The AdmZip instance containing the archive.
166
+ * The unzipped file data.
167
167
  */
168
- private readonly _zip: AdmZip;
168
+ private readonly _files: Unzipped;
169
169
 
170
170
  /**
171
171
  * Optional prefix to prepend to paths.
@@ -179,17 +179,17 @@ export class ZipFileTreeAccessors implements FileTree.IFileTreeAccessors {
179
179
 
180
180
  /**
181
181
  * Constructor for ZipFileTreeAccessors.
182
- * @param zip - The AdmZip instance.
182
+ * @param files - The unzipped file data from fflate.
183
183
  * @param prefix - Optional prefix to prepend to paths.
184
184
  */
185
- private constructor(zip: AdmZip, prefix?: string) {
186
- this._zip = zip;
185
+ private constructor(files: Unzipped, prefix?: string) {
186
+ this._files = files;
187
187
  this._prefix = prefix || '';
188
188
  this._buildItemCache();
189
189
  }
190
190
 
191
191
  /**
192
- * Creates a new ZipFileTreeAccessors instance from a ZIP file buffer.
192
+ * Creates a new ZipFileTreeAccessors instance from a ZIP file buffer (synchronous).
193
193
  * @param zipBuffer - The ZIP file as an ArrayBuffer or Uint8Array.
194
194
  * @param prefix - Optional prefix to prepend to paths.
195
195
  * @returns Result containing the ZipFileTreeAccessors instance.
@@ -199,15 +199,46 @@ export class ZipFileTreeAccessors implements FileTree.IFileTreeAccessors {
199
199
  prefix?: string
200
200
  ): Result<ZipFileTreeAccessors> {
201
201
  try {
202
- const buffer = Buffer.from(zipBuffer);
203
- const zip = new AdmZip(buffer);
204
- return succeed(new ZipFileTreeAccessors(zip, prefix));
202
+ /* c8 ignore next 1 - defense in depth */
203
+ const uint8Buffer = zipBuffer instanceof Uint8Array ? zipBuffer : new Uint8Array(zipBuffer);
204
+ const files = unzipSync(uint8Buffer);
205
+ return succeed(new ZipFileTreeAccessors(files, prefix));
205
206
  } catch (error) {
206
- /* c8 ignore next 1 - defensive coding: AdmZip always throws Error objects in practice */
207
+ /* c8 ignore next 1 - defensive coding: fflate always throws Error objects in practice */
207
208
  return fail(`Failed to load ZIP archive: ${error instanceof Error ? error.message : String(error)}`);
208
209
  }
209
210
  }
210
211
 
212
+ /**
213
+ * Creates a new ZipFileTreeAccessors instance from a ZIP file buffer (asynchronous).
214
+ * @param zipBuffer - The ZIP file as an ArrayBuffer or Uint8Array.
215
+ * @param prefix - Optional prefix to prepend to paths.
216
+ * @returns Promise containing Result with the ZipFileTreeAccessors instance.
217
+ */
218
+ public static async fromBufferAsync(
219
+ zipBuffer: ArrayBuffer | Uint8Array,
220
+ prefix?: string
221
+ ): Promise<Result<ZipFileTreeAccessors>> {
222
+ return new Promise((resolve) => {
223
+ try {
224
+ /* c8 ignore next 1 - defense in depth */
225
+ const uint8Buffer = zipBuffer instanceof Uint8Array ? zipBuffer : new Uint8Array(zipBuffer);
226
+ unzip(uint8Buffer, (err, files) => {
227
+ if (err) {
228
+ resolve(fail(`Failed to load ZIP archive: ${err.message}`));
229
+ } else {
230
+ resolve(succeed(new ZipFileTreeAccessors(files, prefix)));
231
+ }
232
+ });
233
+ } catch (error) {
234
+ /* c8 ignore next 5 - defensive coding: fflate always throws Error objects in practice */
235
+ resolve(
236
+ fail(`Failed to load ZIP archive: ${error instanceof Error ? error.message : String(error)}`)
237
+ );
238
+ }
239
+ });
240
+ }
241
+
211
242
  /**
212
243
  * Creates a new ZipFileTreeAccessors instance from a File object (browser environment).
213
244
  * @param file - The File object containing ZIP data.
@@ -217,7 +248,7 @@ export class ZipFileTreeAccessors implements FileTree.IFileTreeAccessors {
217
248
  public static async fromFile(file: File, prefix?: string): Promise<Result<ZipFileTreeAccessors>> {
218
249
  try {
219
250
  const arrayBuffer = await file.arrayBuffer();
220
- return ZipFileTreeAccessors.fromBuffer(new Uint8Array(arrayBuffer), prefix);
251
+ return await ZipFileTreeAccessors.fromBufferAsync(new Uint8Array(arrayBuffer), prefix);
221
252
  } catch (error) {
222
253
  return fail(`Failed to read file: ${error instanceof Error ? error.message : String(error)}`);
223
254
  }
@@ -228,25 +259,33 @@ export class ZipFileTreeAccessors implements FileTree.IFileTreeAccessors {
228
259
  */
229
260
  private _buildItemCache(): void {
230
261
  const directories = new Set<string>();
231
- const entries = this._zip.getEntries();
232
262
 
233
- // First pass: collect all directories from file paths
234
- entries.forEach((entry) => {
235
- if (!entry.isDirectory) {
236
- // Extract directory path from file path
237
- const pathParts = entry.entryName.split('/');
238
- for (let i = 1; i < pathParts.length; i++) {
239
- const dirPath = pathParts.slice(0, i).join('/');
263
+ // Process all files and collect directory paths
264
+ for (const [relativePath, fileData] of Object.entries(this._files)) {
265
+ // Skip directories in fflate output (they have null data)
266
+ /* c8 ignore next 5 - handles explicit directory entries from external ZIP tools (fflate doesn't create these) */
267
+ if (fileData === null || fileData === undefined) {
268
+ const dirPath = relativePath.replace(/\/$/, '');
269
+ if (dirPath) {
240
270
  directories.add(dirPath);
241
271
  }
242
272
  } else {
243
- // Also add explicit directories
244
- const dirPath = entry.entryName.replace(/\/$/, ''); // Remove trailing slash
245
- if (dirPath) {
246
- directories.add(dirPath);
273
+ // Extract directory paths from file paths
274
+ const pathParts = relativePath.split('/');
275
+ for (let i = 1; i < pathParts.length; i++) {
276
+ const dirPath = pathParts.slice(0, i).join('/');
277
+ if (dirPath) {
278
+ directories.add(dirPath);
279
+ }
247
280
  }
281
+
282
+ // Add the file item with its contents
283
+ const absolutePath = this.resolveAbsolutePath(relativePath);
284
+ const contents = new TextDecoder().decode(fileData);
285
+ const item = new ZipFileItem(relativePath, contents, this);
286
+ this._itemCache.set(absolutePath, item);
248
287
  }
249
- });
288
+ }
250
289
 
251
290
  // Add directory items to cache
252
291
  directories.forEach((dirPath) => {
@@ -254,16 +293,6 @@ export class ZipFileTreeAccessors implements FileTree.IFileTreeAccessors {
254
293
  const item = new ZipDirectoryItem(dirPath, this);
255
294
  this._itemCache.set(absolutePath, item);
256
295
  });
257
-
258
- // Add file items to cache
259
- entries.forEach((entry) => {
260
- if (!entry.isDirectory) {
261
- const absolutePath = this.resolveAbsolutePath(entry.entryName);
262
- const contents = entry.getData().toString('utf8');
263
- const item = new ZipFileItem(entry.entryName, contents, this);
264
- this._itemCache.set(absolutePath, item);
265
- }
266
- });
267
296
  }
268
297
 
269
298
  /**