@antora/ui-loader 3.0.0-beta.1 → 3.0.0-beta.5

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
@@ -11,7 +11,7 @@ module.exports = Object.freeze({
11
11
  nomount: true,
12
12
  nosort: true,
13
13
  nounique: true,
14
- removeBOM: false,
14
+ strict: false,
15
15
  uniqueBy: (m) => m,
16
16
  },
17
17
  })
package/lib/file.js CHANGED
@@ -40,7 +40,7 @@ class ReadableFile extends Readable {
40
40
 
41
41
  _read () {
42
42
  this.push(this._file)
43
- this._file = null
43
+ this.push((this._file = null))
44
44
  }
45
45
  }
46
46
 
package/lib/load-ui.js CHANGED
@@ -1,24 +1,33 @@
1
1
  'use strict'
2
2
 
3
+ const { compile: bracesToGroup } = require('braces')
3
4
  const camelCaseKeys = require('camelcase-keys')
4
- const concat = require('simple-concat')
5
5
  const { createHash } = require('crypto')
6
6
  const expandPath = require('@antora/expand-path-helper')
7
7
  const { File, MemoryFile, ReadableFile } = require('./file')
8
8
  const { promises: fsp } = require('fs')
9
9
  const { concat: get } = require('simple-get')
10
10
  const getCacheDir = require('cache-directory')
11
- const minimatchAll = require('minimatch-all')
11
+ const globStream = require('glob-stream')
12
12
  const ospath = require('path')
13
13
  const { posix: path } = ospath
14
+ const picomatch = require('picomatch')
14
15
  const posixify = ospath.sep === '\\' ? (p) => p.replace(/\\/g, '/') : undefined
15
- const { Transform } = require('stream')
16
- const map = (transform, flush = undefined) => new Transform({ objectMode: true, transform, flush })
16
+ const { pipeline, Transform, Writable } = require('stream')
17
+ const forEach = (write, final) => new Writable({ objectMode: true, write, final })
18
+ const map = (transform) => new Transform({ objectMode: true, transform })
17
19
  const UiCatalog = require('./ui-catalog')
18
20
  const yaml = require('js-yaml')
19
- const vfs = require('vinyl-fs')
20
21
  const vzip = require('gulp-vinyl-zip')
21
22
 
23
+ const STATIC_FILE_MATCHER_OPTS = {
24
+ expandRange: (begin, end, step, opts) => bracesToGroup(opts ? `{${begin}..${end}..${step}}` : `{${begin}..${end}}`),
25
+ fastpaths: false,
26
+ nobracket: true,
27
+ noquantifiers: true,
28
+ regex: false,
29
+ strictSlashes: true,
30
+ }
22
31
  const { UI_CACHE_FOLDER, UI_DESC_FILENAME, UI_SRC_GLOB, UI_SRC_OPTS } = require('./constants')
23
32
  const URI_SCHEME_RX = /^https?:\/\//
24
33
  const EXT_RX = /\.[a-z]{2,3}$/
@@ -31,7 +40,7 @@ const EXT_RX = /\.[a-z]{2,3}$/
31
40
  * the playbook. If the path is a URI, it downloads the file and caches it at a
32
41
  * unique path to avoid this step in future calls. It then reads all the files
33
42
  * from the bundle into memory, skipping any files that fall outside of the
34
- * start path specified in the ui.startPath property of the playbook. Finally,
43
+ * start path specified in the ui.startPath property of the playbook. Finally,
35
44
  * it classifies the files and adds them to a UiCatalog, which is then
36
45
  * returned.
37
46
  *
@@ -94,11 +103,7 @@ async function loadUi (playbook) {
94
103
  resolveBundle.then((bundleFile) =>
95
104
  new Promise((resolve, reject) =>
96
105
  bundleFile.isDirectory()
97
- ? vfs
98
- .src(UI_SRC_GLOB, Object.assign({ cwd: bundleFile.path }, UI_SRC_OPTS))
99
- .on('error', reject)
100
- .pipe(relativizeFiles())
101
- .pipe(collectFiles(resolve))
106
+ ? srcFs(ospath.join(bundleFile.path, bundle.startPath || '', '.')).then(resolve, reject)
102
107
  : vzip
103
108
  .src(bundleFile.path)
104
109
  .on('error', (err) => reject(Object.assign(err, { message: `not a valid zip file; ${err.message}` })))
@@ -181,12 +186,12 @@ function downloadBundle (url, to, agent) {
181
186
  .on('error', (err) =>
182
187
  reject(Object.assign(err, { message: `not a valid zip file; ${err.message}`, summary: 'Invalid UI bundle' }))
183
188
  )
184
- .on('finish', function () {
189
+ .on('finish', () =>
185
190
  fsp
186
191
  .mkdir(ospath.dirname(to), { recursive: true })
187
192
  .then(() => fsp.writeFile(to, contents))
188
193
  .then(() => resolve(new File({ path: to, stat: { isDirectory: () => false } })))
189
- })
194
+ )
190
195
  })
191
196
  }).catch((err) => {
192
197
  const wrapped = new Error(`${err.summary || 'Failed to download UI bundle'}: ${url}`)
@@ -229,25 +234,25 @@ function bufferizeContents () {
229
234
  return map((file, _, next) => {
230
235
  // NOTE gulp-vinyl-zip automatically converts the contents of an empty file to a Buffer
231
236
  if (file.isStream()) {
232
- concat(file.contents, (err, contents) => {
233
- if (err) return next(err)
234
- file.contents = contents
235
- next(null, file)
236
- })
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
+ )
237
243
  } else {
238
244
  next(null, file)
239
245
  }
240
246
  })
241
247
  }
242
248
 
243
- function collectFiles (done) {
244
- const files = new Map()
245
- return map(
246
- (file, _, next) => {
249
+ function collectFiles (resolve, files = new Map()) {
250
+ return forEach(
251
+ (file, _, done) => {
247
252
  files.set(file.path, file)
248
- next()
253
+ done()
249
254
  },
250
- () => done(files)
255
+ (done) => done() || resolve(files)
251
256
  )
252
257
  }
253
258
 
@@ -278,36 +283,16 @@ function srcSupplementalFiles (filesSpec, startDir) {
278
283
  ).then((files) => files.reduce((accum, file) => accum.set(file.path, file) && accum, new Map()))
279
284
  } else {
280
285
  const cwd = expandPath(filesSpec, { dot: startDir })
281
- return fsp
282
- .access(cwd)
283
- .then(
284
- () =>
285
- new Promise((resolve, reject) =>
286
- vfs
287
- .src(UI_SRC_GLOB, Object.assign({ cwd, dot: true }, UI_SRC_OPTS))
288
- .on('error', reject)
289
- .pipe(relativizeFiles())
290
- .pipe(collectFiles(resolve))
291
- )
292
- )
293
- .catch((err) => {
286
+ return fsp.access(cwd).then(
287
+ () => srcFs(cwd),
288
+ (err) => {
294
289
  // Q: should we skip unreadable files?
295
290
  throw Object.assign(err, { message: `problem encountered while reading ui.supplemental_files: ${err.message}` })
296
- })
291
+ }
292
+ )
297
293
  }
298
294
  }
299
295
 
300
- function relativizeFiles () {
301
- return map((file, _, next) => {
302
- if (file.isNull()) {
303
- next()
304
- } else {
305
- const path_ = posixify ? posixify(file.relative) : file.relative
306
- next(null, new File({ cwd: file.cwd, path: path_, contents: file.contents, stat: file.stat, local: true }))
307
- }
308
- })
309
- }
310
-
311
296
  function mergeFiles (files, supplementalFiles) {
312
297
  if (supplementalFiles.size) supplementalFiles.forEach((file) => files.set(file.path, file))
313
298
  return files
@@ -318,15 +303,9 @@ function loadConfig (files, outputDir) {
318
303
  if (configFile) {
319
304
  files.delete(UI_DESC_FILENAME)
320
305
  const config = camelCaseKeys(yaml.load(configFile.contents.toString()), { deep: true })
321
- if (outputDir !== undefined) config.outputDir = outputDir
322
306
  const staticFiles = config.staticFiles
323
- if (staticFiles) {
324
- if (!Array.isArray(staticFiles)) {
325
- config.staticFiles = [staticFiles]
326
- } else if (staticFiles.length === 0) {
327
- delete config.staticFiles
328
- }
329
- }
307
+ if (staticFiles && staticFiles.length) config.isStaticFile = picomatch(staticFiles, STATIC_FILE_MATCHER_OPTS)
308
+ if (outputDir !== undefined) config.outputDir = outputDir
330
309
  return config
331
310
  } else {
332
311
  return { outputDir }
@@ -334,7 +313,7 @@ function loadConfig (files, outputDir) {
334
313
  }
335
314
 
336
315
  function classifyFile (file, config) {
337
- if (config.staticFiles && isStaticFile(file, config.staticFiles)) {
316
+ if (config.isStaticFile && config.isStaticFile(file.path)) {
338
317
  file.type = 'static'
339
318
  file.out = resolveOut(file, '')
340
319
  } else if (file.isDot()) {
@@ -345,10 +324,6 @@ function classifyFile (file, config) {
345
324
  return file
346
325
  }
347
326
 
348
- function isStaticFile (file, staticFiles) {
349
- return minimatchAll(file.path, staticFiles)
350
- }
351
-
352
327
  function resolveType (file) {
353
328
  const firstPathSegment = file.path.split('/', 1)[0]
354
329
  if (firstPathSegment === 'layouts') return 'layout'
@@ -364,4 +339,54 @@ function resolveOut (file, outputDir = '_') {
364
339
  return { dirname, basename, path: path.join(dirname, basename) }
365
340
  }
366
341
 
342
+ function srcFs (cwd) {
343
+ const relpathStart = cwd.length + 1
344
+ return new Promise((resolve, reject, cache = Object.create(null), files = new Map()) =>
345
+ pipeline(
346
+ globStream(UI_SRC_GLOB, Object.assign({ cache, cwd }, UI_SRC_OPTS)),
347
+ forEach(({ path: abspathPosix }, _, done) => {
348
+ if (Array.isArray(cache[abspathPosix])) return done() // detects some directories, but not all
349
+ const abspath = posixify ? ospath.normalize(abspathPosix) : abspathPosix
350
+ const relpath = abspath.substr(relpathStart)
351
+ symlinkAwareStat(abspath).then(
352
+ (stat) => {
353
+ if (stat.isDirectory()) return done() // detects remaining directories
354
+ fsp.readFile(abspath).then(
355
+ (contents) => {
356
+ const path_ = posixify ? posixify(relpath) : relpath
357
+ files.set(path_, new File({ cwd, path: path_, contents, stat, local: true }))
358
+ done()
359
+ },
360
+ (readErr) => {
361
+ done(Object.assign(readErr, { message: readErr.message.replace(`'${abspath}'`, relpath) }))
362
+ }
363
+ )
364
+ },
365
+ (statErr) => {
366
+ if (statErr.symlink) {
367
+ statErr.message =
368
+ statErr.code === 'ELOOP'
369
+ ? `Symbolic link cycle detected at ${relpath}`
370
+ : `Broken symbolic link detected at ${relpath}`
371
+ } else {
372
+ statErr.message = statErr.message.replace(`'${abspath}'`, relpath)
373
+ }
374
+ done(statErr)
375
+ }
376
+ )
377
+ }),
378
+ (err) => (err ? reject(err) : resolve(files))
379
+ )
380
+ )
381
+ }
382
+
383
+ function symlinkAwareStat (path_) {
384
+ return fsp.lstat(path_).then((lstat) => {
385
+ if (!lstat.isSymbolicLink()) return lstat
386
+ return fsp.stat(path_).catch((statErr) => {
387
+ throw Object.assign(statErr, { symlink: true })
388
+ })
389
+ })
390
+ }
391
+
367
392
  module.exports = loadUi
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@antora/ui-loader",
3
- "version": "3.0.0-beta.1",
3
+ "version": "3.0.0-beta.5",
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)",
@@ -18,17 +18,17 @@
18
18
  "main": "lib/index.js",
19
19
  "dependencies": {
20
20
  "@antora/expand-path-helper": "~2.0",
21
+ "braces": "~3.0",
21
22
  "cache-directory": "~2.0",
22
23
  "camelcase-keys": "~7.0",
24
+ "glob-stream": "~7.0",
23
25
  "gulp-vinyl-zip": "~2.5",
24
26
  "hpagent": "~0.1.0",
25
27
  "js-yaml": "~4.1",
26
- "minimatch-all": "~1.1",
28
+ "picomatch": "~2.3",
27
29
  "should-proxy": "~1.0",
28
- "simple-concat": "~1.0",
29
30
  "simple-get": "~4.0",
30
- "vinyl": "~2.2",
31
- "vinyl-fs": "~3.0"
31
+ "vinyl": "~2.2"
32
32
  },
33
33
  "engines": {
34
34
  "node": ">=12.21.0"
@@ -45,5 +45,5 @@
45
45
  "static site",
46
46
  "web publishing"
47
47
  ],
48
- "gitHead": "7c5ef1ea93dd489af533c80a936c736013c41769"
48
+ "gitHead": "a13d03df41654d4deb78211a5a54953ce2a35fb8"
49
49
  }