@antora/content-aggregator 3.0.0-alpha.7 → 3.0.0-beta.2

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
 
@@ -3,20 +3,20 @@
3
3
  const camelCaseKeys = require('camelcase-keys')
4
4
  const { createHash } = require('crypto')
5
5
  const createHttpPlugin = require('./git-plugin-http')
6
- const decodeUint8Data = require('./decode-uint8-data')
6
+ 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
13
14
  const getCacheDir = require('cache-directory')
14
15
  const GitCredentialManagerStore = require('./git-credential-manager-store')
15
- const git = require('isomorphic-git')
16
+ const git = require('./git')
16
17
  const { NotFoundError, ObjectTypeError, UnknownTransportError, UrlParseError } = git.Errors
17
18
  const invariably = { false: () => false, void: () => undefined, emptyArray: () => [] }
18
19
  const { makeRe: makePicomatchRx } = require('picomatch')
19
- const matcher = require('matcher')
20
20
  const MultiProgress = require('multi-progress')
21
21
  const ospath = require('path')
22
22
  const { posix: path } = ospath
@@ -24,18 +24,21 @@ const posixify = ospath.sep === '\\' ? (p) => p.replace(/\\/g, '/') : undefined
24
24
  const { fs: resolvePathGlobsFs, git: resolvePathGlobsGit } = require('./resolve-path-globs')
25
25
  const { Transform } = require('stream')
26
26
  const map = (transform, flush = undefined) => new Transform({ objectMode: true, transform, flush })
27
+ const userRequire = require('@antora/user-require-helper')
27
28
  const vfs = require('vinyl-fs')
28
29
  const yaml = require('js-yaml')
29
30
 
30
31
  const {
31
32
  COMPONENT_DESC_FILENAME,
32
33
  CONTENT_CACHE_FOLDER,
33
- CONTENT_GLOB,
34
+ CONTENT_SRC_GLOB,
35
+ CONTENT_SRC_OPTS,
34
36
  FILE_MODES,
35
37
  GIT_CORE,
36
38
  GIT_OPERATION_LABEL_LENGTH,
37
39
  GIT_PROGRESS_PHASES,
38
40
  PICOMATCH_VERSION_OPTS,
41
+ REF_PATTERN_CACHE_KEY,
39
42
  SYMLINK_FILE_MODE,
40
43
  VALID_STATE_FILENAME,
41
44
  } = require('./constants')
@@ -55,6 +58,7 @@ const SPACE_RX = / /g
55
58
  const SUPERFLUOUS_SEPARATORS_RX = /^\/+|\/+$|\/+(?=\/)/g
56
59
  const URL_AUTH_CLEANER_RX = /^(https?:\/\/)[^/@]*@/
57
60
  const URL_AUTH_EXTRACTOR_RX = /^(https?:\/\/)(?:([^/:@]+)?(?::([^/@]+)?)?@)?(.*)/
61
+ const URL_PORT_CLEANER_RX = /^([^/]+):[0-9]+(?=\/)/
58
62
 
59
63
  /**
60
64
  * Aggregates files from the specified content sources so they can be loaded
@@ -86,69 +90,96 @@ const URL_AUTH_EXTRACTOR_RX = /^(https?:\/\/)(?:([^/:@]+)?(?::([^/@]+)?)?@)?(.*)
86
90
  function aggregateContent (playbook) {
87
91
  const startDir = playbook.dir || '.'
88
92
  const { branches, editUrl, tags, sources } = playbook.content
89
- const { cacheDir, fetch, quiet } = playbook.runtime
90
- return ensureCacheDir(cacheDir, startDir).then((resolvedCacheDir) => {
91
- const gitPlugins = loadGitPlugins(
92
- Object.assign({ ensureGitSuffix: true }, playbook.git),
93
- playbook.network || {},
94
- startDir
95
- )
96
- const sourcesByUrl = sources.reduce(
97
- (accum, source) => accum.set(source.url, [...(accum.get(source.url) || []), source]),
98
- new Map()
99
- )
93
+ const sourceDefaults = { branches, editUrl, tags }
94
+ const { cacheDir: requestedCacheDir, fetch, quiet } = playbook.runtime
95
+ return ensureCacheDir(requestedCacheDir, startDir).then((cacheDir) => {
96
+ const gitConfig = Object.assign({ ensureGitSuffix: true }, playbook.git)
97
+ const gitPlugins = loadGitPlugins(gitConfig, playbook.network || {}, startDir)
98
+ const fetchConcurrency = Math.max(gitConfig.fetchConcurrency || Infinity, 1)
99
+ const sourcesByUrl = sources.reduce((accum, source) => {
100
+ return accum.set(source.url, [...(accum.get(source.url) || []), Object.assign({}, sourceDefaults, source)])
101
+ }, new Map())
100
102
  const progress = !quiet && createProgress(sourcesByUrl.keys(), process.stdout)
101
- return Promise.all(
102
- [...sourcesByUrl.entries()].map(([url, sources]) =>
103
- loadRepository(url, {
104
- cacheDir: resolvedCacheDir,
105
- gitPlugins,
106
- fetchTags: tagsSpecified(sources, tags),
107
- progress,
108
- fetch,
109
- startDir,
110
- }).then(({ repo, authStatus }) =>
111
- Promise.all(
112
- sources.map((source) => {
113
- source = Object.assign({ branches, editUrl, tags }, source)
114
- // NOTE if repository is managed (has a url), we can assume the remote name is origin
115
- // TODO if the repo has no remotes, then remoteName should be undefined
116
- const remoteName = repo.url ? 'origin' : source.remote || 'origin'
117
- return collectFilesFromSource(source, repo, remoteName, authStatus)
118
- })
119
- )
120
- )
121
- )
122
- )
123
- .then(buildAggregate)
124
- .catch((err) => {
125
- progress && progress.terminate()
126
- throw err
127
- })
103
+ const refPatternCache = Object.assign(new Map(), { braces: new Map() })
104
+ const loadOpts = { cacheDir, fetch, gitPlugins, progress, startDir, refPatternCache }
105
+ return collectFiles(sourcesByUrl, loadOpts, fetchConcurrency).then(buildAggregate, (err) => {
106
+ progress && progress.terminate()
107
+ throw err
108
+ })
128
109
  })
129
110
  }
130
111
 
112
+ async function collectFiles (sourcesByUrl, loadOpts, concurrency) {
113
+ const tasks = [...sourcesByUrl.entries()].map(([url, sources]) => [
114
+ () => loadRepository(url, Object.assign({ fetchTags: tagsSpecified(sources) }, loadOpts)),
115
+ ({ repo, authStatus }) =>
116
+ Promise.all(
117
+ sources.map((source) => {
118
+ // NOTE if repository is managed (has a url property), we can assume the remote name is origin
119
+ // TODO if the repo has no remotes, then remoteName should be undefined
120
+ const remoteName = repo.url ? 'origin' : source.remote || 'origin'
121
+ return collectFilesFromSource(source, repo, remoteName, authStatus)
122
+ })
123
+ ),
124
+ ])
125
+ let rejection, started
126
+ const startedContinuations = []
127
+ const recordRejection = (err) => {
128
+ rejection = err
129
+ }
130
+ const runTask = (primary, continuation, idx) =>
131
+ primary().then((value) => {
132
+ if (!rejection) startedContinuations[idx] = continuation(value).catch(recordRejection)
133
+ }, recordRejection)
134
+ if (tasks.length > concurrency) {
135
+ started = []
136
+ const pending = []
137
+ for (const [primary, continuation] of tasks) {
138
+ const current = runTask(primary, continuation, started.length).finally(() =>
139
+ pending.splice(pending.indexOf(current), 1)
140
+ )
141
+ started.push(current)
142
+ if (pending.push(current) < concurrency) continue
143
+ await Promise.race(pending)
144
+ if (rejection) break
145
+ }
146
+ } else {
147
+ started = tasks.map(([primary, continuation], idx) => runTask(primary, continuation, idx))
148
+ }
149
+ return Promise.all(started).then(() =>
150
+ Promise.all(startedContinuations).then((result) => {
151
+ if (rejection) throw rejection
152
+ return result
153
+ })
154
+ )
155
+ }
156
+
131
157
  function buildAggregate (componentVersionBuckets) {
132
- const aggregateMap = flattenDeep(componentVersionBuckets).reduce((accum, batch) => {
133
- const key = batch.version + '@' + batch.name
134
- const entry = accum.get(key)
135
- return accum.set(key, entry ? Object.assign(entry, batch, { files: [...entry.files, ...batch.files] }) : batch)
136
- }, new Map())
137
- return [...aggregateMap.values()]
158
+ return [
159
+ ...flattenDeep(componentVersionBuckets)
160
+ .reduce((accum, batch) => {
161
+ const key = batch.version + '@' + batch.name
162
+ const entry = accum.get(key)
163
+ if (!entry) return accum.set(key, batch)
164
+ const files = batch.files
165
+ ;(batch.files = entry.files).push(...files)
166
+ Object.assign(entry, batch)
167
+ return accum
168
+ }, new Map())
169
+ .values(),
170
+ ]
138
171
  }
139
172
 
140
173
  async function loadRepository (url, opts) {
141
- let dir
142
- let repo
143
- let authStatus
174
+ let authStatus, dir, repo
175
+ const cache = { [REF_PATTERN_CACHE_KEY]: opts.refPatternCache }
144
176
  if (~url.indexOf(':') && GIT_URI_DETECTOR_RX.test(url)) {
145
- let displayUrl
146
- let credentials
177
+ let credentials, displayUrl
147
178
  ;({ displayUrl, url, credentials } = extractCredentials(url))
148
179
  const { cacheDir, fetch, fetchTags, gitPlugins, progress } = opts
149
180
  dir = ospath.join(cacheDir, generateCloneFolderName(displayUrl))
150
181
  // NOTE the presence of the url property on the repo object implies the repository is remote
151
- repo = { cache: {}, dir, fs, gitdir: dir, noCheckout: true, url }
182
+ repo = { cache, dir, fs, gitdir: dir, noCheckout: true, url }
152
183
  const validStateFile = ospath.join(dir, VALID_STATE_FILENAME)
153
184
  try {
154
185
  await fsp.access(validStateFile)
@@ -173,7 +204,7 @@ async function loadRepository (url, opts) {
173
204
  authStatus = await git.getConfig(Object.assign({ path: 'remote.origin.private' }, repo))
174
205
  }
175
206
  } catch (gitErr) {
176
- await rmdir(dir)
207
+ await fsp['rm' in fsp ? 'rm' : 'rmdir'](dir, { recursive: true, force: true })
177
208
  if (gitErr.rethrow) throw transformGitCloneError(gitErr, displayUrl)
178
209
  const fetchOpts = buildFetchOptions(repo, progress, displayUrl, credentials, gitPlugins, fetchTags, 'clone')
179
210
  await git
@@ -184,8 +215,7 @@ async function loadRepository (url, opts) {
184
215
  authStatus = credentials ? 'auth-embedded' : credentialManager.status({ url }) ? 'auth-required' : undefined
185
216
  return git.setConfig(Object.assign({ path: 'remote.origin.private', value: authStatus }, repo))
186
217
  })
187
- .catch(async (cloneErr) => {
188
- await rmdir(dir)
218
+ .catch((cloneErr) => {
189
219
  // FIXME triggering the error handler here causes assertion problems in the test suite
190
220
  //if (fetchOpts.onProgress) fetchOpts.onProgress.finish(cloneErr)
191
221
  throw transformGitCloneError(cloneErr, displayUrl)
@@ -193,10 +223,9 @@ async function loadRepository (url, opts) {
193
223
  .then(() => fsp.writeFile(validStateFile, '').catch(invariably.void))
194
224
  .then(() => fetchOpts.onProgress && fetchOpts.onProgress.finish())
195
225
  }
196
- } else if (await isDirectory((dir = expandPath(url, '~+', opts.startDir)))) {
197
- repo = (await isDirectory(ospath.join(dir, '.git')))
198
- ? { cache: {}, dir, fs }
199
- : { cache: {}, dir, fs, gitdir: dir, noCheckout: true }
226
+ } else if (await isDirectory((dir = expandPath(url, { dot: opts.startDir })))) {
227
+ const gitdir = ospath.join(dir, '.git')
228
+ repo = (await isDirectory(gitdir)) ? { cache, dir, fs, gitdir } : { cache, dir, fs, gitdir: dir, noCheckout: true }
200
229
  try {
201
230
  await git.resolveRef(Object.assign({ ref: 'HEAD', depth: 1 }, repo))
202
231
  } catch {
@@ -240,16 +269,18 @@ async function collectFilesFromSource (source, repo, remoteName, authStatus) {
240
269
  async function selectReferences (source, repo, remote) {
241
270
  let { branches: branchPatterns, tags: tagPatterns, worktrees: worktreePatterns = '.' } = source
242
271
  const isBare = repo.noCheckout
272
+ const patternCache = repo.cache[REF_PATTERN_CACHE_KEY]
273
+ const noWorktree = repo.url ? undefined : null
243
274
  const refs = new Map()
244
275
  if (tagPatterns) {
245
276
  tagPatterns = Array.isArray(tagPatterns)
246
277
  ? tagPatterns.map((pattern) => String(pattern))
247
- : String(tagPatterns).split(CSV_RX)
278
+ : splitRefPatterns(String(tagPatterns))
248
279
  if (tagPatterns.length) {
249
280
  const tags = await git.listTags(repo)
250
- for (const shortname of tags.length ? matcher(tags, tagPatterns) : tags) {
281
+ for (const shortname of tags.length ? filterRefs(tags, tagPatterns, patternCache) : tags) {
251
282
  // NOTE tags are stored using symbol keys to distinguish them from branches
252
- refs.set(Symbol(shortname), { shortname, fullname: 'tags/' + shortname, type: 'tag' })
283
+ refs.set(Symbol(shortname), { shortname, fullname: 'tags/' + shortname, type: 'tag', head: noWorktree })
253
284
  }
254
285
  }
255
286
  }
@@ -262,7 +293,7 @@ async function selectReferences (source, repo, remote) {
262
293
  } else {
263
294
  worktreePatterns = Array.isArray(worktreePatterns)
264
295
  ? worktreePatterns.map((pattern) => String(pattern))
265
- : String(worktreePatterns).split(CSV_RX)
296
+ : splitRefPatterns(String(worktreePatterns))
266
297
  }
267
298
  }
268
299
  const branchPatternsString = String(branchPatterns)
@@ -273,16 +304,15 @@ async function selectReferences (source, repo, remote) {
273
304
  } else {
274
305
  if (!isBare) {
275
306
  // NOTE current branch is undefined when HEAD is detached
276
- const ref = { shortname: 'HEAD', fullname: 'HEAD', type: 'branch', detached: true }
277
- if (worktreePatterns[0] === '.') ref.head = repo.dir
278
- refs.set('HEAD', ref)
307
+ const head = worktreePatterns[0] === '.' ? repo.dir : noWorktree
308
+ refs.set('HEAD', { shortname: 'HEAD', fullname: 'HEAD', type: 'branch', detached: true, head })
279
309
  }
280
310
  return [...refs.values()]
281
311
  }
282
312
  } else if (
283
313
  (branchPatterns = Array.isArray(branchPatterns)
284
314
  ? branchPatterns.map((pattern) => String(pattern))
285
- : branchPatternsString.split(CSV_RX)).length
315
+ : splitRefPatterns(branchPatternsString)).length
286
316
  ) {
287
317
  let headBranchIdx
288
318
  // NOTE we can assume at least two entries if HEAD or . are present
@@ -297,10 +327,13 @@ async function selectReferences (source, repo, remote) {
297
327
  }
298
328
  } else {
299
329
  if (!isBare) {
330
+ let head = noWorktree
331
+ if (worktreePatterns[0] === '.') {
332
+ worktreePatterns = worktreePatterns.slice(1)
333
+ head = repo.dir
334
+ }
300
335
  // NOTE current branch is undefined when HEAD is detached
301
- const ref = { shortname: 'HEAD', fullname: 'HEAD', type: 'branch', detached: true }
302
- if (worktreePatterns[0] === '.' && (worktreePatterns = worktreePatterns.slice(1))) ref.head = repo.dir
303
- refs.set('HEAD', ref)
336
+ refs.set('HEAD', { shortname: 'HEAD', fullname: 'HEAD', type: 'branch', detached: true, head })
304
337
  }
305
338
  branchPatterns.splice(headBranchIdx, 1)
306
339
  }
@@ -311,8 +344,9 @@ async function selectReferences (source, repo, remote) {
311
344
  // NOTE isomorphic-git includes HEAD in list of remote branches (see https://isomorphic-git.org/docs/listBranches)
312
345
  const remoteBranches = (await git.listBranches(Object.assign({ remote }, repo))).filter((it) => it !== 'HEAD')
313
346
  if (remoteBranches.length) {
314
- for (const shortname of matcher(remoteBranches, branchPatterns)) {
315
- refs.set(shortname, { shortname, fullname: path.join('remotes', remote, shortname), type: 'branch', remote })
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 })
316
350
  }
317
351
  }
318
352
  // NOTE only consider local branches if repo has a worktree or there are no remote tracking branches
@@ -320,17 +354,17 @@ async function selectReferences (source, repo, remote) {
320
354
  const localBranches = await git.listBranches(repo)
321
355
  if (localBranches.length) {
322
356
  const worktrees = await findWorktrees(repo, worktreePatterns)
323
- for (const shortname of matcher(localBranches, branchPatterns)) {
324
- const ref = { shortname, fullname: 'heads/' + shortname, type: 'branch' }
325
- if (worktrees.has(shortname)) ref.head = worktrees.get(shortname)
326
- refs.set(shortname, ref)
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 })
327
360
  }
328
361
  }
329
362
  } else if (!remoteBranches.length) {
330
- // QUESTION should local branches be used if the only remote branch is HEAD?
331
363
  const localBranches = await git.listBranches(repo)
332
- for (const shortname of localBranches.length ? matcher(localBranches, branchPatterns) : localBranches) {
333
- refs.set(shortname, { shortname, fullname: 'heads/' + shortname, type: 'branch' })
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
+ }
334
368
  }
335
369
  }
336
370
  }
@@ -391,7 +425,7 @@ function collectFilesFromStartPath (startPath, repo, authStatus, ref, worktreePa
391
425
  )
392
426
  .then((files) => {
393
427
  const componentVersionBucket = loadComponentDescriptor(files, ref, version)
394
- const origin = computeOrigin(originUrl, authStatus, ref, startPath, worktreePath, editUrl)
428
+ const origin = computeOrigin(originUrl, authStatus, repo.gitdir, ref, startPath, worktreePath, editUrl)
395
429
  componentVersionBucket.files = files.map((file) => assignFileProperties(file, origin))
396
430
  return componentVersionBucket
397
431
  })
@@ -404,16 +438,12 @@ function collectFilesFromStartPath (startPath, repo, authStatus, ref, worktreePa
404
438
 
405
439
  function readFilesFromWorktree (worktreePath, startPath) {
406
440
  const cwd = ospath.join(worktreePath, startPath)
407
- return fsp
408
- .stat(cwd)
409
- .catch(() => {
410
- throw new Error(`the start path '${startPath}' does not exist`)
411
- })
412
- .then((stat) => {
441
+ return fsp.stat(cwd).then(
442
+ (stat) => {
413
443
  if (!stat.isDirectory()) throw new Error(`the start path '${startPath}' is not a directory`)
414
444
  return new Promise((resolve, reject) =>
415
445
  vfs
416
- .src(CONTENT_GLOB, { cwd, follow: true, removeBOM: false })
446
+ .src(CONTENT_SRC_GLOB, Object.assign({ cwd }, CONTENT_SRC_OPTS))
417
447
  .on('error', (err) => {
418
448
  if (err.code === 'ENOENT' && err.syscall === 'stat') {
419
449
  try {
@@ -427,9 +457,13 @@ function readFilesFromWorktree (worktreePath, startPath) {
427
457
  reject(err)
428
458
  })
429
459
  .pipe(relativizeFiles())
430
- .pipe(collectFiles(resolve))
460
+ .pipe(collectDataFromStream(resolve))
431
461
  )
432
- })
462
+ },
463
+ () => {
464
+ throw new Error(`the start path '${startPath}' does not exist`)
465
+ }
466
+ )
433
467
  }
434
468
 
435
469
  /**
@@ -458,11 +492,11 @@ function relativizeFiles () {
458
492
  })
459
493
  }
460
494
 
461
- function collectFiles (done) {
495
+ function collectDataFromStream (done) {
462
496
  const accum = []
463
497
  return map(
464
- (file, enc, next) => {
465
- accum.push(file)
498
+ (obj, _, next) => {
499
+ accum.push(obj)
466
500
  next()
467
501
  },
468
502
  () => done(accum)
@@ -480,13 +514,13 @@ function readFilesFromGitTree (repo, oid, startPath) {
480
514
  }
481
515
 
482
516
  function getGitTreeAtStartPath (repo, oid, startPath) {
483
- return git
484
- .readTree(Object.assign({ oid, filepath: startPath }, repo))
485
- .catch((err) => {
517
+ return git.readTree(Object.assign({ oid, filepath: startPath }, repo)).then(
518
+ (result) => Object.assign(result, { dirname: startPath }),
519
+ (err) => {
486
520
  const m = err instanceof ObjectTypeError && err.data.expected === 'tree' ? 'is not a directory' : 'does not exist'
487
521
  throw new Error(`the start path '${startPath}' ${m}`)
488
- })
489
- .then((result) => Object.assign(result, { dirname: startPath }))
522
+ }
523
+ )
490
524
  }
491
525
 
492
526
  function srcGitTree (repo, root, start) {
@@ -505,9 +539,11 @@ function createGitTreeWalker (repo, root, filter) {
505
539
  walk (start) {
506
540
  return (
507
541
  visitGitTree(this, repo, root, filter, start)
508
- .then(() => this.emit('end'))
509
542
  // NOTE if error is thrown, promises already being resolved won't halt
510
- .catch((err) => this.emit('error', err))
543
+ .then(
544
+ () => this.emit('end'),
545
+ (err) => this.emit('error', err)
546
+ )
511
547
  )
512
548
  },
513
549
  })
@@ -530,8 +566,15 @@ function visitGitTree (emitter, repo, root, filter, parent, dirname = '', follow
530
566
  let mode
531
567
  if (entry.mode === SYMLINK_FILE_MODE) {
532
568
  reads.push(
533
- readGitSymlink(repo, root, parent, entry, following)
534
- .catch((err) => {
569
+ readGitSymlink(repo, root, parent, entry, following).then(
570
+ (target) => {
571
+ if (target.type === 'tree') {
572
+ return visitGitTree(emitter, repo, root, filter, target, vfilePath, new Set(following).add(entry.oid))
573
+ } else if (target.type === 'blob' && filterVerdict === true && (mode = FILE_MODES[target.mode])) {
574
+ emitter.emit('entry', Object.assign({ mode, oid: target.oid, path: vfilePath }, repo))
575
+ }
576
+ },
577
+ (err) => {
535
578
  // NOTE this error could be caught after promise chain has already been rejected
536
579
  if (err instanceof NotFoundError) {
537
580
  err.message = `Broken symbolic link detected at ${vfilePath}`
@@ -539,14 +582,8 @@ function visitGitTree (emitter, repo, root, filter, parent, dirname = '', follow
539
582
  err.message = `Symbolic link cycle detected at ${vfilePath}`
540
583
  }
541
584
  throw err
542
- })
543
- .then((target) => {
544
- if (target.type === 'tree') {
545
- return visitGitTree(emitter, repo, root, filter, target, vfilePath, new Set(following).add(entry.oid))
546
- } else if (target.type === 'blob' && filterVerdict === true && (mode = FILE_MODES[target.mode])) {
547
- emitter.emit('entry', Object.assign({ mode, oid: target.oid, path: vfilePath }, repo))
548
- }
549
- })
585
+ }
586
+ )
550
587
  )
551
588
  } else if ((mode = FILE_MODES[entry.mode])) {
552
589
  emitter.emit('entry', Object.assign({ mode, oid: entry.oid, path: vfilePath }, repo))
@@ -560,11 +597,11 @@ function visitGitTree (emitter, repo, root, filter, parent, dirname = '', follow
560
597
  function readGitSymlink (repo, root, parent, { oid }, following) {
561
598
  if (following.size !== (following = new Set(following).add(oid)).size) {
562
599
  return git.readBlob(Object.assign({ oid }, repo)).then(({ blob: target }) => {
563
- target = posixify && process.env.NODE_ENV === 'test' ? posixify(decodeUint8Data(target)) : decodeUint8Data(target)
600
+ target = decodeUint8Array(target)
564
601
  let targetParent
565
602
  if (parent.dirname) {
566
603
  const dirname = parent.dirname + '/'
567
- target = path.join(dirname, target)
604
+ target = path.join(dirname, target) // join doesn't remove trailing separator
568
605
  if (target.startsWith(dirname)) {
569
606
  target = target.substr(dirname.length)
570
607
  targetParent = parent
@@ -572,10 +609,12 @@ function readGitSymlink (repo, root, parent, { oid }, following) {
572
609
  targetParent = root
573
610
  }
574
611
  } else {
575
- target = path.normalize(target)
612
+ target = path.normalize(target) // normalize doesn't remove trailing separator
576
613
  targetParent = root
577
614
  }
578
- return readGitObjectAtPath(repo, root, targetParent, target.split('/'), following)
615
+ const targetSegments = target.split('/')
616
+ if (!targetSegments[targetSegments.length - 1]) targetSegments.pop()
617
+ return readGitObjectAtPath(repo, root, targetParent, targetSegments, following)
579
618
  })
580
619
  }
581
620
  const err = { name: 'SymbolicLinkCycleError', code: 'SymbolicLinkCycleError', oid }
@@ -616,11 +655,8 @@ function filterGitEntry (entry) {
616
655
 
617
656
  function entryToFile (entry) {
618
657
  return git.readBlob(entry).then(({ blob: contents }) => {
619
- const stat = new fs.Stats()
620
- stat.mode = entry.mode
621
- stat.mtime = undefined
622
- stat.size = contents.byteLength
623
658
  contents = Buffer.from(contents.buffer)
659
+ const stat = Object.assign(new fs.Stats(), { mode: entry.mode, mtime: undefined, size: contents.byteLength })
624
660
  return new File({ path: entry.path, contents, stat })
625
661
  })
626
662
  }
@@ -673,22 +709,23 @@ function loadComponentDescriptor (files, ref, version) {
673
709
  return camelCaseKeys(data, { deep: true, stopPaths: ['asciidoc'] })
674
710
  }
675
711
 
676
- function computeOrigin (url, authStatus, ref, startPath, worktreePath = undefined, editUrl = true) {
712
+ function computeOrigin (url, authStatus, gitdir, ref, startPath, worktreePath = undefined, editUrl = true) {
677
713
  const { shortname: refname, oid: refhash, type: reftype } = ref
678
- const remote = !url.startsWith('file://')
679
- const origin = { type: 'git', refname, startPath }
714
+ const origin = { type: 'git', url, gitdir, refname, [reftype]: refname, startPath }
680
715
  if (authStatus) origin.private = authStatus
681
- origin[reftype] = refname
682
- if (worktreePath) {
683
- if (remote) origin.url = url
684
- origin.fileUriPattern =
685
- (posixify ? 'file:///' + posixify(worktreePath) : 'file://' + worktreePath) + path.join('/', startPath, '%s')
686
- origin.worktree = worktreePath
687
- } else {
688
- origin.url = url
716
+ if (worktreePath === undefined) {
689
717
  origin.refhash = refhash
718
+ } else {
719
+ if (worktreePath) {
720
+ origin.fileUriPattern =
721
+ (posixify ? 'file:///' + posixify(worktreePath) : 'file://' + worktreePath) + path.join('/', startPath, '%s')
722
+ } else {
723
+ origin.refhash = refhash
724
+ }
725
+ origin.worktree = worktreePath
726
+ if (url.startsWith('file://')) url = undefined
690
727
  }
691
- if (remote) origin.webUrl = url.replace(GIT_SUFFIX_RX, '')
728
+ if (url) origin.webUrl = url.replace(GIT_SUFFIX_RX, '')
692
729
  if (editUrl === true) {
693
730
  let match
694
731
  if (url && (match = url.match(HOSTED_GIT_REPO_RX))) {
@@ -856,16 +893,20 @@ function generateCloneFolderName (url) {
856
893
  *
857
894
  * @param {Repository} repo - The repository on which to operate.
858
895
  * @param {String} remoteName - The name of the remote to resolve.
859
- * @returns {String} The URL of the specified remote, if present.
896
+ * @returns {String} The URL of the specified remote, if defined, or the file URI to the local repository.
860
897
  */
861
898
  function resolveRemoteUrl (repo, remoteName) {
862
899
  return git.getConfig(Object.assign({ path: 'remote.' + remoteName + '.url' }, repo)).then((url) => {
863
- if (!url) return posixify ? 'file:///' + posixify(repo.dir) : 'file://' + repo.dir
864
- if (url.startsWith('https://') || url.startsWith('http://')) {
865
- return ~url.indexOf('@') ? url.replace(URL_AUTH_CLEANER_RX, '$1') : url
866
- } else if (url.startsWith('git@')) {
867
- return 'https://' + url.substr(4).replace(':', '/')
900
+ if (url) {
901
+ if (url.startsWith('https://') || url.startsWith('http://')) {
902
+ return ~url.indexOf('@') ? url.replace(URL_AUTH_CLEANER_RX, '$1') : url
903
+ } else if (url.startsWith('git@')) {
904
+ return 'https://' + url.substr(4).replace(':', '/')
905
+ } else if (url.startsWith('ssh://')) {
906
+ return 'https://' + url.substr(url.indexOf('@') + 1 || 6).replace(URL_PORT_CLEANER_RX, '$1')
907
+ }
868
908
  }
909
+ return posixify ? 'file:///' + posixify(repo.dir) : 'file://' + repo.dir
869
910
  })
870
911
  }
871
912
 
@@ -876,51 +917,18 @@ function resolveRemoteUrl (repo, remoteName) {
876
917
  * @return {Boolean} A flag indicating whether the URL matches a directory on the local filesystem.
877
918
  */
878
919
  function isDirectory (url) {
879
- return fsp
880
- .stat(url)
881
- .then((stat) => stat.isDirectory())
882
- .catch(invariably.false)
883
- }
884
-
885
- /**
886
- * Removes the specified directory (including all of its contents) or file.
887
- * Equivalent to fs.promises.rmdir(dir, { recursive: true }) in Node 12.
888
- */
889
- function rmdir (dir) {
890
- return fsp
891
- .readdir(dir, { withFileTypes: true })
892
- .then((lst) =>
893
- Promise.all(
894
- lst.map((it) =>
895
- it.isDirectory()
896
- ? rmdir(ospath.join(dir, it.name))
897
- : fsp.unlink(ospath.join(dir, it.name)).catch((unlinkErr) => {
898
- if (unlinkErr.code !== 'ENOENT') throw unlinkErr
899
- })
900
- )
901
- )
902
- )
903
- .then(() => fsp.rmdir(dir))
904
- .catch((err) => {
905
- if (err.code === 'ENOENT') return
906
- if (err.code === 'ENOTDIR') {
907
- return fsp.unlink(dir).catch((unlinkErr) => {
908
- if (unlinkErr.code !== 'ENOENT') throw unlinkErr
909
- })
910
- }
911
- throw err
912
- })
920
+ return fsp.stat(url).then((stat) => stat.isDirectory(), invariably.false)
913
921
  }
914
922
 
915
- function tagsSpecified (sources, defaultTags) {
916
- return ~sources.findIndex((source) => {
917
- const tags = source.tags || defaultTags || []
918
- return Array.isArray(tags) ? tags.length : true
919
- })
923
+ function tagsSpecified (sources) {
924
+ return sources.some(({ tags }) => tags && (Array.isArray(tags) ? tags.length : true))
920
925
  }
921
926
 
922
927
  function loadGitPlugins (gitConfig, networkConfig, startDir) {
923
- const plugins = (git.cores || git.default.cores || new Map()).get(GIT_CORE) || new Map()
928
+ const plugins = new Map((git.cores || git.default.cores || new Map()).get(GIT_CORE))
929
+ for (const [name, request] of Object.entries(gitConfig.plugins || {})) {
930
+ if (request) plugins.set(name, userRequire(request, { dot: startDir, paths: [startDir, __dirname] }))
931
+ }
924
932
  let credentialManager, urlRouter
925
933
  if ((credentialManager = plugins.get('credentialManager'))) {
926
934
  if (typeof credentialManager.configure === 'function') {
@@ -949,19 +957,18 @@ function ensureCacheDir (preferredCacheDir, startDir) {
949
957
  const baseCacheDir =
950
958
  preferredCacheDir == null
951
959
  ? getCacheDir('antora' + (process.env.NODE_ENV === 'test' ? '-test' : '')) || ospath.resolve('.antora/cache')
952
- : expandPath(preferredCacheDir, '~+', startDir)
960
+ : expandPath(preferredCacheDir, { dot: startDir })
953
961
  const cacheDir = ospath.join(baseCacheDir, CONTENT_CACHE_FOLDER)
954
- return fsp
955
- .mkdir(cacheDir, { recursive: true })
956
- .then(() => cacheDir)
957
- .catch((err) => {
962
+ return fsp.mkdir(cacheDir, { recursive: true }).then(
963
+ () => cacheDir,
964
+ (err) => {
958
965
  throw Object.assign(err, { message: `Failed to create content cache directory: ${cacheDir}; ${err.message}` })
959
- })
966
+ }
967
+ )
960
968
  }
961
969
 
962
970
  function transformGitCloneError (err, displayUrl) {
963
- let wrappedMsg
964
- let trimMessage
971
+ let wrappedMsg, trimMessage
965
972
  if (HTTP_ERROR_CODE_RX.test(err.code)) {
966
973
  switch (err.data.statusCode) {
967
974
  case 401:
@@ -992,6 +999,10 @@ function transformGitCloneError (err, displayUrl) {
992
999
  return wrappedErr
993
1000
  }
994
1001
 
1002
+ function splitRefPatterns (str) {
1003
+ return ~str.indexOf('{') ? str.split(VENTILATED_CSV_RX) : str.split(CSV_RX)
1004
+ }
1005
+
995
1006
  function coerceToString (value) {
996
1007
  return value == null ? '' : String(value)
997
1008
  }
@@ -1004,11 +1015,11 @@ function findWorktrees (repo, patterns) {
1004
1015
  if (!patterns.length) return new Map()
1005
1016
  const linkedOnly = patterns[0] === '.' ? !(patterns = patterns.slice(1)) : true
1006
1017
  let worktreesDir
1018
+ const patternCache = repo.cache[REF_PATTERN_CACHE_KEY]
1007
1019
  return (patterns.length
1008
1020
  ? fsp
1009
1021
  .readdir((worktreesDir = ospath.join(repo.dir, '.git', 'worktrees')))
1010
- .catch(invariably.emptyArray)
1011
- .then((worktreeNames) => matcher(worktreeNames, [...patterns]))
1022
+ .then((worktreeNames) => filterRefs(worktreeNames, [...patterns], patternCache), invariably.emptyArray)
1012
1023
  .then((worktreeNames) =>
1013
1024
  worktreeNames.length
1014
1025
  ? Promise.all(
package/lib/constants.js CHANGED
@@ -2,24 +2,25 @@
2
2
 
3
3
  module.exports = Object.freeze({
4
4
  COMPONENT_DESC_FILENAME: 'antora.yml',
5
- CONTENT_CACHE_FOLDER: 'content/2',
6
- CONTENT_GLOB: '**/*[!~]',
5
+ CONTENT_CACHE_FOLDER: 'content',
6
+ CONTENT_SRC_GLOB: '**/*[!~]',
7
+ CONTENT_SRC_OPTS: { follow: true, nomount: true, nosort: true, nounique: true, removeBOM: false, uniqueBy: (m) => m },
7
8
  FILE_MODES: { 100644: 0o100666 & ~process.umask(), 100755: 0o100777 & ~process.umask() },
8
9
  GIT_CORE: 'antora',
9
10
  GIT_OPERATION_LABEL_LENGTH: 8,
10
11
  GIT_PROGRESS_PHASES: ['Counting objects', 'Compressing objects', 'Receiving objects', 'Resolving deltas'],
11
12
  PICOMATCH_VERSION_OPTS: {
12
13
  bash: true,
13
- debug: false,
14
14
  dot: true,
15
15
  fastpaths: false,
16
- lookbehinds: false,
17
- noextglob: true,
16
+ nobracket: true,
18
17
  noglobstar: true,
19
18
  nonegate: true,
20
19
  noquantifiers: true,
20
+ regex: false,
21
21
  strictSlashes: true,
22
22
  },
23
+ REF_PATTERN_CACHE_KEY: Symbol('RefPatternCache'),
23
24
  SYMLINK_FILE_MODE: '120000',
24
25
  VALID_STATE_FILENAME: 'valid',
25
26
  })
@@ -0,0 +1,60 @@
1
+ 'use strict'
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
+ }
22
+
23
+ function compileRx (pattern, opts) {
24
+ if (pattern === '*' || pattern === '**') return { test: () => true }
25
+ return pattern.charAt() === '!' // do our own negate
26
+ ? Object.defineProperty(makePicomatchRx(pattern.substr(1), opts), 'negated', { value: true })
27
+ : makePicomatchRx(pattern, opts)
28
+ }
29
+
30
+ function createMatcher (patterns, cache) {
31
+ let opts
32
+ const rxs = patterns.map(
33
+ (pattern) =>
34
+ cache.get(pattern) ||
35
+ cache.set(pattern, compileRx(pattern, opts || (opts = getPicomatchOpts(cache)))).get(pattern)
36
+ )
37
+ return (candidate) => {
38
+ let first = true
39
+ let matched
40
+ for (const rx of rxs) {
41
+ if (matched) {
42
+ if (rx.negated && rx.test(candidate)) return
43
+ } else if (first || !rx.negated) {
44
+ matched = rx.test(candidate)
45
+ }
46
+ first = false
47
+ }
48
+ return matched
49
+ }
50
+ }
51
+
52
+ function filterRefs (candidates, patterns, cache = Object.assign(new Map(), { braces: new Map() })) {
53
+ const isMatch = createMatcher(patterns, cache)
54
+ return candidates.reduce((accum, candidate) => {
55
+ if (isMatch(candidate)) accum.push(candidate)
56
+ return accum
57
+ }, [])
58
+ }
59
+
60
+ module.exports = filterRefs
@@ -2,6 +2,7 @@
2
2
 
3
3
  const { homedir } = require('os')
4
4
  const expandPath = require('@antora/expand-path-helper')
5
+ const invariably = { void: () => undefined }
5
6
  const { promises: fsp } = require('fs')
6
7
  const ospath = require('path')
7
8
 
@@ -9,12 +10,10 @@ 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, '~+', 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 {
@@ -34,29 +34,23 @@ module.exports = ({ httpProxy, httpsProxy, noProxy }, userAgent) => {
34
34
  headers['user-agent'] = userAgent
35
35
  body = await mergeBuffers(body)
36
36
  const proxy = url.startsWith('https:')
37
- ? { ProxyAgent: HttpsProxyAgent, url: httpsProxy }
38
- : { ProxyAgent: HttpProxyAgent, url: httpProxy }
39
- let agent
40
- if (proxy.url && shouldProxy(url, { no_proxy: noProxy })) {
41
- // see https://github.com/delvedor/hpagent/issues/18
42
- const { protocol, hostname, port, username, password } = new URL(proxy.url)
43
- const proxyUrl = { protocol, hostname, port, username: username || null, password: password || null }
44
- agent = new proxy.ProxyAgent({ proxy: proxyUrl })
45
- }
37
+ ? { Agent: HttpsProxyAgent, url: httpsProxy }
38
+ : { Agent: HttpProxyAgent, url: httpProxy }
39
+ const agent =
40
+ proxy.url && shouldProxy(url, { no_proxy: noProxy }) ? new proxy.Agent({ proxy: proxy.url }) : undefined
46
41
  return new Promise((resolve, reject) =>
47
42
  get({ url, method, agent, headers, body }, (err, res) => (err ? reject(err) : resolve(distillResponse(res))))
48
43
  )
49
44
  },
50
45
  }
51
- } else {
52
- return {
53
- async request ({ url, method, headers, body }) {
54
- headers['user-agent'] = userAgent
55
- body = await mergeBuffers(body)
56
- return new Promise((resolve, reject) =>
57
- get({ url, method, headers, body }, (err, res) => (err ? reject(err) : resolve(distillResponse(res))))
58
- )
59
- },
60
- }
46
+ }
47
+ return {
48
+ async request ({ url, method, headers, body }) {
49
+ headers['user-agent'] = userAgent
50
+ body = await mergeBuffers(body)
51
+ return new Promise((resolve, reject) =>
52
+ get({ url, method, headers, body }, (err, res) => (err ? reject(err) : resolve(distillResponse(res))))
53
+ )
54
+ },
61
55
  }
62
56
  }
package/lib/git.js ADDED
@@ -0,0 +1,3 @@
1
+ 'use strict'
2
+
3
+ module.exports = require('isomorphic-git')
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
  */
@@ -1,26 +1,33 @@
1
1
  'use strict'
2
2
 
3
- const { expand: expandBraces } = require('braces')
3
+ const { expand: expandBraces, compile: bracesToGroup } = require('braces')
4
4
  const flattenDeep = require('./flatten-deep')
5
5
  const { promises: fsp } = require('fs')
6
- const git = require('isomorphic-git')
6
+ const git = require('./git')
7
7
  const invariably = { true: () => true, false: () => false, void: () => undefined, emptyArray: () => [] }
8
8
  const { makeRe: makePicomatchRx } = require('picomatch')
9
9
 
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 }
10
+ const NON_GLOB_SPECIAL_CHARS_RX = /[.+?^${}()|[\]\\]/g
11
+ 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
+ }
15
23
 
16
24
  function resolvePathGlobs (base, patterns, listDirents, retrievePath, tree = { path: '' }) {
17
25
  return patterns.reduce((paths, pattern) => {
18
26
  if (pattern.charAt() === '!') {
19
27
  return paths.then((resolvedPaths) => {
20
28
  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))
29
+ const rx = makePicomatchRx(pattern.substr(1), PICOMATCH_OPTS)
30
+ return resolvedPaths.filter((it) => !rx.test(it))
24
31
  } else {
25
32
  return resolvedPaths
26
33
  }
@@ -38,14 +45,13 @@ async function glob (base, patternSegments, listDirents, retrievePath, { oid, pa
38
45
  let patternSegment = patternSegments[0]
39
46
  patternSegments = patternSegments.slice(1)
40
47
  if (RX_MAGIC_DETECTOR.test(patternSegment)) {
41
- let isMatch
42
- let explicit
48
+ let isMatch, explicit
43
49
  if (patternSegment === '*') {
44
50
  isMatch = (it) => it.charAt() !== '.'
51
+ } else if (~patternSegment.indexOf('(')) {
52
+ isMatch = (isMatch = makePicomatchRx(patternSegment, PICOMATCH_OPTS)).test.bind(isMatch)
45
53
  } else if (~patternSegment.indexOf('{')) {
46
54
  if (globbed) {
47
- if (patternSegment.charAt() === '!') patternSegment = '\\' + patternSegment
48
- if (~patternSegment.indexOf('?')) patternSegment = patternSegment.replace(RX_QUESTION_MARK, '\\?')
49
55
  isMatch = (isMatch = makePicomatchRx(patternSegment, PICOMATCH_OPTS)).test.bind(isMatch)
50
56
  } else if (~patternSegment.indexOf('*')) {
51
57
  const [wildPatterns, literals] = expandBraces(patternSegment).reduce(
@@ -90,22 +96,15 @@ async function glob (base, patternSegments, listDirents, retrievePath, { oid, pa
90
96
  })
91
97
  } else if ((patternSegment += '/' + patternSegments.join('/')).indexOf('{')) {
92
98
  return expandBraces(patternSegment).map((it) => joinPath(path, it))
93
- } else {
94
- return [joinPath(path, patternSegment)]
95
99
  }
100
+ return [joinPath(path, patternSegment)]
96
101
  } else if (globbed) {
97
102
  return (await retrievePath(base, { oid, path }, patternSegment)) ? [joinPath(path, patternSegment)] : []
98
- } else {
99
- return [joinPath(path, patternSegment)]
100
103
  }
104
+ return [joinPath(path, patternSegment)]
101
105
  }
102
106
  }
103
107
 
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
108
  function extractMagicBase (patternSegments, base) {
110
109
  let nextSegment
111
110
  if (patternSegments.length) {
@@ -124,10 +123,11 @@ function listDirentsFs (base, path) {
124
123
  function listDirentsGit (repo, treeOid) {
125
124
  return git
126
125
  .readTree(Object.assign({ oid: treeOid, filepath: '' }, repo))
127
- .then(({ tree: entries }) =>
128
- entries.map(({ type, oid, path: name }) => ({ name, oid, isDirectory: invariably[type === 'tree'] }))
126
+ .then(
127
+ ({ tree: entries }) =>
128
+ entries.map(({ type, oid, path: name }) => ({ name, oid, isDirectory: invariably[type === 'tree'] })),
129
+ invariably.emptyArray
129
130
  )
130
- .catch(invariably.emptyArray)
131
131
  }
132
132
 
133
133
  function makeAlternationMatcherRx (patterns) {
@@ -139,7 +139,13 @@ function makeMatcherRx (pattern) {
139
139
  }
140
140
 
141
141
  function patternToRx (pattern) {
142
- return (pattern.charAt() === '.' ? '' : '(?!\\.)') + regexpEscapeWithGlob(pattern)
142
+ return (
143
+ (pattern.charAt() === '.' ? '' : '(?!\\.)') +
144
+ pattern
145
+ .replace(NON_GLOB_SPECIAL_CHARS_RX, '\\$&')
146
+ .replace('\\\\*', '\\x2a')
147
+ .replace('*', '.*?')
148
+ )
143
149
  }
144
150
 
145
151
  function readdirWithFileTypes (dir) {
@@ -147,10 +153,7 @@ function readdirWithFileTypes (dir) {
147
153
  }
148
154
 
149
155
  function retrievePathFs (base, { path }, subpath) {
150
- return fsp
151
- .access(base + '/' + joinPath(path, subpath))
152
- .then(invariably.true)
153
- .catch(invariably.false)
156
+ return fsp.access(base + '/' + joinPath(path, subpath)).then(invariably.true, invariably.false)
154
157
  }
155
158
 
156
159
  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.7",
3
+ "version": "3.0.0-beta.2",
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)",
@@ -17,14 +17,14 @@
17
17
  },
18
18
  "main": "lib/index.js",
19
19
  "dependencies": {
20
- "@antora/expand-path-helper": "~1.0",
20
+ "@antora/expand-path-helper": "~2.0",
21
+ "@antora/user-require-helper": "~2.0",
21
22
  "braces": "~3.0",
22
23
  "cache-directory": "~2.0",
23
- "camelcase-keys": "~6.2",
24
+ "camelcase-keys": "~7.0",
24
25
  "hpagent": "~0.1.0",
25
- "isomorphic-git": "~1.8",
26
+ "isomorphic-git": "~1.10",
26
27
  "js-yaml": "~4.1",
27
- "matcher": "~4.0",
28
28
  "multi-progress": "~4.0",
29
29
  "picomatch": "~2.3",
30
30
  "progress": "~2.0",
@@ -34,7 +34,7 @@
34
34
  "vinyl-fs": "~3.0"
35
35
  },
36
36
  "engines": {
37
- "node": ">=10.17.0"
37
+ "node": ">=12.21.0"
38
38
  },
39
39
  "files": [
40
40
  "lib/"
@@ -49,5 +49,5 @@
49
49
  "static site",
50
50
  "web publishing"
51
51
  ],
52
- "gitHead": "fbd597b3680474f2083cda8a7facf1e2848c08e0"
52
+ "gitHead": "5cd3f9cc70622e465cb44daf1aa2035ed5a35f54"
53
53
  }