@antora/ui-loader 3.1.6 → 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 +6 -7
- package/lib/file.js +83 -10
- package/lib/load-ui.js +72 -110
- package/package.json +5 -5
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
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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 =
|
|
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
|
|
36
|
-
constructor (
|
|
37
|
-
super({ objectMode: true })
|
|
38
|
-
this.
|
|
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
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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,
|
|
120
|
+
module.exports = { File, MemoryFile, ZipReadable }
|
package/lib/load-ui.js
CHANGED
|
@@ -3,21 +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,
|
|
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
|
|
10
|
+
const { globStream } = require('fast-glob')
|
|
11
|
+
const { inspect } = require('util')
|
|
12
|
+
const invariably = { false: () => false, void: () => undefined }
|
|
11
13
|
const ospath = require('path')
|
|
12
14
|
const { posix: path } = ospath
|
|
13
15
|
const picomatch = require('picomatch')
|
|
14
16
|
const posixify = ospath.sep === '\\' ? (p) => p.replace(/\\/g, '/') : undefined
|
|
15
|
-
const { pipeline,
|
|
17
|
+
const { pipeline, PassThrough, Writable } = require('stream')
|
|
16
18
|
const forEach = (write, final) => new Writable({ objectMode: true, write, final })
|
|
17
|
-
const
|
|
19
|
+
const through = () => new PassThrough({ objectMode: true })
|
|
18
20
|
const UiCatalog = require('./ui-catalog')
|
|
19
21
|
const yaml = require('js-yaml')
|
|
20
|
-
const
|
|
22
|
+
const yauzl = require('yauzl')
|
|
21
23
|
|
|
22
24
|
const STATIC_FILE_MATCHER_OPTS = {
|
|
23
25
|
expandRange: (begin, end, step, opts) => bracesToGroup(opts ? `{${begin}..${end}..${step}}` : `{${begin}..${end}}`),
|
|
@@ -103,20 +105,15 @@ async function loadUi (playbook) {
|
|
|
103
105
|
new Promise((resolve, reject) =>
|
|
104
106
|
bundleFile.isDirectory()
|
|
105
107
|
? srcFs(ospath.join(bundleFile.path, bundle.startPath || '', '.')).then(resolve, reject)
|
|
106
|
-
:
|
|
107
|
-
.src(bundleFile.path)
|
|
108
|
+
: srcZip(bundleFile.path, { startPath: bundle.startPath })
|
|
108
109
|
.on('error', (err) => reject(Object.assign(err, { message: `not a valid zip file; ${err.message}` })))
|
|
109
|
-
.pipe(
|
|
110
|
-
.pipe(bufferizeContents())
|
|
110
|
+
.pipe(bufferizeContentsAndCollectFiles(resolve))
|
|
111
111
|
.on('error', reject)
|
|
112
|
-
.pipe(collectFiles(resolve))
|
|
113
112
|
).catch((err) => {
|
|
114
|
-
const
|
|
113
|
+
const msg =
|
|
115
114
|
`Failed to read UI ${bundleFile.isDirectory() ? 'directory' : 'bundle'}: ` +
|
|
116
|
-
|
|
117
|
-
)
|
|
118
|
-
errWrapper.stack += `\nCaused by: ${err.stack || 'unknown'}`
|
|
119
|
-
throw errWrapper
|
|
115
|
+
(bundleUrl === bundleFile.path ? bundleUrl : `${bundleFile.path} (resolved from url: ${bundleUrl})`)
|
|
116
|
+
throw transformError(err, msg)
|
|
120
117
|
})
|
|
121
118
|
),
|
|
122
119
|
srcSupplementalFiles(supplementalFilesSpec, startDir),
|
|
@@ -180,8 +177,7 @@ function downloadBundle (url, to, agent) {
|
|
|
180
177
|
const message = `Response code ${response.statusCode} (${response.statusMessage})`
|
|
181
178
|
return reject(Object.assign(new Error(message), { name: 'HTTPError' }))
|
|
182
179
|
}
|
|
183
|
-
|
|
184
|
-
.pipe(vzip.src())
|
|
180
|
+
srcZip(contents, { testOnly: true })
|
|
185
181
|
.on('error', (err) =>
|
|
186
182
|
reject(Object.assign(err, { message: `not a valid zip file; ${err.message}`, summary: 'Invalid UI bundle' }))
|
|
187
183
|
)
|
|
@@ -189,74 +185,18 @@ function downloadBundle (url, to, agent) {
|
|
|
189
185
|
fsp
|
|
190
186
|
.mkdir(ospath.dirname(to), { recursive: true })
|
|
191
187
|
.then(() => fsp.writeFile(to, contents))
|
|
192
|
-
.then(() => resolve(new File({ path: to, stat: { isDirectory:
|
|
188
|
+
.then(() => resolve(new File({ path: to, stat: { isDirectory: invariably.false } })))
|
|
193
189
|
)
|
|
194
190
|
})
|
|
195
191
|
}).catch((err) => {
|
|
196
|
-
const errWrapper =
|
|
192
|
+
const errWrapper = transformError(err, `${err.summary || 'Failed to download UI bundle'}: ${url}`)
|
|
197
193
|
if (err.code === 'ECONNRESET' || (err.message || '').toLowerCase() === 'request timed out') {
|
|
198
|
-
errWrapper
|
|
194
|
+
Object.defineProperty(errWrapper, 'recoverable', { value: true })
|
|
199
195
|
}
|
|
200
|
-
throw
|
|
196
|
+
throw errWrapper
|
|
201
197
|
})
|
|
202
198
|
}
|
|
203
199
|
|
|
204
|
-
function selectFilesStartingFrom (startPath) {
|
|
205
|
-
if (!startPath || (startPath = path.join('/', startPath + '/')) === '/') {
|
|
206
|
-
return map((file, _, next) => {
|
|
207
|
-
if (file.isNull()) {
|
|
208
|
-
next()
|
|
209
|
-
} else {
|
|
210
|
-
next(
|
|
211
|
-
null,
|
|
212
|
-
new File({ path: posixify ? posixify(file.path) : file.path, contents: file.contents, stat: file.stat })
|
|
213
|
-
)
|
|
214
|
-
}
|
|
215
|
-
})
|
|
216
|
-
} else {
|
|
217
|
-
startPath = startPath.substr(1)
|
|
218
|
-
const startPathOffset = startPath.length
|
|
219
|
-
return map((file, _, next) => {
|
|
220
|
-
if (file.isNull()) {
|
|
221
|
-
next()
|
|
222
|
-
} else {
|
|
223
|
-
const path_ = posixify ? posixify(file.path) : file.path
|
|
224
|
-
if (path_.length > startPathOffset && path_.startsWith(startPath)) {
|
|
225
|
-
next(null, new File({ path: path_.substr(startPathOffset), contents: file.contents, stat: file.stat }))
|
|
226
|
-
} else {
|
|
227
|
-
next()
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
})
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
function bufferizeContents () {
|
|
235
|
-
return map((file, _, next) => {
|
|
236
|
-
// NOTE gulp-vinyl-zip automatically converts the contents of an empty file to a Buffer
|
|
237
|
-
if (file.isStream()) {
|
|
238
|
-
const buffer = []
|
|
239
|
-
pipeline(
|
|
240
|
-
file.contents,
|
|
241
|
-
forEach((chunk, _, done) => buffer.push(chunk) && done()),
|
|
242
|
-
(err) => (err ? next(err) : next(null, Object.assign(file, { contents: Buffer.concat(buffer) })))
|
|
243
|
-
)
|
|
244
|
-
} else {
|
|
245
|
-
next(null, file)
|
|
246
|
-
}
|
|
247
|
-
})
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
function collectFiles (resolve, files = new Map()) {
|
|
251
|
-
return forEach(
|
|
252
|
-
(file, _, done) => {
|
|
253
|
-
files.set(file.path, file)
|
|
254
|
-
done()
|
|
255
|
-
},
|
|
256
|
-
(done) => done() || resolve(files)
|
|
257
|
-
)
|
|
258
|
-
}
|
|
259
|
-
|
|
260
200
|
function srcSupplementalFiles (filesSpec, startDir) {
|
|
261
201
|
if (!filesSpec) return new Map()
|
|
262
202
|
let cwd
|
|
@@ -291,9 +231,7 @@ function srcSupplementalFiles (filesSpec, startDir) {
|
|
|
291
231
|
if (err.code === 'ENOENT' && err.path === cwd) {
|
|
292
232
|
throw new Error(`Specified ui.supplemental_files directory does not exist: ${dir}`)
|
|
293
233
|
} else {
|
|
294
|
-
|
|
295
|
-
errWrapper.stack += `\nCaused by: ${err.stack || 'unknown'}`
|
|
296
|
-
throw errWrapper
|
|
234
|
+
throw transformError(err, `Failed to read ui.supplemental_files ${cwd ? `directory: ${dir}` : 'entry'}`)
|
|
297
235
|
}
|
|
298
236
|
})
|
|
299
237
|
}
|
|
@@ -355,17 +293,15 @@ function resolveOut (file, outputDir = '_') {
|
|
|
355
293
|
}
|
|
356
294
|
|
|
357
295
|
function srcFs (cwd) {
|
|
358
|
-
return new Promise((resolve, reject,
|
|
296
|
+
return new Promise((resolve, reject, files = new Map()) =>
|
|
359
297
|
pipeline(
|
|
360
|
-
globStream(UI_SRC_GLOB, Object.assign({
|
|
361
|
-
forEach(({ path:
|
|
362
|
-
if ((
|
|
363
|
-
const
|
|
364
|
-
const relpath =
|
|
365
|
-
|
|
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(
|
|
366
304
|
(stat) => {
|
|
367
|
-
if (stat.isDirectory()) return done() // detects directories that slipped through cache check
|
|
368
|
-
const relpathPosix = posixify ? posixify(relpath) : relpath
|
|
369
305
|
fsp.readFile(abspath).then(
|
|
370
306
|
(contents) => {
|
|
371
307
|
files.set(relpathPosix, new File({ cwd, path: relpathPosix, contents, stat, local: true }))
|
|
@@ -376,16 +312,18 @@ function srcFs (cwd) {
|
|
|
376
312
|
}
|
|
377
313
|
)
|
|
378
314
|
},
|
|
379
|
-
(statErr) =>
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
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) }))
|
|
389
327
|
)
|
|
390
328
|
}),
|
|
391
329
|
(err) => (err ? reject(err) : resolve(files))
|
|
@@ -393,18 +331,42 @@ function srcFs (cwd) {
|
|
|
393
331
|
)
|
|
394
332
|
}
|
|
395
333
|
|
|
396
|
-
function
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
.then((symlink) => {
|
|
404
|
-
throw Object.assign(statErr, { symlink })
|
|
405
|
-
})
|
|
406
|
-
)
|
|
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)
|
|
407
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
|
+
)
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
function transformError (err, msg) {
|
|
367
|
+
const errWrapper = new Error(msg)
|
|
368
|
+
errWrapper.stack += `\nCaused by: ${err.stack ? inspect(err).replace(/^Error \[(.+)\](?=: )/, '$1') : err}`
|
|
369
|
+
return errWrapper
|
|
408
370
|
}
|
|
409
371
|
|
|
410
372
|
module.exports = loadUi
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@antora/ui-loader",
|
|
3
|
-
"version": "3.1.
|
|
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
|
|
31
|
+
"fast-glob": "~3.3",
|
|
33
32
|
"hpagent": "~1.2",
|
|
34
33
|
"js-yaml": "~4.1",
|
|
35
|
-
"picomatch": "~
|
|
34
|
+
"picomatch": "~4.0",
|
|
36
35
|
"should-proxy": "~1.0",
|
|
37
36
|
"simple-get": "~4.0",
|
|
38
|
-
"vinyl": "~
|
|
37
|
+
"vinyl": "~3.0",
|
|
38
|
+
"yauzl": "~3.1"
|
|
39
39
|
},
|
|
40
40
|
"engines": {
|
|
41
41
|
"node": ">=16.0.0"
|