@antora/content-aggregator 3.2.0-alpha.2 → 3.2.0-alpha.4

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
@@ -9,4 +9,4 @@ Its site generator aggregates documents from versioned content repositories and
9
9
 
10
10
  Copyright (C) 2017-present [OpenDevise Inc.](https://opendevise.com) and the [Antora Project](https://antora.org).
11
11
 
12
- Use of this software is granted under the terms of the [Mozilla Public License Version 2.0](https://www.mozilla.org/en-US/MPL/2.0/) (MPL-2.0).
12
+ Use of this software is granted under the terms of the [Mozilla Public License Version 2.0](https://www.mozilla.org/en-US/MPL/2.0/) (MPL-2.0).
@@ -5,7 +5,6 @@ const { createHash } = require('crypto')
5
5
  const createGitHttpPlugin = require('./git-plugin-http')
6
6
  const decodeUint8Array = require('./decode-uint8-array')
7
7
  const deepClone = require('./deep-clone')
8
- const deepFlatten = require('./deep-flatten')
9
8
  const EventEmitter = require('events')
10
9
  const expandPath = require('@antora/expand-path-helper')
11
10
  const File = require('./file')
@@ -17,6 +16,7 @@ const GitCredentialManagerStore = require('./git-credential-manager-store')
17
16
  const git = require('./git')
18
17
  const { NotFoundError, ObjectTypeError, UnknownTransportError, UrlParseError } = git.Errors
19
18
  const globStream = require('glob-stream')
19
+ const { inspect } = require('util')
20
20
  const invariably = require('./invariably')
21
21
  const logger = require('./logger')
22
22
  const { makeMatcherRx, versionMatcherOpts: VERSION_MATCHER_OPTS } = require('./matcher')
@@ -93,83 +93,71 @@ function aggregateContent (playbook) {
93
93
  return ensureCacheDir(requestedCacheDir, startDir).then((cacheDir) => {
94
94
  const gitConfig = Object.assign({ ensureGitSuffix: true }, playbook.git)
95
95
  const gitPlugins = loadGitPlugins(gitConfig, playbook.network || {}, startDir)
96
- const fetchConcurrency = Math.max(gitConfig.fetchConcurrency || Infinity, 1)
96
+ const concurrency = {
97
+ fetch: Math.max(gitConfig.fetchConcurrency || Infinity, 1),
98
+ read: Math.max(gitConfig.readConcurrency || Infinity, 1),
99
+ }
97
100
  const sourcesByUrl = sources.reduce((accum, source) => {
98
101
  return accum.set(source.url, [...(accum.get(source.url) || []), Object.assign({}, sourceDefaults, source)])
99
102
  }, new Map())
100
103
  const progress = !quiet && createProgress(sourcesByUrl.keys(), process.stdout)
101
104
  const refPatternCache = Object.assign(new Map(), { braces: new Map() })
102
105
  const loadOpts = { cacheDir, fetch, gitPlugins, progress, startDir, refPatternCache }
103
- return collectFiles(sourcesByUrl, loadOpts, fetchConcurrency).then(buildAggregate, (err) => {
106
+ return collectFiles(sourcesByUrl, loadOpts, concurrency).then(buildAggregate, (err) => {
104
107
  progress && progress.terminate()
105
108
  throw err
106
109
  })
107
110
  })
108
111
  }
109
112
 
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
113
+ async function collectFiles (sourcesByUrl, loadOpts, concurrency, fetchedUrls) {
114
+ const loadTasks = [...sourcesByUrl.entries()].map(([url, sources]) => {
115
+ const loadOptsForUrl = Object.assign({}, loadOpts)
116
+ if (loadOpts.fetch && fetchedUrls && fetchedUrls.length && fetchedUrls.includes(url)) loadOptsForUrl.fetch = false
117
+ if (tagsSpecified(sources)) loadOptsForUrl.fetchTags = true
118
+ return loadRepository.bind(null, url, loadOptsForUrl, { url, sources })
119
+ })
120
+ return gracefulPromiseAllWithLimit(loadTasks, concurrency.fetch).then(([results, rejections]) => {
121
+ if (rejections.length) {
122
+ if (concurrency.fetch > 1 && results.length > 1 && rejections.every(({ recoverable }) => recoverable)) {
123
+ if (loadOpts.progress) loadOpts.progress.terminate() // reset cursor position and allow it be reused
124
+ const msg0 = 'An unexpected error occurred while fetching content sources concurrently.'
125
+ const msg1 = 'Retrying with git.fetch_concurrency value of 1.'
126
+ logger.warn(rejections[0], msg0 + ' ' + msg1)
127
+ const fulfilledUrls = results.map((it) => it && it.repo.url && it.url).filter((it) => it)
128
+ return collectFiles(sourcesByUrl, loadOpts, Object.assign(concurrency, { fetch: 1 }), fulfilledUrls)
129
+ }
130
+ throw rejections[0]
143
131
  }
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
- )
132
+ return Promise.all(
133
+ results.map(({ repo, authStatus, sources }) =>
134
+ selectStartPathsForRepository(repo, authStatus, sources).then((startPaths) =>
135
+ collectFilesFromStartPaths.bind(null, startPaths, repo, authStatus)
136
+ )
137
+ )
138
+ ).then((collectTasks) => promiseAllWithLimit(collectTasks, concurrency.read))
139
+ })
153
140
  }
154
141
 
155
142
  function buildAggregate (componentVersionBuckets) {
156
- return [
157
- ...deepFlatten(componentVersionBuckets)
158
- .reduce((accum, batch) => {
159
- const key = batch.version + '@' + batch.name
160
- const entry = accum.get(key)
161
- if (!entry) return accum.set(key, batch)
143
+ const entries = Object.assign(new Map(), { accum: [] })
144
+ for (const batchesForOrigin of componentVersionBuckets) {
145
+ for (const batch of batchesForOrigin) {
146
+ let key, entry
147
+ if ((entry = entries.get((key = batch.version + '@' + batch.name)))) {
162
148
  const { files, origins } = batch
163
149
  ;(batch.files = entry.files).push(...files)
164
150
  ;(batch.origins = entry.origins).push(origins[0])
165
151
  Object.assign(entry, batch)
166
- return accum
167
- }, new Map())
168
- .values(),
169
- ]
152
+ } else {
153
+ entries.set(key, batch).accum.push(batch)
154
+ }
155
+ }
156
+ }
157
+ return entries.accum
170
158
  }
171
159
 
172
- async function loadRepository (url, opts) {
160
+ async function loadRepository (url, opts, result = {}) {
173
161
  let authStatus, dir, repo
174
162
  const cache = { [REF_PATTERN_CACHE_KEY]: opts.refPatternCache }
175
163
  if (~url.indexOf(':') && GIT_URI_DETECTOR_RX.test(url)) {
@@ -179,6 +167,7 @@ async function loadRepository (url, opts) {
179
167
  dir = ospath.join(cacheDir, generateCloneFolderName(displayUrl))
180
168
  // NOTE the presence of the url property on the repo object implies the repository is remote
181
169
  repo = { cache, dir, fs, gitdir: dir, noCheckout: true, url }
170
+ const { credentialManager } = gitPlugins
182
171
  const validStateFile = ospath.join(dir, VALID_STATE_FILENAME)
183
172
  try {
184
173
  await fsp.access(validStateFile)
@@ -188,7 +177,7 @@ async function loadRepository (url, opts) {
188
177
  await git
189
178
  .fetch(fetchOpts)
190
179
  .then(() => {
191
- authStatus = identifyAuthStatus(gitPlugins.credentialManager, credentials, url)
180
+ authStatus = identifyAuthStatus(credentialManager, credentials, url)
192
181
  return git.setConfig(Object.assign({ path: 'remote.origin.private', value: authStatus }, repo))
193
182
  })
194
183
  .catch((fetchErr) => {
@@ -209,13 +198,13 @@ async function loadRepository (url, opts) {
209
198
  .clone(fetchOpts)
210
199
  .then(() => git.resolveRef(Object.assign({ ref: 'HEAD', depth: 1 }, repo)))
211
200
  .then(() => {
212
- authStatus = identifyAuthStatus(gitPlugins.credentialManager, credentials, url)
201
+ authStatus = identifyAuthStatus(credentialManager, credentials, url)
213
202
  return git.setConfig(Object.assign({ path: 'remote.origin.private', value: authStatus }, repo))
214
203
  })
215
204
  .catch((cloneErr) => {
216
- // FIXME triggering the error handler here causes assertion problems in the test suite
217
- //if (fetchOpts.onProgress) fetchOpts.onProgress.finish(cloneErr)
218
- throw transformGitCloneError(cloneErr, displayUrl)
205
+ if (fetchOpts.onProgress) fetchOpts.onProgress.finish(cloneErr)
206
+ const authRequested = credentialManager.status({ url }) === 'requested'
207
+ throw transformGitCloneError(cloneErr, displayUrl, authRequested)
219
208
  })
220
209
  .then(() => fsp.writeFile(validStateFile, '').catch(invariably.void))
221
210
  .then(() => fetchOpts.onProgress && fetchOpts.onProgress.finish())
@@ -239,7 +228,7 @@ async function loadRepository (url, opts) {
239
228
  } else {
240
229
  throw new Error(`Local content source does not exist: ${dir}${url !== dir ? ' (url: ' + url + ')' : ''}`)
241
230
  }
242
- return { repo, authStatus }
231
+ return Object.assign(result, { repo, authStatus })
243
232
  }
244
233
 
245
234
  function extractCredentials (url) {
@@ -261,27 +250,42 @@ function extractCredentials (url) {
261
250
  }
262
251
  }
263
252
 
264
- async function collectFilesFromSource (source, repo, remoteName, authStatus) {
265
- const originUrl = repo.url || (await resolveRemoteUrl(repo, remoteName))
266
- return selectReferences(source, repo, remoteName).then((refs) => {
267
- if (!refs.length) {
268
- const { url, branches, tags, startPath, startPaths } = source
253
+ async function selectStartPathsForRepository (repo, authStatus, sources) {
254
+ const startPaths = []
255
+ const originUrls = {}
256
+ for (const source of sources) {
257
+ const { version, editUrl } = source
258
+ // NOTE if repository is managed (has a url property), we can assume the remote name is origin
259
+ // TODO if the repo has no remotes, then remoteName should be undefined
260
+ const remoteName = repo.url ? 'origin' : source.remote || 'origin'
261
+ const originUrl = repo.url || (originUrls[remoteName] ||= await resolveRemoteUrl(repo, remoteName))
262
+ const refs = await selectReferences(source, repo, remoteName)
263
+ if (refs.length) {
264
+ for (const ref of refs) {
265
+ for (const startPath of await selectStartPaths(source, repo, remoteName, ref)) {
266
+ startPaths.push({ startPath, ref, originUrl, editUrl, version })
267
+ }
268
+ }
269
+ } else {
270
+ const { url, branches, tags } = source
269
271
  const startPathInfo =
270
- 'startPaths' in source ? { 'start paths': startPaths || undefined } : { 'start path': startPath || undefined }
272
+ 'startPaths' in source
273
+ ? { 'start paths': source.startPaths || undefined }
274
+ : { 'start path': source.startPath || undefined }
271
275
  const sourceInfo = yaml.dump({ url, branches, tags, ...startPathInfo }, { flowLevel: 1 }).trimRight()
272
276
  logger.info(`No matching references found for content source entry (${sourceInfo.replace(NEWLINE_RX, ' | ')})`)
273
- return []
274
277
  }
275
- return Promise.all(refs.map((it) => collectFilesFromReference(source, repo, remoteName, authStatus, it, originUrl)))
276
- })
278
+ }
279
+ return startPaths
277
280
  }
278
281
 
279
282
  // QUESTION should we resolve HEAD to a ref eagerly to avoid having to do a match on it?
280
283
  async function selectReferences (source, repo, remote) {
281
284
  let { branches: branchPatterns, tags: tagPatterns, worktrees: worktreePatterns } = source
282
- const isBare = repo.noCheckout
285
+ const managed = 'url' in repo
286
+ const isBare = managed || repo.noCheckout
283
287
  const patternCache = repo.cache[REF_PATTERN_CACHE_KEY]
284
- const noWorktree = repo.url ? undefined : false
288
+ const noWorktree = managed ? undefined : false
285
289
  const refs = new Map()
286
290
  if (
287
291
  tagPatterns &&
@@ -389,10 +393,11 @@ async function selectReferences (source, repo, remote) {
389
393
  refs.set(shortname, { shortname, fullname: 'heads/' + shortname, type: 'branch', head })
390
394
  }
391
395
  }
392
- } else if (!remoteBranches.length) {
396
+ } else if (!managed || !remoteBranches.length) {
393
397
  const localBranches = await git.listBranches(repo)
394
398
  if (localBranches.length) {
395
399
  for (const shortname of filterRefs(localBranches, branchPatterns, patternCache)) {
400
+ if (refs.has(shortname)) continue // NOTE prefer remote branches in bare repository
396
401
  refs.set(shortname, { shortname, fullname: 'heads/' + shortname, type: 'branch', head: noWorktree })
397
402
  }
398
403
  }
@@ -413,10 +418,9 @@ function getCurrentBranchName (repo, remote) {
413
418
  ).then((ref) => (ref.startsWith('refs/') ? ref.replace(SHORTEN_REF_RX, '') : undefined))
414
419
  }
415
420
 
416
- async function collectFilesFromReference (source, repo, remoteName, authStatus, ref, originUrl) {
421
+ async function selectStartPaths (source, repo, remoteName, ref) {
417
422
  const url = repo.url
418
423
  const displayUrl = url || repo.dir
419
- const { version, editUrl } = source
420
424
  const worktreePath = ref.head
421
425
  if (!worktreePath) {
422
426
  ref.oid = await git.resolveRef(
@@ -436,17 +440,22 @@ async function collectFilesFromReference (source, repo, remoteName, authStatus,
436
440
  const flag = worktreePath ? ' <worktree>' : ref.remote && worktreePath === false ? ` <remotes/${ref.remote}>` : ''
437
441
  throw new Error(`no start paths found in ${where} (${ref.type}: ${ref.shortname}${flag})`)
438
442
  }
439
- return Promise.all(
440
- startPaths.map((startPath) =>
441
- collectFilesFromStartPath(startPath, repo, authStatus, ref, worktreePath, originUrl, editUrl, version)
442
- )
443
- )
443
+ return startPaths
444
444
  }
445
- const startPath = cleanStartPath(coerceToString(source.startPath))
446
- return collectFilesFromStartPath(startPath, repo, authStatus, ref, worktreePath, originUrl, editUrl, version)
445
+ return [cleanStartPath(coerceToString(source.startPath))]
447
446
  }
448
447
 
449
- function collectFilesFromStartPath (startPath, repo, authStatus, ref, worktreePath, originUrl, editUrl, version) {
448
+ async function collectFilesFromStartPaths (startPaths, repo, authStatus) {
449
+ const buckets = []
450
+ for (const { startPath, ref, originUrl, editUrl, version } of startPaths) {
451
+ buckets.push(await collectFilesFromStartPath(startPath, repo, authStatus, ref, originUrl, editUrl, version))
452
+ }
453
+ repo.cache = undefined
454
+ return buckets
455
+ }
456
+
457
+ function collectFilesFromStartPath (startPath, repo, authStatus, ref, originUrl, editUrl, version) {
458
+ const worktreePath = ref.head
450
459
  const origin = computeOrigin(originUrl, authStatus, repo.gitdir, ref, startPath, worktreePath, editUrl)
451
460
  return (worktreePath ? readFilesFromWorktree(origin) : readFilesFromGitTree(repo, ref.oid, startPath))
452
461
  .then((files) =>
@@ -526,26 +535,25 @@ function srcFs (cwd, origin) {
526
535
  }
527
536
 
528
537
  function readFilesFromGitTree (repo, oid, startPath) {
529
- return git
530
- .readTree(Object.assign({ oid }, repo))
531
- .then((root) =>
532
- getGitTreeAtStartPath(repo, oid, startPath).then((start) =>
533
- srcGitTree(repo, Object.assign(root, { dirname: '' }), start)
534
- )
535
- )
538
+ return git.readTree(Object.assign({ oid }, repo)).then((root) => {
539
+ Object.assign(root, { dirname: '' })
540
+ return startPath
541
+ ? getGitTreeAtStartPath(repo, oid, startPath).then((start) => {
542
+ Object.assign(start, { dirname: startPath })
543
+ return srcGitTree(repo, root, start)
544
+ })
545
+ : srcGitTree(repo, root)
546
+ })
536
547
  }
537
548
 
538
549
  function getGitTreeAtStartPath (repo, oid, startPath) {
539
- return git.readTree(Object.assign({ oid, filepath: startPath }, repo)).then(
540
- (result) => Object.assign(result, { dirname: startPath }),
541
- (err) => {
542
- const m = err instanceof ObjectTypeError && err.data.expected === 'tree' ? 'is not a directory' : 'does not exist'
543
- throw new Error(`the start path '${startPath}' ${m}`)
544
- }
545
- )
550
+ return git.readTree(Object.assign({ oid, filepath: startPath }, repo)).catch((err) => {
551
+ const m = err instanceof ObjectTypeError && err.data.expected === 'tree' ? 'is not a directory' : 'does not exist'
552
+ throw new Error(`the start path '${startPath}' ${m}`)
553
+ })
546
554
  }
547
555
 
548
- function srcGitTree (repo, root, start) {
556
+ function srcGitTree (repo, root, start = root) {
549
557
  return new Promise((resolve, reject) => {
550
558
  const files = []
551
559
  createGitTreeWalker(repo, root, filterGitEntry, gitEntryToFile)
@@ -832,7 +840,6 @@ function onGitProgress ({ phase, loaded, total }) {
832
840
 
833
841
  function onGitComplete (err) {
834
842
  if (err) {
835
- // TODO could use progressBar.interrupt() to replace bar with message instead
836
843
  this.chars.incomplete = '?'
837
844
  this.update(0)
838
845
  // NOTE force progress bar to update regardless of throttle setting
@@ -982,8 +989,8 @@ function ensureCacheDir (preferredCacheDir, startDir) {
982
989
  )
983
990
  }
984
991
 
985
- function transformGitCloneError (err, displayUrl) {
986
- let wrappedMsg, trimMessage
992
+ function transformGitCloneError (err, displayUrl, authRequested) {
993
+ let wrappedMsg, recoverable, trimMessage
987
994
  if (HTTP_ERROR_CODE_RX.test(err.code)) {
988
995
  switch (err.data.statusCode) {
989
996
  case 401:
@@ -992,25 +999,26 @@ function transformGitCloneError (err, displayUrl) {
992
999
  : 'Content repository not found or requires credentials'
993
1000
  break
994
1001
  case 404:
995
- wrappedMsg = 'Content repository not found'
1002
+ wrappedMsg = authRequested
1003
+ ? 'Content repository not found or credentials were rejected'
1004
+ : 'Content repository not found'
996
1005
  break
997
1006
  default:
998
1007
  wrappedMsg = err.message
999
- trimMessage = true
1008
+ recoverable = trimMessage = true
1000
1009
  }
1001
1010
  } else if (err instanceof UrlParseError || err instanceof UnknownTransportError) {
1002
1011
  wrappedMsg = 'Content source uses an unsupported transport protocol'
1003
1012
  } else if (err.code === 'ENOTFOUND') {
1004
1013
  wrappedMsg = `Content repository host could not be resolved: ${err.hostname}`
1005
1014
  } else {
1006
- wrappedMsg = `${err.name}: ${err.message}`
1007
- trimMessage = true
1008
- }
1009
- if (trimMessage) {
1010
- wrappedMsg = ~(wrappedMsg = wrappedMsg.trimRight()).indexOf('. ') ? wrappedMsg : wrappedMsg.replace(/\.$/, '')
1015
+ wrappedMsg = err.message || String(err)
1016
+ recoverable = trimMessage = true
1011
1017
  }
1018
+ if (trimMessage && !~(wrappedMsg = wrappedMsg.trimEnd()).indexOf('. ')) wrappedMsg = wrappedMsg.replace(/\.$/, '')
1012
1019
  const errWrapper = new Error(`${wrappedMsg} (url: ${displayUrl})`)
1013
- errWrapper.stack += `\nCaused by: ${err.stack || 'unknown'}`
1020
+ errWrapper.stack += `\nCaused by: ${err.stack ? inspect(err).replace(/^Error \[(.+?)\](?=: )/, '$1') : err}`
1021
+ if (recoverable) Object.defineProperty(errWrapper, 'recoverable', { value: true })
1014
1022
  return errWrapper
1015
1023
  }
1016
1024
 
@@ -1041,11 +1049,11 @@ function coerceToString (value) {
1041
1049
  function resolveRepositoryFromWorktree (repo) {
1042
1050
  return fsp
1043
1051
  .readFile(repo.gitdir, 'utf8')
1044
- .then((contents) => contents.trimRight().substr(8))
1052
+ .then((contents) => contents.substr(8).trimEnd())
1045
1053
  .then((worktreeGitdir) =>
1046
1054
  fsp.readFile(ospath.join(worktreeGitdir, 'commondir'), 'utf8').then(
1047
1055
  (contents) => {
1048
- const gitdir = ospath.join(worktreeGitdir, contents.trimRight())
1056
+ const gitdir = ospath.join(worktreeGitdir, contents.trimEnd())
1049
1057
  const dir = ospath.basename(gitdir) === '.git' ? ospath.dirname(gitdir) : gitdir
1050
1058
  return Object.assign(repo, { dir, gitdir, worktreeName: ospath.basename(worktreeGitdir) })
1051
1059
  },
@@ -1077,7 +1085,7 @@ function findWorktrees (repo, patterns) {
1077
1085
  .then((branch = worktreeName) =>
1078
1086
  fsp
1079
1087
  .readFile(ospath.join(gitdir, 'gitdir'), 'utf8')
1080
- .then((contents) => [branch, { head: ospath.dirname(contents.trimRight()), name: worktreeName }])
1088
+ .then((contents) => [branch, { head: ospath.dirname(contents.trimEnd()), name: worktreeName }])
1081
1089
  )
1082
1090
  })
1083
1091
  )
@@ -1086,4 +1094,37 @@ function findWorktrees (repo, patterns) {
1086
1094
  ).then((entries = []) => mainWorktree.then((entry) => new Map(entry ? entries.push(entry) && entries : entries)))
1087
1095
  }
1088
1096
 
1097
+ async function gracefulPromiseAllWithLimit (tasks, limit = Infinity) {
1098
+ const rejections = []
1099
+ const recordRejection = (err) => rejections.push(err) && undefined
1100
+ const started = []
1101
+ if (tasks.length <= limit) {
1102
+ for (const task of tasks) started.push(task().catch(recordRejection))
1103
+ } else {
1104
+ const pending = []
1105
+ for (const task of tasks) {
1106
+ const current = task()
1107
+ .catch(recordRejection)
1108
+ .finally(() => pending.splice(pending.indexOf(current), 1))
1109
+ started.push(current)
1110
+ if (pending.push(current) < limit) continue
1111
+ await Promise.race(pending)
1112
+ if (rejections.length) break
1113
+ }
1114
+ }
1115
+ return Promise.all(started).then((results) => [results, rejections])
1116
+ }
1117
+
1118
+ async function promiseAllWithLimit (tasks, limit = Infinity) {
1119
+ if (tasks.length <= limit) return Promise.all(tasks.map((task) => task()))
1120
+ const started = []
1121
+ const pending = []
1122
+ for (const task of tasks) {
1123
+ const current = task().finally(() => pending.splice(pending.indexOf(current), 1))
1124
+ started.push(current)
1125
+ if (pending.push(current) >= limit) await Promise.race(pending)
1126
+ }
1127
+ return Promise.all(started)
1128
+ }
1129
+
1089
1130
  module.exports = aggregateContent
@@ -49,11 +49,12 @@ module.exports = ({ headers: extraHeaders, httpProxy, httpsProxy, noProxy } = {}
49
49
  }
50
50
  return {
51
51
  async request ({ url, method, headers, body }) {
52
- headers = mergeHeaders(headers, extraHeaders)
52
+ headers = Object.assign(mergeHeaders(headers, extraHeaders), { connection: 'close' })
53
53
  body = await mergeBuffers(body)
54
- return new Promise((resolve, reject) =>
55
- get({ url, method, headers, body }, (err, res) => (err ? reject(err) : resolve(distillResponse(res))))
56
- )
54
+ return new Promise((resolve, reject) => {
55
+ const opts = { url, method, headers, body, timeout: 0, keepAlive: false }
56
+ return get(opts, (err, res) => (err ? reject(err) : resolve(distillResponse(res))))
57
+ })
57
58
  },
58
59
  }
59
60
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@antora/content-aggregator",
3
- "version": "3.2.0-alpha.2",
3
+ "version": "3.2.0-alpha.4",
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)",
@@ -29,13 +29,13 @@
29
29
  },
30
30
  "dependencies": {
31
31
  "@antora/expand-path-helper": "~2.0",
32
- "@antora/logger": "3.2.0-alpha.2",
32
+ "@antora/logger": "3.2.0-alpha.4",
33
33
  "@antora/user-require-helper": "~2.0",
34
34
  "braces": "~3.0",
35
35
  "cache-directory": "~2.0",
36
36
  "glob-stream": "~7.0",
37
37
  "hpagent": "~1.2",
38
- "isomorphic-git": "~1.21",
38
+ "isomorphic-git": "~1.25",
39
39
  "js-yaml": "~4.1",
40
40
  "multi-progress": "~4.0",
41
41
  "picomatch": "~2.3",
@@ -62,7 +62,7 @@
62
62
  ],
63
63
  "scripts": {
64
64
  "test": "_mocha",
65
- "prepublishOnly": "node $npm_config_local_prefix/npm/prepublishOnly.js",
66
- "postpublish": "node $npm_config_local_prefix/npm/postpublish.js"
65
+ "prepublishOnly": "npx -y downdoc@latest --prepublish",
66
+ "postpublish": "npx -y downdoc@latest --postpublish"
67
67
  }
68
68
  }