@antora/ui-loader 3.0.0-alpha.8 → 3.0.0-beta.3

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/README.md CHANGED
@@ -4,7 +4,7 @@ The UI Loader is a component in Antora responsible for downloading and caching t
4
4
  The UI files are combined with the content files at the end of the Antora documentation pipeline to produce a complete site.
5
5
 
6
6
  [Antora](https://antora.org) is a modular static site generator designed for creating documentation sites from AsciiDoc documents.
7
- Its site generator pipeline aggregates documents from versioned content repositories and processes them using [Asciidoctor](https://asciidoctor.org).
7
+ Its site generator aggregates documents from versioned content repositories and processes them using [Asciidoctor](https://asciidoctor.org).
8
8
 
9
9
  ## Copyright and License
10
10
 
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/load-ui.js CHANGED
@@ -1,5 +1,6 @@
1
1
  'use strict'
2
2
 
3
+ const { compile: bracesToGroup } = require('braces')
3
4
  const camelCaseKeys = require('camelcase-keys')
4
5
  const concat = require('simple-concat')
5
6
  const { createHash } = require('crypto')
@@ -8,17 +9,25 @@ const { File, MemoryFile, ReadableFile } = require('./file')
8
9
  const { promises: fsp } = require('fs')
9
10
  const { concat: get } = require('simple-get')
10
11
  const getCacheDir = require('cache-directory')
11
- const minimatchAll = require('minimatch-all')
12
+ const globStream = require('glob-stream')
12
13
  const ospath = require('path')
13
14
  const { posix: path } = ospath
15
+ const picomatch = require('picomatch')
14
16
  const posixify = ospath.sep === '\\' ? (p) => p.replace(/\\/g, '/') : undefined
15
- const { Transform } = require('stream')
17
+ const { pipeline, Transform } = require('stream')
16
18
  const map = (transform, flush = undefined) => new Transform({ objectMode: true, transform, flush })
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}$/
@@ -48,7 +57,7 @@ const EXT_RX = /\.[a-z]{2,3}$/
48
57
  * @param {String} [playbook.network.noProxy=undefined] - The list of domains and IPs that should not be proxied.
49
58
  * @param {Object} playbook.ui - The UI configuration object for Antora.
50
59
  * @param {String} playbook.ui.bundle - The UI bundle configuration.
51
- * @param {String} playbook.ui.bundle.url - The path (relative or absolute) or URI
60
+ * @param {String} playbook.ui.bundle.url - The path (relative or absolute) or URL
52
61
  * of the UI bundle to use.
53
62
  * @param {String} [playbook.ui.bundle.startPath=''] - The path inside the bundle from
54
63
  * which to start reading files.
@@ -73,32 +82,28 @@ async function loadUi (playbook) {
73
82
  const cachePath = ospath.join(absCacheDir, `${sha1(bundleUrl)}.zip`)
74
83
  return fetch && bundle.snapshot
75
84
  ? downloadBundle(bundleUrl, cachePath, createAgent(bundleUrl, playbook.network || {}))
76
- : fsp
77
- .stat(cachePath)
78
- .then((stat) => new File({ path: cachePath, stat }))
79
- .catch(() => downloadBundle(bundleUrl, cachePath, createAgent(bundleUrl, playbook.network || {})))
85
+ : fsp.stat(cachePath).then(
86
+ (stat) => new File({ path: cachePath, stat }),
87
+ () => downloadBundle(bundleUrl, cachePath, createAgent(bundleUrl, playbook.network || {}))
88
+ )
80
89
  })
81
90
  } else {
82
91
  const localPath = expandPath(bundleUrl, { dot: startDir })
83
- resolveBundle = fsp
84
- .stat(localPath)
85
- .then((stat) => new File({ path: localPath, stat }))
86
- .catch(() => {
92
+ resolveBundle = fsp.stat(localPath).then(
93
+ (stat) => new File({ path: localPath, stat }),
94
+ () => {
87
95
  throw new Error(
88
96
  `Specified UI ${path.extname(localPath) ? 'bundle' : 'directory'} does not exist: ` +
89
97
  (bundleUrl === localPath ? bundleUrl : `${localPath} (resolved from url: ${bundleUrl})`)
90
98
  )
91
- })
99
+ }
100
+ )
92
101
  }
93
102
  const files = await Promise.all([
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(bundleFile.path).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}` })))
@@ -149,16 +154,16 @@ function ensureCacheDir (customCacheDir, startDir) {
149
154
  ? getCacheDir('antora' + (process.env.NODE_ENV === 'test' ? '-test' : '')) || ospath.resolve('.antora/cache')
150
155
  : expandPath(customCacheDir, { dot: startDir })
151
156
  const cacheDir = ospath.join(baseCacheDir, UI_CACHE_FOLDER)
152
- return fsp
153
- .mkdir(cacheDir, { recursive: true })
154
- .then(() => cacheDir)
155
- .catch((err) => {
157
+ return fsp.mkdir(cacheDir, { recursive: true }).then(
158
+ () => cacheDir,
159
+ (err) => {
156
160
  throw Object.assign(err, { message: `Failed to create UI cache directory: ${cacheDir}; ${err.message}` })
157
- })
161
+ }
162
+ )
158
163
  }
159
164
 
160
165
  function createAgent (url, { httpProxy, httpsProxy, noProxy }) {
161
- if (httpsProxy || httpProxy) {
166
+ if ((httpsProxy || httpProxy) && noProxy !== '*') {
162
167
  const { HttpProxyAgent, HttpsProxyAgent } = require('hpagent')
163
168
  const shouldProxy = require('should-proxy')
164
169
  const proxy = url.startsWith('https:')
@@ -280,16 +285,7 @@ function srcSupplementalFiles (filesSpec, startDir) {
280
285
  const cwd = expandPath(filesSpec, { dot: startDir })
281
286
  return fsp
282
287
  .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
- )
288
+ .then(() => srcFs(cwd))
293
289
  .catch((err) => {
294
290
  // Q: should we skip unreadable files?
295
291
  throw Object.assign(err, { message: `problem encountered while reading ui.supplemental_files: ${err.message}` })
@@ -297,17 +293,6 @@ function srcSupplementalFiles (filesSpec, startDir) {
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,52 @@ function resolveOut (file, outputDir = '_') {
364
339
  return { dirname, basename, path: path.join(dirname, basename) }
365
340
  }
366
341
 
342
+ function srcFs (cwd) {
343
+ return new Promise((resolve, reject, cache = {}, files = new Map()) =>
344
+ pipeline(
345
+ globStream(UI_SRC_GLOB, Object.assign({ cache, cwd }, UI_SRC_OPTS)),
346
+ map(({ path: abspathPosix }, _, next) => {
347
+ const abspath = posixify ? ospath.normalize(abspathPosix) : abspathPosix
348
+ const relpath = abspath.substr(cwd.length + 1)
349
+ symlinkAwareStat(abspath).then(
350
+ (stat) => {
351
+ if (stat.isDirectory()) return next()
352
+ fsp.readFile(abspath).then(
353
+ (contents) => {
354
+ const path_ = posixify ? posixify(relpath) : relpath
355
+ files.set(path_, new File({ cwd, path: path_, contents, stat, local: true }))
356
+ next()
357
+ },
358
+ (readErr) => {
359
+ next(Object.assign(readErr, { message: readErr.message.replace(`'${abspath}'`, relpath) }))
360
+ }
361
+ )
362
+ },
363
+ (statErr) => {
364
+ if (statErr.symlink) {
365
+ statErr.message =
366
+ statErr.code === 'ELOOP'
367
+ ? `Symbolic link cycle detected at ${relpath}`
368
+ : `Broken symbolic link detected at ${relpath}`
369
+ } else {
370
+ statErr.message = statErr.message.replace(`'${abspath}'`, relpath)
371
+ }
372
+ next(statErr)
373
+ }
374
+ )
375
+ }),
376
+ (err) => (err ? reject(err) : resolve(files))
377
+ )
378
+ )
379
+ }
380
+
381
+ function symlinkAwareStat (path_) {
382
+ return fsp.lstat(path_).then((lstat) => {
383
+ if (!lstat.isSymbolicLink()) return lstat
384
+ return fsp.stat(path_).catch((statErr) => {
385
+ throw Object.assign(statErr, { symlink: true })
386
+ })
387
+ })
388
+ }
389
+
367
390
  module.exports = loadUi
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@antora/ui-loader",
3
- "version": "3.0.0-alpha.8",
3
+ "version": "3.0.0-beta.3",
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,20 +18,20 @@
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
- "camelcase-keys": "~6.2",
23
+ "camelcase-keys": "~7.0",
23
24
  "gulp-vinyl-zip": "~2.5",
24
25
  "hpagent": "~0.1.0",
25
26
  "js-yaml": "~4.1",
26
- "minimatch-all": "~1.1",
27
+ "picomatch": "~2.3",
27
28
  "should-proxy": "~1.0",
28
29
  "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
- "node": ">=10.17.0"
34
+ "node": ">=12.21.0"
35
35
  },
36
36
  "files": [
37
37
  "lib/"
@@ -45,5 +45,5 @@
45
45
  "static site",
46
46
  "web publishing"
47
47
  ],
48
- "gitHead": "2e5695bea11fb5719989c329c97e66d36e29659f"
48
+ "gitHead": "45da95a2e2dea538379d2d9f42013d2208fb86c3"
49
49
  }