@antora/content-aggregator 3.2.0-alpha.2 → 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/README.md +1 -1
- package/lib/aggregate-content.js +151 -110
- package/package.json +4 -4
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).
|
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 &&
|
|
@@ -389,10 +392,11 @@ async function selectReferences (source, repo, remote) {
|
|
|
389
392
|
refs.set(shortname, { shortname, fullname: 'heads/' + shortname, type: 'branch', head })
|
|
390
393
|
}
|
|
391
394
|
}
|
|
392
|
-
} else if (!remoteBranches.length) {
|
|
395
|
+
} else if (!managed || !remoteBranches.length) {
|
|
393
396
|
const localBranches = await git.listBranches(repo)
|
|
394
397
|
if (localBranches.length) {
|
|
395
398
|
for (const shortname of filterRefs(localBranches, branchPatterns, patternCache)) {
|
|
399
|
+
if (refs.has(shortname)) continue // NOTE prefer remote branches in bare repository
|
|
396
400
|
refs.set(shortname, { shortname, fullname: 'heads/' + shortname, type: 'branch', head: noWorktree })
|
|
397
401
|
}
|
|
398
402
|
}
|
|
@@ -413,10 +417,9 @@ function getCurrentBranchName (repo, remote) {
|
|
|
413
417
|
).then((ref) => (ref.startsWith('refs/') ? ref.replace(SHORTEN_REF_RX, '') : undefined))
|
|
414
418
|
}
|
|
415
419
|
|
|
416
|
-
async function
|
|
420
|
+
async function selectStartPaths (source, repo, remoteName, ref) {
|
|
417
421
|
const url = repo.url
|
|
418
422
|
const displayUrl = url || repo.dir
|
|
419
|
-
const { version, editUrl } = source
|
|
420
423
|
const worktreePath = ref.head
|
|
421
424
|
if (!worktreePath) {
|
|
422
425
|
ref.oid = await git.resolveRef(
|
|
@@ -436,17 +439,22 @@ async function collectFilesFromReference (source, repo, remoteName, authStatus,
|
|
|
436
439
|
const flag = worktreePath ? ' <worktree>' : ref.remote && worktreePath === false ? ` <remotes/${ref.remote}>` : ''
|
|
437
440
|
throw new Error(`no start paths found in ${where} (${ref.type}: ${ref.shortname}${flag})`)
|
|
438
441
|
}
|
|
439
|
-
return
|
|
440
|
-
startPaths.map((startPath) =>
|
|
441
|
-
collectFilesFromStartPath(startPath, repo, authStatus, ref, worktreePath, originUrl, editUrl, version)
|
|
442
|
-
)
|
|
443
|
-
)
|
|
442
|
+
return startPaths
|
|
444
443
|
}
|
|
445
|
-
|
|
446
|
-
return collectFilesFromStartPath(startPath, repo, authStatus, ref, worktreePath, originUrl, editUrl, version)
|
|
444
|
+
return [cleanStartPath(coerceToString(source.startPath))]
|
|
447
445
|
}
|
|
448
446
|
|
|
449
|
-
function
|
|
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))
|
|
451
|
+
}
|
|
452
|
+
repo.cache = undefined
|
|
453
|
+
return buckets
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
function collectFilesFromStartPath (startPath, repo, authStatus, ref, originUrl, editUrl, version) {
|
|
457
|
+
const worktreePath = ref.head
|
|
450
458
|
const origin = computeOrigin(originUrl, authStatus, repo.gitdir, ref, startPath, worktreePath, editUrl)
|
|
451
459
|
return (worktreePath ? readFilesFromWorktree(origin) : readFilesFromGitTree(repo, ref.oid, startPath))
|
|
452
460
|
.then((files) =>
|
|
@@ -526,26 +534,25 @@ function srcFs (cwd, origin) {
|
|
|
526
534
|
}
|
|
527
535
|
|
|
528
536
|
function readFilesFromGitTree (repo, oid, startPath) {
|
|
529
|
-
return git
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
getGitTreeAtStartPath(repo, oid, startPath).then((start) =>
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
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
|
+
})
|
|
536
546
|
}
|
|
537
547
|
|
|
538
548
|
function getGitTreeAtStartPath (repo, oid, startPath) {
|
|
539
|
-
return git.readTree(Object.assign({ oid, filepath: startPath }, repo)).
|
|
540
|
-
|
|
541
|
-
(
|
|
542
|
-
|
|
543
|
-
throw new Error(`the start path '${startPath}' ${m}`)
|
|
544
|
-
}
|
|
545
|
-
)
|
|
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
|
+
})
|
|
546
553
|
}
|
|
547
554
|
|
|
548
|
-
function srcGitTree (repo, root, start) {
|
|
555
|
+
function srcGitTree (repo, root, start = root) {
|
|
549
556
|
return new Promise((resolve, reject) => {
|
|
550
557
|
const files = []
|
|
551
558
|
createGitTreeWalker(repo, root, filterGitEntry, gitEntryToFile)
|
|
@@ -832,7 +839,6 @@ function onGitProgress ({ phase, loaded, total }) {
|
|
|
832
839
|
|
|
833
840
|
function onGitComplete (err) {
|
|
834
841
|
if (err) {
|
|
835
|
-
// TODO could use progressBar.interrupt() to replace bar with message instead
|
|
836
842
|
this.chars.incomplete = '?'
|
|
837
843
|
this.update(0)
|
|
838
844
|
// NOTE force progress bar to update regardless of throttle setting
|
|
@@ -982,8 +988,8 @@ function ensureCacheDir (preferredCacheDir, startDir) {
|
|
|
982
988
|
)
|
|
983
989
|
}
|
|
984
990
|
|
|
985
|
-
function transformGitCloneError (err, displayUrl) {
|
|
986
|
-
let wrappedMsg, trimMessage
|
|
991
|
+
function transformGitCloneError (err, displayUrl, authRequested) {
|
|
992
|
+
let wrappedMsg, recoverable, trimMessage
|
|
987
993
|
if (HTTP_ERROR_CODE_RX.test(err.code)) {
|
|
988
994
|
switch (err.data.statusCode) {
|
|
989
995
|
case 401:
|
|
@@ -992,11 +998,13 @@ function transformGitCloneError (err, displayUrl) {
|
|
|
992
998
|
: 'Content repository not found or requires credentials'
|
|
993
999
|
break
|
|
994
1000
|
case 404:
|
|
995
|
-
wrappedMsg =
|
|
1001
|
+
wrappedMsg = authRequested
|
|
1002
|
+
? 'Content repository not found or credentials were rejected'
|
|
1003
|
+
: 'Content repository not found'
|
|
996
1004
|
break
|
|
997
1005
|
default:
|
|
998
1006
|
wrappedMsg = err.message
|
|
999
|
-
trimMessage = true
|
|
1007
|
+
recoverable = trimMessage = true
|
|
1000
1008
|
}
|
|
1001
1009
|
} else if (err instanceof UrlParseError || err instanceof UnknownTransportError) {
|
|
1002
1010
|
wrappedMsg = 'Content source uses an unsupported transport protocol'
|
|
@@ -1004,14 +1012,14 @@ function transformGitCloneError (err, displayUrl) {
|
|
|
1004
1012
|
wrappedMsg = `Content repository host could not be resolved: ${err.hostname}`
|
|
1005
1013
|
} else {
|
|
1006
1014
|
wrappedMsg = `${err.name}: ${err.message}`
|
|
1007
|
-
trimMessage = true
|
|
1015
|
+
recoverable = trimMessage = true
|
|
1008
1016
|
}
|
|
1009
1017
|
if (trimMessage) {
|
|
1010
|
-
wrappedMsg = ~(wrappedMsg = wrappedMsg.
|
|
1018
|
+
wrappedMsg = ~(wrappedMsg = wrappedMsg.trimEnd()).indexOf('. ') ? wrappedMsg : wrappedMsg.replace(/\.$/, '')
|
|
1011
1019
|
}
|
|
1012
1020
|
const errWrapper = new Error(`${wrappedMsg} (url: ${displayUrl})`)
|
|
1013
1021
|
errWrapper.stack += `\nCaused by: ${err.stack || 'unknown'}`
|
|
1014
|
-
return errWrapper
|
|
1022
|
+
return recoverable ? Object.assign(errWrapper, { recoverable }) : errWrapper
|
|
1015
1023
|
}
|
|
1016
1024
|
|
|
1017
1025
|
function splitRefPatterns (str) {
|
|
@@ -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.
|
|
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.
|
|
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.
|
|
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
|
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,7 +29,7 @@
|
|
|
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",
|
|
@@ -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
|
}
|