zip-lib 1.0.5 → 1.1.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/LICENSE CHANGED
@@ -1,21 +1,21 @@
1
- MIT License
2
-
3
- Copyright (c) 2019 Ouga
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
1
+ MIT License
2
+
3
+ Copyright (c) 2019 Ouga
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,378 +1,381 @@
1
- # zip-lib
2
- zip and unzip library for node.
3
-
4
- [![npm Package](https://img.shields.io/npm/v/zip-lib.svg)](https://www.npmjs.org/package/zip-lib) [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/fpsqdb/zip-lib/blob/master/LICENSE) ![node](https://img.shields.io/node/v/zip-lib) ![Node.js CI](https://github.com/fpsqdb/zip-lib/workflows/Node.js%20CI/badge.svg)
5
-
6
- ## Install
7
-
8
- ```
9
- npm install zip-lib
10
- ```
11
-
12
- ## Quick Start
13
-
14
- * [Zip](#Zip)
15
- - [Zip single file](#zip-single-file)
16
- - [Zip single folder](#zip-single-folder)
17
- * [Unzip](#unzip)
18
- * [Advanced usage](#advance-usage)
19
- - [Sets the compression level](#sets-the-compression-level)
20
- - [Zip multiple files and folders](#zip-multiple-files-and-folders)
21
- - [Zip with metadata](#zip-with-metadata)
22
- - [Unzip with entry callback](#unzip-with-entry-callback)
23
- - [Unzip and exclude specified entries](#unzip-and-exclude-specified-entries)
24
- - [Cancel zip](#cancel-zip)
25
- - [Cancel unzip](#cancel-unzip)
26
- * [API](#api)
27
- - Method: [archiveFile](#archivefile)
28
- - Method: [archiveFolder](#archivefolder)
29
- - Method: [extract](#extract)
30
- - Class: [Zip](#class-zip)
31
- - Class: [Unzip](#class-unzip)
32
- - Options: [IZipOptions](#izipoptions)
33
- - Options: [IExtractOptions](#iextractoptions)
34
-
35
- ## Zip
36
- You can use **zip-lib** to compress files or folders.
37
-
38
- ### Zip single file
39
-
40
- ```js
41
- const zl = require("zip-lib");
42
-
43
- zl.archiveFile("path/to/file.txt", "path/to/target.zip").then(function () {
44
- console.log("done");
45
- }, function (err) {
46
- console.log(err);
47
- });
48
- ```
49
-
50
- ### Zip single folder
51
-
52
- ```js
53
- const zl = require("zip-lib");
54
-
55
- zl.archiveFolder("path/to/folder", "path/to/target.zip").then(function () {
56
- console.log("done");
57
- }, function (err) {
58
- console.log(err);
59
- });
60
- ```
61
-
62
- ## Unzip
63
-
64
- ```js
65
- const zl = require("zip-lib");
66
-
67
- zl.extract("path/to/target.zip", "path/to/target").then(function () {
68
- console.log("done");
69
- }, function (err) {
70
- console.log(err);
71
- });
72
- ```
73
-
74
- ## Advanced usage
75
-
76
- ### Sets the compression level
77
-
78
- ```js
79
- const zl = require("zip-lib");
80
-
81
- zl.archiveFolder("path/to/folder", "path/to/target.zip", { compressionLevel: 9 }).then(function () {
82
- console.log("done");
83
- }, function (err) {
84
- console.log(err);
85
- });
86
- ```
87
-
88
- ### Zip multiple files and folders
89
-
90
- ```js
91
- const zl = require("zip-lib");
92
-
93
- const zip = new zl.Zip();
94
- // Adds a file from the file system
95
- zip.addFile("path/to/file.txt");
96
- // Adds a folder from the file system, putting its contents at the root of archive
97
- zip.addFolder("path/to/folder");
98
- // Generate zip file.
99
- zip.archive("path/to/target.zip").then(function () {
100
- console.log("done");
101
- }, function (err) {
102
- console.log(err);
103
- });
104
- ```
105
-
106
- The `path/to/folder` directory is as follows:
107
-
108
- ```
109
- path/to/folder
110
- .
111
- ├── dir1
112
- │ ├── file.ext
113
- ├── dir2
114
- └── file_in_root.ext
115
- ```
116
-
117
- And the generated `path/to/target.zip` archive file directory will be as follows:
118
-
119
- ```
120
- path/to/target.zip
121
- .
122
- ├── file.txt
123
- ├── dir1
124
- │ ├── file.ext
125
- ├── dir2
126
- └── file_in_root.ext
127
- ```
128
-
129
- ### Zip with metadata
130
-
131
- ```js
132
- const zl = require("zip-lib");
133
-
134
- const zip = new zl.Zip();
135
- // Adds a file from the file system
136
- zip.addFile("path/to/file.txt", "renamedFile.txt");
137
- zip.addFile("path/to/file2.txt", "folder/file.txt");
138
- // Adds a folder from the file system, and naming it `new folder` within the archive
139
- zip.addFolder("path/to/folder", "new folder");
140
- // Generate zip file.
141
- zip.archive("path/to/target.zip").then(function () {
142
- console.log("done");
143
- }, function (err) {
144
- console.log(err);
145
- });
146
- ```
147
-
148
- The `path/to/folder` directory is as follows:
149
-
150
- ```
151
- path/to/folder
152
- .
153
- ├── dir1
154
- │ ├── file.ext
155
- ├── dir2
156
- └── file_in_root.ext
157
- ```
158
-
159
- And the generated `path/to/target.zip` archive file directory will be as follows:
160
-
161
- ```
162
- path/to/target.zip
163
- .
164
- ├── renamedFile.txt
165
- ├── folder
166
- │ ├── file.txt
167
- │── new folder
168
- ├── dir1
169
- │ ├── file.ext
170
- ├── dir2
171
- └── file_in_root.ext
172
- ```
173
-
174
- ### Unzip with entry callback
175
- Using `onEntry` callback we can know the current progress of extracting and control the extraction operation. See [IExtractOptions](#iextractoptions).
176
-
177
- ```js
178
- const zl = require("zip-lib");
179
-
180
- const unzip = new zl.Unzip({
181
- // Called before an item is extracted.
182
- onEntry: function (event) {
183
- console.log(event.entryCount, event.entryName);
184
- }
185
- })
186
- unzip.extract("path/to/target.zip", "path/to/target").then(function () {
187
- console.log("done");
188
- }, function (err) {
189
- console.log(err);
190
- });
191
- ```
192
-
193
- ### Unzip and exclude specified entries
194
- The following code shows how to exclude the `__MACOSX` folder in the zip file when extracting. See [IExtractOptions](#iextractoptions).
195
-
196
- ```js
197
- const zl = require("zip-lib");
198
-
199
- const unzip = new zl.Unzip({
200
- // Called before an item is extracted.
201
- onEntry: function (event) {
202
- if (/^__MACOSX\//.test(event.entryName)) {
203
- // entry name starts with __MACOSX/
204
- event.preventDefault();
205
- }
206
- }
207
- })
208
- unzip.extract("path/to/target.zip", "path/to/target").then(function () {
209
- console.log("done");
210
- }, function (err) {
211
- console.log(err);
212
- });
213
- ```
214
-
215
- ### Cancel zip
216
- If the `cancel` method is called after the archive is complete, nothing will happen.
217
-
218
- ```js
219
- const zl = require("zip-lib");
220
-
221
- const zip = new zl.Zip();
222
- zip.addFile("path/to/file.txt");
223
- zip.archive("path/to/target.zip").then(function () {
224
- console.log("done");
225
- }, function (err) {
226
- if (err.name === "Canceled") {
227
- console.log("cancel");
228
- } else {
229
- console.log(err);
230
- }
231
- });
232
-
233
- // Cancel zip
234
- zip.cancel();
235
- ```
236
-
237
- ### Cancel unzip
238
- If the `cancel` method is called after the extract is complete, nothing will happen.
239
-
240
- ```js
241
- const zl = require("zip-lib");
242
-
243
- const unzip = new zl.Unzip();
244
- unzip.extract("path/to/target.zip", "path/to/target").then(function () {
245
- console.log("done");
246
- }, function (err) {
247
- if (err.name === "Canceled") {
248
- console.log("cancel");
249
- } else {
250
- console.log(err);
251
- }
252
- });
253
-
254
- // cancel
255
- unzip.cancel();
256
- ```
257
-
258
- ## API
259
-
260
- ### Method: archiveFile <a id="archivefile"></a>
261
-
262
- **archiveFile(file, zipFile, [options])**
263
-
264
- Compress a single file to zip.
265
-
266
- - `file`: String
267
- - `zipFile`: String
268
- - `options?`: [IZipOptions](#izipoptions) (optional)
269
-
270
- Returns: `Promise<viod>`
271
-
272
- ### Method: archiveFolder <a id="archivefolder"></a>
273
-
274
- **archiveFolder(folder, zipFile, [options])**
275
-
276
- Compress all the contents of the specified folder to zip.
277
-
278
- - `folder`: String
279
- - `zipFile`: String
280
- - `options?`: [IZipOptions](#izipoptions) (optional)
281
-
282
- Returns: `Promise<void>`
283
-
284
- ### Method: extract <a id="extract"></a>
285
-
286
- **extract(zipFile, targetFolder, [options])**
287
-
288
- Extract the zip file to the specified location.
289
-
290
- - `zipFile`: String
291
- - `targetFolder`: String
292
- - `options?`: [IExtractOptions](#iextractoptions) (optional)
293
-
294
- Returns: `Promise<void>`
295
-
296
- ### Class: Zip<a id="class-zip"></a>
297
- Compress files or folders to a zip file.
298
-
299
- **Constructor: new Zip([options])**
300
-
301
- - `options?`: [IZipOptions](#izipoptions)
302
-
303
- **Method: addFile(file, [metadataPath])**
304
-
305
- Adds a file from the file system at realPath into the zipfile as metadataPath.
306
-
307
- - `file`: String
308
- - `metadataPath?`: String (optional) - Typically metadataPath would be calculated as path.relative(root, realPath). A valid metadataPath must not start with `/` or `/[A-Za-z]:\//`, and must not contain `..`.
309
-
310
- Returns: `void`
311
-
312
- **Method: addFolder(folder, [metadataPath])**
313
-
314
- Adds a folder from the file system at realPath into the zipfile as metadataPath.
315
-
316
- - `folder`: String
317
- - `metadataPath?`: String (optional) - Typically metadataPath would be calculated as path.relative(root, realPath). A valid metadataPath must not start with `/` or `/[A-Za-z]:\//`, and must not contain `..`.
318
-
319
- Returns: `void`
320
-
321
- **Method: archive(zipFile)**
322
-
323
- Generate zip file.
324
-
325
- - `zipFile`: String
326
-
327
- Returns: `Promise<viod>`
328
-
329
- **Method: cancel()**
330
-
331
- Cancel compression. If the `cancel` method is called after the archive is complete, nothing will happen.
332
-
333
- Returns: `void`
334
-
335
- ### Class: Unzip<a id="class-unzip"></a>
336
- Extract the zip file.
337
-
338
- **Constructor: new Unzip([options])**
339
-
340
- - `options?`: [IZipOptions](#izipoptions) (optional)
341
-
342
- **Method: extract(zipFile, targetFolder)**
343
-
344
- Extract the zip file to the specified location.
345
-
346
- - `zipFile`: String
347
- - `targetFolder`: String
348
-
349
- Returns: `Promise<void>`
350
-
351
- **Method: cancel()**
352
-
353
- If the `cancel` method is called after the extract is complete, nothing will happen.
354
-
355
- Returns: `void`
356
-
357
- ### Options: IZipOptions <a id="izipoptions"></a>
358
-
359
- Object
360
- - `followSymlinks?`: Boolean (optional) - Indicates how to handle when the given path is a symbolic link. The default value is `false`.<br>`true`: add the target of the symbolic link to the zip.<br>`false`: add symbolic link itself to the zip.
361
- - `compressionLevel?`: 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 - Sets the compression level. The default value is `6`.<br>`0`: the file data will be stored.<br>`1-9`: the file data will be deflated.
362
-
363
- ### Options: IExtractOptions <a id="iextractoptions"></a>
364
-
365
- Object
366
- - `overwrite?`: String (optional) - If it is true, the target directory will be deleted before extract. The default value is `false`.
367
- - `symlinkAsFileOnWindows?`: Boolean (optional) - Extract symbolic links as files on Windows. This value is only available on Windows and ignored on other platforms. The default value is `true`.<br>If `true`, the symlink in the zip will be extracted as a normal file on Windows.<br>If `false`, the symlink in the zip will be extracted as a symlink correctly on Windows, but an `EPERM` error will be thrown under non-administrators.
368
-
369
- > ⚠**WARNING:** On Windows, the default security policy allows only administrators to create symbolic links. If you set `symlinkAsFileOnWindows` to `false` and the zip contains symlink, be sure to run the code under the administrator, otherwise an `EPERM` error will be thrown.
370
-
371
- - `onEntry?`: Function (optional) - Called before an item is extracted.<br>Arguments:
372
- - `event`: Object - Represents an event that an entry is about to be extracted.
373
- - `entryName`: String (readonly) - Entry name.
374
- - `entryCount`: Number (readonly) - Total number of entries.
375
- - `preventDefault()`: Function - Prevent extracting current entry. This method can be used to prevent extraction of the current item. By calling this method we can control which items can be extracted.
376
-
377
- # License
1
+ # zip-lib
2
+ zip and unzip library for node.
3
+
4
+ [![npm Package](https://img.shields.io/npm/v/zip-lib.svg)](https://www.npmjs.org/package/zip-lib)
5
+ [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/fpsqdb/zip-lib/blob/master/LICENSE)
6
+ ![node](https://img.shields.io/node/v/zip-lib)
7
+ [![Build Status](https://img.shields.io/github/actions/workflow/status/fpsqdb/zip-lib/test.yml?branch=master)](https://github.com/fpsqdb/zip-lib/actions?query=workflow%3Atest)
8
+
9
+ ## Install
10
+
11
+ ```
12
+ npm install zip-lib
13
+ ```
14
+
15
+ ## Quick Start
16
+
17
+ * [Zip](#Zip)
18
+ - [Zip single file](#zip-single-file)
19
+ - [Zip single folder](#zip-single-folder)
20
+ * [Unzip](#unzip)
21
+ * [Advanced usage](#advance-usage)
22
+ - [Sets the compression level](#sets-the-compression-level)
23
+ - [Zip multiple files and folders](#zip-multiple-files-and-folders)
24
+ - [Zip with metadata](#zip-with-metadata)
25
+ - [Unzip with entry callback](#unzip-with-entry-callback)
26
+ - [Unzip and exclude specified entries](#unzip-and-exclude-specified-entries)
27
+ - [Cancel zip](#cancel-zip)
28
+ - [Cancel unzip](#cancel-unzip)
29
+ * [API](#api)
30
+ - Method: [archiveFile](#archivefile)
31
+ - Method: [archiveFolder](#archivefolder)
32
+ - Method: [extract](#extract)
33
+ - Class: [Zip](#class-zip)
34
+ - Class: [Unzip](#class-unzip)
35
+ - Options: [IZipOptions](#izipoptions)
36
+ - Options: [IExtractOptions](#iextractoptions)
37
+
38
+ ## Zip
39
+ You can use **zip-lib** to compress files or folders.
40
+
41
+ ### Zip single file
42
+
43
+ ```js
44
+ const zl = require("zip-lib");
45
+
46
+ zl.archiveFile("path/to/file.txt", "path/to/target.zip").then(function () {
47
+ console.log("done");
48
+ }, function (err) {
49
+ console.log(err);
50
+ });
51
+ ```
52
+
53
+ ### Zip single folder
54
+
55
+ ```js
56
+ const zl = require("zip-lib");
57
+
58
+ zl.archiveFolder("path/to/folder", "path/to/target.zip").then(function () {
59
+ console.log("done");
60
+ }, function (err) {
61
+ console.log(err);
62
+ });
63
+ ```
64
+
65
+ ## Unzip
66
+
67
+ ```js
68
+ const zl = require("zip-lib");
69
+
70
+ zl.extract("path/to/target.zip", "path/to/target").then(function () {
71
+ console.log("done");
72
+ }, function (err) {
73
+ console.log(err);
74
+ });
75
+ ```
76
+
77
+ ## Advanced usage
78
+
79
+ ### Sets the compression level
80
+
81
+ ```js
82
+ const zl = require("zip-lib");
83
+
84
+ zl.archiveFolder("path/to/folder", "path/to/target.zip", { compressionLevel: 9 }).then(function () {
85
+ console.log("done");
86
+ }, function (err) {
87
+ console.log(err);
88
+ });
89
+ ```
90
+
91
+ ### Zip multiple files and folders
92
+
93
+ ```js
94
+ const zl = require("zip-lib");
95
+
96
+ const zip = new zl.Zip();
97
+ // Adds a file from the file system
98
+ zip.addFile("path/to/file.txt");
99
+ // Adds a folder from the file system, putting its contents at the root of archive
100
+ zip.addFolder("path/to/folder");
101
+ // Generate zip file.
102
+ zip.archive("path/to/target.zip").then(function () {
103
+ console.log("done");
104
+ }, function (err) {
105
+ console.log(err);
106
+ });
107
+ ```
108
+
109
+ The `path/to/folder` directory is as follows:
110
+
111
+ ```
112
+ path/to/folder
113
+ .
114
+ ├── dir1
115
+ │ ├── file.ext
116
+ ├── dir2
117
+ └── file_in_root.ext
118
+ ```
119
+
120
+ And the generated `path/to/target.zip` archive file directory will be as follows:
121
+
122
+ ```
123
+ path/to/target.zip
124
+ .
125
+ ├── file.txt
126
+ ├── dir1
127
+ │ ├── file.ext
128
+ ├── dir2
129
+ └── file_in_root.ext
130
+ ```
131
+
132
+ ### Zip with metadata
133
+
134
+ ```js
135
+ const zl = require("zip-lib");
136
+
137
+ const zip = new zl.Zip();
138
+ // Adds a file from the file system
139
+ zip.addFile("path/to/file.txt", "renamedFile.txt");
140
+ zip.addFile("path/to/file2.txt", "folder/file.txt");
141
+ // Adds a folder from the file system, and naming it `new folder` within the archive
142
+ zip.addFolder("path/to/folder", "new folder");
143
+ // Generate zip file.
144
+ zip.archive("path/to/target.zip").then(function () {
145
+ console.log("done");
146
+ }, function (err) {
147
+ console.log(err);
148
+ });
149
+ ```
150
+
151
+ The `path/to/folder` directory is as follows:
152
+
153
+ ```
154
+ path/to/folder
155
+ .
156
+ ├── dir1
157
+ │ ├── file.ext
158
+ ├── dir2
159
+ └── file_in_root.ext
160
+ ```
161
+
162
+ And the generated `path/to/target.zip` archive file directory will be as follows:
163
+
164
+ ```
165
+ path/to/target.zip
166
+ .
167
+ ├── renamedFile.txt
168
+ ├── folder
169
+ │ ├── file.txt
170
+ │── new folder
171
+ ├── dir1
172
+ │ ├── file.ext
173
+ ├── dir2
174
+ └── file_in_root.ext
175
+ ```
176
+
177
+ ### Unzip with entry callback
178
+ Using `onEntry` callback we can know the current progress of extracting and control the extraction operation. See [IExtractOptions](#iextractoptions).
179
+
180
+ ```js
181
+ const zl = require("zip-lib");
182
+
183
+ const unzip = new zl.Unzip({
184
+ // Called before an item is extracted.
185
+ onEntry: function (event) {
186
+ console.log(event.entryCount, event.entryName);
187
+ }
188
+ })
189
+ unzip.extract("path/to/target.zip", "path/to/target").then(function () {
190
+ console.log("done");
191
+ }, function (err) {
192
+ console.log(err);
193
+ });
194
+ ```
195
+
196
+ ### Unzip and exclude specified entries
197
+ The following code shows how to exclude the `__MACOSX` folder in the zip file when extracting. See [IExtractOptions](#iextractoptions).
198
+
199
+ ```js
200
+ const zl = require("zip-lib");
201
+
202
+ const unzip = new zl.Unzip({
203
+ // Called before an item is extracted.
204
+ onEntry: function (event) {
205
+ if (/^__MACOSX\//.test(event.entryName)) {
206
+ // entry name starts with __MACOSX/
207
+ event.preventDefault();
208
+ }
209
+ }
210
+ })
211
+ unzip.extract("path/to/target.zip", "path/to/target").then(function () {
212
+ console.log("done");
213
+ }, function (err) {
214
+ console.log(err);
215
+ });
216
+ ```
217
+
218
+ ### Cancel zip
219
+ If the `cancel` method is called after the archive is complete, nothing will happen.
220
+
221
+ ```js
222
+ const zl = require("zip-lib");
223
+
224
+ const zip = new zl.Zip();
225
+ zip.addFile("path/to/file.txt");
226
+ zip.archive("path/to/target.zip").then(function () {
227
+ console.log("done");
228
+ }, function (err) {
229
+ if (err.name === "Canceled") {
230
+ console.log("cancel");
231
+ } else {
232
+ console.log(err);
233
+ }
234
+ });
235
+
236
+ // Cancel zip
237
+ zip.cancel();
238
+ ```
239
+
240
+ ### Cancel unzip
241
+ If the `cancel` method is called after the extract is complete, nothing will happen.
242
+
243
+ ```js
244
+ const zl = require("zip-lib");
245
+
246
+ const unzip = new zl.Unzip();
247
+ unzip.extract("path/to/target.zip", "path/to/target").then(function () {
248
+ console.log("done");
249
+ }, function (err) {
250
+ if (err.name === "Canceled") {
251
+ console.log("cancel");
252
+ } else {
253
+ console.log(err);
254
+ }
255
+ });
256
+
257
+ // cancel
258
+ unzip.cancel();
259
+ ```
260
+
261
+ ## API
262
+
263
+ ### Method: archiveFile <a id="archivefile"></a>
264
+
265
+ **archiveFile(file, zipFile, [options])**
266
+
267
+ Compress a single file to zip.
268
+
269
+ - `file`: String
270
+ - `zipFile`: String
271
+ - `options?`: [IZipOptions](#izipoptions) (optional)
272
+
273
+ Returns: `Promise<viod>`
274
+
275
+ ### Method: archiveFolder <a id="archivefolder"></a>
276
+
277
+ **archiveFolder(folder, zipFile, [options])**
278
+
279
+ Compress all the contents of the specified folder to zip.
280
+
281
+ - `folder`: String
282
+ - `zipFile`: String
283
+ - `options?`: [IZipOptions](#izipoptions) (optional)
284
+
285
+ Returns: `Promise<void>`
286
+
287
+ ### Method: extract <a id="extract"></a>
288
+
289
+ **extract(zipFile, targetFolder, [options])**
290
+
291
+ Extract the zip file to the specified location.
292
+
293
+ - `zipFile`: String
294
+ - `targetFolder`: String
295
+ - `options?`: [IExtractOptions](#iextractoptions) (optional)
296
+
297
+ Returns: `Promise<void>`
298
+
299
+ ### Class: Zip<a id="class-zip"></a>
300
+ Compress files or folders to a zip file.
301
+
302
+ **Constructor: new Zip([options])**
303
+
304
+ - `options?`: [IZipOptions](#izipoptions)
305
+
306
+ **Method: addFile(file, [metadataPath])**
307
+
308
+ Adds a file from the file system at realPath into the zipfile as metadataPath.
309
+
310
+ - `file`: String
311
+ - `metadataPath?`: String (optional) - Typically metadataPath would be calculated as path.relative(root, realPath). A valid metadataPath must not start with `/` or `/[A-Za-z]:\//`, and must not contain `..`.
312
+
313
+ Returns: `void`
314
+
315
+ **Method: addFolder(folder, [metadataPath])**
316
+
317
+ Adds a folder from the file system at realPath into the zipfile as metadataPath.
318
+
319
+ - `folder`: String
320
+ - `metadataPath?`: String (optional) - Typically metadataPath would be calculated as path.relative(root, realPath). A valid metadataPath must not start with `/` or `/[A-Za-z]:\//`, and must not contain `..`.
321
+
322
+ Returns: `void`
323
+
324
+ **Method: archive(zipFile)**
325
+
326
+ Generate zip file.
327
+
328
+ - `zipFile`: String
329
+
330
+ Returns: `Promise<viod>`
331
+
332
+ **Method: cancel()**
333
+
334
+ Cancel compression. If the `cancel` method is called after the archive is complete, nothing will happen.
335
+
336
+ Returns: `void`
337
+
338
+ ### Class: Unzip<a id="class-unzip"></a>
339
+ Extract the zip file.
340
+
341
+ **Constructor: new Unzip([options])**
342
+
343
+ - `options?`: [IZipOptions](#izipoptions) (optional)
344
+
345
+ **Method: extract(zipFile, targetFolder)**
346
+
347
+ Extract the zip file to the specified location.
348
+
349
+ - `zipFile`: String
350
+ - `targetFolder`: String
351
+
352
+ Returns: `Promise<void>`
353
+
354
+ **Method: cancel()**
355
+
356
+ If the `cancel` method is called after the extract is complete, nothing will happen.
357
+
358
+ Returns: `void`
359
+
360
+ ### Options: IZipOptions <a id="izipoptions"></a>
361
+
362
+ Object
363
+ - `followSymlinks?`: Boolean (optional) - Indicates how to handle when the given path is a symbolic link. The default value is `false`.<br>`true`: add the target of the symbolic link to the zip.<br>`false`: add symbolic link itself to the zip.
364
+ - `compressionLevel?`: 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 - Sets the compression level. The default value is `6`.<br>`0`: the file data will be stored.<br>`1-9`: the file data will be deflated.
365
+
366
+ ### Options: IExtractOptions <a id="iextractoptions"></a>
367
+
368
+ Object
369
+ - `overwrite?`: String (optional) - If it is true, the target directory will be deleted before extract. The default value is `false`.
370
+ - `symlinkAsFileOnWindows?`: Boolean (optional) - Extract symbolic links as files on Windows. This value is only available on Windows and ignored on other platforms. The default value is `true`.<br>If `true`, the symlink in the zip will be extracted as a normal file on Windows.<br>If `false`, the symlink in the zip will be extracted as a symlink correctly on Windows, but an `EPERM` error will be thrown under non-administrators.
371
+
372
+ > ⚠**WARNING:** On Windows, the default security policy allows only administrators to create symbolic links. If you set `symlinkAsFileOnWindows` to `false` and the zip contains symlink, be sure to run the code under the administrator, otherwise an `EPERM` error will be thrown.
373
+
374
+ - `onEntry?`: Function (optional) - Called before an item is extracted.<br>Arguments:
375
+ - `event`: Object - Represents an event that an entry is about to be extracted.
376
+ - `entryName`: String (readonly) - Entry name.
377
+ - `entryCount`: Number (readonly) - Total number of entries.
378
+ - `preventDefault()`: Function - Prevent extracting current entry. This method can be used to prevent extraction of the current item. By calling this method we can control which items can be extracted.
379
+
380
+ # License
378
381
  Licensed under the [MIT](https://github.com/fpsqdb/zip-lib/blob/master/LICENSE) license.
package/fs.d.ts ADDED
@@ -0,0 +1,19 @@
1
+ export interface FileEntry {
2
+ path: string;
3
+ isSymbolicLink: boolean;
4
+ type: FileType;
5
+ mtime: Date;
6
+ mode: number;
7
+ }
8
+ export type FileType = "file" | "dir";
9
+ export declare function realpath(target: string): Promise<string>;
10
+ export declare function readdirp(folder: string): Promise<FileEntry[]>;
11
+ export declare function getFileEntry(target: string): Promise<FileEntry>;
12
+ export declare function ensureFolder(folder: string): Promise<{
13
+ isDirectory: boolean;
14
+ isSymbolicLink: boolean;
15
+ realpath?: string;
16
+ }>;
17
+ export declare function pathExists(target: string): Promise<boolean>;
18
+ export declare function rimraf(target: string): Promise<void>;
19
+ export declare function isRootPath(target: string): boolean;
@@ -1,5 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.realpath = realpath;
3
4
  exports.readdirp = readdirp;
4
5
  exports.getFileEntry = getFileEntry;
5
6
  exports.ensureFolder = ensureFolder;
@@ -7,10 +8,17 @@ exports.pathExists = pathExists;
7
8
  exports.rimraf = rimraf;
8
9
  exports.isRootPath = isRootPath;
9
10
  const path = require("path");
10
- const util = require("./util");
11
+ const fs = require("fs/promises");
12
+ const util = require("node:util");
13
+ const fsSync = require("fs");
14
+ async function realpath(target) {
15
+ // fs.promises.realpath has a bug with long path on Windows.
16
+ // https://github.com/nodejs/node/issues/51031
17
+ return util.promisify(fsSync.realpath)(target);
18
+ }
11
19
  async function readdirp(folder) {
12
20
  const result = [];
13
- const files = await util.readdir(folder);
21
+ const files = await fs.readdir(folder);
14
22
  for (const item of files) {
15
23
  const file = path.join(folder, item);
16
24
  const entry = await getFileEntry(file);
@@ -28,7 +36,7 @@ async function readdirp(folder) {
28
36
  return result;
29
37
  }
30
38
  async function getFileEntry(target) {
31
- const stat = await util.lstat(target);
39
+ const stat = await fs.lstat(target);
32
40
  let isSymbolicLink = false;
33
41
  let fileType = "file";
34
42
  if (stat.isDirectory()) {
@@ -40,7 +48,7 @@ async function getFileEntry(target) {
40
48
  // If the path is a link, we must instead use fs.stat() to find out if the
41
49
  // link is a directory or not because lstat will always return the stat of
42
50
  // the link which is always a file.
43
- const actualStat = await util.stat(target);
51
+ const actualStat = await fs.stat(target);
44
52
  if (actualStat.isDirectory()) {
45
53
  fileType = "dir";
46
54
  }
@@ -57,10 +65,14 @@ async function getFileEntry(target) {
57
65
  async function ensureFolder(folder) {
58
66
  // stop at root
59
67
  if (folder === path.dirname(folder)) {
60
- return Promise.resolve();
68
+ return Promise.resolve({
69
+ isDirectory: true,
70
+ isSymbolicLink: false
71
+ });
61
72
  }
62
73
  try {
63
- await mkdir(folder);
74
+ const result = await mkdir(folder);
75
+ return result;
64
76
  }
65
77
  catch (error) {
66
78
  // ENOENT: a parent folder does not exist yet, continue
@@ -75,7 +87,7 @@ async function ensureFolder(folder) {
75
87
  }
76
88
  async function pathExists(target) {
77
89
  try {
78
- await util.access(target);
90
+ await fs.access(target);
79
91
  return true;
80
92
  }
81
93
  catch (error) {
@@ -88,23 +100,23 @@ async function rimraf(target) {
88
100
  return Promise.reject(new Error(`Refuse to recursively delete root, path: "${target}"`));
89
101
  }
90
102
  try {
91
- const stat = await util.lstat(target);
103
+ const stat = await fs.lstat(target);
92
104
  // Folder delete (recursive) - NOT for symbolic links though!
93
105
  if (stat.isDirectory() && !stat.isSymbolicLink()) {
94
106
  // Children
95
- const children = await util.readdir(target);
107
+ const children = await fs.readdir(target);
96
108
  await Promise.all(children.map(child => rimraf(path.join(target, child))));
97
109
  // Folder
98
- await util.rmdir(target);
110
+ await fs.rmdir(target);
99
111
  }
100
112
  // Single file delete
101
113
  else {
102
114
  // chmod as needed to allow for unlink
103
115
  const mode = stat.mode;
104
116
  if (!(mode & 128)) { // 128 === 0200
105
- await util.chmod(target, mode | 128);
117
+ await fs.chmod(target, mode | 128);
106
118
  }
107
- return util.unlink(target);
119
+ return fs.unlink(target);
108
120
  }
109
121
  }
110
122
  catch (error) {
@@ -115,7 +127,11 @@ async function rimraf(target) {
115
127
  }
116
128
  async function mkdir(folder) {
117
129
  try {
118
- await util.mkdir(folder, 0o777);
130
+ await fs.mkdir(folder, 0o777);
131
+ return {
132
+ isDirectory: true,
133
+ isSymbolicLink: false,
134
+ };
119
135
  }
120
136
  catch (error) {
121
137
  // ENOENT: a parent folder does not exist yet or folder name is invalid.
@@ -125,15 +141,81 @@ async function mkdir(folder) {
125
141
  // Any other error: check if folder exists and
126
142
  // return normally in that case if its a folder
127
143
  try {
128
- const fileStat = await util.stat(folder);
129
- if (!fileStat.isDirectory()) {
130
- return Promise.reject(new Error(`"${folder}" exists and is not a directory.`));
144
+ const fileStat = await fs.lstat(folder);
145
+ if (fileStat.isSymbolicLink()) {
146
+ const realFilePath = await realpath(folder);
147
+ const realFileStat = await fs.lstat(realFilePath);
148
+ if (!realFileStat.isDirectory()) {
149
+ return Promise.reject(new Error(`"${folder}" exists and is not a directory.`));
150
+ }
151
+ return {
152
+ isDirectory: false,
153
+ isSymbolicLink: true,
154
+ realpath: realFilePath,
155
+ };
156
+ }
157
+ else {
158
+ if (!fileStat.isDirectory()) {
159
+ return Promise.reject(new Error(`"${folder}" exists and is not a directory.`));
160
+ }
161
+ return {
162
+ isDirectory: true,
163
+ isSymbolicLink: false,
164
+ };
131
165
  }
132
166
  }
133
167
  catch (statError) {
134
168
  throw error; // rethrow original error
135
169
  }
136
170
  }
171
+ // // Any other error: check if folder exists and
172
+ // // return normally in that case if its a folder
173
+ // try {
174
+ // if (folder.includes("dirlink")) {
175
+ // console.log("tttttt11111", folder);
176
+ // }
177
+ // const fileStat = await fs.lstat(folder);
178
+ // if (folder.includes("dirlink")) {
179
+ // console.log("tttttt22222", fileStat);
180
+ // }
181
+ // if (fileStat.isSymbolicLink()) {
182
+ // console.log("kkkkkkkkk", fileStat);
183
+ // const realFilePath = await realpath(folder);
184
+ // const realFileStat = await fs.lstat(realFilePath);
185
+ // if (!realFileStat.isDirectory()) {
186
+ // return Promise.reject(new Error(`"${folder}" exists and is not a directory.`));
187
+ // }
188
+ // return {
189
+ // isDirectory: false,
190
+ // isSymbolicLink: true,
191
+ // realpath: realFilePath,
192
+ // }
193
+ // } else {
194
+ // if (!fileStat.isDirectory()) {
195
+ // return Promise.reject(new Error(`"${folder}" exists and is not a directory.`));
196
+ // }
197
+ // return {
198
+ // isDirectory: true,
199
+ // isSymbolicLink: false,
200
+ // }
201
+ // }
202
+ // } catch (statError) {
203
+ // if (folder.includes("dirlink")) {
204
+ // console.log("yyyyy", statError);
205
+ // }
206
+ // // ignore
207
+ // }
208
+ // if (folder.includes("dirlink")) {
209
+ // console.log("kkkkkkkkk22222", folder);
210
+ // }
211
+ // await fs.mkdir(folder, { recursive: true, mode: 0o777 });
212
+ // if (folder.includes("dirlink")) {
213
+ // console.log("kkkkkkkkk3333333", folder);
214
+ // }
215
+ // return {
216
+ // isDirectory: true,
217
+ // isSymbolicLink: false,
218
+ // };
137
219
  }
138
220
  // "A"
139
221
  const charA = 65;
package/package.json CHANGED
@@ -1,45 +1,43 @@
1
- {
2
- "name": "zip-lib",
3
- "version": "1.0.5",
4
- "description": "zip and unzip library for node",
5
- "main": "lib/index.js",
6
- "scripts": {
7
- "compile": "rimraf ./lib && tsc -p ./src/tsconfig.json",
8
- "release": "rimraf ./lib && tsc -p ./src/tsconfig.release.json",
9
- "compile-test": "rimraf ./test/out && tsc -p ./test/src/tsconfig.json",
10
- "test": "node ./test/src/before.js && mocha ./test/out --timeout 10000",
11
- "pack": "npm run release && npm pack",
12
- "publish": "npm run release && npm publish"
13
- },
14
- "repository": {
15
- "type": "git",
16
- "url": "https://github.com/fpsqdb/zip-lib.git"
17
- },
18
- "engines": {
19
- "node": ">=12"
20
- },
21
- "keywords": [
22
- "zip",
23
- "folder",
24
- "unzip",
25
- "archive",
26
- "extract",
27
- "promise",
28
- "async"
29
- ],
30
- "author": "fpsqdb",
31
- "license": "MIT",
32
- "dependencies": {
33
- "yauzl": "^3.2.0",
34
- "yazl": "^3.2.1"
35
- },
36
- "devDependencies": {
37
- "@types/mocha": "^10.0.9",
38
- "@types/node": "^12.20.55",
39
- "@types/yauzl": "^2.10.3",
40
- "@types/yazl": "^2.4.5",
41
- "mocha": "^10.8.2",
42
- "rimraf": "^6.0.1",
43
- "typescript": "^5.6.3"
44
- }
45
- }
1
+ {
2
+ "name": "zip-lib",
3
+ "version": "1.1.0",
4
+ "description": "zip and unzip library for node",
5
+ "main": "lib/index.js",
6
+ "scripts": {
7
+ "compile": "rimraf ./dist && tsc -p ./src/tsconfig.json",
8
+ "release": "rimraf ./lib && tsc -p ./src/tsconfig.release.json && node ./build.mjs",
9
+ "compile-test": "rimraf ./test/out && tsc -p ./test/src/tsconfig.json",
10
+ "test": "npm run compile && npm run compile-test && node ./test/src/before.js && mocha ./test/out --timeout 10000"
11
+ },
12
+ "repository": {
13
+ "type": "git",
14
+ "url": "https://github.com/fpsqdb/zip-lib.git"
15
+ },
16
+ "engines": {
17
+ "node": ">=18"
18
+ },
19
+ "keywords": [
20
+ "zip",
21
+ "folder",
22
+ "unzip",
23
+ "archive",
24
+ "extract",
25
+ "promise",
26
+ "async"
27
+ ],
28
+ "author": "fpsqdb",
29
+ "license": "MIT",
30
+ "dependencies": {
31
+ "yauzl": "^3.2.0",
32
+ "yazl": "^3.3.1"
33
+ },
34
+ "devDependencies": {
35
+ "@types/mocha": "^10.0.10",
36
+ "@types/node": "^18.19.86",
37
+ "@types/yauzl": "^2.10.3",
38
+ "@types/yazl": "^2.4.6",
39
+ "mocha": "^11.1.0",
40
+ "rimraf": "^6.0.1",
41
+ "typescript": "^5.8.3"
42
+ }
43
+ }
@@ -4,8 +4,8 @@ exports.Unzip = void 0;
4
4
  const yauzl = require("yauzl");
5
5
  const exfs = require("./fs");
6
6
  const fs_1 = require("fs");
7
+ const fs = require("fs/promises");
7
8
  const path = require("path");
8
- const util = require("./util");
9
9
  const cancelable_1 = require("./cancelable");
10
10
  class EntryEvent {
11
11
  /**
@@ -40,6 +40,7 @@ class EntryContext {
40
40
  this._realTargetFolder = _realTargetFolder;
41
41
  this.symlinkAsFileOnWindows = symlinkAsFileOnWindows;
42
42
  this._symlinkFileNames = [];
43
+ this._symlinkFolders = [];
43
44
  }
44
45
  get decodeEntryFileName() {
45
46
  return this._decodeEntryFileName;
@@ -56,20 +57,31 @@ class EntryContext {
56
57
  get symlinkFileNames() {
57
58
  return this._symlinkFileNames;
58
59
  }
60
+ get symlinkFolders() {
61
+ return this._symlinkFolders;
62
+ }
59
63
  getFilePath() {
60
64
  return path.join(this.targetFolder, this.decodeEntryFileName);
61
65
  }
62
66
  async isOutsideTargetFolder(tpath) {
63
- if (this.symlinkFileNames.length === 0) {
67
+ if (this.symlinkFileNames.length === 0 &&
68
+ this.symlinkFolders.length === 0) {
64
69
  return false;
65
70
  }
66
71
  if (process.platform === "win32" &&
67
72
  this.symlinkAsFileOnWindows) {
68
73
  return false;
69
74
  }
75
+ for (const { folder, realpath } of this.symlinkFolders) {
76
+ if (tpath.includes(folder)) {
77
+ if (realpath.indexOf(this.realTargetFolder) !== 0) {
78
+ return true;
79
+ }
80
+ }
81
+ }
70
82
  for (const fileName of this.symlinkFileNames) {
71
83
  if (tpath.includes(fileName)) {
72
- const realFilePath = await util.realpath(tpath);
84
+ const realFilePath = await exfs.realpath(tpath);
73
85
  if (realFilePath.indexOf(this.realTargetFolder) !== 0) {
74
86
  return true;
75
87
  }
@@ -106,7 +118,7 @@ class Unzip extends cancelable_1.Cancelable {
106
118
  return Promise.reject(this.canceledError());
107
119
  }
108
120
  await exfs.ensureFolder(targetFolder);
109
- const realTargetFolder = await util.realpath(targetFolder);
121
+ const realTargetFolder = await exfs.realpath(targetFolder);
110
122
  const zfile = await this.openZip(zipFile, token);
111
123
  this.zipFile = zfile;
112
124
  zfile.readEntry();
@@ -240,7 +252,13 @@ class Unzip extends cancelable_1.Cancelable {
240
252
  async extractEntry(zfile, entry, entryContext, token) {
241
253
  const filePath = entryContext.getFilePath();
242
254
  const fileDir = path.dirname(filePath);
243
- await exfs.ensureFolder(fileDir);
255
+ const folderStat = await exfs.ensureFolder(fileDir);
256
+ if (folderStat.isSymbolicLink) {
257
+ entryContext.symlinkFolders.push({
258
+ folder: fileDir,
259
+ realpath: folderStat.realpath,
260
+ });
261
+ }
244
262
  const outside = await entryContext.isOutsideTargetFolder(fileDir);
245
263
  if (outside) {
246
264
  const error = new Error(`Refuse to write file outside "${entryContext.targetFolder}", file: "${filePath}"`);
@@ -317,7 +335,7 @@ class Unzip extends cancelable_1.Cancelable {
317
335
  targetPath = path.join(path.dirname(des), linkContent);
318
336
  }
319
337
  try {
320
- const stat = await util.stat(targetPath);
338
+ const stat = await fs.stat(targetPath);
321
339
  if (stat.isDirectory()) {
322
340
  linkType = "dir";
323
341
  }
@@ -327,7 +345,7 @@ class Unzip extends cancelable_1.Cancelable {
327
345
  }
328
346
  }
329
347
  }
330
- await util.symlink(linkContent, des, linkType);
348
+ await fs.symlink(linkContent, des, linkType);
331
349
  }
332
350
  isOverwrite() {
333
351
  if (this.options &&
@@ -3,9 +3,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.Zip = void 0;
4
4
  const yazl = require("yazl");
5
5
  const fs_1 = require("fs");
6
+ const fs = require("fs/promises");
6
7
  const exfs = require("./fs");
7
8
  const path = require("path");
8
- const util = require("./util");
9
9
  const cancelable_1 = require("./cancelable");
10
10
  /**
11
11
  * Compress files or folders to a zip file.
@@ -120,7 +120,7 @@ class Zip extends cancelable_1.Cancelable {
120
120
  if (entry.isSymbolicLink) {
121
121
  if (this.followSymlink()) {
122
122
  if (entry.type === "dir") {
123
- const realPath = await util.realpath(file.path);
123
+ const realPath = await exfs.realpath(file.path);
124
124
  await this.walkDir([{ path: realPath, metadataPath: file.metadataPath }], token);
125
125
  }
126
126
  else {
@@ -160,7 +160,7 @@ class Zip extends cancelable_1.Cancelable {
160
160
  });
161
161
  }
162
162
  async addSymlink(zip, file, metadataPath) {
163
- const linkTarget = await util.readlink(file.path);
163
+ const linkTarget = await fs.readlink(file.path);
164
164
  zip.addBuffer(Buffer.from(linkTarget), metadataPath, Object.assign(Object.assign({}, this.getYazlOption()), { mtime: file.mtime, mode: file.mode }));
165
165
  }
166
166
  async walkDir(folders, token) {
package/lib/util.js DELETED
@@ -1,48 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.unlink = unlink;
4
- exports.mkdir = mkdir;
5
- exports.realpath = realpath;
6
- exports.stat = stat;
7
- exports.lstat = lstat;
8
- exports.chmod = chmod;
9
- exports.readdir = readdir;
10
- exports.access = access;
11
- exports.rmdir = rmdir;
12
- exports.symlink = symlink;
13
- exports.readlink = readlink;
14
- const fs = require("fs");
15
- const util = require("util");
16
- function unlink(path) {
17
- return util.promisify(fs.unlink)(path);
18
- }
19
- function mkdir(path, mode) {
20
- return util.promisify(fs.mkdir)(path, mode);
21
- }
22
- function realpath(path) {
23
- return util.promisify(fs.realpath)(path);
24
- }
25
- function stat(path) {
26
- return util.promisify(fs.stat)(path);
27
- }
28
- function lstat(path) {
29
- return util.promisify(fs.lstat)(path);
30
- }
31
- function chmod(path, mode) {
32
- return util.promisify(fs.chmod)(path, mode);
33
- }
34
- function readdir(path) {
35
- return util.promisify(fs.readdir)(path);
36
- }
37
- function access(path, mode) {
38
- return util.promisify(fs.access)(path, mode);
39
- }
40
- function rmdir(path) {
41
- return util.promisify(fs.rmdir)(path);
42
- }
43
- function symlink(target, path, type) {
44
- return util.promisify(fs.symlink)(target, path, type);
45
- }
46
- function readlink(path) {
47
- return util.promisify(fs.readlink)(path);
48
- }
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes