@antora/content-aggregator 3.0.3 → 3.1.1
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 +150 -156
- package/lib/compute-origin.js +54 -0
- package/lib/constants.js +1 -1
- package/lib/deep-clone.js +18 -0
- package/lib/{flatten-deep.js → deep-flatten.js} +3 -3
- package/lib/logger.js +3 -0
- package/lib/posixify.js +3 -0
- package/lib/remove-git-suffix.js +9 -0
- package/lib/resolve-path-globs.js +2 -2
- package/package.json +16 -5
package/lib/aggregate-content.js
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const
|
|
3
|
+
const computeOrigin = require('./compute-origin')
|
|
4
4
|
const { createHash } = require('crypto')
|
|
5
5
|
const createGitHttpPlugin = require('./git-plugin-http')
|
|
6
6
|
const decodeUint8Array = require('./decode-uint8-array')
|
|
7
|
+
const deepClone = require('./deep-clone')
|
|
8
|
+
const deepFlatten = require('./deep-flatten')
|
|
7
9
|
const EventEmitter = require('events')
|
|
8
10
|
const expandPath = require('@antora/expand-path-helper')
|
|
9
11
|
const File = require('./file')
|
|
10
12
|
const filterRefs = require('./filter-refs')
|
|
11
|
-
const flattenDeep = require('./flatten-deep')
|
|
12
13
|
const fs = require('fs')
|
|
13
14
|
const { promises: fsp } = fs
|
|
14
15
|
const getCacheDir = require('cache-directory')
|
|
@@ -17,11 +18,13 @@ const git = require('./git')
|
|
|
17
18
|
const { NotFoundError, ObjectTypeError, UnknownTransportError, UrlParseError } = git.Errors
|
|
18
19
|
const globStream = require('glob-stream')
|
|
19
20
|
const invariably = require('./invariably')
|
|
21
|
+
const logger = require('./logger')
|
|
20
22
|
const { makeMatcherRx, versionMatcherOpts: VERSION_MATCHER_OPTS } = require('./matcher')
|
|
21
23
|
const MultiProgress = require('multi-progress') // calls require('progress') as a peer dependencies
|
|
22
24
|
const ospath = require('path')
|
|
23
25
|
const { posix: path } = ospath
|
|
24
|
-
const posixify =
|
|
26
|
+
const posixify = require('./posixify')
|
|
27
|
+
const removeGitSuffix = require('./remove-git-suffix')
|
|
25
28
|
const { fs: resolvePathGlobsFs, git: resolvePathGlobsGit } = require('./resolve-path-globs')
|
|
26
29
|
const { pipeline, Writable } = require('stream')
|
|
27
30
|
const forEach = (write) => new Writable({ objectMode: true, write })
|
|
@@ -44,12 +47,9 @@ const {
|
|
|
44
47
|
const ANY_SEPARATOR_RX = /[:/]/
|
|
45
48
|
const CSV_RX = /\s*,\s*/
|
|
46
49
|
const VENTILATED_CSV_RX = /\s*,\s+/
|
|
47
|
-
const EDIT_URL_TEMPLATE_VAR_RX = /\{(web_url|ref(?:hash|name)|path)\}/g
|
|
48
|
-
const GIT_SUFFIX_RX = /(?:(?:(?:\.git)?\/)?\.git|\/)$/
|
|
49
50
|
const GIT_URI_DETECTOR_RX = /:(?:\/\/|[^/\\])/
|
|
50
|
-
const HEADS_DIR_RX = /^heads\//
|
|
51
|
-
const HOSTED_GIT_REPO_RX = /^(?:https?:\/\/|.+@)(git(?:hub|lab)\.com|bitbucket\.org|pagure\.io)[/:](.+?)(?:\.git)?$/
|
|
52
51
|
const HTTP_ERROR_CODE_RX = new RegExp('^' + git.Errors.HttpError.code + '$', 'i')
|
|
52
|
+
const NEWLINE_RX = /\n/g
|
|
53
53
|
const PATH_SEPARATOR_RX = /[/]/g
|
|
54
54
|
const SHORTEN_REF_RX = /^refs\/(?:heads|remotes\/[^/]+|tags)\//
|
|
55
55
|
const SPACE_RX = / /g
|
|
@@ -154,13 +154,14 @@ async function collectFiles (sourcesByUrl, loadOpts, concurrency) {
|
|
|
154
154
|
|
|
155
155
|
function buildAggregate (componentVersionBuckets) {
|
|
156
156
|
return [
|
|
157
|
-
...
|
|
157
|
+
...deepFlatten(componentVersionBuckets)
|
|
158
158
|
.reduce((accum, batch) => {
|
|
159
159
|
const key = batch.version + '@' + batch.name
|
|
160
160
|
const entry = accum.get(key)
|
|
161
161
|
if (!entry) return accum.set(key, batch)
|
|
162
|
-
const files = batch
|
|
162
|
+
const { files, origins } = batch
|
|
163
163
|
;(batch.files = entry.files).push(...files)
|
|
164
|
+
;(batch.origins = entry.origins).push(origins[0])
|
|
164
165
|
Object.assign(entry, batch)
|
|
165
166
|
return accum
|
|
166
167
|
}, new Map())
|
|
@@ -257,9 +258,17 @@ function extractCredentials (url) {
|
|
|
257
258
|
|
|
258
259
|
async function collectFilesFromSource (source, repo, remoteName, authStatus) {
|
|
259
260
|
const originUrl = repo.url || (await resolveRemoteUrl(repo, remoteName))
|
|
260
|
-
return selectReferences(source, repo, remoteName).then((refs) =>
|
|
261
|
-
|
|
262
|
-
|
|
261
|
+
return selectReferences(source, repo, remoteName).then((refs) => {
|
|
262
|
+
if (!refs.length) {
|
|
263
|
+
const { url, branches, tags, startPath, startPaths } = source
|
|
264
|
+
const startPathInfo =
|
|
265
|
+
'startPaths' in source ? { 'start paths': startPaths || undefined } : { 'start path': startPath || undefined }
|
|
266
|
+
const sourceInfo = yaml.dump({ url, branches, tags, ...startPathInfo }, { flowLevel: 1 }).trimRight()
|
|
267
|
+
logger.info(`No matching references found for content source entry (${sourceInfo.replace(NEWLINE_RX, ' | ')})`)
|
|
268
|
+
return []
|
|
269
|
+
}
|
|
270
|
+
return Promise.all(refs.map((it) => collectFilesFromReference(source, repo, remoteName, authStatus, it, originUrl)))
|
|
271
|
+
})
|
|
263
272
|
}
|
|
264
273
|
|
|
265
274
|
// QUESTION should we resolve HEAD to a ref eagerly to avoid having to do a match on it?
|
|
@@ -267,7 +276,7 @@ async function selectReferences (source, repo, remote) {
|
|
|
267
276
|
let { branches: branchPatterns, tags: tagPatterns, worktrees: worktreePatterns = '.' } = source
|
|
268
277
|
const isBare = repo.noCheckout
|
|
269
278
|
const patternCache = repo.cache[REF_PATTERN_CACHE_KEY]
|
|
270
|
-
const noWorktree = repo.url ? undefined :
|
|
279
|
+
const noWorktree = repo.url ? undefined : false
|
|
271
280
|
const refs = new Map()
|
|
272
281
|
if (
|
|
273
282
|
tagPatterns &&
|
|
@@ -403,8 +412,9 @@ async function collectFilesFromReference (source, repo, remoteName, authStatus,
|
|
|
403
412
|
? resolvePathGlobsFs(worktreePath, startPaths)
|
|
404
413
|
: resolvePathGlobsGit(repo, ref.oid, startPaths))
|
|
405
414
|
if (!startPaths.length) {
|
|
406
|
-
const
|
|
407
|
-
|
|
415
|
+
const where = worktreePath || (worktreePath === false ? repo.gitdir : displayUrl)
|
|
416
|
+
const flag = worktreePath ? ' <worktree>' : ref.remote && worktreePath === false ? ` <remotes/${ref.remote}>` : ''
|
|
417
|
+
throw new Error(`no start paths found in ${where} (${ref.type}: ${ref.shortname}${flag})`)
|
|
408
418
|
}
|
|
409
419
|
return Promise.all(
|
|
410
420
|
startPaths.map((startPath) =>
|
|
@@ -417,29 +427,30 @@ async function collectFilesFromReference (source, repo, remoteName, authStatus,
|
|
|
417
427
|
}
|
|
418
428
|
|
|
419
429
|
function collectFilesFromStartPath (startPath, repo, authStatus, ref, worktreePath, originUrl, editUrl, version) {
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
})
|
|
430
|
+
const origin = computeOrigin(originUrl, authStatus, repo.gitdir, ref, startPath, worktreePath, editUrl)
|
|
431
|
+
return (worktreePath ? readFilesFromWorktree(origin) : readFilesFromGitTree(repo, ref.oid, startPath))
|
|
432
|
+
.then((files) =>
|
|
433
|
+
Object.assign(deepClone((origin.descriptor = loadComponentDescriptor(files, ref, version))), {
|
|
434
|
+
files: files.map((file) => assignFileProperties(file, origin)),
|
|
435
|
+
origins: [origin],
|
|
436
|
+
})
|
|
437
|
+
)
|
|
429
438
|
.catch((err) => {
|
|
430
|
-
const
|
|
431
|
-
const
|
|
432
|
-
const pathInfo =
|
|
433
|
-
|
|
439
|
+
const where = worktreePath || (worktreePath === false ? repo.gitdir : repo.url || repo.dir)
|
|
440
|
+
const flag = worktreePath ? ' <worktree>' : ref.remote && worktreePath === false ? ` <remotes/${ref.remote}>` : ''
|
|
441
|
+
const pathInfo = startPath ? (err.message.startsWith('the start path ') ? '' : ` | start path: ${startPath}`) : ''
|
|
442
|
+
const message = err.message.replace(/$/m, ` in ${where} (${ref.type}: ${ref.shortname}${flag}${pathInfo})`)
|
|
443
|
+
throw Object.assign(err, { message })
|
|
434
444
|
})
|
|
435
445
|
}
|
|
436
446
|
|
|
437
|
-
function readFilesFromWorktree (
|
|
438
|
-
const
|
|
447
|
+
function readFilesFromWorktree (origin) {
|
|
448
|
+
const startPath = origin.startPath
|
|
449
|
+
const cwd = ospath.join(origin.worktree, startPath, '.') // . shaves off trailing slash
|
|
439
450
|
return fsp.stat(cwd).then(
|
|
440
451
|
(startPathStat) => {
|
|
441
452
|
if (!startPathStat.isDirectory()) throw new Error(`the start path '${startPath}' is not a directory`)
|
|
442
|
-
return srcFs(cwd)
|
|
453
|
+
return srcFs(cwd, origin)
|
|
443
454
|
},
|
|
444
455
|
() => {
|
|
445
456
|
throw new Error(`the start path '${startPath}' does not exist`)
|
|
@@ -447,38 +458,45 @@ function readFilesFromWorktree (worktreePath, startPath) {
|
|
|
447
458
|
)
|
|
448
459
|
}
|
|
449
460
|
|
|
450
|
-
function srcFs (cwd) {
|
|
451
|
-
|
|
452
|
-
return new Promise((resolve, reject, cache = Object.create(null), files = []) =>
|
|
461
|
+
function srcFs (cwd, origin) {
|
|
462
|
+
return new Promise((resolve, reject, cache = Object.create(null), files = [], relpathStart = cwd.length + 1) =>
|
|
453
463
|
pipeline(
|
|
454
464
|
globStream(CONTENT_SRC_GLOB, Object.assign({ cache, cwd }, CONTENT_SRC_OPTS)),
|
|
455
465
|
forEach(({ path: abspathPosix }, _, done) => {
|
|
456
|
-
if (
|
|
466
|
+
if ((cache[abspathPosix] || {}).constructor === Array) return done() // detects some directories
|
|
457
467
|
const abspath = posixify ? ospath.normalize(abspathPosix) : abspathPosix
|
|
458
468
|
const relpath = abspath.substr(relpathStart)
|
|
459
469
|
symlinkAwareStat(abspath).then(
|
|
460
470
|
(stat) => {
|
|
461
|
-
if (stat.isDirectory()) return done() // detects
|
|
471
|
+
if (stat.isDirectory()) return done() // detects directories that slipped through cache check
|
|
462
472
|
fsp.readFile(abspath).then(
|
|
463
473
|
(contents) => {
|
|
464
474
|
files.push(new File({ path: posixify ? posixify(relpath) : relpath, contents, stat, src: { abspath } }))
|
|
465
475
|
done()
|
|
466
476
|
},
|
|
467
477
|
(readErr) => {
|
|
468
|
-
|
|
478
|
+
const logObject = { file: { abspath, origin } }
|
|
479
|
+
readErr.code === 'ENOENT'
|
|
480
|
+
? logger.warn(logObject, `ENOENT: file or directory disappeared, ${readErr.syscall} ${relpath}`)
|
|
481
|
+
: logger.error(logObject, readErr.message.replace(`'${abspath}'`, relpath))
|
|
482
|
+
done()
|
|
469
483
|
}
|
|
470
484
|
)
|
|
471
485
|
},
|
|
472
486
|
(statErr) => {
|
|
487
|
+
const logObject = { file: { abspath, origin } }
|
|
473
488
|
if (statErr.symlink) {
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
489
|
+
logger.error(
|
|
490
|
+
logObject,
|
|
491
|
+
(statErr.code === 'ELOOP' ? 'ELOOP: symbolic link cycle, ' : 'ENOENT: broken symbolic link, ') +
|
|
492
|
+
`${relpath} -> ${statErr.symlink}`
|
|
493
|
+
)
|
|
494
|
+
} else if (statErr.code === 'ENOENT') {
|
|
495
|
+
logger.warn(logObject, `ENOENT: file or directory disappeared, ${statErr.syscall} ${relpath}`)
|
|
478
496
|
} else {
|
|
479
|
-
|
|
497
|
+
logger.error(logObject, statErr.message.replace(`'${abspath}'`, relpath))
|
|
480
498
|
}
|
|
481
|
-
done(
|
|
499
|
+
done()
|
|
482
500
|
}
|
|
483
501
|
)
|
|
484
502
|
}),
|
|
@@ -510,30 +528,26 @@ function getGitTreeAtStartPath (repo, oid, startPath) {
|
|
|
510
528
|
function srcGitTree (repo, root, start) {
|
|
511
529
|
return new Promise((resolve, reject) => {
|
|
512
530
|
const files = []
|
|
513
|
-
createGitTreeWalker(repo, root, filterGitEntry)
|
|
514
|
-
.on('entry', (
|
|
531
|
+
createGitTreeWalker(repo, root, filterGitEntry, gitEntryToFile)
|
|
532
|
+
.on('entry', (file) => files.push(file))
|
|
515
533
|
.on('error', reject)
|
|
516
|
-
.on('end', () => resolve(
|
|
534
|
+
.on('end', () => resolve(files))
|
|
517
535
|
.walk(start)
|
|
518
536
|
})
|
|
519
537
|
}
|
|
520
538
|
|
|
521
|
-
function createGitTreeWalker (repo, root, filter) {
|
|
539
|
+
function createGitTreeWalker (repo, root, filter, convert) {
|
|
522
540
|
return Object.assign(new EventEmitter(), {
|
|
523
541
|
walk (start) {
|
|
524
|
-
return (
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
.then(
|
|
528
|
-
() => this.emit('end'),
|
|
529
|
-
(err) => this.emit('error', err)
|
|
530
|
-
)
|
|
542
|
+
return visitGitTree(this, repo, root, filter, convert, start).then(
|
|
543
|
+
() => this.emit('end'),
|
|
544
|
+
(err) => this.emit('error', err)
|
|
531
545
|
)
|
|
532
546
|
},
|
|
533
547
|
})
|
|
534
548
|
}
|
|
535
549
|
|
|
536
|
-
function visitGitTree (emitter, repo, root, filter, parent, dirname = '', following = new Set()) {
|
|
550
|
+
function visitGitTree (emitter, repo, root, filter, convert, parent, dirname = '', following = new Set()) {
|
|
537
551
|
const reads = []
|
|
538
552
|
for (const entry of parent.tree) {
|
|
539
553
|
const filterVerdict = filter(entry)
|
|
@@ -543,7 +557,7 @@ function visitGitTree (emitter, repo, root, filter, parent, dirname = '', follow
|
|
|
543
557
|
reads.push(
|
|
544
558
|
git.readTree(Object.assign({ oid: entry.oid }, repo)).then((subtree) => {
|
|
545
559
|
Object.assign(subtree, { dirname: path.join(parent.dirname, entry.path) })
|
|
546
|
-
return visitGitTree(emitter, repo, root, filter, subtree, vfilePath, following)
|
|
560
|
+
return visitGitTree(emitter, repo, root, filter, convert, subtree, vfilePath, following)
|
|
547
561
|
})
|
|
548
562
|
)
|
|
549
563
|
} else if (entry.type === 'blob') {
|
|
@@ -553,56 +567,70 @@ function visitGitTree (emitter, repo, root, filter, parent, dirname = '', follow
|
|
|
553
567
|
readGitSymlink(repo, root, parent, entry, following).then(
|
|
554
568
|
(target) => {
|
|
555
569
|
if (target.type === 'tree') {
|
|
556
|
-
return visitGitTree(emitter, repo, root, filter, target, vfilePath,
|
|
570
|
+
return visitGitTree(emitter, repo, root, filter, convert, target, vfilePath, target.following)
|
|
557
571
|
} else if (target.type === 'blob' && filterVerdict === true && (mode = FILE_MODES[target.mode])) {
|
|
558
|
-
|
|
572
|
+
return convert(Object.assign({ mode, oid: target.oid, path: vfilePath }, repo)).then((result) =>
|
|
573
|
+
emitter.emit('entry', result)
|
|
574
|
+
)
|
|
559
575
|
}
|
|
560
576
|
},
|
|
561
577
|
(err) => {
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
err.message = `Symbolic link cycle detected at ${vfilePath}`
|
|
578
|
+
if (err.symlink) {
|
|
579
|
+
err.message =
|
|
580
|
+
(err.code === 'ELOOP' ? 'ELOOP: symbolic link cycle' : 'ENOENT: broken symbolic link') +
|
|
581
|
+
`, ${vfilePath} -> ${err.symlink}`
|
|
567
582
|
}
|
|
568
583
|
throw err
|
|
569
584
|
}
|
|
570
585
|
)
|
|
571
586
|
)
|
|
572
587
|
} else if ((mode = FILE_MODES[entry.mode])) {
|
|
573
|
-
|
|
588
|
+
reads.push(
|
|
589
|
+
convert(Object.assign({ mode, oid: entry.oid, path: vfilePath }, repo)).then((result) =>
|
|
590
|
+
emitter.emit('entry', result)
|
|
591
|
+
)
|
|
592
|
+
)
|
|
574
593
|
}
|
|
575
594
|
}
|
|
576
595
|
}
|
|
577
596
|
}
|
|
578
|
-
|
|
597
|
+
// NOTE preserve scan order so error for symbolic link cycle is deterministic; ensures no rejections after resolve
|
|
598
|
+
return Promise.allSettled(reads).then((results) => {
|
|
599
|
+
const rejected = results.find(({ reason }) => reason)
|
|
600
|
+
if (rejected) throw rejected.reason
|
|
601
|
+
})
|
|
579
602
|
}
|
|
580
603
|
|
|
581
|
-
function readGitSymlink (repo, root, parent, { oid }, following) {
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
targetParent = root
|
|
604
|
+
function readGitSymlink (repo, root, parent, { oid, path: name }, following) {
|
|
605
|
+
const dirname = parent.dirname
|
|
606
|
+
if (following.size === (following = new Set(following)).add(oid).size) {
|
|
607
|
+
const err = { name: 'SymbolicLinkCycleError', code: 'ELOOP', oid, path: `${path.join(dirname, name)}` }
|
|
608
|
+
return Promise.reject(Object.assign(new Error(`Symbolic link cycle detected at ${oid}:${err.path}`), err))
|
|
609
|
+
}
|
|
610
|
+
return git.readBlob(Object.assign({ oid }, repo)).then(({ blob: symlink }) => {
|
|
611
|
+
symlink = decodeUint8Array(symlink)
|
|
612
|
+
let target
|
|
613
|
+
let targetParent = root
|
|
614
|
+
if (dirname) {
|
|
615
|
+
if (!(target = path.join('/', dirname, symlink).substr(1)) || target === dirname) {
|
|
616
|
+
target = '.'
|
|
617
|
+
} else if (target.startsWith(dirname + '/')) {
|
|
618
|
+
target = target.substr(dirname.length + 1) // join doesn't remove trailing separator
|
|
619
|
+
targetParent = parent
|
|
598
620
|
}
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
621
|
+
} else {
|
|
622
|
+
target = path.normalize(symlink) // normalize doesn't remove trailing separator
|
|
623
|
+
}
|
|
624
|
+
if (target === '.') {
|
|
625
|
+
const err = { name: 'SymbolicLinkCycleError', code: 'ELOOP', oid, path: `${path.join(dirname, name)}`, symlink }
|
|
626
|
+
return Promise.reject(Object.assign(new Error(`Symbolic link cycle detected at ${oid}:${err.path}`), err))
|
|
627
|
+
}
|
|
628
|
+
const targetSegments = target.split('/')
|
|
629
|
+
targetSegments[targetSegments.length - 1] || targetSegments.pop()
|
|
630
|
+
return readGitObjectAtPath(repo, root, targetParent, targetSegments, following).catch((err) => {
|
|
631
|
+
throw Object.assign(err, { symlink })
|
|
602
632
|
})
|
|
603
|
-
}
|
|
604
|
-
const err = { name: 'SymbolicLinkCycleError', code: 'SymbolicLinkCycleError', oid }
|
|
605
|
-
return Promise.reject(Object.assign(new Error(`Symbolic link cycle found at oid: ${err.oid}`), err))
|
|
633
|
+
})
|
|
606
634
|
}
|
|
607
635
|
|
|
608
636
|
// QUESTION: could we use this to resolve the start path too?
|
|
@@ -615,14 +643,14 @@ function readGitObjectAtPath (repo, root, parent, pathSegments, following) {
|
|
|
615
643
|
Object.assign(subtree, { dirname: path.join(parent.dirname, entry.path) })
|
|
616
644
|
return (pathSegments = pathSegments.slice(1)).length
|
|
617
645
|
? readGitObjectAtPath(repo, root, subtree, pathSegments, following)
|
|
618
|
-
: Object.assign(subtree, { type: 'tree' })
|
|
646
|
+
: Object.assign(subtree, { type: 'tree', following }) // Q: should this create copy?
|
|
619
647
|
})
|
|
620
648
|
: entry.mode === SYMLINK_FILE_MODE
|
|
621
649
|
? readGitSymlink(repo, root, parent, entry, following)
|
|
622
650
|
: Promise.resolve(entry)
|
|
623
651
|
}
|
|
624
652
|
}
|
|
625
|
-
return Promise.reject(new NotFoundError(
|
|
653
|
+
return Promise.reject(new NotFoundError(`${parent.oid}:${pathSegments.join('/')}`))
|
|
626
654
|
}
|
|
627
655
|
|
|
628
656
|
/**
|
|
@@ -637,7 +665,7 @@ function filterGitEntry (entry) {
|
|
|
637
665
|
return entryPath.charAt(entryPath.length - 1) !== '~'
|
|
638
666
|
}
|
|
639
667
|
|
|
640
|
-
function
|
|
668
|
+
function gitEntryToFile (entry) {
|
|
641
669
|
return git.readBlob(entry).then(({ blob: contents }) => {
|
|
642
670
|
contents = Buffer.from(contents.buffer)
|
|
643
671
|
const stat = Object.assign(new fs.Stats(), { mode: entry.mode, mtime: undefined, size: contents.byteLength })
|
|
@@ -691,53 +719,7 @@ function loadComponentDescriptor (files, ref, version) {
|
|
|
691
719
|
throw new Error(`version in ${COMPONENT_DESC_FILENAME} cannot have path segments: ${version}`)
|
|
692
720
|
}
|
|
693
721
|
data.version = version
|
|
694
|
-
return camelCaseKeys(data,
|
|
695
|
-
}
|
|
696
|
-
|
|
697
|
-
function computeOrigin (url, authStatus, gitdir, ref, startPath, worktreePath = undefined, editUrl = true) {
|
|
698
|
-
const { shortname: refname, oid: refhash, type: reftype } = ref
|
|
699
|
-
const origin = { type: 'git', url, gitdir, refname, [reftype]: refname, startPath }
|
|
700
|
-
if (authStatus) origin.private = authStatus
|
|
701
|
-
if (worktreePath === undefined) {
|
|
702
|
-
origin.refhash = refhash
|
|
703
|
-
} else {
|
|
704
|
-
if (worktreePath) {
|
|
705
|
-
origin.fileUriPattern =
|
|
706
|
-
(posixify ? 'file:///' + posixify(worktreePath) : 'file://' + worktreePath) +
|
|
707
|
-
(startPath ? '/' + startPath + '/%s' : '/%s')
|
|
708
|
-
} else {
|
|
709
|
-
origin.refhash = refhash
|
|
710
|
-
}
|
|
711
|
-
origin.worktree = worktreePath
|
|
712
|
-
if (url.startsWith('file://')) url = undefined
|
|
713
|
-
}
|
|
714
|
-
if (url) origin.webUrl = url.replace(GIT_SUFFIX_RX, '')
|
|
715
|
-
if (editUrl === true) {
|
|
716
|
-
let match
|
|
717
|
-
if (url && (match = url.match(HOSTED_GIT_REPO_RX))) {
|
|
718
|
-
const host = match[1]
|
|
719
|
-
let action
|
|
720
|
-
let category = ''
|
|
721
|
-
if (host === 'pagure.io') {
|
|
722
|
-
action = 'blob'
|
|
723
|
-
category = 'f'
|
|
724
|
-
} else if (host === 'bitbucket.org') {
|
|
725
|
-
action = 'src'
|
|
726
|
-
} else {
|
|
727
|
-
action = reftype === 'branch' ? 'edit' : 'blob'
|
|
728
|
-
}
|
|
729
|
-
origin.editUrlPattern = 'https://' + path.join(match[1], match[2], action, refname, category, startPath, '%s')
|
|
730
|
-
}
|
|
731
|
-
} else if (editUrl) {
|
|
732
|
-
const vars = {
|
|
733
|
-
path: () => (startPath ? path.join(startPath, '%s') : '%s'),
|
|
734
|
-
refhash: () => refhash,
|
|
735
|
-
refname: () => refname,
|
|
736
|
-
web_url: () => origin.webUrl || '',
|
|
737
|
-
}
|
|
738
|
-
origin.editUrlPattern = editUrl.replace(EDIT_URL_TEMPLATE_VAR_RX, (_, name) => vars[name]())
|
|
739
|
-
}
|
|
740
|
-
return origin
|
|
722
|
+
return camelCaseKeys(data, ['asciidoc'])
|
|
741
723
|
}
|
|
742
724
|
|
|
743
725
|
function assignFileProperties (file, origin) {
|
|
@@ -823,17 +805,17 @@ function onGitProgress ({ phase, loaded, total }) {
|
|
|
823
805
|
const scaleFactor = this.scaleFactor
|
|
824
806
|
let ratio = ((loaded / total) * scaleFactor) / GIT_PROGRESS_PHASES.length
|
|
825
807
|
if (phaseIdx) ratio += (phaseIdx * scaleFactor) / GIT_PROGRESS_PHASES.length
|
|
826
|
-
// NOTE
|
|
808
|
+
// NOTE updates are automatically throttled based on renderThrottle option
|
|
827
809
|
this.update(ratio > scaleFactor ? scaleFactor : ratio)
|
|
828
810
|
}
|
|
829
811
|
}
|
|
830
812
|
|
|
831
813
|
function onGitComplete (err) {
|
|
832
814
|
if (err) {
|
|
833
|
-
// TODO
|
|
815
|
+
// TODO could use progressBar.interrupt() to replace bar with message instead
|
|
834
816
|
this.chars.incomplete = '?'
|
|
835
817
|
this.update(0)
|
|
836
|
-
// NOTE
|
|
818
|
+
// NOTE force progress bar to update regardless of throttle setting
|
|
837
819
|
this.render(undefined, true)
|
|
838
820
|
} else {
|
|
839
821
|
this.update(1)
|
|
@@ -869,13 +851,9 @@ function resolveCredentials (credentialsFromUrlHolder, url, auth) {
|
|
|
869
851
|
* @returns {String} The generated folder name.
|
|
870
852
|
*/
|
|
871
853
|
function generateCloneFolderName (url) {
|
|
872
|
-
|
|
873
|
-
if (posixify) normalizedUrl = posixify(normalizedUrl)
|
|
874
|
-
normalizedUrl = normalizedUrl.replace(GIT_SUFFIX_RX, '')
|
|
854
|
+
const normalizedUrl = removeGitSuffix(posixify ? posixify(url.toLowerCase()) : url.toLowerCase())
|
|
875
855
|
const basename = normalizedUrl.split(ANY_SEPARATOR_RX).pop()
|
|
876
|
-
|
|
877
|
-
hash.update(normalizedUrl)
|
|
878
|
-
return basename + '-' + hash.digest('hex') + '.git'
|
|
856
|
+
return basename + '-' + createHash('sha1').update(normalizedUrl).digest('hex') + '.git'
|
|
879
857
|
}
|
|
880
858
|
|
|
881
859
|
/**
|
|
@@ -914,9 +892,14 @@ function isDirectory (url) {
|
|
|
914
892
|
function symlinkAwareStat (path_) {
|
|
915
893
|
return fsp.lstat(path_).then((lstat) => {
|
|
916
894
|
if (!lstat.isSymbolicLink()) return lstat
|
|
917
|
-
return fsp.stat(path_).catch((statErr) =>
|
|
918
|
-
|
|
919
|
-
|
|
895
|
+
return fsp.stat(path_).catch((statErr) =>
|
|
896
|
+
fsp
|
|
897
|
+
.readlink(path_)
|
|
898
|
+
.catch(invariably.void)
|
|
899
|
+
.then((symlink) => {
|
|
900
|
+
throw Object.assign(statErr, { symlink })
|
|
901
|
+
})
|
|
902
|
+
)
|
|
920
903
|
})
|
|
921
904
|
}
|
|
922
905
|
|
|
@@ -994,23 +977,35 @@ function transformGitCloneError (err, displayUrl) {
|
|
|
994
977
|
if (trimMessage) {
|
|
995
978
|
wrappedMsg = ~(wrappedMsg = wrappedMsg.trimRight()).indexOf('. ') ? wrappedMsg : wrappedMsg.replace(/\.$/, '')
|
|
996
979
|
}
|
|
997
|
-
const
|
|
998
|
-
|
|
999
|
-
return
|
|
980
|
+
const errWrapper = new Error(`${wrappedMsg} (url: ${displayUrl})`)
|
|
981
|
+
errWrapper.stack += `\nCaused by: ${err.stack || 'unknown'}`
|
|
982
|
+
return errWrapper
|
|
1000
983
|
}
|
|
1001
984
|
|
|
1002
985
|
function splitRefPatterns (str) {
|
|
1003
986
|
return ~str.indexOf('{') ? str.split(VENTILATED_CSV_RX) : str.split(CSV_RX)
|
|
1004
987
|
}
|
|
1005
988
|
|
|
1006
|
-
function
|
|
1007
|
-
|
|
989
|
+
function camelCaseKeys (o, stopPaths = [], p = '') {
|
|
990
|
+
if (Array.isArray(o)) return o.map((it) => camelCaseKeys(it, stopPaths, p))
|
|
991
|
+
if (o == null || o.constructor !== Object) return o
|
|
992
|
+
const pathPrefix = p && p + '.'
|
|
993
|
+
const accum = {}
|
|
994
|
+
for (const [k, v] of Object.entries(o)) {
|
|
995
|
+
const camelKey = k.toLowerCase().replace(/[_-]([a-z0-9])/g, (_, l, idx) => (idx ? l.toUpperCase() : l))
|
|
996
|
+
accum[camelKey] = ~stopPaths.indexOf(pathPrefix + camelKey) ? v : camelCaseKeys(v, stopPaths, pathPrefix + camelKey)
|
|
997
|
+
}
|
|
998
|
+
return accum
|
|
1008
999
|
}
|
|
1009
1000
|
|
|
1010
1001
|
function cleanStartPath (value) {
|
|
1011
1002
|
return value && ~value.indexOf('/') ? value.replace(SUPERFLUOUS_SEPARATORS_RX, '') : value
|
|
1012
1003
|
}
|
|
1013
1004
|
|
|
1005
|
+
function coerceToString (value) {
|
|
1006
|
+
return value == null ? '' : String(value)
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1014
1009
|
function findWorktrees (repo, patterns) {
|
|
1015
1010
|
if (!patterns.length) return new Map()
|
|
1016
1011
|
const linkedOnly = patterns[0] === '.' ? !(patterns = patterns.slice(1)) : true
|
|
@@ -1047,4 +1042,3 @@ function findWorktrees (repo, patterns) {
|
|
|
1047
1042
|
}
|
|
1048
1043
|
|
|
1049
1044
|
module.exports = aggregateContent
|
|
1050
|
-
module.exports._computeOrigin = computeOrigin
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { posix: path } = require('path')
|
|
4
|
+
const posixify = require('./posixify')
|
|
5
|
+
const removeGitSuffix = require('./remove-git-suffix')
|
|
6
|
+
|
|
7
|
+
const EDIT_URL_TEMPLATE_VAR_RX = /\{(web_url|ref(?:hash|name|type)|path)\}/g
|
|
8
|
+
const HOSTED_GIT_REPO_RX = /^(?:https?:\/\/|.+@)(git(?:hub|lab)\.com|bitbucket\.org|pagure\.io)[/:](.+?)(?:\.git)?$/
|
|
9
|
+
|
|
10
|
+
function computeOrigin (url, authStatus, gitdir, ref, startPath, worktreePath = undefined, editUrl = true) {
|
|
11
|
+
const { shortname: refname, oid: refhash, remote, type: reftype } = ref
|
|
12
|
+
const origin = { type: 'git', url, gitdir, reftype, refname, [reftype]: refname, refhash, startPath }
|
|
13
|
+
if (worktreePath !== undefined) {
|
|
14
|
+
if ((origin.worktree = worktreePath)) {
|
|
15
|
+
delete origin.refhash
|
|
16
|
+
origin.fileUriPattern =
|
|
17
|
+
(posixify ? 'file:///' + posixify(worktreePath) : 'file://' + worktreePath) +
|
|
18
|
+
(startPath ? '/' + startPath + '/%s' : '/%s')
|
|
19
|
+
} else if (remote) {
|
|
20
|
+
origin.remote = remote
|
|
21
|
+
}
|
|
22
|
+
if (url.startsWith('file://')) url = undefined
|
|
23
|
+
}
|
|
24
|
+
if (authStatus) origin.private = authStatus
|
|
25
|
+
if (url) origin.webUrl = removeGitSuffix(url)
|
|
26
|
+
if (editUrl === true) {
|
|
27
|
+
const match = url && url.match(HOSTED_GIT_REPO_RX)
|
|
28
|
+
if (match) {
|
|
29
|
+
const host = match[1]
|
|
30
|
+
let action = 'blob'
|
|
31
|
+
let category = ''
|
|
32
|
+
if (host === 'pagure.io') {
|
|
33
|
+
category = 'f'
|
|
34
|
+
} else if (host === 'bitbucket.org') {
|
|
35
|
+
action = 'src'
|
|
36
|
+
} else if (reftype === 'branch') {
|
|
37
|
+
action = 'edit'
|
|
38
|
+
}
|
|
39
|
+
origin.editUrlPattern = 'https://' + path.join(match[1], match[2], action, refname, category, startPath, '%s')
|
|
40
|
+
}
|
|
41
|
+
} else if (editUrl) {
|
|
42
|
+
const vars = {
|
|
43
|
+
path: () => (startPath ? path.join(startPath, '%s') : '%s'),
|
|
44
|
+
refhash: () => refhash,
|
|
45
|
+
reftype: () => reftype,
|
|
46
|
+
refname: () => refname,
|
|
47
|
+
web_url: () => origin.webUrl || '',
|
|
48
|
+
}
|
|
49
|
+
origin.editUrlPattern = editUrl.replace(EDIT_URL_TEMPLATE_VAR_RX, (_, name) => vars[name]())
|
|
50
|
+
}
|
|
51
|
+
return origin
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
module.exports = computeOrigin
|
package/lib/constants.js
CHANGED
|
@@ -4,7 +4,7 @@ module.exports = Object.freeze({
|
|
|
4
4
|
COMPONENT_DESC_FILENAME: 'antora.yml',
|
|
5
5
|
CONTENT_CACHE_FOLDER: 'content',
|
|
6
6
|
CONTENT_SRC_GLOB: '**/*[!~]',
|
|
7
|
-
CONTENT_SRC_OPTS: { follow: true, nomount: true, nosort: true, nounique: true, strict: false
|
|
7
|
+
CONTENT_SRC_OPTS: { follow: true, nomount: true, nosort: true, nounique: true, strict: false },
|
|
8
8
|
FILE_MODES: { 100644: 0o100666 & ~process.umask(), 100755: 0o100777 & ~process.umask() },
|
|
9
9
|
GIT_CORE: 'antora',
|
|
10
10
|
GIT_OPERATION_LABEL_LENGTH: 8,
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
function deepClone (o) {
|
|
4
|
+
switch (o.constructor) {
|
|
5
|
+
case Object:
|
|
6
|
+
return Object.keys(o).reduce((accum, k) => {
|
|
7
|
+
const v = o[k]
|
|
8
|
+
accum[k] = !v || typeof v !== 'object' ? v : deepClone(v)
|
|
9
|
+
return accum
|
|
10
|
+
}, {})
|
|
11
|
+
case Array:
|
|
12
|
+
return o.map((it) => (!it || typeof it !== 'object' ? it : deepClone(it)))
|
|
13
|
+
default:
|
|
14
|
+
return o
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
module.exports = deepClone
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
function
|
|
3
|
+
function deepFlatten (array, accum = []) {
|
|
4
4
|
const len = array.length
|
|
5
|
-
for (let i = 0, it; i < len; i++) Array.isArray((it = array[i])) ?
|
|
5
|
+
for (let i = 0, it; i < len; i++) Array.isArray((it = array[i])) ? deepFlatten(it, accum) : accum.push(it)
|
|
6
6
|
return accum
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
-
module.exports =
|
|
9
|
+
module.exports = deepFlatten
|
package/lib/logger.js
ADDED
package/lib/posixify.js
ADDED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const
|
|
3
|
+
const deepFlatten = require('./deep-flatten')
|
|
4
4
|
const { promises: fsp } = require('fs')
|
|
5
5
|
const git = require('./git')
|
|
6
6
|
const invariably = require('./invariably')
|
|
@@ -56,7 +56,7 @@ async function glob (base, patternSegments, listDirents, retrievePath, { oid, pa
|
|
|
56
56
|
}
|
|
57
57
|
let dirents = await listDirents(base, oid || path)
|
|
58
58
|
if (explicit) dirents = dirents.filter((dirent) => !explicit.has(dirent.name))
|
|
59
|
-
const discovered =
|
|
59
|
+
const discovered = deepFlatten(
|
|
60
60
|
await Promise.all(
|
|
61
61
|
dirents.map((dirent) =>
|
|
62
62
|
dirent.isDirectory() && isMatch(dirent.name)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@antora/content-aggregator",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.1.1",
|
|
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)",
|
|
@@ -16,15 +16,26 @@
|
|
|
16
16
|
"url": "https://gitlab.com/antora/antora/issues"
|
|
17
17
|
},
|
|
18
18
|
"main": "lib/index.js",
|
|
19
|
+
"exports": {
|
|
20
|
+
".": "./lib/index.js",
|
|
21
|
+
"./git": "./lib/git.js",
|
|
22
|
+
"./git/http-plugin": "./lib/git-plugin-http.js",
|
|
23
|
+
"./lib/git-plugin-http": "./lib/git-plugin-http.js",
|
|
24
|
+
"./package.json": "./package.json"
|
|
25
|
+
},
|
|
26
|
+
"imports": {
|
|
27
|
+
"#compute-origin": "./lib/compute-origin.js",
|
|
28
|
+
"#constants": "./lib/constants.js"
|
|
29
|
+
},
|
|
19
30
|
"dependencies": {
|
|
20
31
|
"@antora/expand-path-helper": "~2.0",
|
|
32
|
+
"@antora/logger": "3.1.1",
|
|
21
33
|
"@antora/user-require-helper": "~2.0",
|
|
22
34
|
"braces": "~3.0",
|
|
23
35
|
"cache-directory": "~2.0",
|
|
24
|
-
"camelcase-keys": "~7.0",
|
|
25
36
|
"glob-stream": "~7.0",
|
|
26
|
-
"hpagent": "~
|
|
27
|
-
"isomorphic-git": "~1.
|
|
37
|
+
"hpagent": "~1.0",
|
|
38
|
+
"isomorphic-git": "~1.19",
|
|
28
39
|
"js-yaml": "~4.1",
|
|
29
40
|
"multi-progress": "~4.0",
|
|
30
41
|
"picomatch": "~2.3",
|
|
@@ -34,7 +45,7 @@
|
|
|
34
45
|
"vinyl": "~2.2"
|
|
35
46
|
},
|
|
36
47
|
"engines": {
|
|
37
|
-
"node": ">=
|
|
48
|
+
"node": ">=16.0.0"
|
|
38
49
|
},
|
|
39
50
|
"files": [
|
|
40
51
|
"lib/"
|