@antora/content-aggregator 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.
@@ -1,7 +1,5 @@
1
1
  'use strict'
2
2
 
3
- if (!Promise.allSettled) require('./promise-all-settled-polyfill')
4
-
5
3
  const camelCaseKeys = require('camelcase-keys')
6
4
  const { createHash } = require('crypto')
7
5
  const createHttpPlugin = require('./git-plugin-http')
@@ -17,17 +15,17 @@ const getCacheDir = require('cache-directory')
17
15
  const GitCredentialManagerStore = require('./git-credential-manager-store')
18
16
  const git = require('./git')
19
17
  const { NotFoundError, ObjectTypeError, UnknownTransportError, UrlParseError } = git.Errors
20
- const invariably = { true: () => true, false: () => false, void: () => undefined, emptyArray: () => [] }
21
- const { makeRe: makePicomatchRx } = require('picomatch')
22
- const MultiProgress = require('multi-progress')
18
+ const globStream = require('glob-stream')
19
+ const invariably = require('./invariably')
20
+ const { makeMatcherRx, versionMatcherOpts: VERSION_MATCHER_OPTS } = require('./matcher')
21
+ const MultiProgress = require('multi-progress') // calls require('progress') as a peer dependencies
23
22
  const ospath = require('path')
24
23
  const { posix: path } = ospath
25
24
  const posixify = ospath.sep === '\\' ? (p) => p.replace(/\\/g, '/') : undefined
26
25
  const { fs: resolvePathGlobsFs, git: resolvePathGlobsGit } = require('./resolve-path-globs')
27
- const { Transform } = require('stream')
28
- const map = (transform, flush = undefined) => new Transform({ objectMode: true, transform, flush })
26
+ const { pipeline, Writable } = require('stream')
27
+ const forEach = (write) => new Writable({ objectMode: true, write })
29
28
  const userRequire = require('@antora/user-require-helper')
30
- const vfs = require('vinyl-fs')
31
29
  const yaml = require('js-yaml')
32
30
 
33
31
  const {
@@ -39,12 +37,10 @@ const {
39
37
  GIT_CORE,
40
38
  GIT_OPERATION_LABEL_LENGTH,
41
39
  GIT_PROGRESS_PHASES,
42
- PICOMATCH_VERSION_OPTS,
43
40
  REF_PATTERN_CACHE_KEY,
44
41
  SYMLINK_FILE_MODE,
45
42
  VALID_STATE_FILENAME,
46
43
  } = require('./constants')
47
-
48
44
  const ANY_SEPARATOR_RX = /[:/]/
49
45
  const CSV_RX = /\s*,\s*/
50
46
  const VENTILATED_CSV_RX = /\s*,\s+/
@@ -124,14 +120,14 @@ async function collectFiles (sourcesByUrl, loadOpts, concurrency) {
124
120
  })
125
121
  ),
126
122
  ])
127
- let rejected, started
123
+ let rejection, started
128
124
  const startedContinuations = []
129
125
  const recordRejection = (err) => {
130
- throw (rejected = true) && err
126
+ rejection = err
131
127
  }
132
128
  const runTask = (primary, continuation, idx) =>
133
129
  primary().then((value) => {
134
- if (!rejected) startedContinuations[idx] = continuation(value).catch(recordRejection)
130
+ if (!rejection) startedContinuations[idx] = continuation(value).catch(recordRejection)
135
131
  }, recordRejection)
136
132
  if (tasks.length > concurrency) {
137
133
  started = []
@@ -142,17 +138,16 @@ async function collectFiles (sourcesByUrl, loadOpts, concurrency) {
142
138
  )
143
139
  started.push(current)
144
140
  if (pending.push(current) < concurrency) continue
145
- if (await Promise.race(pending).then(invariably.true, invariably.false)) continue
146
- break
141
+ await Promise.race(pending)
142
+ if (rejection) break
147
143
  }
148
144
  } else {
149
145
  started = tasks.map(([primary, continuation], idx) => runTask(primary, continuation, idx))
150
146
  }
151
- return Promise.allSettled(started).then((outcomes) =>
152
- Promise.allSettled(startedContinuations).then((continuationOutcomes) => {
153
- const rejection = outcomes.push(...continuationOutcomes) && outcomes.find(({ status }) => status === 'rejected')
154
- if (rejection) throw rejection.reason
155
- return continuationOutcomes.map(({ value }) => value)
147
+ return Promise.all(started).then(() =>
148
+ Promise.all(startedContinuations).then((result) => {
149
+ if (rejection) throw rejection
150
+ return result
156
151
  })
157
152
  )
158
153
  }
@@ -232,9 +227,8 @@ async function loadRepository (url, opts) {
232
227
  try {
233
228
  await git.resolveRef(Object.assign({ ref: 'HEAD', depth: 1 }, repo))
234
229
  } catch {
235
- throw new Error(
236
- `Local content source must be a git repository: ${dir}${url !== dir ? ' (url: ' + url + ')' : ''}`
237
- )
230
+ const msg = `Local content source must be a git repository: ${dir}${url !== dir ? ' (url: ' + url + ')' : ''}`
231
+ throw new Error(msg)
238
232
  }
239
233
  } else {
240
234
  throw new Error(`Local content source does not exist: ${dir}${url !== dir ? ' (url: ' + url + ')' : ''}`)
@@ -275,99 +269,100 @@ async function selectReferences (source, repo, remote) {
275
269
  const patternCache = repo.cache[REF_PATTERN_CACHE_KEY]
276
270
  const noWorktree = repo.url ? undefined : null
277
271
  const refs = new Map()
278
- if (tagPatterns) {
279
- tagPatterns = Array.isArray(tagPatterns)
272
+ if (
273
+ tagPatterns &&
274
+ (tagPatterns = Array.isArray(tagPatterns)
280
275
  ? tagPatterns.map((pattern) => String(pattern))
281
- : splitRefPatterns(String(tagPatterns))
282
- if (tagPatterns.length) {
283
- const tags = await git.listTags(repo)
284
- for (const shortname of tags.length ? filterRefs(tags, tagPatterns, patternCache) : tags) {
276
+ : splitRefPatterns(String(tagPatterns))).length
277
+ ) {
278
+ const tags = await git.listTags(repo)
279
+ if (tags.length) {
280
+ for (const shortname of filterRefs(tags, tagPatterns, patternCache)) {
285
281
  // NOTE tags are stored using symbol keys to distinguish them from branches
286
282
  refs.set(Symbol(shortname), { shortname, fullname: 'tags/' + shortname, type: 'tag', head: noWorktree })
287
283
  }
288
284
  }
289
285
  }
290
- if (branchPatterns) {
291
- if (worktreePatterns) {
292
- if (worktreePatterns === '.') {
293
- worktreePatterns = ['.']
294
- } else if (worktreePatterns === true) {
295
- worktreePatterns = ['.', '*']
296
- } else {
297
- worktreePatterns = Array.isArray(worktreePatterns)
298
- ? worktreePatterns.map((pattern) => String(pattern))
299
- : splitRefPatterns(String(worktreePatterns))
300
- }
286
+ if (!branchPatterns) return [...refs.values()]
287
+ if (worktreePatterns) {
288
+ if (worktreePatterns === '.') {
289
+ worktreePatterns = ['.']
290
+ } else if (worktreePatterns === true) {
291
+ worktreePatterns = ['.', '*']
292
+ } else {
293
+ worktreePatterns = Array.isArray(worktreePatterns)
294
+ ? worktreePatterns.map((pattern) => String(pattern))
295
+ : splitRefPatterns(String(worktreePatterns))
301
296
  }
302
- const branchPatternsString = String(branchPatterns)
303
- if (branchPatternsString === 'HEAD' || branchPatternsString === '.') {
297
+ }
298
+ const branchPatternsString = String(branchPatterns)
299
+ if (branchPatternsString === 'HEAD' || branchPatternsString === '.') {
300
+ const currentBranch = await getCurrentBranchName(repo, remote)
301
+ if (currentBranch) {
302
+ branchPatterns = [currentBranch]
303
+ } else if (isBare) {
304
+ return [...refs.values()]
305
+ } else {
306
+ // NOTE current branch is undefined when HEAD is detached
307
+ const head = worktreePatterns[0] === '.' ? repo.dir : noWorktree
308
+ refs.set('HEAD', { shortname: 'HEAD', fullname: 'HEAD', type: 'branch', detached: true, head })
309
+ return [...refs.values()]
310
+ }
311
+ } else if (
312
+ (branchPatterns = Array.isArray(branchPatterns)
313
+ ? branchPatterns.map((pattern) => String(pattern))
314
+ : splitRefPatterns(branchPatternsString)).length
315
+ ) {
316
+ let headBranchIdx
317
+ // NOTE we can assume at least two entries if HEAD or . are present
318
+ if (~(headBranchIdx = branchPatterns.indexOf('HEAD')) || ~(headBranchIdx = branchPatterns.indexOf('.'))) {
304
319
  const currentBranch = await getCurrentBranchName(repo, remote)
305
320
  if (currentBranch) {
306
- branchPatterns = [currentBranch]
307
- } else {
308
- if (!isBare) {
309
- // NOTE current branch is undefined when HEAD is detached
310
- const head = worktreePatterns[0] === '.' ? repo.dir : noWorktree
311
- refs.set('HEAD', { shortname: 'HEAD', fullname: 'HEAD', type: 'branch', detached: true, head })
312
- }
313
- return [...refs.values()]
314
- }
315
- } else if (
316
- (branchPatterns = Array.isArray(branchPatterns)
317
- ? branchPatterns.map((pattern) => String(pattern))
318
- : splitRefPatterns(branchPatternsString)).length
319
- ) {
320
- let headBranchIdx
321
- // NOTE we can assume at least two entries if HEAD or . are present
322
- if (~(headBranchIdx = branchPatterns.indexOf('HEAD')) || ~(headBranchIdx = branchPatterns.indexOf('.'))) {
323
- const currentBranch = await getCurrentBranchName(repo, remote)
324
- if (currentBranch) {
325
- // NOTE ignore if current branch is already in list
326
- if (~branchPatterns.indexOf(currentBranch)) {
327
- branchPatterns.splice(headBranchIdx, 1)
328
- } else {
329
- branchPatterns[headBranchIdx] = currentBranch
330
- }
331
- } else {
332
- if (!isBare) {
333
- let head = noWorktree
334
- if (worktreePatterns[0] === '.') {
335
- worktreePatterns = worktreePatterns.slice(1)
336
- head = repo.dir
337
- }
338
- // NOTE current branch is undefined when HEAD is detached
339
- refs.set('HEAD', { shortname: 'HEAD', fullname: 'HEAD', type: 'branch', detached: true, head })
340
- }
321
+ // NOTE ignore if current branch is already in list
322
+ if (~branchPatterns.indexOf(currentBranch)) {
341
323
  branchPatterns.splice(headBranchIdx, 1)
324
+ } else {
325
+ branchPatterns[headBranchIdx] = currentBranch
326
+ }
327
+ } else if (isBare) {
328
+ branchPatterns.splice(headBranchIdx, 1)
329
+ } else {
330
+ let head = noWorktree
331
+ if (worktreePatterns[0] === '.') {
332
+ worktreePatterns = worktreePatterns.slice(1)
333
+ head = repo.dir
342
334
  }
335
+ // NOTE current branch is undefined when HEAD is detached
336
+ refs.set('HEAD', { shortname: 'HEAD', fullname: 'HEAD', type: 'branch', detached: true, head })
337
+ branchPatterns.splice(headBranchIdx, 1)
343
338
  }
344
- } else {
345
- return [...refs.values()]
346
339
  }
347
- // NOTE isomorphic-git includes HEAD in list of remote branches (see https://isomorphic-git.org/docs/listBranches)
348
- const remoteBranches = (await git.listBranches(Object.assign({ remote }, repo))).filter((it) => it !== 'HEAD')
349
- if (remoteBranches.length) {
350
- for (const shortname of filterRefs(remoteBranches, branchPatterns, patternCache)) {
351
- const fullname = 'remotes/' + remote + '/' + shortname
352
- refs.set(shortname, { shortname, fullname, type: 'branch', remote, head: noWorktree })
353
- }
340
+ } else {
341
+ return [...refs.values()]
342
+ }
343
+ // NOTE isomorphic-git includes HEAD in list of remote branches (see https://isomorphic-git.org/docs/listBranches)
344
+ const remoteBranches = (await git.listBranches(Object.assign({ remote }, repo))).filter((it) => it !== 'HEAD')
345
+ if (remoteBranches.length) {
346
+ for (const shortname of filterRefs(remoteBranches, branchPatterns, patternCache)) {
347
+ const fullname = 'remotes/' + remote + '/' + shortname
348
+ refs.set(shortname, { shortname, fullname, type: 'branch', remote, head: noWorktree })
354
349
  }
355
- // NOTE only consider local branches if repo has a worktree or there are no remote tracking branches
356
- if (!isBare) {
357
- const localBranches = await git.listBranches(repo)
358
- if (localBranches.length) {
359
- const worktrees = await findWorktrees(repo, worktreePatterns)
360
- for (const shortname of filterRefs(localBranches, branchPatterns, patternCache)) {
361
- const head = worktrees.get(shortname) || noWorktree
362
- refs.set(shortname, { shortname, fullname: 'heads/' + shortname, type: 'branch', head })
363
- }
350
+ }
351
+ // NOTE only consider local branches if repo has a worktree or there are no remote tracking branches
352
+ if (!isBare) {
353
+ const localBranches = await git.listBranches(repo)
354
+ if (localBranches.length) {
355
+ const worktrees = await findWorktrees(repo, worktreePatterns)
356
+ for (const shortname of filterRefs(localBranches, branchPatterns, patternCache)) {
357
+ const head = worktrees.get(shortname) || noWorktree
358
+ refs.set(shortname, { shortname, fullname: 'heads/' + shortname, type: 'branch', head })
364
359
  }
365
- } else if (!remoteBranches.length) {
366
- const localBranches = await git.listBranches(repo)
367
- if (localBranches.length) {
368
- for (const shortname of filterRefs(localBranches, branchPatterns, patternCache)) {
369
- refs.set(shortname, { shortname, fullname: 'heads/' + shortname, type: 'branch', head: noWorktree })
370
- }
360
+ }
361
+ } else if (!remoteBranches.length) {
362
+ const localBranches = await git.listBranches(repo)
363
+ if (localBranches.length) {
364
+ for (const shortname of filterRefs(localBranches, branchPatterns, patternCache)) {
365
+ refs.set(shortname, { shortname, fullname: 'heads/' + shortname, type: 'branch', head: noWorktree })
371
366
  }
372
367
  }
373
368
  }
@@ -433,35 +428,19 @@ function collectFilesFromStartPath (startPath, repo, authStatus, ref, worktreePa
433
428
  return componentVersionBucket
434
429
  })
435
430
  .catch((err) => {
431
+ const msg = err.message
436
432
  const refInfo = `ref: ${ref.fullname.replace(HEADS_DIR_RX, '')}${worktreePath ? ' <worktree>' : ''}`
437
- const pathInfo = !startPath || err.message.startsWith('the start path ') ? '' : ' | path: ' + startPath
438
- throw Object.assign(err, { message: `${err.message} in ${repo.url || repo.dir} (${refInfo}${pathInfo})` })
433
+ const pathInfo = !startPath || msg.startsWith('the start path ') ? '' : ' | path: ' + startPath
434
+ throw Object.assign(err, { message: msg.replace(/$/m, ` in ${repo.url || repo.dir} (${refInfo}${pathInfo})`) })
439
435
  })
440
436
  }
441
437
 
442
438
  function readFilesFromWorktree (worktreePath, startPath) {
443
- const cwd = ospath.join(worktreePath, startPath)
439
+ const cwd = ospath.join(worktreePath, startPath, '.') // . shaves off trailing slash
444
440
  return fsp.stat(cwd).then(
445
- (stat) => {
446
- if (!stat.isDirectory()) throw new Error(`the start path '${startPath}' is not a directory`)
447
- return new Promise((resolve, reject) =>
448
- vfs
449
- .src(CONTENT_SRC_GLOB, Object.assign({ cwd }, CONTENT_SRC_OPTS))
450
- .on('error', (err) => {
451
- if (err.code === 'ENOENT' && err.syscall === 'stat') {
452
- try {
453
- if (fs.lstatSync(err.path).isSymbolicLink()) {
454
- err.message = `Broken symbolic link detected at ${ospath.relative(cwd, err.path)}`
455
- }
456
- } catch {}
457
- } else if (err.code === 'ELOOP') {
458
- err.message = `Symbolic link cycle detected at ${ospath.relative(cwd, err.path)}`
459
- }
460
- reject(err)
461
- })
462
- .pipe(relativizeFiles())
463
- .pipe(collectDataFromStream(resolve))
464
- )
441
+ (startPathStat) => {
442
+ if (!startPathStat.isDirectory()) throw new Error(`the start path '${startPath}' is not a directory`)
443
+ return srcFs(cwd)
465
444
  },
466
445
  () => {
467
446
  throw new Error(`the start path '${startPath}' does not exist`)
@@ -469,40 +448,43 @@ function readFilesFromWorktree (worktreePath, startPath) {
469
448
  )
470
449
  }
471
450
 
472
- /**
473
- * Transforms the path of every file in the stream to a relative posix path.
474
- *
475
- * Applies a mapping function to all files in the stream so they end up with a
476
- * posixified path relative to the file's base instead of the filesystem root.
477
- * This mapper also filters out any directories (indicated by file.isNull())
478
- * that got caught up in the glob.
479
- */
480
- function relativizeFiles () {
481
- return map((file, enc, next) => {
482
- if (file.isNull()) {
483
- next()
484
- } else {
485
- next(
486
- null,
487
- new File({
488
- path: posixify ? posixify(file.relative) : file.relative,
489
- contents: file.contents,
490
- stat: file.stat,
491
- src: { abspath: file.path },
492
- })
493
- )
494
- }
495
- })
496
- }
497
-
498
- function collectDataFromStream (done) {
499
- const accum = []
500
- return map(
501
- (obj, _, next) => {
502
- accum.push(obj)
503
- next()
504
- },
505
- () => done(accum)
451
+ function srcFs (cwd) {
452
+ const relpathStart = cwd.length + 1
453
+ return new Promise((resolve, reject, cache = Object.create(null), files = []) =>
454
+ pipeline(
455
+ globStream(CONTENT_SRC_GLOB, Object.assign({ cache, cwd }, CONTENT_SRC_OPTS)),
456
+ forEach(({ path: abspathPosix }, _, done) => {
457
+ if (Array.isArray(cache[abspathPosix])) return done() // detects some directories, but not all
458
+ const abspath = posixify ? ospath.normalize(abspathPosix) : abspathPosix
459
+ const relpath = abspath.substr(relpathStart)
460
+ symlinkAwareStat(abspath).then(
461
+ (stat) => {
462
+ if (stat.isDirectory()) return done() // detects remaining directories
463
+ fsp.readFile(abspath).then(
464
+ (contents) => {
465
+ files.push(new File({ path: posixify ? posixify(relpath) : relpath, contents, stat, src: { abspath } }))
466
+ done()
467
+ },
468
+ (readErr) => {
469
+ done(Object.assign(readErr, { message: readErr.message.replace(`'${abspath}'`, relpath) }))
470
+ }
471
+ )
472
+ },
473
+ (statErr) => {
474
+ if (statErr.symlink) {
475
+ statErr.message =
476
+ statErr.code === 'ELOOP'
477
+ ? `Symbolic link cycle detected at ${relpath}`
478
+ : `Broken symbolic link detected at ${relpath}`
479
+ } else {
480
+ statErr.message = statErr.message.replace(`'${abspath}'`, relpath)
481
+ }
482
+ done(statErr)
483
+ }
484
+ )
485
+ }),
486
+ (err) => (err ? reject(err) : resolve(files))
487
+ )
506
488
  )
507
489
  }
508
490
 
@@ -671,7 +653,7 @@ function loadComponentDescriptor (files, ref, version) {
671
653
  files.splice(descriptorFileIdx, 1)
672
654
  let data
673
655
  try {
674
- data = yaml.load(descriptorFile.contents.toString())
656
+ data = yaml.load(descriptorFile.contents.toString(), { schema: yaml.CORE_SCHEMA })
675
657
  } catch (err) {
676
658
  throw Object.assign(err, { message: `${COMPONENT_DESC_FILENAME} has invalid syntax; ${err.message}` })
677
659
  }
@@ -683,7 +665,8 @@ function loadComponentDescriptor (files, ref, version) {
683
665
  if ('version' in data) version = data.version
684
666
  if (!version) {
685
667
  if (version === undefined) throw new Error(`${COMPONENT_DESC_FILENAME} is missing a version`)
686
- version = ''
668
+ if (version === false) throw new Error(`${COMPONENT_DESC_FILENAME} has an invalid version`)
669
+ version = '' + (typeof version === 'number' ? version : '')
687
670
  } else if (version === true) {
688
671
  version = ref.shortname.replace(PATH_SEPARATOR_RX, '-')
689
672
  } else if (version.constructor === Object) {
@@ -693,7 +676,7 @@ function loadComponentDescriptor (files, ref, version) {
693
676
  matched = version[refname]
694
677
  } else if (
695
678
  !Object.entries(version).some(([pattern, replacement]) => {
696
- const result = refname.replace(makePicomatchRx(pattern, PICOMATCH_VERSION_OPTS), '\0' + replacement)
679
+ const result = refname.replace(makeMatcherRx(pattern, VERSION_MATCHER_OPTS), '\0' + replacement)
697
680
  if (result === refname) return false
698
681
  matched = result.substr(1)
699
682
  return true
@@ -705,7 +688,7 @@ function loadComponentDescriptor (files, ref, version) {
705
688
  throw new Error(`version in ${COMPONENT_DESC_FILENAME} cannot have path segments: ${matched}`)
706
689
  }
707
690
  version = matched.replace(PATH_SEPARATOR_RX, '-')
708
- } else if ((version = String(version)) === '.' || version === '..' || ~version.indexOf('/')) {
691
+ } else if ((version = '' + version) === '.' || version === '..' || ~version.indexOf('/')) {
709
692
  throw new Error(`version in ${COMPONENT_DESC_FILENAME} cannot have path segments: ${version}`)
710
693
  }
711
694
  data.version = version
@@ -721,7 +704,8 @@ function computeOrigin (url, authStatus, gitdir, ref, startPath, worktreePath =
721
704
  } else {
722
705
  if (worktreePath) {
723
706
  origin.fileUriPattern =
724
- (posixify ? 'file:///' + posixify(worktreePath) : 'file://' + worktreePath) + path.join('/', startPath, '%s')
707
+ (posixify ? 'file:///' + posixify(worktreePath) : 'file://' + worktreePath) +
708
+ (startPath ? '/' + startPath + '/%s' : '/%s')
725
709
  } else {
726
710
  origin.refhash = refhash
727
711
  }
@@ -909,7 +893,8 @@ function resolveRemoteUrl (repo, remoteName) {
909
893
  return 'https://' + url.substr(url.indexOf('@') + 1 || 6).replace(URL_PORT_CLEANER_RX, '$1')
910
894
  }
911
895
  }
912
- return posixify ? 'file:///' + posixify(repo.dir) : 'file://' + repo.dir
896
+ url = posixify ? 'file:///' + posixify(repo.dir) : 'file://' + repo.dir
897
+ return ~url.indexOf(' ') ? url.replace(SPACE_RX, '%20') : url
913
898
  })
914
899
  }
915
900
 
@@ -923,6 +908,15 @@ function isDirectory (url) {
923
908
  return fsp.stat(url).then((stat) => stat.isDirectory(), invariably.false)
924
909
  }
925
910
 
911
+ function symlinkAwareStat (path_) {
912
+ return fsp.lstat(path_).then((lstat) => {
913
+ if (!lstat.isSymbolicLink()) return lstat
914
+ return fsp.stat(path_).catch((statErr) => {
915
+ throw Object.assign(statErr, { symlink: true })
916
+ })
917
+ })
918
+ }
919
+
926
920
  function tagsSpecified (sources) {
927
921
  return sources.some(({ tags }) => tags && (Array.isArray(tags) ? tags.length : true))
928
922
  }
@@ -1022,7 +1016,7 @@ function findWorktrees (repo, patterns) {
1022
1016
  return (patterns.length
1023
1017
  ? fsp
1024
1018
  .readdir((worktreesDir = ospath.join(repo.dir, '.git', 'worktrees')))
1025
- .then((worktreeNames) => filterRefs(worktreeNames, [...patterns], patternCache), invariably.emptyArray)
1019
+ .then((worktreeNames) => filterRefs(worktreeNames, patterns, patternCache), invariably.emptyArray)
1026
1020
  .then((worktreeNames) =>
1027
1021
  worktreeNames.length
1028
1022
  ? Promise.all(
package/lib/constants.js CHANGED
@@ -4,22 +4,11 @@ module.exports = Object.freeze({
4
4
  COMPONENT_DESC_FILENAME: 'antora.yml',
5
5
  CONTENT_CACHE_FOLDER: 'content',
6
6
  CONTENT_SRC_GLOB: '**/*[!~]',
7
- CONTENT_SRC_OPTS: { follow: true, nomount: true, nosort: true, nounique: true, removeBOM: false, uniqueBy: (m) => m },
7
+ CONTENT_SRC_OPTS: { follow: true, nomount: true, nosort: true, nounique: true, strict: false, uniqueBy: (m) => m },
8
8
  FILE_MODES: { 100644: 0o100666 & ~process.umask(), 100755: 0o100777 & ~process.umask() },
9
9
  GIT_CORE: 'antora',
10
10
  GIT_OPERATION_LABEL_LENGTH: 8,
11
11
  GIT_PROGRESS_PHASES: ['Counting objects', 'Compressing objects', 'Receiving objects', 'Resolving deltas'],
12
- PICOMATCH_VERSION_OPTS: {
13
- bash: true,
14
- dot: true,
15
- fastpaths: false,
16
- nobracket: true,
17
- noglobstar: true,
18
- nonegate: true,
19
- noquantifiers: true,
20
- regex: false,
21
- strictSlashes: true,
22
- },
23
12
  REF_PATTERN_CACHE_KEY: Symbol('RefPatternCache'),
24
13
  SYMLINK_FILE_MODE: '120000',
25
14
  VALID_STATE_FILENAME: 'valid',
@@ -1,49 +1,31 @@
1
1
  'use strict'
2
2
 
3
- const { compile: bracesToGroup } = require('braces')
4
- const { makeRe: makePicomatchRx } = require('picomatch')
5
-
6
- function getPicomatchOpts (cache) {
7
- return {
8
- bash: true,
9
- dot: true,
10
- expandRange: (begin, end, step, opts) => {
11
- const pattern = opts ? `{${begin}..${end}..${step}}` : `{${begin}..${end}}`
12
- return cache.braces.get(pattern) || cache.braces.set(pattern, bracesToGroup(pattern)).get(pattern)
13
- },
14
- fastpaths: false,
15
- nobracket: true,
16
- noglobstar: true,
17
- noquantifiers: true,
18
- regex: false,
19
- strictSlashes: true,
20
- }
21
- }
3
+ const { makeMatcherRx, refMatcherOpts: getMatcherOpts, MATCH_ALL_RX } = require('./matcher')
22
4
 
23
5
  function compileRx (pattern, opts) {
24
- if (pattern === '*' || pattern === '**') return { test: () => true }
6
+ if (pattern === '*' || pattern === '**') return MATCH_ALL_RX
25
7
  return pattern.charAt() === '!' // do our own negate
26
- ? Object.defineProperty(makePicomatchRx(pattern.substr(1), opts), 'negated', { value: true })
27
- : makePicomatchRx(pattern, opts)
8
+ ? Object.defineProperty(makeMatcherRx(pattern.substr(1), opts), 'negated', { value: true })
9
+ : makeMatcherRx(pattern, opts)
28
10
  }
29
11
 
30
- function createMatcher (patterns, cache) {
31
- let opts
12
+ function createMatcher (patterns, cache, opts) {
32
13
  const rxs = patterns.map(
33
14
  (pattern) =>
34
- cache.get(pattern) ||
35
- cache.set(pattern, compileRx(pattern, opts || (opts = getPicomatchOpts(cache)))).get(pattern)
15
+ cache.get(pattern) || cache.set(pattern, compileRx(pattern, opts || (opts = getMatcherOpts(cache)))).get(pattern)
36
16
  )
17
+ if (rxs[0].negated) rxs.unshift(MATCH_ALL_RX)
37
18
  return (candidate) => {
38
- let first = true
39
19
  let matched
40
20
  for (const rx of rxs) {
21
+ let voteIfMatched = true
41
22
  if (matched) {
42
- if (rx.negated && rx.test(candidate)) return
43
- } else if (first || !rx.negated) {
44
- matched = rx.test(candidate)
23
+ if (!rx.negated) continue
24
+ voteIfMatched = false
25
+ } else if (rx.negated) {
26
+ continue
45
27
  }
46
- first = false
28
+ if (rx.test(candidate)) matched = voteIfMatched
47
29
  }
48
30
  return matched
49
31
  }
@@ -2,17 +2,16 @@
2
2
 
3
3
  const { homedir } = require('os')
4
4
  const expandPath = require('@antora/expand-path-helper')
5
- const invariably = { void: () => undefined }
6
5
  const { promises: fsp } = require('fs')
6
+ const invariably = require('./invariably')
7
7
  const ospath = require('path')
8
8
 
9
9
  class GitCredentialManagerStore {
10
10
  configure ({ config, startDir }) {
11
11
  this.entries = undefined
12
+ this.path = undefined
12
13
  this.urls = {}
13
- if ((this.contents = (config = config || {}).contents) || !config.path) {
14
- this.path = undefined
15
- } else {
14
+ if (!(this.contents = (config = config || {}).contents) && config.path) {
16
15
  this.path = expandPath(config.path, { dot: startDir })
17
16
  }
18
17
  return this
@@ -35,14 +34,13 @@ class GitCredentialManagerStore {
35
34
  'git',
36
35
  'credentials'
37
36
  )
38
- contentsPromise = fsp
39
- .access(homeGitCredentialsPath)
40
- .then(() => fsp.readFile(homeGitCredentialsPath, 'utf8'))
41
- .catch(() =>
37
+ contentsPromise = fsp.access(homeGitCredentialsPath).then(
38
+ () => fsp.readFile(homeGitCredentialsPath, 'utf8'),
39
+ () =>
42
40
  fsp
43
41
  .access(xdgConfigGitCredentialsPath)
44
42
  .then(() => fsp.readFile(xdgConfigGitCredentialsPath, 'utf8'), invariably.void)
45
- )
43
+ )
46
44
  }
47
45
  contentsPromise.then((contents) => {
48
46
  if (contents) {
@@ -0,0 +1,3 @@
1
+ 'use strict'
2
+
3
+ module.exports = { true: () => true, false: () => false, void: () => undefined, emptyArray: () => [] }
package/lib/matcher.js ADDED
@@ -0,0 +1,32 @@
1
+ 'use strict'
2
+
3
+ const { compile: bracesToGroup, expand: expandBraces } = require('braces')
4
+ const { makeRe: makeMatcherRx } = require('picomatch')
5
+
6
+ const BASE_OPTS = {
7
+ bash: true,
8
+ dot: true,
9
+ expandRange: (begin, end, step, opts) => bracesToGroup(opts ? `{${begin}..${end}..${step}}` : `{${begin}..${end}}`),
10
+ fastpaths: false,
11
+ nobracket: true,
12
+ noglobstar: true,
13
+ nonegate: true,
14
+ noquantifiers: true,
15
+ regex: false,
16
+ strictSlashes: true,
17
+ }
18
+
19
+ module.exports = {
20
+ MATCH_ALL_RX: { test: () => true },
21
+ expandBraces,
22
+ makeMatcherRx,
23
+ pathMatcherOpts: Object.assign({}, BASE_OPTS, { dot: false }),
24
+ refMatcherOpts: (cache) =>
25
+ Object.assign({}, BASE_OPTS, {
26
+ expandRange: (begin, end, step, opts) => {
27
+ const pattern = opts ? `{${begin}..${end}..${step}}` : `{${begin}..${end}}`
28
+ return cache.braces.get(pattern) || cache.braces.set(pattern, bracesToGroup(pattern)).get(pattern)
29
+ },
30
+ }),
31
+ versionMatcherOpts: Object.assign({}, BASE_OPTS, { nonegate: false }),
32
+ }
@@ -1,32 +1,20 @@
1
1
  'use strict'
2
2
 
3
- const { expand: expandBraces, compile: bracesToGroup } = require('braces')
4
3
  const flattenDeep = require('./flatten-deep')
5
4
  const { promises: fsp } = require('fs')
6
5
  const git = require('./git')
7
- const invariably = { true: () => true, false: () => false, void: () => undefined, emptyArray: () => [] }
8
- const { makeRe: makePicomatchRx } = require('picomatch')
6
+ const invariably = require('./invariably')
7
+ const { expandBraces, makeMatcherRx, pathMatcherOpts: MATCHER_OPTS } = require('./matcher')
9
8
 
10
9
  const NON_GLOB_SPECIAL_CHARS_RX = /[.+?^${}()|[\]\\]/g
11
10
  const RX_MAGIC_DETECTOR = /[*{(]/
12
- const PICOMATCH_OPTS = {
13
- bash: true,
14
- expandRange: (begin, end, step, opts) => bracesToGroup(opts ? `{${begin}..${end}..${step}}` : `{${begin}..${end}}`),
15
- fastpaths: false,
16
- nobracket: true,
17
- noglobstar: true,
18
- nonegate: true,
19
- noquantifiers: true,
20
- regex: false,
21
- strictSlashes: true,
22
- }
23
11
 
24
12
  function resolvePathGlobs (base, patterns, listDirents, retrievePath, tree = { path: '' }) {
25
13
  return patterns.reduce((paths, pattern) => {
26
14
  if (pattern.charAt() === '!') {
27
15
  return paths.then((resolvedPaths) => {
28
16
  if (resolvedPaths.length) {
29
- const rx = makePicomatchRx(pattern.substr(1), PICOMATCH_OPTS)
17
+ const rx = makeMatcherRx(pattern.substr(1), MATCHER_OPTS)
30
18
  return resolvedPaths.filter((it) => !rx.test(it))
31
19
  } else {
32
20
  return resolvedPaths
@@ -49,10 +37,10 @@ async function glob (base, patternSegments, listDirents, retrievePath, { oid, pa
49
37
  if (patternSegment === '*') {
50
38
  isMatch = (it) => it.charAt() !== '.'
51
39
  } else if (~patternSegment.indexOf('(')) {
52
- isMatch = (isMatch = makePicomatchRx(patternSegment, PICOMATCH_OPTS)).test.bind(isMatch)
40
+ isMatch = (isMatch = makeMatcherRx(patternSegment, MATCHER_OPTS)).test.bind(isMatch)
53
41
  } else if (~patternSegment.indexOf('{')) {
54
42
  if (globbed) {
55
- isMatch = (isMatch = makePicomatchRx(patternSegment, PICOMATCH_OPTS)).test.bind(isMatch)
43
+ isMatch = (isMatch = makeMatcherRx(patternSegment, MATCHER_OPTS)).test.bind(isMatch)
56
44
  } else if (~patternSegment.indexOf('*')) {
57
45
  const [wildPatterns, literals] = expandBraces(patternSegment).reduce(
58
46
  ([wild, literal], it) => (~it.indexOf('*') ? [[...wild, it], literal] : [wild, [...literal, it]]),
@@ -64,7 +52,7 @@ async function glob (base, patternSegments, listDirents, retrievePath, { oid, pa
64
52
  return expandBraces(patternSegment).map((it) => joinPath(path, it))
65
53
  }
66
54
  } else {
67
- isMatch = (isMatch = makeMatcherRx(patternSegment)).test.bind(isMatch)
55
+ isMatch = (isMatch = makeSingleMatcherRx(patternSegment)).test.bind(isMatch)
68
56
  }
69
57
  let dirents = await listDirents(base, oid || path)
70
58
  if (explicit) dirents = dirents.filter((dirent) => !explicit.has(dirent.name))
@@ -134,7 +122,7 @@ function makeAlternationMatcherRx (patterns) {
134
122
  return new RegExp('^(?:' + patterns.map(patternToRx).join('|') + ')$')
135
123
  }
136
124
 
137
- function makeMatcherRx (pattern) {
125
+ function makeSingleMatcherRx (pattern) {
138
126
  return new RegExp('^' + patternToRx(pattern) + '$')
139
127
  }
140
128
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@antora/content-aggregator",
3
- "version": "3.0.0-beta.1",
3
+ "version": "3.0.0-beta.5",
4
4
  "description": "Fetches and aggregates content from distributed sources for use in an Antora documentation pipeline.",
5
5
  "license": "MPL-2.0",
6
6
  "author": "OpenDevise Inc. (https://opendevise.com)",
@@ -22,6 +22,7 @@
22
22
  "braces": "~3.0",
23
23
  "cache-directory": "~2.0",
24
24
  "camelcase-keys": "~7.0",
25
+ "glob-stream": "~7.0",
25
26
  "hpagent": "~0.1.0",
26
27
  "isomorphic-git": "~1.10",
27
28
  "js-yaml": "~4.1",
@@ -30,8 +31,7 @@
30
31
  "progress": "~2.0",
31
32
  "should-proxy": "~1.0",
32
33
  "simple-get": "~4.0",
33
- "vinyl": "~2.2",
34
- "vinyl-fs": "~3.0"
34
+ "vinyl": "~2.2"
35
35
  },
36
36
  "engines": {
37
37
  "node": ">=12.21.0"
@@ -49,5 +49,5 @@
49
49
  "static site",
50
50
  "web publishing"
51
51
  ],
52
- "gitHead": "7c5ef1ea93dd489af533c80a936c736013c41769"
52
+ "gitHead": "a13d03df41654d4deb78211a5a54953ce2a35fb8"
53
53
  }
@@ -1,11 +0,0 @@
1
- 'use strict'
2
-
3
- Promise.allSettled = (iterable) =>
4
- Promise.all(
5
- iterable.map((it) =>
6
- it.then(
7
- (value) => ({ status: 'fulfilled', value }),
8
- (reason) => ({ status: 'rejected', reason })
9
- )
10
- )
11
- )