Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
seleniumhq
GitHub Repository: seleniumhq/selenium
Path: blob/trunk/javascript/selenium-webdriver/io/zip.js
4500 views
1
// Licensed to the Software Freedom Conservancy (SFC) under one
2
// or more contributor license agreements. See the NOTICE file
3
// distributed with this work for additional information
4
// regarding copyright ownership. The SFC licenses this file
5
// to you under the Apache License, Version 2.0 (the
6
// "License"); you may not use this file except in compliance
7
// with the License. You may obtain a copy of the License at
8
//
9
// http://www.apache.org/licenses/LICENSE-2.0
10
//
11
// Unless required by applicable law or agreed to in writing,
12
// software distributed under the License is distributed on an
13
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14
// KIND, either express or implied. See the License for the
15
// specific language governing permissions and limitations
16
// under the License.
17
18
'use strict'
19
20
const jszip = require('jszip')
21
const path = require('node:path')
22
const fs = require('fs/promises')
23
const io = require('./index')
24
const { InvalidArgumentError } = require('../lib/error')
25
26
/**
27
* Manages a zip archive.
28
*/
29
class Zip {
30
constructor() {
31
/** @private @const */
32
this.z_ = new jszip()
33
34
/** @private @const {!Set<!Promise<?>>} */
35
this.pendingAdds_ = new Set()
36
}
37
38
/**
39
* Adds a file to this zip.
40
*
41
* @param {string} filePath path to the file to add.
42
* @param {string=} zipPath path to the file in the zip archive, defaults
43
* to the basename of `filePath`.
44
* @return {!Promise<?>} a promise that will resolve when added.
45
*/
46
addFile(filePath, zipPath = path.basename(filePath)) {
47
let add = Promise.all([io.read(filePath), fs.stat(filePath)]).then(([buffer, stats]) =>
48
this.z_.file(/** @type {string} */ (zipPath.replace(/\\/g, '/')), buffer, {
49
date: stats.mtime, // preserve file's "last modified" value
50
}),
51
)
52
this.pendingAdds_.add(add)
53
return add.then(
54
() => this.pendingAdds_.delete(add),
55
(e) => {
56
this.pendingAdds_.delete(add)
57
throw e
58
},
59
)
60
}
61
62
/**
63
* Recursively adds a directory and all of its contents to this archive.
64
*
65
* @param {string} dirPath path to the directory to add.
66
* @param {string=} zipPath path to the folder in the archive to add the
67
* directory contents to. Defaults to the root folder.
68
* @return {!Promise<?>} returns a promise that will resolve when
69
* the operation is complete.
70
*/
71
addDir(dirPath, zipPath = '') {
72
return io.walkDir(dirPath).then((entries) => {
73
let archive = this.z_
74
if (zipPath) {
75
archive = archive.folder(zipPath)
76
}
77
78
let files = []
79
entries.forEach((spec) => {
80
if (spec.dir) {
81
archive.folder(spec.path)
82
} else {
83
files.push(this.addFile(path.join(dirPath, spec.path), path.join(zipPath, spec.path)))
84
}
85
})
86
87
return Promise.all(files)
88
})
89
}
90
91
/**
92
* @param {string} path File path to test for within the archive.
93
* @return {boolean} Whether this zip archive contains an entry with the given
94
* path.
95
*/
96
has(path) {
97
return this.z_.file(path) !== null
98
}
99
100
/**
101
* Returns the contents of the file in this zip archive with the given `path`.
102
* The returned promise will be rejected with an {@link InvalidArgumentError}
103
* if either `path` does not exist within the archive, or if `path` refers
104
* to a directory.
105
*
106
* @param {string} path the path to the file whose contents to return.
107
* @return {!Promise<!Buffer>} a promise that will be resolved with the file's
108
* contents as a buffer.
109
*/
110
getFile(path) {
111
let file = this.z_.file(path)
112
if (!file) {
113
return Promise.reject(new InvalidArgumentError(`No such file in zip archive: ${path}`))
114
}
115
116
if (file.dir) {
117
return Promise.reject(new InvalidArgumentError(`The requested file is a directory: ${path}`))
118
}
119
120
return Promise.resolve(file.async('nodebuffer'))
121
}
122
123
/**
124
* Returns the compressed data for this archive in a buffer. _This method will
125
* not wait for any outstanding {@link #addFile add}
126
* {@link #addDir operations} before encoding the archive._
127
*
128
* @param {string} compression The desired compression.
129
* Must be `STORE` (the default) or `DEFLATE`.
130
* @return {!Promise<!Buffer>} a promise that will resolve with this archive
131
* as a buffer.
132
*/
133
toBuffer(compression = 'STORE') {
134
if (compression !== 'STORE' && compression !== 'DEFLATE') {
135
return Promise.reject(new InvalidArgumentError(`compression must be one of {STORE, DEFLATE}, got ${compression}`))
136
}
137
return Promise.resolve(this.z_.generateAsync({ compression, type: 'nodebuffer' }))
138
}
139
}
140
141
/**
142
* Asynchronously opens a zip archive.
143
*
144
* @param {string} path to the zip archive to load.
145
* @return {!Promise<!Zip>} a promise that will resolve with the opened
146
* archive.
147
*/
148
function load(path) {
149
return io.read(path).then((data) => {
150
let zip = new Zip()
151
return zip.z_.loadAsync(data).then(() => zip)
152
})
153
}
154
155
/**
156
* Asynchronously unzips an archive file.
157
*
158
* @param {string} src path to the source file to unzip.
159
* @param {string} dst path to the destination directory.
160
* @return {!Promise<string>} a promise that will resolve with `dst` once the
161
* archive has been unzipped.
162
*/
163
function unzip(src, dst) {
164
return load(src).then((zip) => {
165
const promisedDirs = new Map()
166
const promises = []
167
168
zip.z_.forEach((relPath, file) => {
169
let p
170
if (file.dir) {
171
p = createDir(relPath)
172
} else {
173
let dirname = path.dirname(relPath)
174
if (dirname === '.') {
175
p = writeFile(relPath, file)
176
} else {
177
p = createDir(dirname).then(() => writeFile(relPath, file))
178
}
179
}
180
promises.push(p)
181
})
182
183
return Promise.all(promises).then(() => dst)
184
185
function createDir(dir) {
186
let p = promisedDirs.get(dir)
187
if (!p) {
188
p = io.mkdirp(path.join(dst, dir))
189
promisedDirs.set(dir, p)
190
}
191
return p
192
}
193
194
function writeFile(relPath, file) {
195
return file.async('nodebuffer').then((buffer) => io.write(path.join(dst, relPath), buffer))
196
}
197
})
198
}
199
200
// PUBLIC API
201
module.exports = { Zip, load, unzip }
202
203