@antora/content-aggregator 3.2.0-alpha.1 → 3.2.0-alpha.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/aggregate-content.js +165 -123
- package/package.json +5 -5
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,7 +176,7 @@ async function loadRepository (url, opts) {
|
|
|
188
176
|
await git
|
|
189
177
|
.fetch(fetchOpts)
|
|
190
178
|
.then(() => {
|
|
191
|
-
authStatus = identifyAuthStatus(
|
|
179
|
+
authStatus = identifyAuthStatus(credentialManager, credentials, url)
|
|
192
180
|
return git.setConfig(Object.assign({ path: 'remote.origin.private', value: authStatus }, repo))
|
|
193
181
|
})
|
|
194
182
|
.catch((fetchErr) => {
|
|
@@ -209,13 +197,13 @@ async function loadRepository (url, opts) {
|
|
|
209
197
|
.clone(fetchOpts)
|
|
210
198
|
.then(() => git.resolveRef(Object.assign({ ref: 'HEAD', depth: 1 }, repo)))
|
|
211
199
|
.then(() => {
|
|
212
|
-
authStatus = identifyAuthStatus(
|
|
200
|
+
authStatus = identifyAuthStatus(credentialManager, credentials, url)
|
|
213
201
|
return git.setConfig(Object.assign({ path: 'remote.origin.private', value: authStatus }, repo))
|
|
214
202
|
})
|
|
215
203
|
.catch((cloneErr) => {
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
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)
|
|
219
207
|
})
|
|
220
208
|
.then(() => fsp.writeFile(validStateFile, '').catch(invariably.void))
|
|
221
209
|
.then(() => fetchOpts.onProgress && fetchOpts.onProgress.finish())
|
|
@@ -239,7 +227,7 @@ async function loadRepository (url, opts) {
|
|
|
239
227
|
} else {
|
|
240
228
|
throw new Error(`Local content source does not exist: ${dir}${url !== dir ? ' (url: ' + url + ')' : ''}`)
|
|
241
229
|
}
|
|
242
|
-
return { repo, authStatus }
|
|
230
|
+
return Object.assign(result, { repo, authStatus })
|
|
243
231
|
}
|
|
244
232
|
|
|
245
233
|
function extractCredentials (url) {
|
|
@@ -261,27 +249,42 @@ function extractCredentials (url) {
|
|
|
261
249
|
}
|
|
262
250
|
}
|
|
263
251
|
|
|
264
|
-
async function
|
|
265
|
-
const
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
252
|
+
async function selectStartPathsForRepository (repo, authStatus, sources) {
|
|
253
|
+
const startPaths = []
|
|
254
|
+
const originUrls = {}
|
|
255
|
+
for (const source of sources) {
|
|
256
|
+
const { version, editUrl } = source
|
|
257
|
+
// NOTE if repository is managed (has a url property), we can assume the remote name is origin
|
|
258
|
+
// TODO if the repo has no remotes, then remoteName should be undefined
|
|
259
|
+
const remoteName = repo.url ? 'origin' : source.remote || 'origin'
|
|
260
|
+
const originUrl = repo.url || (originUrls[remoteName] ||= await resolveRemoteUrl(repo, remoteName))
|
|
261
|
+
const refs = await selectReferences(source, repo, remoteName)
|
|
262
|
+
if (refs.length) {
|
|
263
|
+
for (const ref of refs) {
|
|
264
|
+
for (const startPath of await selectStartPaths(source, repo, remoteName, ref)) {
|
|
265
|
+
startPaths.push({ startPath, ref, originUrl, editUrl, version })
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
} else {
|
|
269
|
+
const { url, branches, tags } = source
|
|
269
270
|
const startPathInfo =
|
|
270
|
-
'startPaths' in source
|
|
271
|
+
'startPaths' in source
|
|
272
|
+
? { 'start paths': source.startPaths || undefined }
|
|
273
|
+
: { 'start path': source.startPath || undefined }
|
|
271
274
|
const sourceInfo = yaml.dump({ url, branches, tags, ...startPathInfo }, { flowLevel: 1 }).trimRight()
|
|
272
275
|
logger.info(`No matching references found for content source entry (${sourceInfo.replace(NEWLINE_RX, ' | ')})`)
|
|
273
|
-
return []
|
|
274
276
|
}
|
|
275
|
-
|
|
276
|
-
|
|
277
|
+
}
|
|
278
|
+
return startPaths
|
|
277
279
|
}
|
|
278
280
|
|
|
279
281
|
// QUESTION should we resolve HEAD to a ref eagerly to avoid having to do a match on it?
|
|
280
282
|
async function selectReferences (source, repo, remote) {
|
|
281
283
|
let { branches: branchPatterns, tags: tagPatterns, worktrees: worktreePatterns } = source
|
|
282
|
-
const
|
|
284
|
+
const managed = 'url' in repo
|
|
285
|
+
const isBare = managed || repo.noCheckout
|
|
283
286
|
const patternCache = repo.cache[REF_PATTERN_CACHE_KEY]
|
|
284
|
-
const noWorktree =
|
|
287
|
+
const noWorktree = managed ? undefined : false
|
|
285
288
|
const refs = new Map()
|
|
286
289
|
if (
|
|
287
290
|
tagPatterns &&
|
|
@@ -297,11 +300,14 @@ async function selectReferences (source, repo, remote) {
|
|
|
297
300
|
}
|
|
298
301
|
}
|
|
299
302
|
}
|
|
300
|
-
if (
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
303
|
+
if (
|
|
304
|
+
!branchPatterns ||
|
|
305
|
+
!(branchPatterns = Array.isArray(branchPatterns)
|
|
306
|
+
? branchPatterns.map((pattern) => String(pattern))
|
|
307
|
+
: splitRefPatterns(String(branchPatterns))).length
|
|
308
|
+
) {
|
|
309
|
+
return [...refs.values()]
|
|
310
|
+
}
|
|
305
311
|
const worktreeName = repo.worktreeName // possibly switch to worktree property ({ name, dir}) in future
|
|
306
312
|
if (worktreeName) branchPatterns = branchPatterns.map((it) => (it === 'HEAD' ? 'HEAD@' + worktreeName : it))
|
|
307
313
|
if (worktreePatterns) {
|
|
@@ -386,10 +392,11 @@ async function selectReferences (source, repo, remote) {
|
|
|
386
392
|
refs.set(shortname, { shortname, fullname: 'heads/' + shortname, type: 'branch', head })
|
|
387
393
|
}
|
|
388
394
|
}
|
|
389
|
-
} else if (!remoteBranches.length) {
|
|
395
|
+
} else if (!managed || !remoteBranches.length) {
|
|
390
396
|
const localBranches = await git.listBranches(repo)
|
|
391
397
|
if (localBranches.length) {
|
|
392
398
|
for (const shortname of filterRefs(localBranches, branchPatterns, patternCache)) {
|
|
399
|
+
if (refs.has(shortname)) continue // NOTE prefer remote branches in bare repository
|
|
393
400
|
refs.set(shortname, { shortname, fullname: 'heads/' + shortname, type: 'branch', head: noWorktree })
|
|
394
401
|
}
|
|
395
402
|
}
|
|
@@ -410,10 +417,9 @@ function getCurrentBranchName (repo, remote) {
|
|
|
410
417
|
).then((ref) => (ref.startsWith('refs/') ? ref.replace(SHORTEN_REF_RX, '') : undefined))
|
|
411
418
|
}
|
|
412
419
|
|
|
413
|
-
async function
|
|
420
|
+
async function selectStartPaths (source, repo, remoteName, ref) {
|
|
414
421
|
const url = repo.url
|
|
415
422
|
const displayUrl = url || repo.dir
|
|
416
|
-
const { version, editUrl } = source
|
|
417
423
|
const worktreePath = ref.head
|
|
418
424
|
if (!worktreePath) {
|
|
419
425
|
ref.oid = await git.resolveRef(
|
|
@@ -433,17 +439,22 @@ async function collectFilesFromReference (source, repo, remoteName, authStatus,
|
|
|
433
439
|
const flag = worktreePath ? ' <worktree>' : ref.remote && worktreePath === false ? ` <remotes/${ref.remote}>` : ''
|
|
434
440
|
throw new Error(`no start paths found in ${where} (${ref.type}: ${ref.shortname}${flag})`)
|
|
435
441
|
}
|
|
436
|
-
return
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
442
|
+
return startPaths
|
|
443
|
+
}
|
|
444
|
+
return [cleanStartPath(coerceToString(source.startPath))]
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
async function collectFilesFromStartPaths (startPaths, repo, authStatus) {
|
|
448
|
+
const buckets = []
|
|
449
|
+
for (const { startPath, ref, originUrl, editUrl, version } of startPaths) {
|
|
450
|
+
buckets.push(await collectFilesFromStartPath(startPath, repo, authStatus, ref, originUrl, editUrl, version))
|
|
441
451
|
}
|
|
442
|
-
|
|
443
|
-
return
|
|
452
|
+
repo.cache = undefined
|
|
453
|
+
return buckets
|
|
444
454
|
}
|
|
445
455
|
|
|
446
|
-
function collectFilesFromStartPath (startPath, repo, authStatus, ref,
|
|
456
|
+
function collectFilesFromStartPath (startPath, repo, authStatus, ref, originUrl, editUrl, version) {
|
|
457
|
+
const worktreePath = ref.head
|
|
447
458
|
const origin = computeOrigin(originUrl, authStatus, repo.gitdir, ref, startPath, worktreePath, editUrl)
|
|
448
459
|
return (worktreePath ? readFilesFromWorktree(origin) : readFilesFromGitTree(repo, ref.oid, startPath))
|
|
449
460
|
.then((files) =>
|
|
@@ -523,26 +534,25 @@ function srcFs (cwd, origin) {
|
|
|
523
534
|
}
|
|
524
535
|
|
|
525
536
|
function readFilesFromGitTree (repo, oid, startPath) {
|
|
526
|
-
return git
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
getGitTreeAtStartPath(repo, oid, startPath).then((start) =>
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
537
|
+
return git.readTree(Object.assign({ oid }, repo)).then((root) => {
|
|
538
|
+
Object.assign(root, { dirname: '' })
|
|
539
|
+
return startPath
|
|
540
|
+
? getGitTreeAtStartPath(repo, oid, startPath).then((start) => {
|
|
541
|
+
Object.assign(start, { dirname: startPath })
|
|
542
|
+
return srcGitTree(repo, root, start)
|
|
543
|
+
})
|
|
544
|
+
: srcGitTree(repo, root)
|
|
545
|
+
})
|
|
533
546
|
}
|
|
534
547
|
|
|
535
548
|
function getGitTreeAtStartPath (repo, oid, startPath) {
|
|
536
|
-
return git.readTree(Object.assign({ oid, filepath: startPath }, repo)).
|
|
537
|
-
|
|
538
|
-
(
|
|
539
|
-
|
|
540
|
-
throw new Error(`the start path '${startPath}' ${m}`)
|
|
541
|
-
}
|
|
542
|
-
)
|
|
549
|
+
return git.readTree(Object.assign({ oid, filepath: startPath }, repo)).catch((err) => {
|
|
550
|
+
const m = err instanceof ObjectTypeError && err.data.expected === 'tree' ? 'is not a directory' : 'does not exist'
|
|
551
|
+
throw new Error(`the start path '${startPath}' ${m}`)
|
|
552
|
+
})
|
|
543
553
|
}
|
|
544
554
|
|
|
545
|
-
function srcGitTree (repo, root, start) {
|
|
555
|
+
function srcGitTree (repo, root, start = root) {
|
|
546
556
|
return new Promise((resolve, reject) => {
|
|
547
557
|
const files = []
|
|
548
558
|
createGitTreeWalker(repo, root, filterGitEntry, gitEntryToFile)
|
|
@@ -829,7 +839,6 @@ function onGitProgress ({ phase, loaded, total }) {
|
|
|
829
839
|
|
|
830
840
|
function onGitComplete (err) {
|
|
831
841
|
if (err) {
|
|
832
|
-
// TODO could use progressBar.interrupt() to replace bar with message instead
|
|
833
842
|
this.chars.incomplete = '?'
|
|
834
843
|
this.update(0)
|
|
835
844
|
// NOTE force progress bar to update regardless of throttle setting
|
|
@@ -979,8 +988,8 @@ function ensureCacheDir (preferredCacheDir, startDir) {
|
|
|
979
988
|
)
|
|
980
989
|
}
|
|
981
990
|
|
|
982
|
-
function transformGitCloneError (err, displayUrl) {
|
|
983
|
-
let wrappedMsg, trimMessage
|
|
991
|
+
function transformGitCloneError (err, displayUrl, authRequested) {
|
|
992
|
+
let wrappedMsg, recoverable, trimMessage
|
|
984
993
|
if (HTTP_ERROR_CODE_RX.test(err.code)) {
|
|
985
994
|
switch (err.data.statusCode) {
|
|
986
995
|
case 401:
|
|
@@ -989,11 +998,13 @@ function transformGitCloneError (err, displayUrl) {
|
|
|
989
998
|
: 'Content repository not found or requires credentials'
|
|
990
999
|
break
|
|
991
1000
|
case 404:
|
|
992
|
-
wrappedMsg =
|
|
1001
|
+
wrappedMsg = authRequested
|
|
1002
|
+
? 'Content repository not found or credentials were rejected'
|
|
1003
|
+
: 'Content repository not found'
|
|
993
1004
|
break
|
|
994
1005
|
default:
|
|
995
1006
|
wrappedMsg = err.message
|
|
996
|
-
trimMessage = true
|
|
1007
|
+
recoverable = trimMessage = true
|
|
997
1008
|
}
|
|
998
1009
|
} else if (err instanceof UrlParseError || err instanceof UnknownTransportError) {
|
|
999
1010
|
wrappedMsg = 'Content source uses an unsupported transport protocol'
|
|
@@ -1001,14 +1012,14 @@ function transformGitCloneError (err, displayUrl) {
|
|
|
1001
1012
|
wrappedMsg = `Content repository host could not be resolved: ${err.hostname}`
|
|
1002
1013
|
} else {
|
|
1003
1014
|
wrappedMsg = `${err.name}: ${err.message}`
|
|
1004
|
-
trimMessage = true
|
|
1015
|
+
recoverable = trimMessage = true
|
|
1005
1016
|
}
|
|
1006
1017
|
if (trimMessage) {
|
|
1007
|
-
wrappedMsg = ~(wrappedMsg = wrappedMsg.
|
|
1018
|
+
wrappedMsg = ~(wrappedMsg = wrappedMsg.trimEnd()).indexOf('. ') ? wrappedMsg : wrappedMsg.replace(/\.$/, '')
|
|
1008
1019
|
}
|
|
1009
1020
|
const errWrapper = new Error(`${wrappedMsg} (url: ${displayUrl})`)
|
|
1010
1021
|
errWrapper.stack += `\nCaused by: ${err.stack || 'unknown'}`
|
|
1011
|
-
return errWrapper
|
|
1022
|
+
return recoverable ? Object.assign(errWrapper, { recoverable }) : errWrapper
|
|
1012
1023
|
}
|
|
1013
1024
|
|
|
1014
1025
|
function splitRefPatterns (str) {
|
|
@@ -1035,22 +1046,20 @@ function coerceToString (value) {
|
|
|
1035
1046
|
return value == null ? '' : String(value)
|
|
1036
1047
|
}
|
|
1037
1048
|
|
|
1038
|
-
|
|
1049
|
+
function resolveRepositoryFromWorktree (repo) {
|
|
1039
1050
|
return fsp
|
|
1040
1051
|
.readFile(repo.gitdir, 'utf8')
|
|
1041
|
-
.then((contents) => contents.
|
|
1042
|
-
.then((worktreeGitdir) =>
|
|
1043
|
-
|
|
1044
|
-
return fsp.readFile(ospath.join(worktreeGitdir, 'commondir'), 'utf8').then(
|
|
1052
|
+
.then((contents) => contents.substr(8).trimEnd())
|
|
1053
|
+
.then((worktreeGitdir) =>
|
|
1054
|
+
fsp.readFile(ospath.join(worktreeGitdir, 'commondir'), 'utf8').then(
|
|
1045
1055
|
(contents) => {
|
|
1046
|
-
const gitdir = ospath.join(worktreeGitdir, contents.
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
: Object.assign(repo, { dir: gitdir, gitdir, worktreeName })
|
|
1056
|
+
const gitdir = ospath.join(worktreeGitdir, contents.trimEnd())
|
|
1057
|
+
const dir = ospath.basename(gitdir) === '.git' ? ospath.dirname(gitdir) : gitdir
|
|
1058
|
+
return Object.assign(repo, { dir, gitdir, worktreeName: ospath.basename(worktreeGitdir) })
|
|
1050
1059
|
},
|
|
1051
1060
|
() => repo
|
|
1052
1061
|
)
|
|
1053
|
-
|
|
1062
|
+
)
|
|
1054
1063
|
}
|
|
1055
1064
|
|
|
1056
1065
|
function findWorktrees (repo, patterns) {
|
|
@@ -1076,7 +1085,7 @@ function findWorktrees (repo, patterns) {
|
|
|
1076
1085
|
.then((branch = worktreeName) =>
|
|
1077
1086
|
fsp
|
|
1078
1087
|
.readFile(ospath.join(gitdir, 'gitdir'), 'utf8')
|
|
1079
|
-
.then((contents) => [branch, { head: ospath.dirname(contents.
|
|
1088
|
+
.then((contents) => [branch, { head: ospath.dirname(contents.trimEnd()), name: worktreeName }])
|
|
1080
1089
|
)
|
|
1081
1090
|
})
|
|
1082
1091
|
)
|
|
@@ -1085,4 +1094,37 @@ function findWorktrees (repo, patterns) {
|
|
|
1085
1094
|
).then((entries = []) => mainWorktree.then((entry) => new Map(entry ? entries.push(entry) && entries : entries)))
|
|
1086
1095
|
}
|
|
1087
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
|
+
|
|
1088
1130
|
module.exports = aggregateContent
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@antora/content-aggregator",
|
|
3
|
-
"version": "3.2.0-alpha.
|
|
3
|
+
"version": "3.2.0-alpha.3",
|
|
4
4
|
"description": "Fetches and aggregates content from distributed sources for use in an Antora documentation pipeline.",
|
|
5
5
|
"license": "MPL-2.0",
|
|
6
6
|
"author": "OpenDevise Inc. (https://opendevise.com)",
|
|
@@ -29,12 +29,12 @@
|
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
31
|
"@antora/expand-path-helper": "~2.0",
|
|
32
|
-
"@antora/logger": "3.2.0-alpha.
|
|
32
|
+
"@antora/logger": "3.2.0-alpha.3",
|
|
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
|
-
"hpagent": "~1.
|
|
37
|
+
"hpagent": "~1.2",
|
|
38
38
|
"isomorphic-git": "~1.21",
|
|
39
39
|
"js-yaml": "~4.1",
|
|
40
40
|
"multi-progress": "~4.0",
|
|
@@ -62,7 +62,7 @@
|
|
|
62
62
|
],
|
|
63
63
|
"scripts": {
|
|
64
64
|
"test": "_mocha",
|
|
65
|
-
"prepublishOnly": "
|
|
66
|
-
"postpublish": "
|
|
65
|
+
"prepublishOnly": "npx -y downdoc@latest --prepublish",
|
|
66
|
+
"postpublish": "npx -y downdoc@latest --postpublish"
|
|
67
67
|
}
|
|
68
68
|
}
|