@antora/content-aggregator 3.1.4 → 3.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/aggregate-content.js +148 -107
- package/package.json +2 -2
package/lib/aggregate-content.js
CHANGED
|
@@ -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')
|
|
@@ -93,83 +92,71 @@ function aggregateContent (playbook) {
|
|
|
93
92
|
return ensureCacheDir(requestedCacheDir, startDir).then((cacheDir) => {
|
|
94
93
|
const gitConfig = Object.assign({ ensureGitSuffix: true }, playbook.git)
|
|
95
94
|
const gitPlugins = loadGitPlugins(gitConfig, playbook.network || {}, startDir)
|
|
96
|
-
const
|
|
95
|
+
const concurrency = {
|
|
96
|
+
fetch: Math.max(gitConfig.fetchConcurrency || Infinity, 1),
|
|
97
|
+
read: Math.max(gitConfig.readConcurrency || Infinity, 1),
|
|
98
|
+
}
|
|
97
99
|
const sourcesByUrl = sources.reduce((accum, source) => {
|
|
98
100
|
return accum.set(source.url, [...(accum.get(source.url) || []), Object.assign({}, sourceDefaults, source)])
|
|
99
101
|
}, new Map())
|
|
100
102
|
const progress = !quiet && createProgress(sourcesByUrl.keys(), process.stdout)
|
|
101
103
|
const refPatternCache = Object.assign(new Map(), { braces: new Map() })
|
|
102
104
|
const loadOpts = { cacheDir, fetch, gitPlugins, progress, startDir, refPatternCache }
|
|
103
|
-
return collectFiles(sourcesByUrl, loadOpts,
|
|
105
|
+
return collectFiles(sourcesByUrl, loadOpts, concurrency).then(buildAggregate, (err) => {
|
|
104
106
|
progress && progress.terminate()
|
|
105
107
|
throw err
|
|
106
108
|
})
|
|
107
109
|
})
|
|
108
110
|
}
|
|
109
111
|
|
|
110
|
-
async function collectFiles (sourcesByUrl, loadOpts, concurrency) {
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
(
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
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
|
|
112
|
+
async function collectFiles (sourcesByUrl, loadOpts, concurrency, fetchedUrls) {
|
|
113
|
+
const loadTasks = [...sourcesByUrl.entries()].map(([url, sources]) => {
|
|
114
|
+
const loadOptsForUrl = Object.assign({}, loadOpts)
|
|
115
|
+
if (loadOpts.fetch && fetchedUrls && fetchedUrls.length && fetchedUrls.includes(url)) loadOptsForUrl.fetch = false
|
|
116
|
+
if (tagsSpecified(sources)) loadOptsForUrl.fetchTags = true
|
|
117
|
+
return loadRepository.bind(null, url, loadOptsForUrl, { url, sources })
|
|
118
|
+
})
|
|
119
|
+
return gracefulPromiseAllWithLimit(loadTasks, concurrency.fetch).then(([results, rejections]) => {
|
|
120
|
+
if (rejections.length) {
|
|
121
|
+
if (concurrency.fetch > 1 && rejections.every(({ recoverable }) => recoverable)) {
|
|
122
|
+
if (loadOpts.progress) loadOpts.progress.terminate() // reset cursor position and allow it be reused
|
|
123
|
+
const msg0 = 'An unexpected error occurred while concurrently fetching content sources.'
|
|
124
|
+
const msg1 = 'Retrying with git.fetch_concurrency value of 1.'
|
|
125
|
+
logger.warn(msg0 + ' ' + msg1)
|
|
126
|
+
const fulfilledUrls = results.map((it) => it && it.repo.url && it.url).filter((it) => it)
|
|
127
|
+
return collectFiles(sourcesByUrl, loadOpts, Object.assign(concurrency, { fetch: 1 }), fulfilledUrls)
|
|
128
|
+
}
|
|
129
|
+
throw rejections[0]
|
|
143
130
|
}
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
)
|
|
131
|
+
return Promise.all(
|
|
132
|
+
results.map(({ repo, authStatus, sources }) =>
|
|
133
|
+
selectStartPathsForRepository(repo, authStatus, sources).then((startPaths) =>
|
|
134
|
+
collectFilesFromStartPaths.bind(null, startPaths, repo, authStatus)
|
|
135
|
+
)
|
|
136
|
+
)
|
|
137
|
+
).then((collectTasks) => promiseAllWithLimit(collectTasks, concurrency.read))
|
|
138
|
+
})
|
|
153
139
|
}
|
|
154
140
|
|
|
155
141
|
function buildAggregate (componentVersionBuckets) {
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
if (!entry) return accum.set(key, batch)
|
|
142
|
+
const entries = Object.assign(new Map(), { accum: [] })
|
|
143
|
+
for (const batchesForOrigin of componentVersionBuckets) {
|
|
144
|
+
for (const batch of batchesForOrigin) {
|
|
145
|
+
let key, entry
|
|
146
|
+
if ((entry = entries.get((key = batch.version + '@' + batch.name)))) {
|
|
162
147
|
const { files, origins } = batch
|
|
163
148
|
;(batch.files = entry.files).push(...files)
|
|
164
149
|
;(batch.origins = entry.origins).push(origins[0])
|
|
165
150
|
Object.assign(entry, batch)
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
151
|
+
} else {
|
|
152
|
+
entries.set(key, batch).accum.push(batch)
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return entries.accum
|
|
170
157
|
}
|
|
171
158
|
|
|
172
|
-
async function loadRepository (url, opts) {
|
|
159
|
+
async function loadRepository (url, opts, result = {}) {
|
|
173
160
|
let authStatus, dir, repo
|
|
174
161
|
const cache = { [REF_PATTERN_CACHE_KEY]: opts.refPatternCache }
|
|
175
162
|
if (~url.indexOf(':') && GIT_URI_DETECTOR_RX.test(url)) {
|
|
@@ -179,6 +166,7 @@ async function loadRepository (url, opts) {
|
|
|
179
166
|
dir = ospath.join(cacheDir, generateCloneFolderName(displayUrl))
|
|
180
167
|
// NOTE the presence of the url property on the repo object implies the repository is remote
|
|
181
168
|
repo = { cache, dir, fs, gitdir: dir, noCheckout: true, url }
|
|
169
|
+
const { credentialManager } = gitPlugins
|
|
182
170
|
const validStateFile = ospath.join(dir, VALID_STATE_FILENAME)
|
|
183
171
|
try {
|
|
184
172
|
await fsp.access(validStateFile)
|
|
@@ -188,8 +176,7 @@ async function loadRepository (url, opts) {
|
|
|
188
176
|
await git
|
|
189
177
|
.fetch(fetchOpts)
|
|
190
178
|
.then(() => {
|
|
191
|
-
|
|
192
|
-
authStatus = credentials ? 'auth-embedded' : credentialManager.status({ url }) ? 'auth-required' : undefined
|
|
179
|
+
authStatus = identifyAuthStatus(credentialManager, credentials, url)
|
|
193
180
|
return git.setConfig(Object.assign({ path: 'remote.origin.private', value: authStatus }, repo))
|
|
194
181
|
})
|
|
195
182
|
.catch((fetchErr) => {
|
|
@@ -210,14 +197,13 @@ async function loadRepository (url, opts) {
|
|
|
210
197
|
.clone(fetchOpts)
|
|
211
198
|
.then(() => git.resolveRef(Object.assign({ ref: 'HEAD', depth: 1 }, repo)))
|
|
212
199
|
.then(() => {
|
|
213
|
-
|
|
214
|
-
authStatus = credentials ? 'auth-embedded' : credentialManager.status({ url }) ? 'auth-required' : undefined
|
|
200
|
+
authStatus = identifyAuthStatus(credentialManager, credentials, url)
|
|
215
201
|
return git.setConfig(Object.assign({ path: 'remote.origin.private', value: authStatus }, repo))
|
|
216
202
|
})
|
|
217
203
|
.catch((cloneErr) => {
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
throw transformGitCloneError(cloneErr, displayUrl)
|
|
204
|
+
if (fetchOpts.onProgress) fetchOpts.onProgress.finish(cloneErr)
|
|
205
|
+
const authRequested = credentialManager.status({ url }) === 'requested'
|
|
206
|
+
throw transformGitCloneError(cloneErr, displayUrl, authRequested)
|
|
221
207
|
})
|
|
222
208
|
.then(() => fsp.writeFile(validStateFile, '').catch(invariably.void))
|
|
223
209
|
.then(() => fetchOpts.onProgress && fetchOpts.onProgress.finish())
|
|
@@ -234,7 +220,7 @@ async function loadRepository (url, opts) {
|
|
|
234
220
|
} else {
|
|
235
221
|
throw new Error(`Local content source does not exist: ${dir}${url !== dir ? ' (url: ' + url + ')' : ''}`)
|
|
236
222
|
}
|
|
237
|
-
return { repo, authStatus }
|
|
223
|
+
return Object.assign(result, { repo, authStatus })
|
|
238
224
|
}
|
|
239
225
|
|
|
240
226
|
function extractCredentials (url) {
|
|
@@ -256,19 +242,33 @@ function extractCredentials (url) {
|
|
|
256
242
|
}
|
|
257
243
|
}
|
|
258
244
|
|
|
259
|
-
async function
|
|
260
|
-
const
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
245
|
+
async function selectStartPathsForRepository (repo, authStatus, sources) {
|
|
246
|
+
const startPaths = []
|
|
247
|
+
const originUrls = {}
|
|
248
|
+
for (const source of sources) {
|
|
249
|
+
const { version, editUrl } = source
|
|
250
|
+
// NOTE if repository is managed (has a url property), we can assume the remote name is origin
|
|
251
|
+
// TODO if the repo has no remotes, then remoteName should be undefined
|
|
252
|
+
const remoteName = repo.url ? 'origin' : source.remote || 'origin'
|
|
253
|
+
const originUrl = repo.url || (originUrls[remoteName] ||= await resolveRemoteUrl(repo, remoteName))
|
|
254
|
+
const refs = await selectReferences(source, repo, remoteName)
|
|
255
|
+
if (refs.length) {
|
|
256
|
+
for (const ref of refs) {
|
|
257
|
+
for (const startPath of await selectStartPaths(source, repo, remoteName, ref)) {
|
|
258
|
+
startPaths.push({ startPath, ref, originUrl, editUrl, version })
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
} else {
|
|
262
|
+
const { url, branches, tags } = source
|
|
264
263
|
const startPathInfo =
|
|
265
|
-
'startPaths' in source
|
|
264
|
+
'startPaths' in source
|
|
265
|
+
? { 'start paths': source.startPaths || undefined }
|
|
266
|
+
: { 'start path': source.startPath || undefined }
|
|
266
267
|
const sourceInfo = yaml.dump({ url, branches, tags, ...startPathInfo }, { flowLevel: 1 }).trimRight()
|
|
267
268
|
logger.info(`No matching references found for content source entry (${sourceInfo.replace(NEWLINE_RX, ' | ')})`)
|
|
268
|
-
return []
|
|
269
269
|
}
|
|
270
|
-
|
|
271
|
-
|
|
270
|
+
}
|
|
271
|
+
return startPaths
|
|
272
272
|
}
|
|
273
273
|
|
|
274
274
|
// QUESTION should we resolve HEAD to a ref eagerly to avoid having to do a match on it?
|
|
@@ -397,10 +397,9 @@ function getCurrentBranchName (repo, remote) {
|
|
|
397
397
|
return refPromise.then((ref) => (ref.startsWith('refs/') ? ref.replace(SHORTEN_REF_RX, '') : undefined))
|
|
398
398
|
}
|
|
399
399
|
|
|
400
|
-
async function
|
|
400
|
+
async function selectStartPaths (source, repo, remoteName, ref) {
|
|
401
401
|
const url = repo.url
|
|
402
402
|
const displayUrl = url || repo.dir
|
|
403
|
-
const { version, editUrl } = source
|
|
404
403
|
const worktreePath = ref.head
|
|
405
404
|
if (!worktreePath) {
|
|
406
405
|
ref.oid = await git.resolveRef(
|
|
@@ -420,17 +419,22 @@ async function collectFilesFromReference (source, repo, remoteName, authStatus,
|
|
|
420
419
|
const flag = worktreePath ? ' <worktree>' : ref.remote && worktreePath === false ? ` <remotes/${ref.remote}>` : ''
|
|
421
420
|
throw new Error(`no start paths found in ${where} (${ref.type}: ${ref.shortname}${flag})`)
|
|
422
421
|
}
|
|
423
|
-
return
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
422
|
+
return startPaths
|
|
423
|
+
}
|
|
424
|
+
return [cleanStartPath(coerceToString(source.startPath))]
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
async function collectFilesFromStartPaths (startPaths, repo, authStatus) {
|
|
428
|
+
const buckets = []
|
|
429
|
+
for (const { startPath, ref, originUrl, editUrl, version } of startPaths) {
|
|
430
|
+
buckets.push(await collectFilesFromStartPath(startPath, repo, authStatus, ref, originUrl, editUrl, version))
|
|
428
431
|
}
|
|
429
|
-
|
|
430
|
-
return
|
|
432
|
+
repo.cache = undefined
|
|
433
|
+
return buckets
|
|
431
434
|
}
|
|
432
435
|
|
|
433
|
-
function collectFilesFromStartPath (startPath, repo, authStatus, ref,
|
|
436
|
+
function collectFilesFromStartPath (startPath, repo, authStatus, ref, originUrl, editUrl, version) {
|
|
437
|
+
const worktreePath = ref.head
|
|
434
438
|
const origin = computeOrigin(originUrl, authStatus, repo.gitdir, ref, startPath, worktreePath, editUrl)
|
|
435
439
|
return (worktreePath ? readFilesFromWorktree(origin) : readFilesFromGitTree(repo, ref.oid, startPath))
|
|
436
440
|
.then((files) =>
|
|
@@ -510,26 +514,25 @@ function srcFs (cwd, origin) {
|
|
|
510
514
|
}
|
|
511
515
|
|
|
512
516
|
function readFilesFromGitTree (repo, oid, startPath) {
|
|
513
|
-
return git
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
getGitTreeAtStartPath(repo, oid, startPath).then((start) =>
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
517
|
+
return git.readTree(Object.assign({ oid }, repo)).then((root) => {
|
|
518
|
+
Object.assign(root, { dirname: '' })
|
|
519
|
+
return startPath
|
|
520
|
+
? getGitTreeAtStartPath(repo, oid, startPath).then((start) => {
|
|
521
|
+
Object.assign(start, { dirname: startPath })
|
|
522
|
+
return srcGitTree(repo, root, start)
|
|
523
|
+
})
|
|
524
|
+
: srcGitTree(repo, root)
|
|
525
|
+
})
|
|
520
526
|
}
|
|
521
527
|
|
|
522
528
|
function getGitTreeAtStartPath (repo, oid, startPath) {
|
|
523
|
-
return git.readTree(Object.assign({ oid, filepath: startPath }, repo)).
|
|
524
|
-
|
|
525
|
-
(
|
|
526
|
-
|
|
527
|
-
throw new Error(`the start path '${startPath}' ${m}`)
|
|
528
|
-
}
|
|
529
|
-
)
|
|
529
|
+
return git.readTree(Object.assign({ oid, filepath: startPath }, repo)).catch((err) => {
|
|
530
|
+
const m = err instanceof ObjectTypeError && err.data.expected === 'tree' ? 'is not a directory' : 'does not exist'
|
|
531
|
+
throw new Error(`the start path '${startPath}' ${m}`)
|
|
532
|
+
})
|
|
530
533
|
}
|
|
531
534
|
|
|
532
|
-
function srcGitTree (repo, root, start) {
|
|
535
|
+
function srcGitTree (repo, root, start = root) {
|
|
533
536
|
return new Promise((resolve, reject) => {
|
|
534
537
|
const files = []
|
|
535
538
|
createGitTreeWalker(repo, root, filterGitEntry, gitEntryToFile)
|
|
@@ -816,7 +819,6 @@ function onGitProgress ({ phase, loaded, total }) {
|
|
|
816
819
|
|
|
817
820
|
function onGitComplete (err) {
|
|
818
821
|
if (err) {
|
|
819
|
-
// TODO could use progressBar.interrupt() to replace bar with message instead
|
|
820
822
|
this.chars.incomplete = '?'
|
|
821
823
|
this.update(0)
|
|
822
824
|
// NOTE force progress bar to update regardless of throttle setting
|
|
@@ -846,6 +848,10 @@ function resolveCredentials (credentialsFromUrlHolder, url, auth) {
|
|
|
846
848
|
)
|
|
847
849
|
}
|
|
848
850
|
|
|
851
|
+
function identifyAuthStatus (credentialManager, credentials, url) {
|
|
852
|
+
return credentials ? 'auth-embedded' : credentialManager.status({ url }) ? 'auth-required' : undefined
|
|
853
|
+
}
|
|
854
|
+
|
|
849
855
|
/**
|
|
850
856
|
* Generates a safe, unique folder name for a git URL.
|
|
851
857
|
*
|
|
@@ -957,8 +963,8 @@ function ensureCacheDir (preferredCacheDir, startDir) {
|
|
|
957
963
|
)
|
|
958
964
|
}
|
|
959
965
|
|
|
960
|
-
function transformGitCloneError (err, displayUrl) {
|
|
961
|
-
let wrappedMsg, trimMessage
|
|
966
|
+
function transformGitCloneError (err, displayUrl, authRequested) {
|
|
967
|
+
let wrappedMsg, recoverable, trimMessage
|
|
962
968
|
if (HTTP_ERROR_CODE_RX.test(err.code)) {
|
|
963
969
|
switch (err.data.statusCode) {
|
|
964
970
|
case 401:
|
|
@@ -967,11 +973,13 @@ function transformGitCloneError (err, displayUrl) {
|
|
|
967
973
|
: 'Content repository not found or requires credentials'
|
|
968
974
|
break
|
|
969
975
|
case 404:
|
|
970
|
-
wrappedMsg =
|
|
976
|
+
wrappedMsg = authRequested
|
|
977
|
+
? 'Content repository not found or credentials were rejected'
|
|
978
|
+
: 'Content repository not found'
|
|
971
979
|
break
|
|
972
980
|
default:
|
|
973
981
|
wrappedMsg = err.message
|
|
974
|
-
trimMessage = true
|
|
982
|
+
recoverable = trimMessage = true
|
|
975
983
|
}
|
|
976
984
|
} else if (err instanceof UrlParseError || err instanceof UnknownTransportError) {
|
|
977
985
|
wrappedMsg = 'Content source uses an unsupported transport protocol'
|
|
@@ -979,14 +987,14 @@ function transformGitCloneError (err, displayUrl) {
|
|
|
979
987
|
wrappedMsg = `Content repository host could not be resolved: ${err.hostname}`
|
|
980
988
|
} else {
|
|
981
989
|
wrappedMsg = `${err.name}: ${err.message}`
|
|
982
|
-
trimMessage = true
|
|
990
|
+
recoverable = trimMessage = true
|
|
983
991
|
}
|
|
984
992
|
if (trimMessage) {
|
|
985
|
-
wrappedMsg = ~(wrappedMsg = wrappedMsg.
|
|
993
|
+
wrappedMsg = ~(wrappedMsg = wrappedMsg.trimEnd()).indexOf('. ') ? wrappedMsg : wrappedMsg.replace(/\.$/, '')
|
|
986
994
|
}
|
|
987
995
|
const errWrapper = new Error(`${wrappedMsg} (url: ${displayUrl})`)
|
|
988
996
|
errWrapper.stack += `\nCaused by: ${err.stack || 'unknown'}`
|
|
989
|
-
return errWrapper
|
|
997
|
+
return recoverable ? Object.assign(errWrapper, { recoverable }) : errWrapper
|
|
990
998
|
}
|
|
991
999
|
|
|
992
1000
|
function splitRefPatterns (str) {
|
|
@@ -1034,7 +1042,7 @@ function findWorktrees (repo, patterns) {
|
|
|
1034
1042
|
.then((branch = worktreeName) =>
|
|
1035
1043
|
fsp
|
|
1036
1044
|
.readFile(ospath.join(gitdir, 'gitdir'), 'utf8')
|
|
1037
|
-
.then((contents) => ({ branch, dir: ospath.dirname(contents.
|
|
1045
|
+
.then((contents) => ({ branch, dir: ospath.dirname(contents.trimEnd()) }))
|
|
1038
1046
|
)
|
|
1039
1047
|
})
|
|
1040
1048
|
).then((entries) => entries.reduce((accum, it) => accum.set(it.branch, it.dir), new Map()))
|
|
@@ -1048,4 +1056,37 @@ function findWorktrees (repo, patterns) {
|
|
|
1048
1056
|
)
|
|
1049
1057
|
}
|
|
1050
1058
|
|
|
1059
|
+
async function gracefulPromiseAllWithLimit (tasks, limit = Infinity) {
|
|
1060
|
+
const rejections = []
|
|
1061
|
+
const recordRejection = (err) => rejections.push(err) && undefined
|
|
1062
|
+
const started = []
|
|
1063
|
+
if (tasks.length <= limit) {
|
|
1064
|
+
for (const task of tasks) started.push(task().catch(recordRejection))
|
|
1065
|
+
} else {
|
|
1066
|
+
const pending = []
|
|
1067
|
+
for (const task of tasks) {
|
|
1068
|
+
const current = task()
|
|
1069
|
+
.catch(recordRejection)
|
|
1070
|
+
.finally(() => pending.splice(pending.indexOf(current), 1))
|
|
1071
|
+
started.push(current)
|
|
1072
|
+
if (pending.push(current) < limit) continue
|
|
1073
|
+
await Promise.race(pending)
|
|
1074
|
+
if (rejections.length) break
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
return Promise.all(started).then((results) => [results, rejections])
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
async function promiseAllWithLimit (tasks, limit = Infinity) {
|
|
1081
|
+
if (tasks.length <= limit) return Promise.all(tasks.map((task) => task()))
|
|
1082
|
+
const started = []
|
|
1083
|
+
const pending = []
|
|
1084
|
+
for (const task of tasks) {
|
|
1085
|
+
const current = task().finally(() => pending.splice(pending.indexOf(current), 1))
|
|
1086
|
+
started.push(current)
|
|
1087
|
+
if (pending.push(current) >= limit) await Promise.race(pending)
|
|
1088
|
+
}
|
|
1089
|
+
return Promise.all(started)
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1051
1092
|
module.exports = aggregateContent
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@antora/content-aggregator",
|
|
3
|
-
"version": "3.1.
|
|
3
|
+
"version": "3.1.6",
|
|
4
4
|
"description": "Fetches and aggregates content from distributed sources for use in an Antora documentation pipeline.",
|
|
5
5
|
"license": "MPL-2.0",
|
|
6
6
|
"author": "OpenDevise Inc. (https://opendevise.com)",
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
31
|
"@antora/expand-path-helper": "~2.0",
|
|
32
|
-
"@antora/logger": "3.1.
|
|
32
|
+
"@antora/logger": "3.1.6",
|
|
33
33
|
"@antora/user-require-helper": "~2.0",
|
|
34
34
|
"braces": "~3.0",
|
|
35
35
|
"cache-directory": "~2.0",
|