@antora/content-aggregator 3.0.0-beta.2 → 3.0.0-beta.6

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.
@@ -15,17 +15,17 @@ const getCacheDir = require('cache-directory')
15
15
  const GitCredentialManagerStore = require('./git-credential-manager-store')
16
16
  const git = require('./git')
17
17
  const { NotFoundError, ObjectTypeError, UnknownTransportError, UrlParseError } = git.Errors
18
- const invariably = { false: () => false, void: () => undefined, emptyArray: () => [] }
19
- const { makeRe: makePicomatchRx } = require('picomatch')
20
- 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
21
22
  const ospath = require('path')
22
23
  const { posix: path } = ospath
23
24
  const posixify = ospath.sep === '\\' ? (p) => p.replace(/\\/g, '/') : undefined
24
25
  const { fs: resolvePathGlobsFs, git: resolvePathGlobsGit } = require('./resolve-path-globs')
25
- const { Transform } = require('stream')
26
- 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 })
27
28
  const userRequire = require('@antora/user-require-helper')
28
- const vfs = require('vinyl-fs')
29
29
  const yaml = require('js-yaml')
30
30
 
31
31
  const {
@@ -37,12 +37,10 @@ const {
37
37
  GIT_CORE,
38
38
  GIT_OPERATION_LABEL_LENGTH,
39
39
  GIT_PROGRESS_PHASES,
40
- PICOMATCH_VERSION_OPTS,
41
40
  REF_PATTERN_CACHE_KEY,
42
41
  SYMLINK_FILE_MODE,
43
42
  VALID_STATE_FILENAME,
44
43
  } = require('./constants')
45
-
46
44
  const ANY_SEPARATOR_RX = /[:/]/
47
45
  const CSV_RX = /\s*,\s*/
48
46
  const VENTILATED_CSV_RX = /\s*,\s+/
@@ -229,9 +227,8 @@ async function loadRepository (url, opts) {
229
227
  try {
230
228
  await git.resolveRef(Object.assign({ ref: 'HEAD', depth: 1 }, repo))
231
229
  } catch {
232
- throw new Error(
233
- `Local content source must be a git repository: ${dir}${url !== dir ? ' (url: ' + url + ')' : ''}`
234
- )
230
+ const msg = `Local content source must be a git repository: ${dir}${url !== dir ? ' (url: ' + url + ')' : ''}`
231
+ throw new Error(msg)
235
232
  }
236
233
  } else {
237
234
  throw new Error(`Local content source does not exist: ${dir}${url !== dir ? ' (url: ' + url + ')' : ''}`)
@@ -272,99 +269,100 @@ async function selectReferences (source, repo, remote) {
272
269
  const patternCache = repo.cache[REF_PATTERN_CACHE_KEY]
273
270
  const noWorktree = repo.url ? undefined : null
274
271
  const refs = new Map()
275
- if (tagPatterns) {
276
- tagPatterns = Array.isArray(tagPatterns)
272
+ if (
273
+ tagPatterns &&
274
+ (tagPatterns = Array.isArray(tagPatterns)
277
275
  ? tagPatterns.map((pattern) => String(pattern))
278
- : splitRefPatterns(String(tagPatterns))
279
- if (tagPatterns.length) {
280
- const tags = await git.listTags(repo)
281
- 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)) {
282
281
  // NOTE tags are stored using symbol keys to distinguish them from branches
283
282
  refs.set(Symbol(shortname), { shortname, fullname: 'tags/' + shortname, type: 'tag', head: noWorktree })
284
283
  }
285
284
  }
286
285
  }
287
- if (branchPatterns) {
288
- if (worktreePatterns) {
289
- if (worktreePatterns === '.') {
290
- worktreePatterns = ['.']
291
- } else if (worktreePatterns === true) {
292
- worktreePatterns = ['.', '*']
293
- } else {
294
- worktreePatterns = Array.isArray(worktreePatterns)
295
- ? worktreePatterns.map((pattern) => String(pattern))
296
- : splitRefPatterns(String(worktreePatterns))
297
- }
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))
298
296
  }
299
- const branchPatternsString = String(branchPatterns)
300
- 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('.'))) {
301
319
  const currentBranch = await getCurrentBranchName(repo, remote)
302
320
  if (currentBranch) {
303
- branchPatterns = [currentBranch]
304
- } else {
305
- if (!isBare) {
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
- }
310
- return [...refs.values()]
311
- }
312
- } else if (
313
- (branchPatterns = Array.isArray(branchPatterns)
314
- ? branchPatterns.map((pattern) => String(pattern))
315
- : splitRefPatterns(branchPatternsString)).length
316
- ) {
317
- let headBranchIdx
318
- // NOTE we can assume at least two entries if HEAD or . are present
319
- if (~(headBranchIdx = branchPatterns.indexOf('HEAD')) || ~(headBranchIdx = branchPatterns.indexOf('.'))) {
320
- const currentBranch = await getCurrentBranchName(repo, remote)
321
- if (currentBranch) {
322
- // NOTE ignore if current branch is already in list
323
- if (~branchPatterns.indexOf(currentBranch)) {
324
- branchPatterns.splice(headBranchIdx, 1)
325
- } else {
326
- branchPatterns[headBranchIdx] = currentBranch
327
- }
328
- } else {
329
- if (!isBare) {
330
- let head = noWorktree
331
- if (worktreePatterns[0] === '.') {
332
- worktreePatterns = worktreePatterns.slice(1)
333
- head = repo.dir
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
- }
321
+ // NOTE ignore if current branch is already in list
322
+ if (~branchPatterns.indexOf(currentBranch)) {
338
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
339
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)
340
338
  }
341
- } else {
342
- return [...refs.values()]
343
339
  }
344
- // NOTE isomorphic-git includes HEAD in list of remote branches (see https://isomorphic-git.org/docs/listBranches)
345
- const remoteBranches = (await git.listBranches(Object.assign({ remote }, repo))).filter((it) => it !== 'HEAD')
346
- if (remoteBranches.length) {
347
- for (const shortname of filterRefs(remoteBranches, branchPatterns, patternCache)) {
348
- const fullname = 'remotes/' + remote + '/' + shortname
349
- refs.set(shortname, { shortname, fullname, type: 'branch', remote, head: noWorktree })
350
- }
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 })
351
349
  }
352
- // NOTE only consider local branches if repo has a worktree or there are no remote tracking branches
353
- if (!isBare) {
354
- const localBranches = await git.listBranches(repo)
355
- if (localBranches.length) {
356
- const worktrees = await findWorktrees(repo, worktreePatterns)
357
- for (const shortname of filterRefs(localBranches, branchPatterns, patternCache)) {
358
- const head = worktrees.get(shortname) || noWorktree
359
- refs.set(shortname, { shortname, fullname: 'heads/' + shortname, type: 'branch', head })
360
- }
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 })
361
359
  }
362
- } else if (!remoteBranches.length) {
363
- const localBranches = await git.listBranches(repo)
364
- if (localBranches.length) {
365
- for (const shortname of filterRefs(localBranches, branchPatterns, patternCache)) {
366
- refs.set(shortname, { shortname, fullname: 'heads/' + shortname, type: 'branch', head: noWorktree })
367
- }
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 })
368
366
  }
369
367
  }
370
368
  }
@@ -430,35 +428,19 @@ function collectFilesFromStartPath (startPath, repo, authStatus, ref, worktreePa
430
428
  return componentVersionBucket
431
429
  })
432
430
  .catch((err) => {
431
+ const msg = err.message
433
432
  const refInfo = `ref: ${ref.fullname.replace(HEADS_DIR_RX, '')}${worktreePath ? ' <worktree>' : ''}`
434
- const pathInfo = !startPath || err.message.startsWith('the start path ') ? '' : ' | path: ' + startPath
435
- 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})`) })
436
435
  })
437
436
  }
438
437
 
439
438
  function readFilesFromWorktree (worktreePath, startPath) {
440
- const cwd = ospath.join(worktreePath, startPath)
439
+ const cwd = ospath.join(worktreePath, startPath, '.') // . shaves off trailing slash
441
440
  return fsp.stat(cwd).then(
442
- (stat) => {
443
- if (!stat.isDirectory()) throw new Error(`the start path '${startPath}' is not a directory`)
444
- return new Promise((resolve, reject) =>
445
- vfs
446
- .src(CONTENT_SRC_GLOB, Object.assign({ cwd }, CONTENT_SRC_OPTS))
447
- .on('error', (err) => {
448
- if (err.code === 'ENOENT' && err.syscall === 'stat') {
449
- try {
450
- if (fs.lstatSync(err.path).isSymbolicLink()) {
451
- err.message = `Broken symbolic link detected at ${ospath.relative(cwd, err.path)}`
452
- }
453
- } catch {}
454
- } else if (err.code === 'ELOOP') {
455
- err.message = `Symbolic link cycle detected at ${ospath.relative(cwd, err.path)}`
456
- }
457
- reject(err)
458
- })
459
- .pipe(relativizeFiles())
460
- .pipe(collectDataFromStream(resolve))
461
- )
441
+ (startPathStat) => {
442
+ if (!startPathStat.isDirectory()) throw new Error(`the start path '${startPath}' is not a directory`)
443
+ return srcFs(cwd)
462
444
  },
463
445
  () => {
464
446
  throw new Error(`the start path '${startPath}' does not exist`)
@@ -466,40 +448,43 @@ function readFilesFromWorktree (worktreePath, startPath) {
466
448
  )
467
449
  }
468
450
 
469
- /**
470
- * Transforms the path of every file in the stream to a relative posix path.
471
- *
472
- * Applies a mapping function to all files in the stream so they end up with a
473
- * posixified path relative to the file's base instead of the filesystem root.
474
- * This mapper also filters out any directories (indicated by file.isNull())
475
- * that got caught up in the glob.
476
- */
477
- function relativizeFiles () {
478
- return map((file, enc, next) => {
479
- if (file.isNull()) {
480
- next()
481
- } else {
482
- next(
483
- null,
484
- new File({
485
- path: posixify ? posixify(file.relative) : file.relative,
486
- contents: file.contents,
487
- stat: file.stat,
488
- src: { abspath: file.path },
489
- })
490
- )
491
- }
492
- })
493
- }
494
-
495
- function collectDataFromStream (done) {
496
- const accum = []
497
- return map(
498
- (obj, _, next) => {
499
- accum.push(obj)
500
- next()
501
- },
502
- () => 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
+ )
503
488
  )
504
489
  }
505
490
 
@@ -668,7 +653,7 @@ function loadComponentDescriptor (files, ref, version) {
668
653
  files.splice(descriptorFileIdx, 1)
669
654
  let data
670
655
  try {
671
- data = yaml.load(descriptorFile.contents.toString())
656
+ data = yaml.load(descriptorFile.contents.toString(), { schema: yaml.CORE_SCHEMA })
672
657
  } catch (err) {
673
658
  throw Object.assign(err, { message: `${COMPONENT_DESC_FILENAME} has invalid syntax; ${err.message}` })
674
659
  }
@@ -680,7 +665,8 @@ function loadComponentDescriptor (files, ref, version) {
680
665
  if ('version' in data) version = data.version
681
666
  if (!version) {
682
667
  if (version === undefined) throw new Error(`${COMPONENT_DESC_FILENAME} is missing a version`)
683
- version = ''
668
+ if (version === false) throw new Error(`${COMPONENT_DESC_FILENAME} has an invalid version`)
669
+ version = '' + (typeof version === 'number' ? version : '')
684
670
  } else if (version === true) {
685
671
  version = ref.shortname.replace(PATH_SEPARATOR_RX, '-')
686
672
  } else if (version.constructor === Object) {
@@ -690,7 +676,7 @@ function loadComponentDescriptor (files, ref, version) {
690
676
  matched = version[refname]
691
677
  } else if (
692
678
  !Object.entries(version).some(([pattern, replacement]) => {
693
- const result = refname.replace(makePicomatchRx(pattern, PICOMATCH_VERSION_OPTS), '\0' + replacement)
679
+ const result = refname.replace(makeMatcherRx(pattern, VERSION_MATCHER_OPTS), '\0' + replacement)
694
680
  if (result === refname) return false
695
681
  matched = result.substr(1)
696
682
  return true
@@ -702,7 +688,7 @@ function loadComponentDescriptor (files, ref, version) {
702
688
  throw new Error(`version in ${COMPONENT_DESC_FILENAME} cannot have path segments: ${matched}`)
703
689
  }
704
690
  version = matched.replace(PATH_SEPARATOR_RX, '-')
705
- } else if ((version = String(version)) === '.' || version === '..' || ~version.indexOf('/')) {
691
+ } else if ((version = '' + version) === '.' || version === '..' || ~version.indexOf('/')) {
706
692
  throw new Error(`version in ${COMPONENT_DESC_FILENAME} cannot have path segments: ${version}`)
707
693
  }
708
694
  data.version = version
@@ -718,7 +704,8 @@ function computeOrigin (url, authStatus, gitdir, ref, startPath, worktreePath =
718
704
  } else {
719
705
  if (worktreePath) {
720
706
  origin.fileUriPattern =
721
- (posixify ? 'file:///' + posixify(worktreePath) : 'file://' + worktreePath) + path.join('/', startPath, '%s')
707
+ (posixify ? 'file:///' + posixify(worktreePath) : 'file://' + worktreePath) +
708
+ (startPath ? '/' + startPath + '/%s' : '/%s')
722
709
  } else {
723
710
  origin.refhash = refhash
724
711
  }
@@ -906,7 +893,8 @@ function resolveRemoteUrl (repo, remoteName) {
906
893
  return 'https://' + url.substr(url.indexOf('@') + 1 || 6).replace(URL_PORT_CLEANER_RX, '$1')
907
894
  }
908
895
  }
909
- 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
910
898
  })
911
899
  }
912
900
 
@@ -920,6 +908,15 @@ function isDirectory (url) {
920
908
  return fsp.stat(url).then((stat) => stat.isDirectory(), invariably.false)
921
909
  }
922
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
+
923
920
  function tagsSpecified (sources) {
924
921
  return sources.some(({ tags }) => tags && (Array.isArray(tags) ? tags.length : true))
925
922
  }
@@ -1019,7 +1016,7 @@ function findWorktrees (repo, patterns) {
1019
1016
  return (patterns.length
1020
1017
  ? fsp
1021
1018
  .readdir((worktreesDir = ospath.join(repo.dir, '.git', 'worktrees')))
1022
- .then((worktreeNames) => filterRefs(worktreeNames, [...patterns], patternCache), invariably.emptyArray)
1019
+ .then((worktreeNames) => filterRefs(worktreeNames, patterns, patternCache), invariably.emptyArray)
1023
1020
  .then((worktreeNames) =>
1024
1021
  worktreeNames.length
1025
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.2",
3
+ "version": "3.0.0-beta.6",
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)",
@@ -16,12 +16,16 @@
16
16
  "url": "https://gitlab.com/antora/antora/issues"
17
17
  },
18
18
  "main": "lib/index.js",
19
+ "scripts": {
20
+ "test": "_mocha"
21
+ },
19
22
  "dependencies": {
20
23
  "@antora/expand-path-helper": "~2.0",
21
24
  "@antora/user-require-helper": "~2.0",
22
25
  "braces": "~3.0",
23
26
  "cache-directory": "~2.0",
24
27
  "camelcase-keys": "~7.0",
28
+ "glob-stream": "~7.0",
25
29
  "hpagent": "~0.1.0",
26
30
  "isomorphic-git": "~1.10",
27
31
  "js-yaml": "~4.1",
@@ -30,8 +34,7 @@
30
34
  "progress": "~2.0",
31
35
  "should-proxy": "~1.0",
32
36
  "simple-get": "~4.0",
33
- "vinyl": "~2.2",
34
- "vinyl-fs": "~3.0"
37
+ "vinyl": "~2.2"
35
38
  },
36
39
  "engines": {
37
40
  "node": ">=12.21.0"
@@ -49,5 +52,5 @@
49
52
  "static site",
50
53
  "web publishing"
51
54
  ],
52
- "gitHead": "5cd3f9cc70622e465cb44daf1aa2035ed5a35f54"
55
+ "gitHead": "f9f747965f599442562f59206d870b7e38864806"
53
56
  }