@antora/ui-loader 3.1.7 → 3.1.8

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/constants.js CHANGED
@@ -3,14 +3,13 @@
3
3
  module.exports = Object.freeze({
4
4
  UI_CACHE_FOLDER: 'ui',
5
5
  UI_DESC_FILENAME: 'ui.yml',
6
- UI_SRC_GLOB: '**/*[!~]',
6
+ UI_SRC_GLOB: '**/!(*~)',
7
7
  UI_SRC_OPTS: {
8
+ braceExpansion: false,
8
9
  dot: true,
9
- follow: true,
10
- ignore: ['.git/**'],
11
- nomount: true,
12
- nosort: true,
13
- nounique: true,
14
- strict: false,
10
+ ignore: ['.git'],
11
+ objectMode: true,
12
+ onlyFiles: false,
13
+ unique: false,
15
14
  },
16
15
  })
package/lib/file.js CHANGED
@@ -1,10 +1,12 @@
1
1
  'use strict'
2
2
 
3
+ const { constants: fsc } = require('fs')
4
+ const { posix: path } = require('path')
3
5
  const { Readable } = require('stream')
4
- const { Stats } = require('fs')
5
6
  const Vinyl = require('vinyl')
6
7
 
7
8
  const DEFAULT_FILE_MODE = 0o100666 & ~process.umask()
9
+ const invariably = { true: () => true, false: () => false }
8
10
 
9
11
  class File extends Vinyl {
10
12
  get path () {
@@ -27,21 +29,92 @@ class File extends Vinyl {
27
29
  class MemoryFile extends File {
28
30
  constructor (file) {
29
31
  const contents = file.contents || Buffer.alloc(0)
30
- const stat = Object.assign(new Stats(), { mode: DEFAULT_FILE_MODE, mtime: undefined, size: contents.length })
32
+ const stat = {
33
+ mode: DEFAULT_FILE_MODE,
34
+ size: contents.length,
35
+ isDirectory: invariably.false,
36
+ isFile: invariably.true,
37
+ isSymbolicLink: invariably.false,
38
+ }
31
39
  super(Object.assign({}, file, { contents, stat }))
32
40
  }
33
41
  }
34
42
 
35
- class ReadableFile extends Readable {
36
- constructor (file) {
37
- super({ objectMode: true })
38
- this._file = file
43
+ class ZipReadable extends Readable {
44
+ constructor (zipFile, options = {}) {
45
+ super({ objectMode: true, highWaterMark: 1 })
46
+ if ((this._closeable = (this._zipFile = zipFile).reader.fd != null) && !zipFile.autoClose) {
47
+ throw new Error('ZipReadable requires file-based ZipFile to be initialized with autoClose:true option')
48
+ }
49
+ if (!zipFile.lazyEntries) {
50
+ throw new Error('ZipReadable requires ZipFile to be initialized with lazyEntries:true option')
51
+ }
52
+ if ((this._startPath = options.startPath) && (this._startPath = path.join('/', this._startPath + '/')) !== '/') {
53
+ this._startPath = this._startPath.slice(1)
54
+ } else {
55
+ this._startPath = undefined
56
+ }
57
+ this._init()
58
+ }
59
+
60
+ _init () {
61
+ const zipFile = this._zipFile
62
+ zipFile
63
+ .on('entry', (entry) => {
64
+ const mode = this.getFileMode(entry)
65
+ if ((mode & fsc.S_IFMT) === fsc.S_IFDIR) return zipFile.readEntry()
66
+ let path_ = entry.fileName
67
+ if (this._startPath) {
68
+ if (path_.length < this._startPath.length || !path_.startsWith(this._startPath)) return zipFile.readEntry()
69
+ path_ = path_.slice(this._startPath.length)
70
+ }
71
+ const isLink = (mode & fsc.S_IFMT) === fsc.S_IFLNK
72
+ const stat = {
73
+ mode,
74
+ mtime: entry.getLastModDate(),
75
+ size: entry.uncompressedSize,
76
+ isDirectory: invariably.false,
77
+ isFile: invariably[!isLink],
78
+ isSymbolicLink: invariably[isLink],
79
+ }
80
+ const file = { path: path_, stat }
81
+ if (stat.size === 0) {
82
+ file.contents = Buffer.alloc(0)
83
+ this.push(new File(file))
84
+ } else {
85
+ zipFile.openReadStream(entry, (readErr, readStream) => {
86
+ if (readErr) {
87
+ zipFile.close()
88
+ this.emit('error', readErr)
89
+ return
90
+ }
91
+ if (isLink) {
92
+ const buffer = []
93
+ readStream
94
+ .on('data', (chunk) => buffer.push(chunk))
95
+ .on('error', (readStreamErr) => this.emit('error', readStreamErr))
96
+ .on('end', () => {
97
+ file.symlink = (buffer.length === 1 ? buffer[0] : Buffer.concat(buffer)).toString()
98
+ this.push(new File(file))
99
+ })
100
+ } else {
101
+ file.contents = readStream
102
+ this.push(new File(file))
103
+ }
104
+ })
105
+ }
106
+ })
107
+ .on(this._closeable ? 'close' : 'end', () => zipFile.emittedError || this.push(null))
108
+ }
109
+
110
+ _read (_n) {
111
+ this._zipFile.readEntry()
39
112
  }
40
113
 
41
- _read () {
42
- this.push(this._file)
43
- this.push((this._file = null))
114
+ getFileMode ({ externalFileAttributes }) {
115
+ const attr = externalFileAttributes >> 16 || 33188
116
+ return [448, 56, 7].map((mask) => attr & mask).reduce((a, b) => a + b, attr & fsc.S_IFMT)
44
117
  }
45
118
  }
46
119
 
47
- module.exports = { File, MemoryFile, ReadableFile }
120
+ module.exports = { File, MemoryFile, ZipReadable }
package/lib/load-ui.js CHANGED
@@ -3,22 +3,23 @@
3
3
  const { compile: bracesToGroup } = require('braces')
4
4
  const { createHash } = require('crypto')
5
5
  const expandPath = require('@antora/expand-path-helper')
6
- const { File, MemoryFile, ReadableFile } = require('./file')
6
+ const { File, MemoryFile, ZipReadable } = require('./file')
7
7
  const { promises: fsp } = require('fs')
8
8
  const { concat: get } = require('simple-get')
9
9
  const getCacheDir = require('cache-directory')
10
- const globStream = require('glob-stream')
10
+ const { globStream } = require('fast-glob')
11
11
  const { inspect } = require('util')
12
+ const invariably = { false: () => false, void: () => undefined }
12
13
  const ospath = require('path')
13
14
  const { posix: path } = ospath
14
15
  const picomatch = require('picomatch')
15
16
  const posixify = ospath.sep === '\\' ? (p) => p.replace(/\\/g, '/') : undefined
16
- const { pipeline, Transform, Writable } = require('stream')
17
+ const { pipeline, PassThrough, Writable } = require('stream')
17
18
  const forEach = (write, final) => new Writable({ objectMode: true, write, final })
18
- const map = (transform) => new Transform({ objectMode: true, transform })
19
+ const through = () => new PassThrough({ objectMode: true })
19
20
  const UiCatalog = require('./ui-catalog')
20
21
  const yaml = require('js-yaml')
21
- const vzip = require('@vscode/gulp-vinyl-zip')
22
+ const yauzl = require('yauzl')
22
23
 
23
24
  const STATIC_FILE_MATCHER_OPTS = {
24
25
  expandRange: (begin, end, step, opts) => bracesToGroup(opts ? `{${begin}..${end}..${step}}` : `{${begin}..${end}}`),
@@ -104,13 +105,10 @@ async function loadUi (playbook) {
104
105
  new Promise((resolve, reject) =>
105
106
  bundleFile.isDirectory()
106
107
  ? srcFs(ospath.join(bundleFile.path, bundle.startPath || '', '.')).then(resolve, reject)
107
- : vzip
108
- .src(bundleFile.path)
108
+ : srcZip(bundleFile.path, { startPath: bundle.startPath })
109
109
  .on('error', (err) => reject(Object.assign(err, { message: `not a valid zip file; ${err.message}` })))
110
- .pipe(selectFilesStartingFrom(bundle.startPath))
111
- .pipe(bufferizeContents())
110
+ .pipe(bufferizeContentsAndCollectFiles(resolve))
112
111
  .on('error', reject)
113
- .pipe(collectFiles(resolve))
114
112
  ).catch((err) => {
115
113
  const msg =
116
114
  `Failed to read UI ${bundleFile.isDirectory() ? 'directory' : 'bundle'}: ` +
@@ -179,8 +177,7 @@ function downloadBundle (url, to, agent) {
179
177
  const message = `Response code ${response.statusCode} (${response.statusMessage})`
180
178
  return reject(Object.assign(new Error(message), { name: 'HTTPError' }))
181
179
  }
182
- new ReadableFile(new MemoryFile({ path: ospath.basename(to), contents }))
183
- .pipe(vzip.src())
180
+ srcZip(contents, { testOnly: true })
184
181
  .on('error', (err) =>
185
182
  reject(Object.assign(err, { message: `not a valid zip file; ${err.message}`, summary: 'Invalid UI bundle' }))
186
183
  )
@@ -188,7 +185,7 @@ function downloadBundle (url, to, agent) {
188
185
  fsp
189
186
  .mkdir(ospath.dirname(to), { recursive: true })
190
187
  .then(() => fsp.writeFile(to, contents))
191
- .then(() => resolve(new File({ path: to, stat: { isDirectory: () => false } })))
188
+ .then(() => resolve(new File({ path: to, stat: { isDirectory: invariably.false } })))
192
189
  )
193
190
  })
194
191
  }).catch((err) => {
@@ -200,62 +197,6 @@ function downloadBundle (url, to, agent) {
200
197
  })
201
198
  }
202
199
 
203
- function selectFilesStartingFrom (startPath) {
204
- if (!startPath || (startPath = path.join('/', startPath + '/')) === '/') {
205
- return map((file, _, next) => {
206
- if (file.isNull()) {
207
- next()
208
- } else {
209
- next(
210
- null,
211
- new File({ path: posixify ? posixify(file.path) : file.path, contents: file.contents, stat: file.stat })
212
- )
213
- }
214
- })
215
- } else {
216
- startPath = startPath.substr(1)
217
- const startPathOffset = startPath.length
218
- return map((file, _, next) => {
219
- if (file.isNull()) {
220
- next()
221
- } else {
222
- const path_ = posixify ? posixify(file.path) : file.path
223
- if (path_.length > startPathOffset && path_.startsWith(startPath)) {
224
- next(null, new File({ path: path_.substr(startPathOffset), contents: file.contents, stat: file.stat }))
225
- } else {
226
- next()
227
- }
228
- }
229
- })
230
- }
231
- }
232
-
233
- function bufferizeContents () {
234
- return map((file, _, next) => {
235
- // NOTE gulp-vinyl-zip automatically converts the contents of an empty file to a Buffer
236
- if (file.isStream()) {
237
- const buffer = []
238
- pipeline(
239
- file.contents,
240
- forEach((chunk, _, done) => buffer.push(chunk) && done()),
241
- (err) => (err ? next(err) : next(null, Object.assign(file, { contents: Buffer.concat(buffer) })))
242
- )
243
- } else {
244
- next(null, file)
245
- }
246
- })
247
- }
248
-
249
- function collectFiles (resolve, files = new Map()) {
250
- return forEach(
251
- (file, _, done) => {
252
- files.set(file.path, file)
253
- done()
254
- },
255
- (done) => done() || resolve(files)
256
- )
257
- }
258
-
259
200
  function srcSupplementalFiles (filesSpec, startDir) {
260
201
  if (!filesSpec) return new Map()
261
202
  let cwd
@@ -352,17 +293,15 @@ function resolveOut (file, outputDir = '_') {
352
293
  }
353
294
 
354
295
  function srcFs (cwd) {
355
- return new Promise((resolve, reject, cache = Object.create(null), files = new Map(), relpathStart = cwd.length + 1) =>
296
+ return new Promise((resolve, reject, files = new Map()) =>
356
297
  pipeline(
357
- globStream(UI_SRC_GLOB, Object.assign({ cache, cwd }, UI_SRC_OPTS)),
358
- forEach(({ path: abspathPosix }, _, done) => {
359
- if ((cache[abspathPosix] || {}).constructor === Array) return done() // detects some directories
360
- const abspath = posixify ? ospath.normalize(abspathPosix) : abspathPosix
361
- const relpath = abspath.substr(relpathStart)
362
- symlinkAwareStat(abspath).then(
298
+ globStream(UI_SRC_GLOB, Object.assign({ cwd }, UI_SRC_OPTS)),
299
+ forEach(({ path: relpath, dirent }, _, done) => {
300
+ if (dirent.isDirectory()) return done()
301
+ const relpathPosix = relpath
302
+ const abspath = posixify ? ospath.join(cwd, (relpath = ospath.normalize(relpath))) : cwd + '/' + relpath
303
+ fsp.stat(abspath).then(
363
304
  (stat) => {
364
- if (stat.isDirectory()) return done() // detects directories that slipped through cache check
365
- const relpathPosix = posixify ? posixify(relpath) : relpath
366
305
  fsp.readFile(abspath).then(
367
306
  (contents) => {
368
307
  files.set(relpathPosix, new File({ cwd, path: relpathPosix, contents, stat, local: true }))
@@ -373,16 +312,18 @@ function srcFs (cwd) {
373
312
  }
374
313
  )
375
314
  },
376
- (statErr) => {
377
- done(
378
- Object.assign(statErr, {
379
- message: statErr.symlink
380
- ? (statErr.code === 'ELOOP' ? 'ELOOP: symbolic link cycle, ' : 'ENOENT: broken symbolic link, ') +
381
- `${relpath} -> ${statErr.symlink}`
382
- : statErr.message.replace(`'${abspath}'`, relpath),
383
- })
384
- )
385
- }
315
+ (statErr) =>
316
+ dirent.isSymbolicLink()
317
+ ? fsp
318
+ .readlink(abspath)
319
+ .then(
320
+ (symlink) =>
321
+ (statErr.code === 'ELOOP' ? 'ELOOP: symbolic link cycle, ' : 'ENOENT: broken symbolic link, ') +
322
+ `${relpath} -> ${symlink}`,
323
+ () => statErr.message.replace(`'${abspath}'`, relpath)
324
+ )
325
+ .then((message) => done(Object.assign(statErr, { message })))
326
+ : done(Object.assign(statErr, { message: statErr.message.replace(`'${abspath}'`, relpath) }))
386
327
  )
387
328
  }),
388
329
  (err) => (err ? reject(err) : resolve(files))
@@ -390,18 +331,36 @@ function srcFs (cwd) {
390
331
  )
391
332
  }
392
333
 
393
- function symlinkAwareStat (path_) {
394
- return fsp.lstat(path_).then((lstat) => {
395
- if (!lstat.isSymbolicLink()) return lstat
396
- return fsp.stat(path_).catch((statErr) =>
397
- fsp
398
- .readlink(path_)
399
- .catch(() => undefined)
400
- .then((symlink) => {
401
- throw Object.assign(statErr, { symlink })
402
- })
403
- )
334
+ function srcZip (file, options = {}) {
335
+ const result = options.testOnly // is it necessary to close streams in this case, or just sink()?
336
+ ? forEach((file_, _, done) => (file_.isStream() ? file_.contents.on('close', done).destroy() : done()))
337
+ : through()
338
+ yauzl[file instanceof Buffer ? 'fromBuffer' : 'open'](file, { lazyEntries: true }, (err, zipFile) => {
339
+ if (err) return result.emit('error', err)
340
+ new ZipReadable(zipFile, options).pipe(result)
404
341
  })
342
+ return result
343
+ }
344
+
345
+ function bufferizeContentsAndCollectFiles (resolve, files = new Map()) {
346
+ return forEach(
347
+ (file, _, done) => {
348
+ if (file.isStream()) {
349
+ const buffer = []
350
+ file.contents
351
+ .on('data', (chunk) => buffer.push(chunk))
352
+ .on('end', () => {
353
+ file.contents = buffer.length === 1 ? buffer[0] : Buffer.concat(buffer)
354
+ files.set(file.path, file)
355
+ done()
356
+ })
357
+ } else {
358
+ files.set(file.path, file)
359
+ done()
360
+ }
361
+ },
362
+ (done) => done() || resolve(files)
363
+ )
405
364
  }
406
365
 
407
366
  function transformError (err, msg) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@antora/ui-loader",
3
- "version": "3.1.7",
3
+ "version": "3.1.8",
4
4
  "description": "Downloads a UI bundle, if necessary, and loads the files into a UI catalog for use in an Antora documentation pipeline.",
5
5
  "license": "MPL-2.0",
6
6
  "author": "OpenDevise Inc. (https://opendevise.com)",
@@ -26,16 +26,16 @@
26
26
  },
27
27
  "dependencies": {
28
28
  "@antora/expand-path-helper": "~2.0",
29
- "@vscode/gulp-vinyl-zip": "~2.5",
30
29
  "braces": "~3.0",
31
30
  "cache-directory": "~2.0",
32
- "glob-stream": "~7.0",
31
+ "fast-glob": "~3.3",
33
32
  "hpagent": "~1.2",
34
33
  "js-yaml": "~4.1",
35
- "picomatch": "~2.3",
34
+ "picomatch": "~4.0",
36
35
  "should-proxy": "~1.0",
37
36
  "simple-get": "~4.0",
38
- "vinyl": "~2.2"
37
+ "vinyl": "~3.0",
38
+ "yauzl": "~3.1"
39
39
  },
40
40
  "engines": {
41
41
  "node": ">=16.0.0"