jspurefix 5.6.2 → 5.8.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/src/util/unzip.js CHANGED
@@ -1,202 +1,72 @@
1
1
  const yauzl = require('yauzl')
2
2
  const path = require('path')
3
3
  const fs = require('fs')
4
- const util = require('util')
5
- const Transform = require('stream').Transform
4
+ const { pipeline } = require('stream/promises')
6
5
 
7
- // thanks to https://github.com/thejoshwolfe/yauzl/blob/master/examples/unzip.js
6
+ // Extracts a zip file into the current working directory.
7
+ //
8
+ // Replaces an older copy of the yauzl example unzip script. The previous
9
+ // version drove a 60Hz progress interval via terminal backspaces and did
10
+ // not attach error handlers to any of the streams in its pipeline. In a
11
+ // non-TTY environment (eg. GitHub Actions) the backspaces produced
12
+ // unreadable output; worse, if any stream errored silently the script
13
+ // hung forever and CI runs were cancelled at the workflow timeout.
14
+ //
15
+ // Follows the canonical yauzl lazyEntries pattern: install 'entry' /
16
+ // 'end' / 'error' handlers once on the zipfile, drive the next read
17
+ // with zipfile.readEntry() at the end of each entry's work.
18
+ // NB: requires yauzl >= 3.3.1 — 3.3.0 truncates inflated streams on
19
+ // Node 26+ (the next entry's readStream stalls without emitting 'end').
8
20
 
9
- let zipFilePath = null
10
- let offsetArg
11
- let lenArg
12
- let endArg
13
- const args = process.argv.slice(2)
14
- for (let i = 0; i < args.length; i++) {
15
- const arg = args[i]
16
- if (arg === '--offset') {
17
- i += 1
18
- offsetArg = parseInt(args[i])
19
- if (isNaN(offsetArg)) throw new Error('--offset argument not parsable as an int')
20
- } else if (arg === '--len') {
21
- i += 1
22
- lenArg = parseInt(args[i])
23
- if (isNaN(lenArg)) throw new Error('--len argument not parsable as an int')
24
- } else if (arg === '--end') {
25
- i += 1
26
- endArg = parseInt(args[i])
27
- if (isNaN(endArg)) throw new Error('--end argument not parsable as an int')
28
- } else if (['-h', '--help'].includes(arg)) {
29
- // print help
30
- zipFilePath = null
31
- break
32
- } else if (/^--/.test(arg)) {
33
- throw new Error('unrecognized option: ' + arg)
34
- } else {
35
- if (zipFilePath != null) throw new Error('too many arguments')
36
- zipFilePath = arg
37
- }
38
- }
39
- if (zipFilePath == null || /^-/.test(zipFilePath) || (lenArg != null && endArg != null)) {
40
- console.log(
41
- 'usage: node unzip.js [options] path/to/file.zip\n' +
42
- '\n' +
43
- 'unzips the specified zip file into the current directory\n' +
44
- '\n' +
45
- 'options:\n' +
46
- ' --offset START\n' +
47
- ' --len LEN\n' +
48
- ' --end END\n' +
49
- ' interprets the middle of the specified file as a zipfile.\n' +
50
- ' starting START number of bytes in from the beginning (default 0).\n' +
51
- ' end with length of LEN (default is all the way to the end of the file).\n' +
52
- ' or end at byte offset END (exclusive) (default is the end of the file).\n' +
53
- ' end can be negative to count backwards from the end of the file\n' +
54
- ' (example, `--end -1` excludes the last byte of the file).\n' +
55
- '')
21
+ const zipFilePath = process.argv[2]
22
+ if (!zipFilePath) {
23
+ console.error('usage: node unzip.js path/to/file.zip')
56
24
  process.exit(1)
57
25
  }
58
26
 
59
- function mkdirp (dir, cb) {
60
- if (dir === '.') return cb()
61
- fs.stat(dir, function (err) {
62
- if (err == null) return cb() // already exists
63
-
64
- const parent = path.dirname(dir)
65
- mkdirp(parent, function () {
66
- process.stdout.write(dir.replace(/\/$/, '') + '/\n')
67
- fs.mkdir(dir, cb)
68
- })
69
- })
70
- }
71
-
72
- if (offsetArg != null || lenArg != null || endArg != null) {
73
- openMiddleOfFile(zipFilePath, { lazyEntries: true }, offsetArg, lenArg, endArg, handleZipFile)
74
- } else {
75
- yauzl.open(zipFilePath, { lazyEntries: true }, handleZipFile)
76
- }
77
-
78
- function openMiddleOfFile (zipFilePath, options, offsetArg, lenArg, endArg, handleZipFile) {
79
- fs.open(zipFilePath, 'r', function (err, fd) {
80
- if (err != null) throw err
81
- fs.fstat(fd, function (err, stats) {
27
+ function extract (zipFilePath) {
28
+ return new Promise((resolve, reject) => {
29
+ yauzl.open(zipFilePath, { lazyEntries: true }, (err, zipfile) => {
82
30
  if (err) {
83
- console.log(err)
31
+ reject(err)
32
+ return
84
33
  }
85
- // resolve optional parameters
86
- if (offsetArg == null) offsetArg = 0
87
- if (lenArg == null && endArg == null) endArg = stats.size
88
- if (endArg == null) endArg = lenArg + offsetArg
89
- else if (endArg < 0) endArg = stats.size + endArg
90
- // validate parameters
91
- if (offsetArg < 0) throw new Error('--offset < 0')
92
- if (lenArg < 0) throw new Error('--len < 0')
93
- if (offsetArg > endArg) throw new Error('--offset > --end')
94
- if (endArg > stats.size) throw new Error('--end/--len goes past EOF')
95
34
 
96
- // extend RandomAccessReader
97
- function MiddleOfFileReader () {
98
- yauzl.RandomAccessReader.call(this)
99
- }
100
- util.inherits(MiddleOfFileReader, yauzl.RandomAccessReader)
101
- // implement required and option methods
102
- MiddleOfFileReader.prototype._readStreamForRange = function (start, end) {
103
- return fs.createReadStream(null, {
104
- fd,
105
- // shift the start and end offsets
106
- start: start + offsetArg,
107
- end: end + offsetArg - 1, // the -1 is because fs.createReadStream()'s end option is inclusive
108
- autoClose: false
109
- })
110
- }
111
- MiddleOfFileReader.prototype.read = function (buffer, offset, length, position, callback) {
112
- // shift the position
113
- fs.read(fd, buffer, offset, length, position + offsetArg, callback)
114
- }
115
- MiddleOfFileReader.prototype.close = function (callback) {
116
- fs.close(fd, callback)
117
- }
35
+ zipfile.on('error', reject)
36
+ zipfile.on('end', resolve)
118
37
 
119
- yauzl.fromRandomAccessReader(new MiddleOfFileReader(), endArg - offsetArg, options, handleZipFile)
120
- })
121
- })
122
- }
123
-
124
- function handleZipFile (err, zipfile) {
125
- if (err) throw err
38
+ zipfile.on('entry', (entry) => {
39
+ const next = () => zipfile.readEntry()
126
40
 
127
- // track when we've closed all our file handles
128
- let handleCount = 0
129
-
130
- function incrementHandleCount () {
131
- handleCount++
132
- }
133
- function decrementHandleCount () {
134
- handleCount--
135
- if (handleCount === 0) {
136
- console.log('all input and output handles closed')
137
- }
138
- }
139
-
140
- incrementHandleCount()
141
- zipfile.on('close', function () {
142
- console.log('closed input file')
143
- decrementHandleCount()
144
- })
41
+ if (/\/$/.test(entry.fileName)) {
42
+ fs.promises.mkdir(entry.fileName, { recursive: true })
43
+ .then(next, reject)
44
+ return
45
+ }
145
46
 
146
- zipfile.readEntry()
147
- zipfile.on('entry', function (entry) {
148
- if (/\/$/.test(entry.fileName)) {
149
- // directory file names end with '/'
150
- mkdirp(entry.fileName, function () {
151
- if (err) throw err
152
- zipfile.readEntry()
153
- })
154
- } else {
155
- // ensure parent directory exists
156
- mkdirp(path.dirname(entry.fileName), function () {
157
- zipfile.openReadStream(entry, function (err, readStream) {
158
- if (err) throw err
159
- // report progress through large files
160
- let byteCount = 0
161
- const totalBytes = entry.uncompressedSize
162
- let lastReportedString = byteCount + '/' + totalBytes + ' 0%'
163
- process.stdout.write(entry.fileName + '...' + lastReportedString)
164
- function reportString (msg) {
165
- let clearString = ''
166
- for (let i = 0; i < lastReportedString.length; i++) {
167
- clearString += '\b'
168
- if (i >= msg.length) {
169
- clearString += ' \b'
47
+ fs.promises.mkdir(path.dirname(entry.fileName), { recursive: true })
48
+ .then(() => new Promise((res, rej) => {
49
+ zipfile.openReadStream(entry, (err, readStream) => {
50
+ if (err) {
51
+ rej(err)
52
+ return
170
53
  }
171
- }
172
- process.stdout.write(clearString + msg)
173
- lastReportedString = msg
174
- }
175
- // report progress at 60Hz
176
- const progressInterval = setInterval(function () {
177
- reportString(byteCount + '/' + totalBytes + ' ' + ((byteCount / totalBytes * 100) | 0) + '%')
178
- }, 1000 / 60)
179
- const filter = new Transform()
180
- filter._transform = function (chunk, encoding, cb) {
181
- byteCount += chunk.length
182
- cb(null, chunk)
183
- }
184
- filter._flush = function (cb) {
185
- clearInterval(progressInterval)
186
- reportString('')
187
- // delete the "..."
188
- process.stdout.write('\b \b\b \b\b \b\n')
189
- cb()
190
- zipfile.readEntry()
191
- }
192
-
193
- // pump file contents
194
- const writeStream = fs.createWriteStream(entry.fileName)
195
- incrementHandleCount()
196
- writeStream.on('close', decrementHandleCount)
197
- readStream.pipe(filter).pipe(writeStream)
198
- })
54
+ pipeline(readStream, fs.createWriteStream(entry.fileName))
55
+ .then(() => {
56
+ console.log(`${entry.fileName} (${entry.uncompressedSize} bytes)`)
57
+ res()
58
+ }, rej)
59
+ })
60
+ }))
61
+ .then(next, reject)
199
62
  })
200
- }
63
+
64
+ zipfile.readEntry()
65
+ })
201
66
  })
202
67
  }
68
+
69
+ extract(zipFilePath).catch((err) => {
70
+ console.error(`unzip failed: ${err.message}`)
71
+ process.exit(1)
72
+ })