@antora/content-aggregator 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
@@ -3,7 +3,7 @@
3
3
  The Content Aggregator is a component in Antora responsible for fetching and aggregating content distributed across multiple local and remote git repositories for use in an Antora documentation pipeline.
4
4
 
5
5
  [Antora](https://antora.org) is a modular static site generator designed for creating documentation sites from AsciiDoc documents.
6
- Its site generator pipeline aggregates documents from versioned content repositories and processes them using [Asciidoctor](https://asciidoctor.org).
6
+ Its site generator aggregates documents from versioned content repositories and processes them using [Asciidoctor](https://asciidoctor.org).
7
7
 
8
8
  ## Copyright and License
9
9
 
@@ -7,6 +7,7 @@ const decodeUint8Array = require('./decode-uint8-array')
7
7
  const EventEmitter = require('events')
8
8
  const expandPath = require('@antora/expand-path-helper')
9
9
  const File = require('./file')
10
+ const filterRefs = require('./filter-refs')
10
11
  const flattenDeep = require('./flatten-deep')
11
12
  const fs = require('fs')
12
13
  const { promises: fsp } = fs
@@ -14,18 +15,17 @@ const getCacheDir = require('cache-directory')
14
15
  const GitCredentialManagerStore = require('./git-credential-manager-store')
15
16
  const git = require('./git')
16
17
  const { NotFoundError, ObjectTypeError, UnknownTransportError, UrlParseError } = git.Errors
17
- const invariably = { false: () => false, void: () => undefined, emptyArray: () => [] }
18
- const { makeRe: makePicomatchRx } = require('picomatch')
19
- const matcher = require('matcher')
18
+ const globStream = require('glob-stream')
19
+ const invariably = require('./invariably')
20
+ const { makeMatcherRx, versionMatcherOpts: VERSION_MATCHER_OPTS } = require('./matcher')
20
21
  const MultiProgress = require('multi-progress')
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, Transform } = require('stream')
27
+ const map = (transform) => new Transform({ objectMode: true, transform })
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,11 +37,10 @@ const {
37
37
  GIT_CORE,
38
38
  GIT_OPERATION_LABEL_LENGTH,
39
39
  GIT_PROGRESS_PHASES,
40
- PICOMATCH_VERSION_OPTS,
40
+ REF_PATTERN_CACHE_KEY,
41
41
  SYMLINK_FILE_MODE,
42
42
  VALID_STATE_FILENAME,
43
43
  } = require('./constants')
44
-
45
44
  const ANY_SEPARATOR_RX = /[:/]/
46
45
  const CSV_RX = /\s*,\s*/
47
46
  const VENTILATED_CSV_RX = /\s*,\s+/
@@ -89,48 +88,70 @@ const URL_PORT_CLEANER_RX = /^([^/]+):[0-9]+(?=\/)/
89
88
  function aggregateContent (playbook) {
90
89
  const startDir = playbook.dir || '.'
91
90
  const { branches, editUrl, tags, sources } = playbook.content
92
- const { cacheDir, fetch, quiet } = playbook.runtime
93
- return ensureCacheDir(cacheDir, startDir).then((resolvedCacheDir) => {
94
- const gitPlugins = loadGitPlugins(
95
- Object.assign({ ensureGitSuffix: true }, playbook.git),
96
- playbook.network || {},
97
- startDir
98
- )
99
- const sourcesByUrl = sources.reduce(
100
- (accum, source) => accum.set(source.url, [...(accum.get(source.url) || []), source]),
101
- new Map()
102
- )
91
+ const sourceDefaults = { branches, editUrl, tags }
92
+ const { cacheDir: requestedCacheDir, fetch, quiet } = playbook.runtime
93
+ return ensureCacheDir(requestedCacheDir, startDir).then((cacheDir) => {
94
+ const gitConfig = Object.assign({ ensureGitSuffix: true }, playbook.git)
95
+ const gitPlugins = loadGitPlugins(gitConfig, playbook.network || {}, startDir)
96
+ const fetchConcurrency = Math.max(gitConfig.fetchConcurrency || Infinity, 1)
97
+ const sourcesByUrl = sources.reduce((accum, source) => {
98
+ return accum.set(source.url, [...(accum.get(source.url) || []), Object.assign({}, sourceDefaults, source)])
99
+ }, new Map())
103
100
  const progress = !quiet && createProgress(sourcesByUrl.keys(), process.stdout)
104
- return Promise.all(
105
- [...sourcesByUrl.entries()].map(([url, sources]) =>
106
- loadRepository(url, {
107
- cacheDir: resolvedCacheDir,
108
- gitPlugins,
109
- fetchTags: tagsSpecified(sources, tags),
110
- progress,
111
- fetch,
112
- startDir,
113
- }).then(({ repo, authStatus }) =>
114
- Promise.all(
115
- sources.map((source) => {
116
- source = Object.assign({ branches, editUrl, tags }, source)
117
- // NOTE if repository is managed (has a url property), we can assume the remote name is origin
118
- // TODO if the repo has no remotes, then remoteName should be undefined
119
- const remoteName = repo.url ? 'origin' : source.remote || 'origin'
120
- return collectFilesFromSource(source, repo, remoteName, authStatus)
121
- })
122
- )
123
- )
124
- )
125
- )
126
- .then(buildAggregate)
127
- .catch((err) => {
128
- progress && progress.terminate()
129
- throw err
130
- })
101
+ const refPatternCache = Object.assign(new Map(), { braces: new Map() })
102
+ const loadOpts = { cacheDir, fetch, gitPlugins, progress, startDir, refPatternCache }
103
+ return collectFiles(sourcesByUrl, loadOpts, fetchConcurrency).then(buildAggregate, (err) => {
104
+ progress && progress.terminate()
105
+ throw err
106
+ })
131
107
  })
132
108
  }
133
109
 
110
+ async function collectFiles (sourcesByUrl, loadOpts, concurrency) {
111
+ const tasks = [...sourcesByUrl.entries()].map(([url, sources]) => [
112
+ () => loadRepository(url, Object.assign({ fetchTags: tagsSpecified(sources) }, loadOpts)),
113
+ ({ repo, authStatus }) =>
114
+ Promise.all(
115
+ sources.map((source) => {
116
+ // NOTE if repository is managed (has a url property), we can assume the remote name is origin
117
+ // TODO if the repo has no remotes, then remoteName should be undefined
118
+ const remoteName = repo.url ? 'origin' : source.remote || 'origin'
119
+ return collectFilesFromSource(source, repo, remoteName, authStatus)
120
+ })
121
+ ),
122
+ ])
123
+ let rejection, started
124
+ const startedContinuations = []
125
+ const recordRejection = (err) => {
126
+ rejection = err
127
+ }
128
+ const runTask = (primary, continuation, idx) =>
129
+ primary().then((value) => {
130
+ if (!rejection) startedContinuations[idx] = continuation(value).catch(recordRejection)
131
+ }, recordRejection)
132
+ if (tasks.length > concurrency) {
133
+ started = []
134
+ const pending = []
135
+ for (const [primary, continuation] of tasks) {
136
+ const current = runTask(primary, continuation, started.length).finally(() =>
137
+ pending.splice(pending.indexOf(current), 1)
138
+ )
139
+ started.push(current)
140
+ if (pending.push(current) < concurrency) continue
141
+ await Promise.race(pending)
142
+ if (rejection) break
143
+ }
144
+ } else {
145
+ started = tasks.map(([primary, continuation], idx) => runTask(primary, continuation, idx))
146
+ }
147
+ return Promise.all(started).then(() =>
148
+ Promise.all(startedContinuations).then((result) => {
149
+ if (rejection) throw rejection
150
+ return result
151
+ })
152
+ )
153
+ }
154
+
134
155
  function buildAggregate (componentVersionBuckets) {
135
156
  return [
136
157
  ...flattenDeep(componentVersionBuckets)
@@ -148,17 +169,15 @@ function buildAggregate (componentVersionBuckets) {
148
169
  }
149
170
 
150
171
  async function loadRepository (url, opts) {
151
- let dir
152
- let repo
153
- let authStatus
172
+ let authStatus, dir, repo
173
+ const cache = { [REF_PATTERN_CACHE_KEY]: opts.refPatternCache }
154
174
  if (~url.indexOf(':') && GIT_URI_DETECTOR_RX.test(url)) {
155
- let displayUrl
156
- let credentials
175
+ let credentials, displayUrl
157
176
  ;({ displayUrl, url, credentials } = extractCredentials(url))
158
177
  const { cacheDir, fetch, fetchTags, gitPlugins, progress } = opts
159
178
  dir = ospath.join(cacheDir, generateCloneFolderName(displayUrl))
160
179
  // NOTE the presence of the url property on the repo object implies the repository is remote
161
- repo = { cache: {}, dir, fs, gitdir: dir, noCheckout: true, url }
180
+ repo = { cache, dir, fs, gitdir: dir, noCheckout: true, url }
162
181
  const validStateFile = ospath.join(dir, VALID_STATE_FILENAME)
163
182
  try {
164
183
  await fsp.access(validStateFile)
@@ -183,7 +202,7 @@ async function loadRepository (url, opts) {
183
202
  authStatus = await git.getConfig(Object.assign({ path: 'remote.origin.private' }, repo))
184
203
  }
185
204
  } catch (gitErr) {
186
- await rmdir(dir)
205
+ await fsp['rm' in fsp ? 'rm' : 'rmdir'](dir, { recursive: true, force: true })
187
206
  if (gitErr.rethrow) throw transformGitCloneError(gitErr, displayUrl)
188
207
  const fetchOpts = buildFetchOptions(repo, progress, displayUrl, credentials, gitPlugins, fetchTags, 'clone')
189
208
  await git
@@ -194,7 +213,7 @@ async function loadRepository (url, opts) {
194
213
  authStatus = credentials ? 'auth-embedded' : credentialManager.status({ url }) ? 'auth-required' : undefined
195
214
  return git.setConfig(Object.assign({ path: 'remote.origin.private', value: authStatus }, repo))
196
215
  })
197
- .catch(async (cloneErr) => {
216
+ .catch((cloneErr) => {
198
217
  // FIXME triggering the error handler here causes assertion problems in the test suite
199
218
  //if (fetchOpts.onProgress) fetchOpts.onProgress.finish(cloneErr)
200
219
  throw transformGitCloneError(cloneErr, displayUrl)
@@ -204,9 +223,7 @@ async function loadRepository (url, opts) {
204
223
  }
205
224
  } else if (await isDirectory((dir = expandPath(url, { dot: opts.startDir })))) {
206
225
  const gitdir = ospath.join(dir, '.git')
207
- repo = (await isDirectory(gitdir))
208
- ? { cache: {}, dir, fs, gitdir }
209
- : { cache: {}, dir, fs, gitdir: dir, noCheckout: true }
226
+ repo = (await isDirectory(gitdir)) ? { cache, dir, fs, gitdir } : { cache, dir, fs, gitdir: dir, noCheckout: true }
210
227
  try {
211
228
  await git.resolveRef(Object.assign({ ref: 'HEAD', depth: 1 }, repo))
212
229
  } catch {
@@ -250,99 +267,102 @@ async function collectFilesFromSource (source, repo, remoteName, authStatus) {
250
267
  async function selectReferences (source, repo, remote) {
251
268
  let { branches: branchPatterns, tags: tagPatterns, worktrees: worktreePatterns = '.' } = source
252
269
  const isBare = repo.noCheckout
270
+ const patternCache = repo.cache[REF_PATTERN_CACHE_KEY]
253
271
  const noWorktree = repo.url ? undefined : null
254
272
  const refs = new Map()
255
- if (tagPatterns) {
256
- tagPatterns = Array.isArray(tagPatterns)
273
+ if (
274
+ tagPatterns &&
275
+ (tagPatterns = Array.isArray(tagPatterns)
257
276
  ? tagPatterns.map((pattern) => String(pattern))
258
- : String(tagPatterns).split(CSV_RX)
259
- if (tagPatterns.length) {
260
- const tags = await git.listTags(repo)
261
- for (const shortname of tags.length ? matcher(tags, tagPatterns) : tags) {
277
+ : splitRefPatterns(String(tagPatterns))).length
278
+ ) {
279
+ const tags = await git.listTags(repo)
280
+ if (tags.length) {
281
+ for (const shortname of filterRefs(tags, tagPatterns, patternCache)) {
262
282
  // NOTE tags are stored using symbol keys to distinguish them from branches
263
283
  refs.set(Symbol(shortname), { shortname, fullname: 'tags/' + shortname, type: 'tag', head: noWorktree })
264
284
  }
265
285
  }
266
286
  }
267
- if (branchPatterns) {
268
- if (worktreePatterns) {
269
- if (worktreePatterns === '.') {
270
- worktreePatterns = ['.']
271
- } else if (worktreePatterns === true) {
272
- worktreePatterns = ['.', '*']
273
- } else {
274
- worktreePatterns = Array.isArray(worktreePatterns)
275
- ? worktreePatterns.map((pattern) => String(pattern))
276
- : String(worktreePatterns).split(CSV_RX)
277
- }
287
+ if (!branchPatterns) return [...refs.values()]
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
+ }
298
+ }
299
+ const branchPatternsString = String(branchPatterns)
300
+ if (branchPatternsString === 'HEAD' || branchPatternsString === '.') {
301
+ const currentBranch = await getCurrentBranchName(repo, remote)
302
+ if (currentBranch) {
303
+ branchPatterns = [currentBranch]
304
+ } else if (isBare) {
305
+ return [...refs.values()]
306
+ } else {
307
+ // NOTE current branch is undefined when HEAD is detached
308
+ const head = worktreePatterns[0] === '.' ? repo.dir : noWorktree
309
+ refs.set('HEAD', { shortname: 'HEAD', fullname: 'HEAD', type: 'branch', detached: true, head })
310
+ return [...refs.values()]
278
311
  }
279
- const branchPatternsString = String(branchPatterns)
280
- if (branchPatternsString === 'HEAD' || branchPatternsString === '.') {
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('.'))) {
281
320
  const currentBranch = await getCurrentBranchName(repo, remote)
282
321
  if (currentBranch) {
283
- branchPatterns = [currentBranch]
284
- } else {
285
- if (!isBare) {
286
- // NOTE current branch is undefined when HEAD is detached
287
- const head = worktreePatterns[0] === '.' ? repo.dir : noWorktree
288
- refs.set('HEAD', { shortname: 'HEAD', fullname: 'HEAD', type: 'branch', detached: true, head })
289
- }
290
- return [...refs.values()]
291
- }
292
- } else if (
293
- (branchPatterns = Array.isArray(branchPatterns)
294
- ? branchPatterns.map((pattern) => String(pattern))
295
- : branchPatternsString.split(CSV_RX)).length
296
- ) {
297
- let headBranchIdx
298
- // NOTE we can assume at least two entries if HEAD or . are present
299
- if (~(headBranchIdx = branchPatterns.indexOf('HEAD')) || ~(headBranchIdx = branchPatterns.indexOf('.'))) {
300
- const currentBranch = await getCurrentBranchName(repo, remote)
301
- if (currentBranch) {
302
- // NOTE ignore if current branch is already in list
303
- if (~branchPatterns.indexOf(currentBranch)) {
304
- branchPatterns.splice(headBranchIdx, 1)
305
- } else {
306
- branchPatterns[headBranchIdx] = currentBranch
307
- }
308
- } else {
309
- if (!isBare) {
310
- let head = noWorktree
311
- if (worktreePatterns[0] === '.') {
312
- worktreePatterns = worktreePatterns.slice(1)
313
- head = repo.dir
314
- }
315
- // NOTE current branch is undefined when HEAD is detached
316
- refs.set('HEAD', { shortname: 'HEAD', fullname: 'HEAD', type: 'branch', detached: true, head })
317
- }
322
+ // NOTE ignore if current branch is already in list
323
+ if (~branchPatterns.indexOf(currentBranch)) {
318
324
  branchPatterns.splice(headBranchIdx, 1)
325
+ } else {
326
+ branchPatterns[headBranchIdx] = currentBranch
319
327
  }
328
+ } else if (isBare) {
329
+ branchPatterns.splice(headBranchIdx, 1)
330
+ } else {
331
+ let head = noWorktree
332
+ if (worktreePatterns[0] === '.') {
333
+ worktreePatterns = worktreePatterns.slice(1)
334
+ head = repo.dir
335
+ }
336
+ // NOTE current branch is undefined when HEAD is detached
337
+ refs.set('HEAD', { shortname: 'HEAD', fullname: 'HEAD', type: 'branch', detached: true, head })
338
+ branchPatterns.splice(headBranchIdx, 1)
320
339
  }
321
- } else {
322
- return [...refs.values()]
323
340
  }
324
- // NOTE isomorphic-git includes HEAD in list of remote branches (see https://isomorphic-git.org/docs/listBranches)
325
- const remoteBranches = (await git.listBranches(Object.assign({ remote }, repo))).filter((it) => it !== 'HEAD')
326
- if (remoteBranches.length) {
327
- for (const shortname of matcher(remoteBranches, branchPatterns)) {
328
- const fullname = 'remotes/' + remote + '/' + shortname
329
- refs.set(shortname, { shortname, fullname, type: 'branch', remote, head: noWorktree })
330
- }
341
+ } else {
342
+ return [...refs.values()]
343
+ }
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 })
331
350
  }
332
- // NOTE only consider local branches if repo has a worktree or there are no remote tracking branches
333
- if (!isBare) {
334
- const localBranches = await git.listBranches(repo)
335
- if (localBranches.length) {
336
- const worktrees = await findWorktrees(repo, worktreePatterns)
337
- for (const shortname of matcher(localBranches, branchPatterns)) {
338
- const head = worktrees.get(shortname) || noWorktree
339
- refs.set(shortname, { shortname, fullname: 'heads/' + shortname, type: 'branch', head })
340
- }
351
+ }
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 })
341
360
  }
342
- } else if (!remoteBranches.length) {
343
- // QUESTION should local branches be used if the only remote branch is HEAD?
344
- const localBranches = await git.listBranches(repo)
345
- for (const shortname of localBranches.length ? matcher(localBranches, branchPatterns) : localBranches) {
361
+ }
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)) {
346
366
  refs.set(shortname, { shortname, fullname: 'heads/' + shortname, type: 'branch', head: noWorktree })
347
367
  }
348
368
  }
@@ -417,68 +437,53 @@ function collectFilesFromStartPath (startPath, repo, authStatus, ref, worktreePa
417
437
 
418
438
  function readFilesFromWorktree (worktreePath, startPath) {
419
439
  const cwd = ospath.join(worktreePath, startPath)
420
- return fsp
421
- .stat(cwd)
422
- .catch(() => {
440
+ return fsp.stat(cwd).then(
441
+ (startPathStat) => {
442
+ if (!startPathStat.isDirectory()) throw new Error(`the start path '${startPath}' is not a directory`)
443
+ return srcFs(cwd)
444
+ },
445
+ () => {
423
446
  throw new Error(`the start path '${startPath}' does not exist`)
424
- })
425
- .then((stat) => {
426
- if (!stat.isDirectory()) throw new Error(`the start path '${startPath}' is not a directory`)
427
- return new Promise((resolve, reject) =>
428
- vfs
429
- .src(CONTENT_SRC_GLOB, Object.assign({ cwd }, CONTENT_SRC_OPTS))
430
- .on('error', (err) => {
431
- if (err.code === 'ENOENT' && err.syscall === 'stat') {
432
- try {
433
- if (fs.lstatSync(err.path).isSymbolicLink()) {
434
- err.message = `Broken symbolic link detected at ${ospath.relative(cwd, err.path)}`
435
- }
436
- } catch {}
437
- } else if (err.code === 'ELOOP') {
438
- err.message = `Symbolic link cycle detected at ${ospath.relative(cwd, err.path)}`
439
- }
440
- reject(err)
441
- })
442
- .pipe(relativizeFiles())
443
- .pipe(collectFiles(resolve))
444
- )
445
- })
446
- }
447
-
448
- /**
449
- * Transforms the path of every file in the stream to a relative posix path.
450
- *
451
- * Applies a mapping function to all files in the stream so they end up with a
452
- * posixified path relative to the file's base instead of the filesystem root.
453
- * This mapper also filters out any directories (indicated by file.isNull())
454
- * that got caught up in the glob.
455
- */
456
- function relativizeFiles () {
457
- return map((file, enc, next) => {
458
- if (file.isNull()) {
459
- next()
460
- } else {
461
- next(
462
- null,
463
- new File({
464
- path: posixify ? posixify(file.relative) : file.relative,
465
- contents: file.contents,
466
- stat: file.stat,
467
- src: { abspath: file.path },
468
- })
469
- )
470
447
  }
471
- })
448
+ )
472
449
  }
473
450
 
474
- function collectFiles (done) {
475
- const accum = []
476
- return map(
477
- (file, enc, next) => {
478
- accum.push(file)
479
- next()
480
- },
481
- () => done(accum)
451
+ function srcFs (cwd) {
452
+ return new Promise((resolve, reject, cache = {}, files = []) =>
453
+ pipeline(
454
+ globStream(CONTENT_SRC_GLOB, Object.assign({ cache, cwd }, CONTENT_SRC_OPTS)),
455
+ map(({ path: abspathPosix }, _, next) => {
456
+ if (Array.isArray(cache[abspathPosix])) return next() // optimization, but not guaranteed
457
+ const abspath = posixify ? ospath.normalize(abspathPosix) : abspathPosix
458
+ const relpath = abspath.substr(cwd.length + 1)
459
+ symlinkAwareStat(abspath).then(
460
+ (stat) => {
461
+ if (stat.isDirectory()) return next()
462
+ fsp.readFile(abspath).then(
463
+ (contents) => {
464
+ files.push(new File({ path: posixify ? posixify(relpath) : relpath, contents, stat, src: { abspath } }))
465
+ next()
466
+ },
467
+ (readErr) => {
468
+ next(Object.assign(readErr, { message: readErr.message.replace(`'${abspath}'`, relpath) }))
469
+ }
470
+ )
471
+ },
472
+ (statErr) => {
473
+ if (statErr.symlink) {
474
+ statErr.message =
475
+ statErr.code === 'ELOOP'
476
+ ? `Symbolic link cycle detected at ${relpath}`
477
+ : `Broken symbolic link detected at ${relpath}`
478
+ } else {
479
+ statErr.message = statErr.message.replace(`'${abspath}'`, relpath)
480
+ }
481
+ next(statErr)
482
+ }
483
+ )
484
+ }),
485
+ (err) => (err ? reject(err) : resolve(files))
486
+ )
482
487
  )
483
488
  }
484
489
 
@@ -493,13 +498,13 @@ function readFilesFromGitTree (repo, oid, startPath) {
493
498
  }
494
499
 
495
500
  function getGitTreeAtStartPath (repo, oid, startPath) {
496
- return git
497
- .readTree(Object.assign({ oid, filepath: startPath }, repo))
498
- .catch((err) => {
501
+ return git.readTree(Object.assign({ oid, filepath: startPath }, repo)).then(
502
+ (result) => Object.assign(result, { dirname: startPath }),
503
+ (err) => {
499
504
  const m = err instanceof ObjectTypeError && err.data.expected === 'tree' ? 'is not a directory' : 'does not exist'
500
505
  throw new Error(`the start path '${startPath}' ${m}`)
501
- })
502
- .then((result) => Object.assign(result, { dirname: startPath }))
506
+ }
507
+ )
503
508
  }
504
509
 
505
510
  function srcGitTree (repo, root, start) {
@@ -518,9 +523,11 @@ function createGitTreeWalker (repo, root, filter) {
518
523
  walk (start) {
519
524
  return (
520
525
  visitGitTree(this, repo, root, filter, start)
521
- .then(() => this.emit('end'))
522
526
  // NOTE if error is thrown, promises already being resolved won't halt
523
- .catch((err) => this.emit('error', err))
527
+ .then(
528
+ () => this.emit('end'),
529
+ (err) => this.emit('error', err)
530
+ )
524
531
  )
525
532
  },
526
533
  })
@@ -543,8 +550,15 @@ function visitGitTree (emitter, repo, root, filter, parent, dirname = '', follow
543
550
  let mode
544
551
  if (entry.mode === SYMLINK_FILE_MODE) {
545
552
  reads.push(
546
- readGitSymlink(repo, root, parent, entry, following)
547
- .catch((err) => {
553
+ readGitSymlink(repo, root, parent, entry, following).then(
554
+ (target) => {
555
+ if (target.type === 'tree') {
556
+ return visitGitTree(emitter, repo, root, filter, target, vfilePath, new Set(following).add(entry.oid))
557
+ } else if (target.type === 'blob' && filterVerdict === true && (mode = FILE_MODES[target.mode])) {
558
+ emitter.emit('entry', Object.assign({ mode, oid: target.oid, path: vfilePath }, repo))
559
+ }
560
+ },
561
+ (err) => {
548
562
  // NOTE this error could be caught after promise chain has already been rejected
549
563
  if (err instanceof NotFoundError) {
550
564
  err.message = `Broken symbolic link detected at ${vfilePath}`
@@ -552,14 +566,8 @@ function visitGitTree (emitter, repo, root, filter, parent, dirname = '', follow
552
566
  err.message = `Symbolic link cycle detected at ${vfilePath}`
553
567
  }
554
568
  throw err
555
- })
556
- .then((target) => {
557
- if (target.type === 'tree') {
558
- return visitGitTree(emitter, repo, root, filter, target, vfilePath, new Set(following).add(entry.oid))
559
- } else if (target.type === 'blob' && filterVerdict === true && (mode = FILE_MODES[target.mode])) {
560
- emitter.emit('entry', Object.assign({ mode, oid: target.oid, path: vfilePath }, repo))
561
- }
562
- })
569
+ }
570
+ )
563
571
  )
564
572
  } else if ((mode = FILE_MODES[entry.mode])) {
565
573
  emitter.emit('entry', Object.assign({ mode, oid: entry.oid, path: vfilePath }, repo))
@@ -577,7 +585,7 @@ function readGitSymlink (repo, root, parent, { oid }, following) {
577
585
  let targetParent
578
586
  if (parent.dirname) {
579
587
  const dirname = parent.dirname + '/'
580
- target = path.join(dirname, target)
588
+ target = path.join(dirname, target) // join doesn't remove trailing separator
581
589
  if (target.startsWith(dirname)) {
582
590
  target = target.substr(dirname.length)
583
591
  targetParent = parent
@@ -585,10 +593,12 @@ function readGitSymlink (repo, root, parent, { oid }, following) {
585
593
  targetParent = root
586
594
  }
587
595
  } else {
588
- target = path.normalize(target)
596
+ target = path.normalize(target) // normalize doesn't remove trailing separator
589
597
  targetParent = root
590
598
  }
591
- return readGitObjectAtPath(repo, root, targetParent, target.split('/'), following)
599
+ const targetSegments = target.split('/')
600
+ if (!targetSegments[targetSegments.length - 1]) targetSegments.pop()
601
+ return readGitObjectAtPath(repo, root, targetParent, targetSegments, following)
592
602
  })
593
603
  }
594
604
  const err = { name: 'SymbolicLinkCycleError', code: 'SymbolicLinkCycleError', oid }
@@ -664,7 +674,7 @@ function loadComponentDescriptor (files, ref, version) {
664
674
  matched = version[refname]
665
675
  } else if (
666
676
  !Object.entries(version).some(([pattern, replacement]) => {
667
- const result = refname.replace(makePicomatchRx(pattern, PICOMATCH_VERSION_OPTS), '\0' + replacement)
677
+ const result = refname.replace(makeMatcherRx(pattern, VERSION_MATCHER_OPTS), '\0' + replacement)
668
678
  if (result === refname) return false
669
679
  matched = result.substr(1)
670
680
  return true
@@ -891,47 +901,20 @@ function resolveRemoteUrl (repo, remoteName) {
891
901
  * @return {Boolean} A flag indicating whether the URL matches a directory on the local filesystem.
892
902
  */
893
903
  function isDirectory (url) {
894
- return fsp
895
- .stat(url)
896
- .then((stat) => stat.isDirectory())
897
- .catch(invariably.false)
904
+ return fsp.stat(url).then((stat) => stat.isDirectory(), invariably.false)
898
905
  }
899
906
 
900
- /**
901
- * Removes the specified directory (including all of its contents) or file.
902
- * Equivalent to fs.promises.rmdir(dir, { recursive: true }) in Node 12.
903
- */
904
- function rmdir (dir) {
905
- return fsp
906
- .readdir(dir, { withFileTypes: true })
907
- .then((lst) =>
908
- Promise.all(
909
- lst.map((it) =>
910
- it.isDirectory()
911
- ? rmdir(ospath.join(dir, it.name))
912
- : fsp.unlink(ospath.join(dir, it.name)).catch((unlinkErr) => {
913
- if (unlinkErr.code !== 'ENOENT') throw unlinkErr
914
- })
915
- )
916
- )
917
- )
918
- .then(() => fsp.rmdir(dir))
919
- .catch((err) => {
920
- if (err.code === 'ENOENT') return
921
- if (err.code === 'ENOTDIR') {
922
- return fsp.unlink(dir).catch((unlinkErr) => {
923
- if (unlinkErr.code !== 'ENOENT') throw unlinkErr
924
- })
925
- }
926
- throw err
907
+ function symlinkAwareStat (path_) {
908
+ return fsp.lstat(path_).then((lstat) => {
909
+ if (!lstat.isSymbolicLink()) return lstat
910
+ return fsp.stat(path_).catch((statErr) => {
911
+ throw Object.assign(statErr, { symlink: true })
927
912
  })
913
+ })
928
914
  }
929
915
 
930
- function tagsSpecified (sources, defaultTags) {
931
- return ~sources.findIndex((source) => {
932
- const tags = source.tags || defaultTags || []
933
- return Array.isArray(tags) ? tags.length : true
934
- })
916
+ function tagsSpecified (sources) {
917
+ return sources.some(({ tags }) => tags && (Array.isArray(tags) ? tags.length : true))
935
918
  }
936
919
 
937
920
  function loadGitPlugins (gitConfig, networkConfig, startDir) {
@@ -969,17 +952,16 @@ function ensureCacheDir (preferredCacheDir, startDir) {
969
952
  ? getCacheDir('antora' + (process.env.NODE_ENV === 'test' ? '-test' : '')) || ospath.resolve('.antora/cache')
970
953
  : expandPath(preferredCacheDir, { dot: startDir })
971
954
  const cacheDir = ospath.join(baseCacheDir, CONTENT_CACHE_FOLDER)
972
- return fsp
973
- .mkdir(cacheDir, { recursive: true })
974
- .then(() => cacheDir)
975
- .catch((err) => {
955
+ return fsp.mkdir(cacheDir, { recursive: true }).then(
956
+ () => cacheDir,
957
+ (err) => {
976
958
  throw Object.assign(err, { message: `Failed to create content cache directory: ${cacheDir}; ${err.message}` })
977
- })
959
+ }
960
+ )
978
961
  }
979
962
 
980
963
  function transformGitCloneError (err, displayUrl) {
981
- let wrappedMsg
982
- let trimMessage
964
+ let wrappedMsg, trimMessage
983
965
  if (HTTP_ERROR_CODE_RX.test(err.code)) {
984
966
  switch (err.data.statusCode) {
985
967
  case 401:
@@ -1010,6 +992,10 @@ function transformGitCloneError (err, displayUrl) {
1010
992
  return wrappedErr
1011
993
  }
1012
994
 
995
+ function splitRefPatterns (str) {
996
+ return ~str.indexOf('{') ? str.split(VENTILATED_CSV_RX) : str.split(CSV_RX)
997
+ }
998
+
1013
999
  function coerceToString (value) {
1014
1000
  return value == null ? '' : String(value)
1015
1001
  }
@@ -1022,11 +1008,11 @@ function findWorktrees (repo, patterns) {
1022
1008
  if (!patterns.length) return new Map()
1023
1009
  const linkedOnly = patterns[0] === '.' ? !(patterns = patterns.slice(1)) : true
1024
1010
  let worktreesDir
1011
+ const patternCache = repo.cache[REF_PATTERN_CACHE_KEY]
1025
1012
  return (patterns.length
1026
1013
  ? fsp
1027
1014
  .readdir((worktreesDir = ospath.join(repo.dir, '.git', 'worktrees')))
1028
- .catch(invariably.emptyArray)
1029
- .then((worktreeNames) => matcher(worktreeNames, [...patterns]))
1015
+ .then((worktreeNames) => filterRefs(worktreeNames, patterns, patternCache), invariably.emptyArray)
1030
1016
  .then((worktreeNames) =>
1031
1017
  worktreeNames.length
1032
1018
  ? Promise.all(
package/lib/constants.js CHANGED
@@ -2,25 +2,14 @@
2
2
 
3
3
  module.exports = Object.freeze({
4
4
  COMPONENT_DESC_FILENAME: 'antora.yml',
5
- CONTENT_CACHE_FOLDER: 'content/2',
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
- debug: false,
15
- dot: true,
16
- fastpaths: false,
17
- lookbehinds: false,
18
- noextglob: true,
19
- noglobstar: true,
20
- nonegate: true,
21
- noquantifiers: true,
22
- strictSlashes: true,
23
- },
12
+ REF_PATTERN_CACHE_KEY: Symbol('RefPatternCache'),
24
13
  SYMLINK_FILE_MODE: '120000',
25
14
  VALID_STATE_FILENAME: 'valid',
26
15
  })
@@ -0,0 +1,42 @@
1
+ 'use strict'
2
+
3
+ const { makeMatcherRx, refMatcherOpts: getMatcherOpts, MATCH_ALL_RX } = require('./matcher')
4
+
5
+ function compileRx (pattern, opts) {
6
+ if (pattern === '*' || pattern === '**') return MATCH_ALL_RX
7
+ return pattern.charAt() === '!' // do our own negate
8
+ ? Object.defineProperty(makeMatcherRx(pattern.substr(1), opts), 'negated', { value: true })
9
+ : makeMatcherRx(pattern, opts)
10
+ }
11
+
12
+ function createMatcher (patterns, cache, opts) {
13
+ const rxs = patterns.map(
14
+ (pattern) =>
15
+ cache.get(pattern) || cache.set(pattern, compileRx(pattern, opts || (opts = getMatcherOpts(cache)))).get(pattern)
16
+ )
17
+ if (rxs[0].negated) rxs.unshift(MATCH_ALL_RX)
18
+ return (candidate) => {
19
+ let matched
20
+ for (const rx of rxs) {
21
+ let voteIfMatched = true
22
+ if (matched) {
23
+ if (!rx.negated) continue
24
+ voteIfMatched = false
25
+ } else if (rx.negated) {
26
+ continue
27
+ }
28
+ if (rx.test(candidate)) matched = voteIfMatched
29
+ }
30
+ return matched
31
+ }
32
+ }
33
+
34
+ function filterRefs (candidates, patterns, cache = Object.assign(new Map(), { braces: new Map() })) {
35
+ const isMatch = createMatcher(patterns, cache)
36
+ return candidates.reduce((accum, candidate) => {
37
+ if (isMatch(candidate)) accum.push(candidate)
38
+ return accum
39
+ }, [])
40
+ }
41
+
42
+ module.exports = filterRefs
@@ -3,18 +3,17 @@
3
3
  const { homedir } = require('os')
4
4
  const expandPath = require('@antora/expand-path-helper')
5
5
  const { promises: fsp } = require('fs')
6
+ const invariably = require('./invariably')
6
7
  const ospath = require('path')
7
8
 
8
9
  class GitCredentialManagerStore {
9
10
  configure ({ config, startDir }) {
10
11
  this.entries = undefined
11
12
  this.urls = {}
12
- if ((this.contents = (config = config || {}).contents)) {
13
+ if ((this.contents = (config = config || {}).contents) || !config.path) {
13
14
  this.path = undefined
14
- } else if (config.path) {
15
- this.path = expandPath(config.path, { dot: startDir })
16
15
  } else {
17
- this.path = undefined
16
+ this.path = expandPath(config.path, { dot: startDir })
18
17
  }
19
18
  return this
20
19
  }
@@ -28,10 +27,7 @@ class GitCredentialManagerStore {
28
27
  contentsPromise = Promise.resolve(this.contents)
29
28
  delimiter = /[,\n]/
30
29
  } else if (this.path) {
31
- contentsPromise = fsp
32
- .access(this.path)
33
- .then(() => fsp.readFile(this.path, 'utf8'))
34
- .catch(() => undefined)
30
+ contentsPromise = fsp.access(this.path).then(() => fsp.readFile(this.path, 'utf8'), invariably.void)
35
31
  } else {
36
32
  const homeGitCredentialsPath = ospath.join(homedir(), '.git-credentials')
37
33
  const xdgConfigGitCredentialsPath = ospath.join(
@@ -45,8 +41,7 @@ class GitCredentialManagerStore {
45
41
  .catch(() =>
46
42
  fsp
47
43
  .access(xdgConfigGitCredentialsPath)
48
- .then(() => fsp.readFile(xdgConfigGitCredentialsPath, 'utf8'))
49
- .catch(() => undefined)
44
+ .then(() => fsp.readFile(xdgConfigGitCredentialsPath, 'utf8'), invariably.void)
50
45
  )
51
46
  }
52
47
  contentsPromise.then((contents) => {
@@ -26,7 +26,7 @@ async function mergeBuffers (data) {
26
26
  }
27
27
 
28
28
  module.exports = ({ httpProxy, httpsProxy, noProxy }, userAgent) => {
29
- if (httpsProxy || httpProxy) {
29
+ if ((httpsProxy || httpProxy) && noProxy !== '*') {
30
30
  const { HttpProxyAgent, HttpsProxyAgent } = require('hpagent')
31
31
  const shouldProxy = require('should-proxy')
32
32
  return {
package/lib/index.js CHANGED
@@ -5,7 +5,7 @@
5
5
  *
6
6
  * Responsible for aggregating the content from multiple repositories and
7
7
  * references into a raw aggregate of virtual files that can be organized by a
8
- * subsequent step in the pipeline.
8
+ * subsequent step in the generator.
9
9
  *
10
10
  * @namespace content-aggregator
11
11
  */
@@ -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,26 +1,21 @@
1
1
  'use strict'
2
2
 
3
- const { expand: expandBraces } = 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
- const RX_ESCAPE_EXCEPT_GLOB = /[.+?^${}()|[\]\\]/g
11
- const RX_MAGIC_DETECTOR = /[*{]/
12
- const RX_QUESTION_MARK = /\?/g
13
- const PICOMATCH_OPTS = { nobracket: true, noextglob: true, noglobstar: true, noquantifiers: true }
14
- const PICOMATCH_NEGATED_OPTS = { nobracket: true, noextglob: true, noquantifiers: true }
9
+ const NON_GLOB_SPECIAL_CHARS_RX = /[.+?^${}()|[\]\\]/g
10
+ const RX_MAGIC_DETECTOR = /[*{(]/
15
11
 
16
12
  function resolvePathGlobs (base, patterns, listDirents, retrievePath, tree = { path: '' }) {
17
13
  return patterns.reduce((paths, pattern) => {
18
14
  if (pattern.charAt() === '!') {
19
15
  return paths.then((resolvedPaths) => {
20
16
  if (resolvedPaths.length) {
21
- if (~pattern.indexOf('?')) pattern = pattern.replace(RX_QUESTION_MARK, '\\?')
22
- const rx = makePicomatchRx(pattern, PICOMATCH_NEGATED_OPTS)
23
- return resolvedPaths.filter(rx.test.bind(rx))
17
+ const rx = makeMatcherRx(pattern.substr(1), MATCHER_OPTS)
18
+ return resolvedPaths.filter((it) => !rx.test(it))
24
19
  } else {
25
20
  return resolvedPaths
26
21
  }
@@ -38,15 +33,14 @@ async function glob (base, patternSegments, listDirents, retrievePath, { oid, pa
38
33
  let patternSegment = patternSegments[0]
39
34
  patternSegments = patternSegments.slice(1)
40
35
  if (RX_MAGIC_DETECTOR.test(patternSegment)) {
41
- let isMatch
42
- let explicit
36
+ let isMatch, explicit
43
37
  if (patternSegment === '*') {
44
38
  isMatch = (it) => it.charAt() !== '.'
39
+ } else if (~patternSegment.indexOf('(')) {
40
+ isMatch = (isMatch = makeMatcherRx(patternSegment, MATCHER_OPTS)).test.bind(isMatch)
45
41
  } else if (~patternSegment.indexOf('{')) {
46
42
  if (globbed) {
47
- if (patternSegment.charAt() === '!') patternSegment = '\\' + patternSegment
48
- if (~patternSegment.indexOf('?')) patternSegment = patternSegment.replace(RX_QUESTION_MARK, '\\?')
49
- isMatch = (isMatch = makePicomatchRx(patternSegment, PICOMATCH_OPTS)).test.bind(isMatch)
43
+ isMatch = (isMatch = makeMatcherRx(patternSegment, MATCHER_OPTS)).test.bind(isMatch)
50
44
  } else if (~patternSegment.indexOf('*')) {
51
45
  const [wildPatterns, literals] = expandBraces(patternSegment).reduce(
52
46
  ([wild, literal], it) => (~it.indexOf('*') ? [[...wild, it], literal] : [wild, [...literal, it]]),
@@ -58,7 +52,7 @@ async function glob (base, patternSegments, listDirents, retrievePath, { oid, pa
58
52
  return expandBraces(patternSegment).map((it) => joinPath(path, it))
59
53
  }
60
54
  } else {
61
- isMatch = (isMatch = makeMatcherRx(patternSegment)).test.bind(isMatch)
55
+ isMatch = (isMatch = makeSingleMatcherRx(patternSegment)).test.bind(isMatch)
62
56
  }
63
57
  let dirents = await listDirents(base, oid || path)
64
58
  if (explicit) dirents = dirents.filter((dirent) => !explicit.has(dirent.name))
@@ -90,22 +84,15 @@ async function glob (base, patternSegments, listDirents, retrievePath, { oid, pa
90
84
  })
91
85
  } else if ((patternSegment += '/' + patternSegments.join('/')).indexOf('{')) {
92
86
  return expandBraces(patternSegment).map((it) => joinPath(path, it))
93
- } else {
94
- return [joinPath(path, patternSegment)]
95
87
  }
88
+ return [joinPath(path, patternSegment)]
96
89
  } else if (globbed) {
97
90
  return (await retrievePath(base, { oid, path }, patternSegment)) ? [joinPath(path, patternSegment)] : []
98
- } else {
99
- return [joinPath(path, patternSegment)]
100
91
  }
92
+ return [joinPath(path, patternSegment)]
101
93
  }
102
94
  }
103
95
 
104
- function regexpEscapeWithGlob (str) {
105
- // we don't escape "-" since it's meaningless in a literal
106
- return str.replace(RX_ESCAPE_EXCEPT_GLOB, '\\$&').replace('*', '.*')
107
- }
108
-
109
96
  function extractMagicBase (patternSegments, base) {
110
97
  let nextSegment
111
98
  if (patternSegments.length) {
@@ -124,22 +111,29 @@ function listDirentsFs (base, path) {
124
111
  function listDirentsGit (repo, treeOid) {
125
112
  return git
126
113
  .readTree(Object.assign({ oid: treeOid, filepath: '' }, repo))
127
- .then(({ tree: entries }) =>
128
- entries.map(({ type, oid, path: name }) => ({ name, oid, isDirectory: invariably[type === 'tree'] }))
114
+ .then(
115
+ ({ tree: entries }) =>
116
+ entries.map(({ type, oid, path: name }) => ({ name, oid, isDirectory: invariably[type === 'tree'] })),
117
+ invariably.emptyArray
129
118
  )
130
- .catch(invariably.emptyArray)
131
119
  }
132
120
 
133
121
  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
 
141
129
  function patternToRx (pattern) {
142
- return (pattern.charAt() === '.' ? '' : '(?!\\.)') + regexpEscapeWithGlob(pattern)
130
+ return (
131
+ (pattern.charAt() === '.' ? '' : '(?!\\.)') +
132
+ pattern
133
+ .replace(NON_GLOB_SPECIAL_CHARS_RX, '\\$&')
134
+ .replace('\\\\*', '\\x2a')
135
+ .replace('*', '.*?')
136
+ )
143
137
  }
144
138
 
145
139
  function readdirWithFileTypes (dir) {
@@ -147,10 +141,7 @@ function readdirWithFileTypes (dir) {
147
141
  }
148
142
 
149
143
  function retrievePathFs (base, { path }, subpath) {
150
- return fsp
151
- .access(base + '/' + joinPath(path, subpath))
152
- .then(invariably.true)
153
- .catch(invariably.false)
144
+ return fsp.access(base + '/' + joinPath(path, subpath)).then(invariably.true, invariably.false)
154
145
  }
155
146
 
156
147
  function retrievePathGit (repo, { oid }, filepath) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@antora/content-aggregator",
3
- "version": "3.0.0-alpha.8",
3
+ "version": "3.0.0-beta.3",
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)",
@@ -21,21 +21,19 @@
21
21
  "@antora/user-require-helper": "~2.0",
22
22
  "braces": "~3.0",
23
23
  "cache-directory": "~2.0",
24
- "camelcase-keys": "~6.2",
24
+ "camelcase-keys": "~7.0",
25
25
  "hpagent": "~0.1.0",
26
- "isomorphic-git": "~1.9",
26
+ "isomorphic-git": "~1.10",
27
27
  "js-yaml": "~4.1",
28
- "matcher": "~4.0",
29
28
  "multi-progress": "~4.0",
30
29
  "picomatch": "~2.3",
31
30
  "progress": "~2.0",
32
31
  "should-proxy": "~1.0",
33
32
  "simple-get": "~4.0",
34
- "vinyl": "~2.2",
35
- "vinyl-fs": "~3.0"
33
+ "vinyl": "~2.2"
36
34
  },
37
35
  "engines": {
38
- "node": ">=10.17.0"
36
+ "node": ">=12.21.0"
39
37
  },
40
38
  "files": [
41
39
  "lib/"
@@ -50,5 +48,5 @@
50
48
  "static site",
51
49
  "web publishing"
52
50
  ],
53
- "gitHead": "2e5695bea11fb5719989c329c97e66d36e29659f"
51
+ "gitHead": "45da95a2e2dea538379d2d9f42013d2208fb86c3"
54
52
  }