@antora/content-aggregator 3.0.0-alpha.7 → 3.0.0-beta.2
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 +198 -187
- package/lib/constants.js +6 -5
- package/lib/{decode-uint8-data.js → decode-uint8-array.js} +0 -0
- package/lib/filter-refs.js +60 -0
- package/lib/git-credential-manager-store.js +5 -10
- package/lib/git-plugin-http.js +14 -20
- package/lib/git.js +3 -0
- package/lib/index.js +1 -1
- package/lib/resolve-path-globs.js +34 -31
- package/package.json +7 -7
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
The Content Aggregator is a component in Antora responsible for fetching and aggregating content distributed across multiple local and remote git repositories for use in an Antora documentation pipeline.
|
|
4
4
|
|
|
5
5
|
[Antora](https://antora.org) is a modular static site generator designed for creating documentation sites from AsciiDoc documents.
|
|
6
|
-
Its site generator
|
|
6
|
+
Its site generator aggregates documents from versioned content repositories and processes them using [Asciidoctor](https://asciidoctor.org).
|
|
7
7
|
|
|
8
8
|
## Copyright and License
|
|
9
9
|
|
package/lib/aggregate-content.js
CHANGED
|
@@ -3,20 +3,20 @@
|
|
|
3
3
|
const camelCaseKeys = require('camelcase-keys')
|
|
4
4
|
const { createHash } = require('crypto')
|
|
5
5
|
const createHttpPlugin = require('./git-plugin-http')
|
|
6
|
-
const
|
|
6
|
+
const decodeUint8Array = require('./decode-uint8-array')
|
|
7
7
|
const EventEmitter = require('events')
|
|
8
8
|
const expandPath = require('@antora/expand-path-helper')
|
|
9
9
|
const File = require('./file')
|
|
10
|
+
const filterRefs = require('./filter-refs')
|
|
10
11
|
const flattenDeep = require('./flatten-deep')
|
|
11
12
|
const fs = require('fs')
|
|
12
13
|
const { promises: fsp } = fs
|
|
13
14
|
const getCacheDir = require('cache-directory')
|
|
14
15
|
const GitCredentialManagerStore = require('./git-credential-manager-store')
|
|
15
|
-
const git = require('
|
|
16
|
+
const git = require('./git')
|
|
16
17
|
const { NotFoundError, ObjectTypeError, UnknownTransportError, UrlParseError } = git.Errors
|
|
17
18
|
const invariably = { false: () => false, void: () => undefined, emptyArray: () => [] }
|
|
18
19
|
const { makeRe: makePicomatchRx } = require('picomatch')
|
|
19
|
-
const matcher = require('matcher')
|
|
20
20
|
const MultiProgress = require('multi-progress')
|
|
21
21
|
const ospath = require('path')
|
|
22
22
|
const { posix: path } = ospath
|
|
@@ -24,18 +24,21 @@ const posixify = ospath.sep === '\\' ? (p) => p.replace(/\\/g, '/') : undefined
|
|
|
24
24
|
const { fs: resolvePathGlobsFs, git: resolvePathGlobsGit } = require('./resolve-path-globs')
|
|
25
25
|
const { Transform } = require('stream')
|
|
26
26
|
const map = (transform, flush = undefined) => new Transform({ objectMode: true, transform, flush })
|
|
27
|
+
const userRequire = require('@antora/user-require-helper')
|
|
27
28
|
const vfs = require('vinyl-fs')
|
|
28
29
|
const yaml = require('js-yaml')
|
|
29
30
|
|
|
30
31
|
const {
|
|
31
32
|
COMPONENT_DESC_FILENAME,
|
|
32
33
|
CONTENT_CACHE_FOLDER,
|
|
33
|
-
|
|
34
|
+
CONTENT_SRC_GLOB,
|
|
35
|
+
CONTENT_SRC_OPTS,
|
|
34
36
|
FILE_MODES,
|
|
35
37
|
GIT_CORE,
|
|
36
38
|
GIT_OPERATION_LABEL_LENGTH,
|
|
37
39
|
GIT_PROGRESS_PHASES,
|
|
38
40
|
PICOMATCH_VERSION_OPTS,
|
|
41
|
+
REF_PATTERN_CACHE_KEY,
|
|
39
42
|
SYMLINK_FILE_MODE,
|
|
40
43
|
VALID_STATE_FILENAME,
|
|
41
44
|
} = require('./constants')
|
|
@@ -55,6 +58,7 @@ const SPACE_RX = / /g
|
|
|
55
58
|
const SUPERFLUOUS_SEPARATORS_RX = /^\/+|\/+$|\/+(?=\/)/g
|
|
56
59
|
const URL_AUTH_CLEANER_RX = /^(https?:\/\/)[^/@]*@/
|
|
57
60
|
const URL_AUTH_EXTRACTOR_RX = /^(https?:\/\/)(?:([^/:@]+)?(?::([^/@]+)?)?@)?(.*)/
|
|
61
|
+
const URL_PORT_CLEANER_RX = /^([^/]+):[0-9]+(?=\/)/
|
|
58
62
|
|
|
59
63
|
/**
|
|
60
64
|
* Aggregates files from the specified content sources so they can be loaded
|
|
@@ -86,69 +90,96 @@ const URL_AUTH_EXTRACTOR_RX = /^(https?:\/\/)(?:([^/:@]+)?(?::([^/@]+)?)?@)?(.*)
|
|
|
86
90
|
function aggregateContent (playbook) {
|
|
87
91
|
const startDir = playbook.dir || '.'
|
|
88
92
|
const { branches, editUrl, tags, sources } = playbook.content
|
|
89
|
-
const {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
)
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
new Map()
|
|
99
|
-
)
|
|
93
|
+
const sourceDefaults = { branches, editUrl, tags }
|
|
94
|
+
const { cacheDir: requestedCacheDir, fetch, quiet } = playbook.runtime
|
|
95
|
+
return ensureCacheDir(requestedCacheDir, startDir).then((cacheDir) => {
|
|
96
|
+
const gitConfig = Object.assign({ ensureGitSuffix: true }, playbook.git)
|
|
97
|
+
const gitPlugins = loadGitPlugins(gitConfig, playbook.network || {}, startDir)
|
|
98
|
+
const fetchConcurrency = Math.max(gitConfig.fetchConcurrency || Infinity, 1)
|
|
99
|
+
const sourcesByUrl = sources.reduce((accum, source) => {
|
|
100
|
+
return accum.set(source.url, [...(accum.get(source.url) || []), Object.assign({}, sourceDefaults, source)])
|
|
101
|
+
}, new Map())
|
|
100
102
|
const progress = !quiet && createProgress(sourcesByUrl.keys(), process.stdout)
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
progress,
|
|
108
|
-
fetch,
|
|
109
|
-
startDir,
|
|
110
|
-
}).then(({ repo, authStatus }) =>
|
|
111
|
-
Promise.all(
|
|
112
|
-
sources.map((source) => {
|
|
113
|
-
source = Object.assign({ branches, editUrl, tags }, source)
|
|
114
|
-
// NOTE if repository is managed (has a url), we can assume the remote name is origin
|
|
115
|
-
// TODO if the repo has no remotes, then remoteName should be undefined
|
|
116
|
-
const remoteName = repo.url ? 'origin' : source.remote || 'origin'
|
|
117
|
-
return collectFilesFromSource(source, repo, remoteName, authStatus)
|
|
118
|
-
})
|
|
119
|
-
)
|
|
120
|
-
)
|
|
121
|
-
)
|
|
122
|
-
)
|
|
123
|
-
.then(buildAggregate)
|
|
124
|
-
.catch((err) => {
|
|
125
|
-
progress && progress.terminate()
|
|
126
|
-
throw err
|
|
127
|
-
})
|
|
103
|
+
const refPatternCache = Object.assign(new Map(), { braces: new Map() })
|
|
104
|
+
const loadOpts = { cacheDir, fetch, gitPlugins, progress, startDir, refPatternCache }
|
|
105
|
+
return collectFiles(sourcesByUrl, loadOpts, fetchConcurrency).then(buildAggregate, (err) => {
|
|
106
|
+
progress && progress.terminate()
|
|
107
|
+
throw err
|
|
108
|
+
})
|
|
128
109
|
})
|
|
129
110
|
}
|
|
130
111
|
|
|
112
|
+
async function collectFiles (sourcesByUrl, loadOpts, concurrency) {
|
|
113
|
+
const tasks = [...sourcesByUrl.entries()].map(([url, sources]) => [
|
|
114
|
+
() => loadRepository(url, Object.assign({ fetchTags: tagsSpecified(sources) }, loadOpts)),
|
|
115
|
+
({ repo, authStatus }) =>
|
|
116
|
+
Promise.all(
|
|
117
|
+
sources.map((source) => {
|
|
118
|
+
// NOTE if repository is managed (has a url property), we can assume the remote name is origin
|
|
119
|
+
// TODO if the repo has no remotes, then remoteName should be undefined
|
|
120
|
+
const remoteName = repo.url ? 'origin' : source.remote || 'origin'
|
|
121
|
+
return collectFilesFromSource(source, repo, remoteName, authStatus)
|
|
122
|
+
})
|
|
123
|
+
),
|
|
124
|
+
])
|
|
125
|
+
let rejection, started
|
|
126
|
+
const startedContinuations = []
|
|
127
|
+
const recordRejection = (err) => {
|
|
128
|
+
rejection = err
|
|
129
|
+
}
|
|
130
|
+
const runTask = (primary, continuation, idx) =>
|
|
131
|
+
primary().then((value) => {
|
|
132
|
+
if (!rejection) startedContinuations[idx] = continuation(value).catch(recordRejection)
|
|
133
|
+
}, recordRejection)
|
|
134
|
+
if (tasks.length > concurrency) {
|
|
135
|
+
started = []
|
|
136
|
+
const pending = []
|
|
137
|
+
for (const [primary, continuation] of tasks) {
|
|
138
|
+
const current = runTask(primary, continuation, started.length).finally(() =>
|
|
139
|
+
pending.splice(pending.indexOf(current), 1)
|
|
140
|
+
)
|
|
141
|
+
started.push(current)
|
|
142
|
+
if (pending.push(current) < concurrency) continue
|
|
143
|
+
await Promise.race(pending)
|
|
144
|
+
if (rejection) break
|
|
145
|
+
}
|
|
146
|
+
} else {
|
|
147
|
+
started = tasks.map(([primary, continuation], idx) => runTask(primary, continuation, idx))
|
|
148
|
+
}
|
|
149
|
+
return Promise.all(started).then(() =>
|
|
150
|
+
Promise.all(startedContinuations).then((result) => {
|
|
151
|
+
if (rejection) throw rejection
|
|
152
|
+
return result
|
|
153
|
+
})
|
|
154
|
+
)
|
|
155
|
+
}
|
|
156
|
+
|
|
131
157
|
function buildAggregate (componentVersionBuckets) {
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
158
|
+
return [
|
|
159
|
+
...flattenDeep(componentVersionBuckets)
|
|
160
|
+
.reduce((accum, batch) => {
|
|
161
|
+
const key = batch.version + '@' + batch.name
|
|
162
|
+
const entry = accum.get(key)
|
|
163
|
+
if (!entry) return accum.set(key, batch)
|
|
164
|
+
const files = batch.files
|
|
165
|
+
;(batch.files = entry.files).push(...files)
|
|
166
|
+
Object.assign(entry, batch)
|
|
167
|
+
return accum
|
|
168
|
+
}, new Map())
|
|
169
|
+
.values(),
|
|
170
|
+
]
|
|
138
171
|
}
|
|
139
172
|
|
|
140
173
|
async function loadRepository (url, opts) {
|
|
141
|
-
let dir
|
|
142
|
-
|
|
143
|
-
let authStatus
|
|
174
|
+
let authStatus, dir, repo
|
|
175
|
+
const cache = { [REF_PATTERN_CACHE_KEY]: opts.refPatternCache }
|
|
144
176
|
if (~url.indexOf(':') && GIT_URI_DETECTOR_RX.test(url)) {
|
|
145
|
-
let displayUrl
|
|
146
|
-
let credentials
|
|
177
|
+
let credentials, displayUrl
|
|
147
178
|
;({ displayUrl, url, credentials } = extractCredentials(url))
|
|
148
179
|
const { cacheDir, fetch, fetchTags, gitPlugins, progress } = opts
|
|
149
180
|
dir = ospath.join(cacheDir, generateCloneFolderName(displayUrl))
|
|
150
181
|
// NOTE the presence of the url property on the repo object implies the repository is remote
|
|
151
|
-
repo = { cache
|
|
182
|
+
repo = { cache, dir, fs, gitdir: dir, noCheckout: true, url }
|
|
152
183
|
const validStateFile = ospath.join(dir, VALID_STATE_FILENAME)
|
|
153
184
|
try {
|
|
154
185
|
await fsp.access(validStateFile)
|
|
@@ -173,7 +204,7 @@ async function loadRepository (url, opts) {
|
|
|
173
204
|
authStatus = await git.getConfig(Object.assign({ path: 'remote.origin.private' }, repo))
|
|
174
205
|
}
|
|
175
206
|
} catch (gitErr) {
|
|
176
|
-
await rmdir(dir)
|
|
207
|
+
await fsp['rm' in fsp ? 'rm' : 'rmdir'](dir, { recursive: true, force: true })
|
|
177
208
|
if (gitErr.rethrow) throw transformGitCloneError(gitErr, displayUrl)
|
|
178
209
|
const fetchOpts = buildFetchOptions(repo, progress, displayUrl, credentials, gitPlugins, fetchTags, 'clone')
|
|
179
210
|
await git
|
|
@@ -184,8 +215,7 @@ async function loadRepository (url, opts) {
|
|
|
184
215
|
authStatus = credentials ? 'auth-embedded' : credentialManager.status({ url }) ? 'auth-required' : undefined
|
|
185
216
|
return git.setConfig(Object.assign({ path: 'remote.origin.private', value: authStatus }, repo))
|
|
186
217
|
})
|
|
187
|
-
.catch(
|
|
188
|
-
await rmdir(dir)
|
|
218
|
+
.catch((cloneErr) => {
|
|
189
219
|
// FIXME triggering the error handler here causes assertion problems in the test suite
|
|
190
220
|
//if (fetchOpts.onProgress) fetchOpts.onProgress.finish(cloneErr)
|
|
191
221
|
throw transformGitCloneError(cloneErr, displayUrl)
|
|
@@ -193,10 +223,9 @@ async function loadRepository (url, opts) {
|
|
|
193
223
|
.then(() => fsp.writeFile(validStateFile, '').catch(invariably.void))
|
|
194
224
|
.then(() => fetchOpts.onProgress && fetchOpts.onProgress.finish())
|
|
195
225
|
}
|
|
196
|
-
} else if (await isDirectory((dir = expandPath(url,
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
: { cache: {}, dir, fs, gitdir: dir, noCheckout: true }
|
|
226
|
+
} else if (await isDirectory((dir = expandPath(url, { dot: opts.startDir })))) {
|
|
227
|
+
const gitdir = ospath.join(dir, '.git')
|
|
228
|
+
repo = (await isDirectory(gitdir)) ? { cache, dir, fs, gitdir } : { cache, dir, fs, gitdir: dir, noCheckout: true }
|
|
200
229
|
try {
|
|
201
230
|
await git.resolveRef(Object.assign({ ref: 'HEAD', depth: 1 }, repo))
|
|
202
231
|
} catch {
|
|
@@ -240,16 +269,18 @@ async function collectFilesFromSource (source, repo, remoteName, authStatus) {
|
|
|
240
269
|
async function selectReferences (source, repo, remote) {
|
|
241
270
|
let { branches: branchPatterns, tags: tagPatterns, worktrees: worktreePatterns = '.' } = source
|
|
242
271
|
const isBare = repo.noCheckout
|
|
272
|
+
const patternCache = repo.cache[REF_PATTERN_CACHE_KEY]
|
|
273
|
+
const noWorktree = repo.url ? undefined : null
|
|
243
274
|
const refs = new Map()
|
|
244
275
|
if (tagPatterns) {
|
|
245
276
|
tagPatterns = Array.isArray(tagPatterns)
|
|
246
277
|
? tagPatterns.map((pattern) => String(pattern))
|
|
247
|
-
: String(tagPatterns)
|
|
278
|
+
: splitRefPatterns(String(tagPatterns))
|
|
248
279
|
if (tagPatterns.length) {
|
|
249
280
|
const tags = await git.listTags(repo)
|
|
250
|
-
for (const shortname of tags.length ?
|
|
281
|
+
for (const shortname of tags.length ? filterRefs(tags, tagPatterns, patternCache) : tags) {
|
|
251
282
|
// NOTE tags are stored using symbol keys to distinguish them from branches
|
|
252
|
-
refs.set(Symbol(shortname), { shortname, fullname: 'tags/' + shortname, type: 'tag' })
|
|
283
|
+
refs.set(Symbol(shortname), { shortname, fullname: 'tags/' + shortname, type: 'tag', head: noWorktree })
|
|
253
284
|
}
|
|
254
285
|
}
|
|
255
286
|
}
|
|
@@ -262,7 +293,7 @@ async function selectReferences (source, repo, remote) {
|
|
|
262
293
|
} else {
|
|
263
294
|
worktreePatterns = Array.isArray(worktreePatterns)
|
|
264
295
|
? worktreePatterns.map((pattern) => String(pattern))
|
|
265
|
-
: String(worktreePatterns)
|
|
296
|
+
: splitRefPatterns(String(worktreePatterns))
|
|
266
297
|
}
|
|
267
298
|
}
|
|
268
299
|
const branchPatternsString = String(branchPatterns)
|
|
@@ -273,16 +304,15 @@ async function selectReferences (source, repo, remote) {
|
|
|
273
304
|
} else {
|
|
274
305
|
if (!isBare) {
|
|
275
306
|
// NOTE current branch is undefined when HEAD is detached
|
|
276
|
-
const
|
|
277
|
-
|
|
278
|
-
refs.set('HEAD', ref)
|
|
307
|
+
const head = worktreePatterns[0] === '.' ? repo.dir : noWorktree
|
|
308
|
+
refs.set('HEAD', { shortname: 'HEAD', fullname: 'HEAD', type: 'branch', detached: true, head })
|
|
279
309
|
}
|
|
280
310
|
return [...refs.values()]
|
|
281
311
|
}
|
|
282
312
|
} else if (
|
|
283
313
|
(branchPatterns = Array.isArray(branchPatterns)
|
|
284
314
|
? branchPatterns.map((pattern) => String(pattern))
|
|
285
|
-
: branchPatternsString
|
|
315
|
+
: splitRefPatterns(branchPatternsString)).length
|
|
286
316
|
) {
|
|
287
317
|
let headBranchIdx
|
|
288
318
|
// NOTE we can assume at least two entries if HEAD or . are present
|
|
@@ -297,10 +327,13 @@ async function selectReferences (source, repo, remote) {
|
|
|
297
327
|
}
|
|
298
328
|
} else {
|
|
299
329
|
if (!isBare) {
|
|
330
|
+
let head = noWorktree
|
|
331
|
+
if (worktreePatterns[0] === '.') {
|
|
332
|
+
worktreePatterns = worktreePatterns.slice(1)
|
|
333
|
+
head = repo.dir
|
|
334
|
+
}
|
|
300
335
|
// NOTE current branch is undefined when HEAD is detached
|
|
301
|
-
|
|
302
|
-
if (worktreePatterns[0] === '.' && (worktreePatterns = worktreePatterns.slice(1))) ref.head = repo.dir
|
|
303
|
-
refs.set('HEAD', ref)
|
|
336
|
+
refs.set('HEAD', { shortname: 'HEAD', fullname: 'HEAD', type: 'branch', detached: true, head })
|
|
304
337
|
}
|
|
305
338
|
branchPatterns.splice(headBranchIdx, 1)
|
|
306
339
|
}
|
|
@@ -311,8 +344,9 @@ async function selectReferences (source, repo, remote) {
|
|
|
311
344
|
// NOTE isomorphic-git includes HEAD in list of remote branches (see https://isomorphic-git.org/docs/listBranches)
|
|
312
345
|
const remoteBranches = (await git.listBranches(Object.assign({ remote }, repo))).filter((it) => it !== 'HEAD')
|
|
313
346
|
if (remoteBranches.length) {
|
|
314
|
-
for (const shortname of
|
|
315
|
-
|
|
347
|
+
for (const shortname of filterRefs(remoteBranches, branchPatterns, patternCache)) {
|
|
348
|
+
const fullname = 'remotes/' + remote + '/' + shortname
|
|
349
|
+
refs.set(shortname, { shortname, fullname, type: 'branch', remote, head: noWorktree })
|
|
316
350
|
}
|
|
317
351
|
}
|
|
318
352
|
// NOTE only consider local branches if repo has a worktree or there are no remote tracking branches
|
|
@@ -320,17 +354,17 @@ async function selectReferences (source, repo, remote) {
|
|
|
320
354
|
const localBranches = await git.listBranches(repo)
|
|
321
355
|
if (localBranches.length) {
|
|
322
356
|
const worktrees = await findWorktrees(repo, worktreePatterns)
|
|
323
|
-
for (const shortname of
|
|
324
|
-
const
|
|
325
|
-
|
|
326
|
-
refs.set(shortname, ref)
|
|
357
|
+
for (const shortname of filterRefs(localBranches, branchPatterns, patternCache)) {
|
|
358
|
+
const head = worktrees.get(shortname) || noWorktree
|
|
359
|
+
refs.set(shortname, { shortname, fullname: 'heads/' + shortname, type: 'branch', head })
|
|
327
360
|
}
|
|
328
361
|
}
|
|
329
362
|
} else if (!remoteBranches.length) {
|
|
330
|
-
// QUESTION should local branches be used if the only remote branch is HEAD?
|
|
331
363
|
const localBranches = await git.listBranches(repo)
|
|
332
|
-
|
|
333
|
-
|
|
364
|
+
if (localBranches.length) {
|
|
365
|
+
for (const shortname of filterRefs(localBranches, branchPatterns, patternCache)) {
|
|
366
|
+
refs.set(shortname, { shortname, fullname: 'heads/' + shortname, type: 'branch', head: noWorktree })
|
|
367
|
+
}
|
|
334
368
|
}
|
|
335
369
|
}
|
|
336
370
|
}
|
|
@@ -391,7 +425,7 @@ function collectFilesFromStartPath (startPath, repo, authStatus, ref, worktreePa
|
|
|
391
425
|
)
|
|
392
426
|
.then((files) => {
|
|
393
427
|
const componentVersionBucket = loadComponentDescriptor(files, ref, version)
|
|
394
|
-
const origin = computeOrigin(originUrl, authStatus, ref, startPath, worktreePath, editUrl)
|
|
428
|
+
const origin = computeOrigin(originUrl, authStatus, repo.gitdir, ref, startPath, worktreePath, editUrl)
|
|
395
429
|
componentVersionBucket.files = files.map((file) => assignFileProperties(file, origin))
|
|
396
430
|
return componentVersionBucket
|
|
397
431
|
})
|
|
@@ -404,16 +438,12 @@ function collectFilesFromStartPath (startPath, repo, authStatus, ref, worktreePa
|
|
|
404
438
|
|
|
405
439
|
function readFilesFromWorktree (worktreePath, startPath) {
|
|
406
440
|
const cwd = ospath.join(worktreePath, startPath)
|
|
407
|
-
return fsp
|
|
408
|
-
|
|
409
|
-
.catch(() => {
|
|
410
|
-
throw new Error(`the start path '${startPath}' does not exist`)
|
|
411
|
-
})
|
|
412
|
-
.then((stat) => {
|
|
441
|
+
return fsp.stat(cwd).then(
|
|
442
|
+
(stat) => {
|
|
413
443
|
if (!stat.isDirectory()) throw new Error(`the start path '${startPath}' is not a directory`)
|
|
414
444
|
return new Promise((resolve, reject) =>
|
|
415
445
|
vfs
|
|
416
|
-
.src(
|
|
446
|
+
.src(CONTENT_SRC_GLOB, Object.assign({ cwd }, CONTENT_SRC_OPTS))
|
|
417
447
|
.on('error', (err) => {
|
|
418
448
|
if (err.code === 'ENOENT' && err.syscall === 'stat') {
|
|
419
449
|
try {
|
|
@@ -427,9 +457,13 @@ function readFilesFromWorktree (worktreePath, startPath) {
|
|
|
427
457
|
reject(err)
|
|
428
458
|
})
|
|
429
459
|
.pipe(relativizeFiles())
|
|
430
|
-
.pipe(
|
|
460
|
+
.pipe(collectDataFromStream(resolve))
|
|
431
461
|
)
|
|
432
|
-
}
|
|
462
|
+
},
|
|
463
|
+
() => {
|
|
464
|
+
throw new Error(`the start path '${startPath}' does not exist`)
|
|
465
|
+
}
|
|
466
|
+
)
|
|
433
467
|
}
|
|
434
468
|
|
|
435
469
|
/**
|
|
@@ -458,11 +492,11 @@ function relativizeFiles () {
|
|
|
458
492
|
})
|
|
459
493
|
}
|
|
460
494
|
|
|
461
|
-
function
|
|
495
|
+
function collectDataFromStream (done) {
|
|
462
496
|
const accum = []
|
|
463
497
|
return map(
|
|
464
|
-
(
|
|
465
|
-
accum.push(
|
|
498
|
+
(obj, _, next) => {
|
|
499
|
+
accum.push(obj)
|
|
466
500
|
next()
|
|
467
501
|
},
|
|
468
502
|
() => done(accum)
|
|
@@ -480,13 +514,13 @@ function readFilesFromGitTree (repo, oid, startPath) {
|
|
|
480
514
|
}
|
|
481
515
|
|
|
482
516
|
function getGitTreeAtStartPath (repo, oid, startPath) {
|
|
483
|
-
return git
|
|
484
|
-
|
|
485
|
-
|
|
517
|
+
return git.readTree(Object.assign({ oid, filepath: startPath }, repo)).then(
|
|
518
|
+
(result) => Object.assign(result, { dirname: startPath }),
|
|
519
|
+
(err) => {
|
|
486
520
|
const m = err instanceof ObjectTypeError && err.data.expected === 'tree' ? 'is not a directory' : 'does not exist'
|
|
487
521
|
throw new Error(`the start path '${startPath}' ${m}`)
|
|
488
|
-
}
|
|
489
|
-
|
|
522
|
+
}
|
|
523
|
+
)
|
|
490
524
|
}
|
|
491
525
|
|
|
492
526
|
function srcGitTree (repo, root, start) {
|
|
@@ -505,9 +539,11 @@ function createGitTreeWalker (repo, root, filter) {
|
|
|
505
539
|
walk (start) {
|
|
506
540
|
return (
|
|
507
541
|
visitGitTree(this, repo, root, filter, start)
|
|
508
|
-
.then(() => this.emit('end'))
|
|
509
542
|
// NOTE if error is thrown, promises already being resolved won't halt
|
|
510
|
-
.
|
|
543
|
+
.then(
|
|
544
|
+
() => this.emit('end'),
|
|
545
|
+
(err) => this.emit('error', err)
|
|
546
|
+
)
|
|
511
547
|
)
|
|
512
548
|
},
|
|
513
549
|
})
|
|
@@ -530,8 +566,15 @@ function visitGitTree (emitter, repo, root, filter, parent, dirname = '', follow
|
|
|
530
566
|
let mode
|
|
531
567
|
if (entry.mode === SYMLINK_FILE_MODE) {
|
|
532
568
|
reads.push(
|
|
533
|
-
readGitSymlink(repo, root, parent, entry, following)
|
|
534
|
-
|
|
569
|
+
readGitSymlink(repo, root, parent, entry, following).then(
|
|
570
|
+
(target) => {
|
|
571
|
+
if (target.type === 'tree') {
|
|
572
|
+
return visitGitTree(emitter, repo, root, filter, target, vfilePath, new Set(following).add(entry.oid))
|
|
573
|
+
} else if (target.type === 'blob' && filterVerdict === true && (mode = FILE_MODES[target.mode])) {
|
|
574
|
+
emitter.emit('entry', Object.assign({ mode, oid: target.oid, path: vfilePath }, repo))
|
|
575
|
+
}
|
|
576
|
+
},
|
|
577
|
+
(err) => {
|
|
535
578
|
// NOTE this error could be caught after promise chain has already been rejected
|
|
536
579
|
if (err instanceof NotFoundError) {
|
|
537
580
|
err.message = `Broken symbolic link detected at ${vfilePath}`
|
|
@@ -539,14 +582,8 @@ function visitGitTree (emitter, repo, root, filter, parent, dirname = '', follow
|
|
|
539
582
|
err.message = `Symbolic link cycle detected at ${vfilePath}`
|
|
540
583
|
}
|
|
541
584
|
throw err
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
if (target.type === 'tree') {
|
|
545
|
-
return visitGitTree(emitter, repo, root, filter, target, vfilePath, new Set(following).add(entry.oid))
|
|
546
|
-
} else if (target.type === 'blob' && filterVerdict === true && (mode = FILE_MODES[target.mode])) {
|
|
547
|
-
emitter.emit('entry', Object.assign({ mode, oid: target.oid, path: vfilePath }, repo))
|
|
548
|
-
}
|
|
549
|
-
})
|
|
585
|
+
}
|
|
586
|
+
)
|
|
550
587
|
)
|
|
551
588
|
} else if ((mode = FILE_MODES[entry.mode])) {
|
|
552
589
|
emitter.emit('entry', Object.assign({ mode, oid: entry.oid, path: vfilePath }, repo))
|
|
@@ -560,11 +597,11 @@ function visitGitTree (emitter, repo, root, filter, parent, dirname = '', follow
|
|
|
560
597
|
function readGitSymlink (repo, root, parent, { oid }, following) {
|
|
561
598
|
if (following.size !== (following = new Set(following).add(oid)).size) {
|
|
562
599
|
return git.readBlob(Object.assign({ oid }, repo)).then(({ blob: target }) => {
|
|
563
|
-
target =
|
|
600
|
+
target = decodeUint8Array(target)
|
|
564
601
|
let targetParent
|
|
565
602
|
if (parent.dirname) {
|
|
566
603
|
const dirname = parent.dirname + '/'
|
|
567
|
-
target = path.join(dirname, target)
|
|
604
|
+
target = path.join(dirname, target) // join doesn't remove trailing separator
|
|
568
605
|
if (target.startsWith(dirname)) {
|
|
569
606
|
target = target.substr(dirname.length)
|
|
570
607
|
targetParent = parent
|
|
@@ -572,10 +609,12 @@ function readGitSymlink (repo, root, parent, { oid }, following) {
|
|
|
572
609
|
targetParent = root
|
|
573
610
|
}
|
|
574
611
|
} else {
|
|
575
|
-
target = path.normalize(target)
|
|
612
|
+
target = path.normalize(target) // normalize doesn't remove trailing separator
|
|
576
613
|
targetParent = root
|
|
577
614
|
}
|
|
578
|
-
|
|
615
|
+
const targetSegments = target.split('/')
|
|
616
|
+
if (!targetSegments[targetSegments.length - 1]) targetSegments.pop()
|
|
617
|
+
return readGitObjectAtPath(repo, root, targetParent, targetSegments, following)
|
|
579
618
|
})
|
|
580
619
|
}
|
|
581
620
|
const err = { name: 'SymbolicLinkCycleError', code: 'SymbolicLinkCycleError', oid }
|
|
@@ -616,11 +655,8 @@ function filterGitEntry (entry) {
|
|
|
616
655
|
|
|
617
656
|
function entryToFile (entry) {
|
|
618
657
|
return git.readBlob(entry).then(({ blob: contents }) => {
|
|
619
|
-
const stat = new fs.Stats()
|
|
620
|
-
stat.mode = entry.mode
|
|
621
|
-
stat.mtime = undefined
|
|
622
|
-
stat.size = contents.byteLength
|
|
623
658
|
contents = Buffer.from(contents.buffer)
|
|
659
|
+
const stat = Object.assign(new fs.Stats(), { mode: entry.mode, mtime: undefined, size: contents.byteLength })
|
|
624
660
|
return new File({ path: entry.path, contents, stat })
|
|
625
661
|
})
|
|
626
662
|
}
|
|
@@ -673,22 +709,23 @@ function loadComponentDescriptor (files, ref, version) {
|
|
|
673
709
|
return camelCaseKeys(data, { deep: true, stopPaths: ['asciidoc'] })
|
|
674
710
|
}
|
|
675
711
|
|
|
676
|
-
function computeOrigin (url, authStatus, ref, startPath, worktreePath = undefined, editUrl = true) {
|
|
712
|
+
function computeOrigin (url, authStatus, gitdir, ref, startPath, worktreePath = undefined, editUrl = true) {
|
|
677
713
|
const { shortname: refname, oid: refhash, type: reftype } = ref
|
|
678
|
-
const
|
|
679
|
-
const origin = { type: 'git', refname, startPath }
|
|
714
|
+
const origin = { type: 'git', url, gitdir, refname, [reftype]: refname, startPath }
|
|
680
715
|
if (authStatus) origin.private = authStatus
|
|
681
|
-
|
|
682
|
-
if (worktreePath) {
|
|
683
|
-
if (remote) origin.url = url
|
|
684
|
-
origin.fileUriPattern =
|
|
685
|
-
(posixify ? 'file:///' + posixify(worktreePath) : 'file://' + worktreePath) + path.join('/', startPath, '%s')
|
|
686
|
-
origin.worktree = worktreePath
|
|
687
|
-
} else {
|
|
688
|
-
origin.url = url
|
|
716
|
+
if (worktreePath === undefined) {
|
|
689
717
|
origin.refhash = refhash
|
|
718
|
+
} else {
|
|
719
|
+
if (worktreePath) {
|
|
720
|
+
origin.fileUriPattern =
|
|
721
|
+
(posixify ? 'file:///' + posixify(worktreePath) : 'file://' + worktreePath) + path.join('/', startPath, '%s')
|
|
722
|
+
} else {
|
|
723
|
+
origin.refhash = refhash
|
|
724
|
+
}
|
|
725
|
+
origin.worktree = worktreePath
|
|
726
|
+
if (url.startsWith('file://')) url = undefined
|
|
690
727
|
}
|
|
691
|
-
if (
|
|
728
|
+
if (url) origin.webUrl = url.replace(GIT_SUFFIX_RX, '')
|
|
692
729
|
if (editUrl === true) {
|
|
693
730
|
let match
|
|
694
731
|
if (url && (match = url.match(HOSTED_GIT_REPO_RX))) {
|
|
@@ -856,16 +893,20 @@ function generateCloneFolderName (url) {
|
|
|
856
893
|
*
|
|
857
894
|
* @param {Repository} repo - The repository on which to operate.
|
|
858
895
|
* @param {String} remoteName - The name of the remote to resolve.
|
|
859
|
-
* @returns {String} The URL of the specified remote, if
|
|
896
|
+
* @returns {String} The URL of the specified remote, if defined, or the file URI to the local repository.
|
|
860
897
|
*/
|
|
861
898
|
function resolveRemoteUrl (repo, remoteName) {
|
|
862
899
|
return git.getConfig(Object.assign({ path: 'remote.' + remoteName + '.url' }, repo)).then((url) => {
|
|
863
|
-
if (
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
900
|
+
if (url) {
|
|
901
|
+
if (url.startsWith('https://') || url.startsWith('http://')) {
|
|
902
|
+
return ~url.indexOf('@') ? url.replace(URL_AUTH_CLEANER_RX, '$1') : url
|
|
903
|
+
} else if (url.startsWith('git@')) {
|
|
904
|
+
return 'https://' + url.substr(4).replace(':', '/')
|
|
905
|
+
} else if (url.startsWith('ssh://')) {
|
|
906
|
+
return 'https://' + url.substr(url.indexOf('@') + 1 || 6).replace(URL_PORT_CLEANER_RX, '$1')
|
|
907
|
+
}
|
|
868
908
|
}
|
|
909
|
+
return posixify ? 'file:///' + posixify(repo.dir) : 'file://' + repo.dir
|
|
869
910
|
})
|
|
870
911
|
}
|
|
871
912
|
|
|
@@ -876,51 +917,18 @@ function resolveRemoteUrl (repo, remoteName) {
|
|
|
876
917
|
* @return {Boolean} A flag indicating whether the URL matches a directory on the local filesystem.
|
|
877
918
|
*/
|
|
878
919
|
function isDirectory (url) {
|
|
879
|
-
return fsp
|
|
880
|
-
.stat(url)
|
|
881
|
-
.then((stat) => stat.isDirectory())
|
|
882
|
-
.catch(invariably.false)
|
|
883
|
-
}
|
|
884
|
-
|
|
885
|
-
/**
|
|
886
|
-
* Removes the specified directory (including all of its contents) or file.
|
|
887
|
-
* Equivalent to fs.promises.rmdir(dir, { recursive: true }) in Node 12.
|
|
888
|
-
*/
|
|
889
|
-
function rmdir (dir) {
|
|
890
|
-
return fsp
|
|
891
|
-
.readdir(dir, { withFileTypes: true })
|
|
892
|
-
.then((lst) =>
|
|
893
|
-
Promise.all(
|
|
894
|
-
lst.map((it) =>
|
|
895
|
-
it.isDirectory()
|
|
896
|
-
? rmdir(ospath.join(dir, it.name))
|
|
897
|
-
: fsp.unlink(ospath.join(dir, it.name)).catch((unlinkErr) => {
|
|
898
|
-
if (unlinkErr.code !== 'ENOENT') throw unlinkErr
|
|
899
|
-
})
|
|
900
|
-
)
|
|
901
|
-
)
|
|
902
|
-
)
|
|
903
|
-
.then(() => fsp.rmdir(dir))
|
|
904
|
-
.catch((err) => {
|
|
905
|
-
if (err.code === 'ENOENT') return
|
|
906
|
-
if (err.code === 'ENOTDIR') {
|
|
907
|
-
return fsp.unlink(dir).catch((unlinkErr) => {
|
|
908
|
-
if (unlinkErr.code !== 'ENOENT') throw unlinkErr
|
|
909
|
-
})
|
|
910
|
-
}
|
|
911
|
-
throw err
|
|
912
|
-
})
|
|
920
|
+
return fsp.stat(url).then((stat) => stat.isDirectory(), invariably.false)
|
|
913
921
|
}
|
|
914
922
|
|
|
915
|
-
function tagsSpecified (sources
|
|
916
|
-
return
|
|
917
|
-
const tags = source.tags || defaultTags || []
|
|
918
|
-
return Array.isArray(tags) ? tags.length : true
|
|
919
|
-
})
|
|
923
|
+
function tagsSpecified (sources) {
|
|
924
|
+
return sources.some(({ tags }) => tags && (Array.isArray(tags) ? tags.length : true))
|
|
920
925
|
}
|
|
921
926
|
|
|
922
927
|
function loadGitPlugins (gitConfig, networkConfig, startDir) {
|
|
923
|
-
const plugins = (git.cores || git.default.cores || new Map()).get(GIT_CORE)
|
|
928
|
+
const plugins = new Map((git.cores || git.default.cores || new Map()).get(GIT_CORE))
|
|
929
|
+
for (const [name, request] of Object.entries(gitConfig.plugins || {})) {
|
|
930
|
+
if (request) plugins.set(name, userRequire(request, { dot: startDir, paths: [startDir, __dirname] }))
|
|
931
|
+
}
|
|
924
932
|
let credentialManager, urlRouter
|
|
925
933
|
if ((credentialManager = plugins.get('credentialManager'))) {
|
|
926
934
|
if (typeof credentialManager.configure === 'function') {
|
|
@@ -949,19 +957,18 @@ function ensureCacheDir (preferredCacheDir, startDir) {
|
|
|
949
957
|
const baseCacheDir =
|
|
950
958
|
preferredCacheDir == null
|
|
951
959
|
? getCacheDir('antora' + (process.env.NODE_ENV === 'test' ? '-test' : '')) || ospath.resolve('.antora/cache')
|
|
952
|
-
: expandPath(preferredCacheDir,
|
|
960
|
+
: expandPath(preferredCacheDir, { dot: startDir })
|
|
953
961
|
const cacheDir = ospath.join(baseCacheDir, CONTENT_CACHE_FOLDER)
|
|
954
|
-
return fsp
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
.catch((err) => {
|
|
962
|
+
return fsp.mkdir(cacheDir, { recursive: true }).then(
|
|
963
|
+
() => cacheDir,
|
|
964
|
+
(err) => {
|
|
958
965
|
throw Object.assign(err, { message: `Failed to create content cache directory: ${cacheDir}; ${err.message}` })
|
|
959
|
-
}
|
|
966
|
+
}
|
|
967
|
+
)
|
|
960
968
|
}
|
|
961
969
|
|
|
962
970
|
function transformGitCloneError (err, displayUrl) {
|
|
963
|
-
let wrappedMsg
|
|
964
|
-
let trimMessage
|
|
971
|
+
let wrappedMsg, trimMessage
|
|
965
972
|
if (HTTP_ERROR_CODE_RX.test(err.code)) {
|
|
966
973
|
switch (err.data.statusCode) {
|
|
967
974
|
case 401:
|
|
@@ -992,6 +999,10 @@ function transformGitCloneError (err, displayUrl) {
|
|
|
992
999
|
return wrappedErr
|
|
993
1000
|
}
|
|
994
1001
|
|
|
1002
|
+
function splitRefPatterns (str) {
|
|
1003
|
+
return ~str.indexOf('{') ? str.split(VENTILATED_CSV_RX) : str.split(CSV_RX)
|
|
1004
|
+
}
|
|
1005
|
+
|
|
995
1006
|
function coerceToString (value) {
|
|
996
1007
|
return value == null ? '' : String(value)
|
|
997
1008
|
}
|
|
@@ -1004,11 +1015,11 @@ function findWorktrees (repo, patterns) {
|
|
|
1004
1015
|
if (!patterns.length) return new Map()
|
|
1005
1016
|
const linkedOnly = patterns[0] === '.' ? !(patterns = patterns.slice(1)) : true
|
|
1006
1017
|
let worktreesDir
|
|
1018
|
+
const patternCache = repo.cache[REF_PATTERN_CACHE_KEY]
|
|
1007
1019
|
return (patterns.length
|
|
1008
1020
|
? fsp
|
|
1009
1021
|
.readdir((worktreesDir = ospath.join(repo.dir, '.git', 'worktrees')))
|
|
1010
|
-
.
|
|
1011
|
-
.then((worktreeNames) => matcher(worktreeNames, [...patterns]))
|
|
1022
|
+
.then((worktreeNames) => filterRefs(worktreeNames, [...patterns], patternCache), invariably.emptyArray)
|
|
1012
1023
|
.then((worktreeNames) =>
|
|
1013
1024
|
worktreeNames.length
|
|
1014
1025
|
? Promise.all(
|
package/lib/constants.js
CHANGED
|
@@ -2,24 +2,25 @@
|
|
|
2
2
|
|
|
3
3
|
module.exports = Object.freeze({
|
|
4
4
|
COMPONENT_DESC_FILENAME: 'antora.yml',
|
|
5
|
-
CONTENT_CACHE_FOLDER: 'content
|
|
6
|
-
|
|
5
|
+
CONTENT_CACHE_FOLDER: 'content',
|
|
6
|
+
CONTENT_SRC_GLOB: '**/*[!~]',
|
|
7
|
+
CONTENT_SRC_OPTS: { follow: true, nomount: true, nosort: true, nounique: true, removeBOM: false, uniqueBy: (m) => m },
|
|
7
8
|
FILE_MODES: { 100644: 0o100666 & ~process.umask(), 100755: 0o100777 & ~process.umask() },
|
|
8
9
|
GIT_CORE: 'antora',
|
|
9
10
|
GIT_OPERATION_LABEL_LENGTH: 8,
|
|
10
11
|
GIT_PROGRESS_PHASES: ['Counting objects', 'Compressing objects', 'Receiving objects', 'Resolving deltas'],
|
|
11
12
|
PICOMATCH_VERSION_OPTS: {
|
|
12
13
|
bash: true,
|
|
13
|
-
debug: false,
|
|
14
14
|
dot: true,
|
|
15
15
|
fastpaths: false,
|
|
16
|
-
|
|
17
|
-
noextglob: true,
|
|
16
|
+
nobracket: true,
|
|
18
17
|
noglobstar: true,
|
|
19
18
|
nonegate: true,
|
|
20
19
|
noquantifiers: true,
|
|
20
|
+
regex: false,
|
|
21
21
|
strictSlashes: true,
|
|
22
22
|
},
|
|
23
|
+
REF_PATTERN_CACHE_KEY: Symbol('RefPatternCache'),
|
|
23
24
|
SYMLINK_FILE_MODE: '120000',
|
|
24
25
|
VALID_STATE_FILENAME: 'valid',
|
|
25
26
|
})
|
|
File without changes
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { compile: bracesToGroup } = require('braces')
|
|
4
|
+
const { makeRe: makePicomatchRx } = require('picomatch')
|
|
5
|
+
|
|
6
|
+
function getPicomatchOpts (cache) {
|
|
7
|
+
return {
|
|
8
|
+
bash: true,
|
|
9
|
+
dot: true,
|
|
10
|
+
expandRange: (begin, end, step, opts) => {
|
|
11
|
+
const pattern = opts ? `{${begin}..${end}..${step}}` : `{${begin}..${end}}`
|
|
12
|
+
return cache.braces.get(pattern) || cache.braces.set(pattern, bracesToGroup(pattern)).get(pattern)
|
|
13
|
+
},
|
|
14
|
+
fastpaths: false,
|
|
15
|
+
nobracket: true,
|
|
16
|
+
noglobstar: true,
|
|
17
|
+
noquantifiers: true,
|
|
18
|
+
regex: false,
|
|
19
|
+
strictSlashes: true,
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function compileRx (pattern, opts) {
|
|
24
|
+
if (pattern === '*' || pattern === '**') return { test: () => true }
|
|
25
|
+
return pattern.charAt() === '!' // do our own negate
|
|
26
|
+
? Object.defineProperty(makePicomatchRx(pattern.substr(1), opts), 'negated', { value: true })
|
|
27
|
+
: makePicomatchRx(pattern, opts)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function createMatcher (patterns, cache) {
|
|
31
|
+
let opts
|
|
32
|
+
const rxs = patterns.map(
|
|
33
|
+
(pattern) =>
|
|
34
|
+
cache.get(pattern) ||
|
|
35
|
+
cache.set(pattern, compileRx(pattern, opts || (opts = getPicomatchOpts(cache)))).get(pattern)
|
|
36
|
+
)
|
|
37
|
+
return (candidate) => {
|
|
38
|
+
let first = true
|
|
39
|
+
let matched
|
|
40
|
+
for (const rx of rxs) {
|
|
41
|
+
if (matched) {
|
|
42
|
+
if (rx.negated && rx.test(candidate)) return
|
|
43
|
+
} else if (first || !rx.negated) {
|
|
44
|
+
matched = rx.test(candidate)
|
|
45
|
+
}
|
|
46
|
+
first = false
|
|
47
|
+
}
|
|
48
|
+
return matched
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function filterRefs (candidates, patterns, cache = Object.assign(new Map(), { braces: new Map() })) {
|
|
53
|
+
const isMatch = createMatcher(patterns, cache)
|
|
54
|
+
return candidates.reduce((accum, candidate) => {
|
|
55
|
+
if (isMatch(candidate)) accum.push(candidate)
|
|
56
|
+
return accum
|
|
57
|
+
}, [])
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
module.exports = filterRefs
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const { homedir } = require('os')
|
|
4
4
|
const expandPath = require('@antora/expand-path-helper')
|
|
5
|
+
const invariably = { void: () => undefined }
|
|
5
6
|
const { promises: fsp } = require('fs')
|
|
6
7
|
const ospath = require('path')
|
|
7
8
|
|
|
@@ -9,12 +10,10 @@ class GitCredentialManagerStore {
|
|
|
9
10
|
configure ({ config, startDir }) {
|
|
10
11
|
this.entries = undefined
|
|
11
12
|
this.urls = {}
|
|
12
|
-
if ((this.contents = (config = config || {}).contents)) {
|
|
13
|
+
if ((this.contents = (config = config || {}).contents) || !config.path) {
|
|
13
14
|
this.path = undefined
|
|
14
|
-
} else if (config.path) {
|
|
15
|
-
this.path = expandPath(config.path, '~+', startDir)
|
|
16
15
|
} else {
|
|
17
|
-
this.path =
|
|
16
|
+
this.path = expandPath(config.path, { dot: startDir })
|
|
18
17
|
}
|
|
19
18
|
return this
|
|
20
19
|
}
|
|
@@ -28,10 +27,7 @@ class GitCredentialManagerStore {
|
|
|
28
27
|
contentsPromise = Promise.resolve(this.contents)
|
|
29
28
|
delimiter = /[,\n]/
|
|
30
29
|
} else if (this.path) {
|
|
31
|
-
contentsPromise = fsp
|
|
32
|
-
.access(this.path)
|
|
33
|
-
.then(() => fsp.readFile(this.path, 'utf8'))
|
|
34
|
-
.catch(() => undefined)
|
|
30
|
+
contentsPromise = fsp.access(this.path).then(() => fsp.readFile(this.path, 'utf8'), invariably.void)
|
|
35
31
|
} else {
|
|
36
32
|
const homeGitCredentialsPath = ospath.join(homedir(), '.git-credentials')
|
|
37
33
|
const xdgConfigGitCredentialsPath = ospath.join(
|
|
@@ -45,8 +41,7 @@ class GitCredentialManagerStore {
|
|
|
45
41
|
.catch(() =>
|
|
46
42
|
fsp
|
|
47
43
|
.access(xdgConfigGitCredentialsPath)
|
|
48
|
-
.then(() => fsp.readFile(xdgConfigGitCredentialsPath, 'utf8'))
|
|
49
|
-
.catch(() => undefined)
|
|
44
|
+
.then(() => fsp.readFile(xdgConfigGitCredentialsPath, 'utf8'), invariably.void)
|
|
50
45
|
)
|
|
51
46
|
}
|
|
52
47
|
contentsPromise.then((contents) => {
|
package/lib/git-plugin-http.js
CHANGED
|
@@ -26,7 +26,7 @@ async function mergeBuffers (data) {
|
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
module.exports = ({ httpProxy, httpsProxy, noProxy }, userAgent) => {
|
|
29
|
-
if (httpsProxy || httpProxy) {
|
|
29
|
+
if ((httpsProxy || httpProxy) && noProxy !== '*') {
|
|
30
30
|
const { HttpProxyAgent, HttpsProxyAgent } = require('hpagent')
|
|
31
31
|
const shouldProxy = require('should-proxy')
|
|
32
32
|
return {
|
|
@@ -34,29 +34,23 @@ module.exports = ({ httpProxy, httpsProxy, noProxy }, userAgent) => {
|
|
|
34
34
|
headers['user-agent'] = userAgent
|
|
35
35
|
body = await mergeBuffers(body)
|
|
36
36
|
const proxy = url.startsWith('https:')
|
|
37
|
-
? {
|
|
38
|
-
: {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
// see https://github.com/delvedor/hpagent/issues/18
|
|
42
|
-
const { protocol, hostname, port, username, password } = new URL(proxy.url)
|
|
43
|
-
const proxyUrl = { protocol, hostname, port, username: username || null, password: password || null }
|
|
44
|
-
agent = new proxy.ProxyAgent({ proxy: proxyUrl })
|
|
45
|
-
}
|
|
37
|
+
? { Agent: HttpsProxyAgent, url: httpsProxy }
|
|
38
|
+
: { Agent: HttpProxyAgent, url: httpProxy }
|
|
39
|
+
const agent =
|
|
40
|
+
proxy.url && shouldProxy(url, { no_proxy: noProxy }) ? new proxy.Agent({ proxy: proxy.url }) : undefined
|
|
46
41
|
return new Promise((resolve, reject) =>
|
|
47
42
|
get({ url, method, agent, headers, body }, (err, res) => (err ? reject(err) : resolve(distillResponse(res))))
|
|
48
43
|
)
|
|
49
44
|
},
|
|
50
45
|
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
}
|
|
46
|
+
}
|
|
47
|
+
return {
|
|
48
|
+
async request ({ url, method, headers, body }) {
|
|
49
|
+
headers['user-agent'] = userAgent
|
|
50
|
+
body = await mergeBuffers(body)
|
|
51
|
+
return new Promise((resolve, reject) =>
|
|
52
|
+
get({ url, method, headers, body }, (err, res) => (err ? reject(err) : resolve(distillResponse(res))))
|
|
53
|
+
)
|
|
54
|
+
},
|
|
61
55
|
}
|
|
62
56
|
}
|
package/lib/git.js
ADDED
package/lib/index.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*
|
|
6
6
|
* Responsible for aggregating the content from multiple repositories and
|
|
7
7
|
* references into a raw aggregate of virtual files that can be organized by a
|
|
8
|
-
* subsequent step in the
|
|
8
|
+
* subsequent step in the generator.
|
|
9
9
|
*
|
|
10
10
|
* @namespace content-aggregator
|
|
11
11
|
*/
|
|
@@ -1,26 +1,33 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const { expand: expandBraces } = require('braces')
|
|
3
|
+
const { expand: expandBraces, compile: bracesToGroup } = require('braces')
|
|
4
4
|
const flattenDeep = require('./flatten-deep')
|
|
5
5
|
const { promises: fsp } = require('fs')
|
|
6
|
-
const git = require('
|
|
6
|
+
const git = require('./git')
|
|
7
7
|
const invariably = { true: () => true, false: () => false, void: () => undefined, emptyArray: () => [] }
|
|
8
8
|
const { makeRe: makePicomatchRx } = require('picomatch')
|
|
9
9
|
|
|
10
|
-
const
|
|
11
|
-
const RX_MAGIC_DETECTOR = /[*{]/
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
10
|
+
const NON_GLOB_SPECIAL_CHARS_RX = /[.+?^${}()|[\]\\]/g
|
|
11
|
+
const RX_MAGIC_DETECTOR = /[*{(]/
|
|
12
|
+
const PICOMATCH_OPTS = {
|
|
13
|
+
bash: true,
|
|
14
|
+
expandRange: (begin, end, step, opts) => bracesToGroup(opts ? `{${begin}..${end}..${step}}` : `{${begin}..${end}}`),
|
|
15
|
+
fastpaths: false,
|
|
16
|
+
nobracket: true,
|
|
17
|
+
noglobstar: true,
|
|
18
|
+
nonegate: true,
|
|
19
|
+
noquantifiers: true,
|
|
20
|
+
regex: false,
|
|
21
|
+
strictSlashes: true,
|
|
22
|
+
}
|
|
15
23
|
|
|
16
24
|
function resolvePathGlobs (base, patterns, listDirents, retrievePath, tree = { path: '' }) {
|
|
17
25
|
return patterns.reduce((paths, pattern) => {
|
|
18
26
|
if (pattern.charAt() === '!') {
|
|
19
27
|
return paths.then((resolvedPaths) => {
|
|
20
28
|
if (resolvedPaths.length) {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
return resolvedPaths.filter(rx.test.bind(rx))
|
|
29
|
+
const rx = makePicomatchRx(pattern.substr(1), PICOMATCH_OPTS)
|
|
30
|
+
return resolvedPaths.filter((it) => !rx.test(it))
|
|
24
31
|
} else {
|
|
25
32
|
return resolvedPaths
|
|
26
33
|
}
|
|
@@ -38,14 +45,13 @@ async function glob (base, patternSegments, listDirents, retrievePath, { oid, pa
|
|
|
38
45
|
let patternSegment = patternSegments[0]
|
|
39
46
|
patternSegments = patternSegments.slice(1)
|
|
40
47
|
if (RX_MAGIC_DETECTOR.test(patternSegment)) {
|
|
41
|
-
let isMatch
|
|
42
|
-
let explicit
|
|
48
|
+
let isMatch, explicit
|
|
43
49
|
if (patternSegment === '*') {
|
|
44
50
|
isMatch = (it) => it.charAt() !== '.'
|
|
51
|
+
} else if (~patternSegment.indexOf('(')) {
|
|
52
|
+
isMatch = (isMatch = makePicomatchRx(patternSegment, PICOMATCH_OPTS)).test.bind(isMatch)
|
|
45
53
|
} else if (~patternSegment.indexOf('{')) {
|
|
46
54
|
if (globbed) {
|
|
47
|
-
if (patternSegment.charAt() === '!') patternSegment = '\\' + patternSegment
|
|
48
|
-
if (~patternSegment.indexOf('?')) patternSegment = patternSegment.replace(RX_QUESTION_MARK, '\\?')
|
|
49
55
|
isMatch = (isMatch = makePicomatchRx(patternSegment, PICOMATCH_OPTS)).test.bind(isMatch)
|
|
50
56
|
} else if (~patternSegment.indexOf('*')) {
|
|
51
57
|
const [wildPatterns, literals] = expandBraces(patternSegment).reduce(
|
|
@@ -90,22 +96,15 @@ async function glob (base, patternSegments, listDirents, retrievePath, { oid, pa
|
|
|
90
96
|
})
|
|
91
97
|
} else if ((patternSegment += '/' + patternSegments.join('/')).indexOf('{')) {
|
|
92
98
|
return expandBraces(patternSegment).map((it) => joinPath(path, it))
|
|
93
|
-
} else {
|
|
94
|
-
return [joinPath(path, patternSegment)]
|
|
95
99
|
}
|
|
100
|
+
return [joinPath(path, patternSegment)]
|
|
96
101
|
} else if (globbed) {
|
|
97
102
|
return (await retrievePath(base, { oid, path }, patternSegment)) ? [joinPath(path, patternSegment)] : []
|
|
98
|
-
} else {
|
|
99
|
-
return [joinPath(path, patternSegment)]
|
|
100
103
|
}
|
|
104
|
+
return [joinPath(path, patternSegment)]
|
|
101
105
|
}
|
|
102
106
|
}
|
|
103
107
|
|
|
104
|
-
function regexpEscapeWithGlob (str) {
|
|
105
|
-
// we don't escape "-" since it's meaningless in a literal
|
|
106
|
-
return str.replace(RX_ESCAPE_EXCEPT_GLOB, '\\$&').replace('*', '.*')
|
|
107
|
-
}
|
|
108
|
-
|
|
109
108
|
function extractMagicBase (patternSegments, base) {
|
|
110
109
|
let nextSegment
|
|
111
110
|
if (patternSegments.length) {
|
|
@@ -124,10 +123,11 @@ function listDirentsFs (base, path) {
|
|
|
124
123
|
function listDirentsGit (repo, treeOid) {
|
|
125
124
|
return git
|
|
126
125
|
.readTree(Object.assign({ oid: treeOid, filepath: '' }, repo))
|
|
127
|
-
.then(
|
|
128
|
-
|
|
126
|
+
.then(
|
|
127
|
+
({ tree: entries }) =>
|
|
128
|
+
entries.map(({ type, oid, path: name }) => ({ name, oid, isDirectory: invariably[type === 'tree'] })),
|
|
129
|
+
invariably.emptyArray
|
|
129
130
|
)
|
|
130
|
-
.catch(invariably.emptyArray)
|
|
131
131
|
}
|
|
132
132
|
|
|
133
133
|
function makeAlternationMatcherRx (patterns) {
|
|
@@ -139,7 +139,13 @@ function makeMatcherRx (pattern) {
|
|
|
139
139
|
}
|
|
140
140
|
|
|
141
141
|
function patternToRx (pattern) {
|
|
142
|
-
return (
|
|
142
|
+
return (
|
|
143
|
+
(pattern.charAt() === '.' ? '' : '(?!\\.)') +
|
|
144
|
+
pattern
|
|
145
|
+
.replace(NON_GLOB_SPECIAL_CHARS_RX, '\\$&')
|
|
146
|
+
.replace('\\\\*', '\\x2a')
|
|
147
|
+
.replace('*', '.*?')
|
|
148
|
+
)
|
|
143
149
|
}
|
|
144
150
|
|
|
145
151
|
function readdirWithFileTypes (dir) {
|
|
@@ -147,10 +153,7 @@ function readdirWithFileTypes (dir) {
|
|
|
147
153
|
}
|
|
148
154
|
|
|
149
155
|
function retrievePathFs (base, { path }, subpath) {
|
|
150
|
-
return fsp
|
|
151
|
-
.access(base + '/' + joinPath(path, subpath))
|
|
152
|
-
.then(invariably.true)
|
|
153
|
-
.catch(invariably.false)
|
|
156
|
+
return fsp.access(base + '/' + joinPath(path, subpath)).then(invariably.true, invariably.false)
|
|
154
157
|
}
|
|
155
158
|
|
|
156
159
|
function retrievePathGit (repo, { oid }, filepath) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@antora/content-aggregator",
|
|
3
|
-
"version": "3.0.0-
|
|
3
|
+
"version": "3.0.0-beta.2",
|
|
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)",
|
|
@@ -17,14 +17,14 @@
|
|
|
17
17
|
},
|
|
18
18
|
"main": "lib/index.js",
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@antora/expand-path-helper": "~
|
|
20
|
+
"@antora/expand-path-helper": "~2.0",
|
|
21
|
+
"@antora/user-require-helper": "~2.0",
|
|
21
22
|
"braces": "~3.0",
|
|
22
23
|
"cache-directory": "~2.0",
|
|
23
|
-
"camelcase-keys": "~
|
|
24
|
+
"camelcase-keys": "~7.0",
|
|
24
25
|
"hpagent": "~0.1.0",
|
|
25
|
-
"isomorphic-git": "~1.
|
|
26
|
+
"isomorphic-git": "~1.10",
|
|
26
27
|
"js-yaml": "~4.1",
|
|
27
|
-
"matcher": "~4.0",
|
|
28
28
|
"multi-progress": "~4.0",
|
|
29
29
|
"picomatch": "~2.3",
|
|
30
30
|
"progress": "~2.0",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"vinyl-fs": "~3.0"
|
|
35
35
|
},
|
|
36
36
|
"engines": {
|
|
37
|
-
"node": ">=
|
|
37
|
+
"node": ">=12.21.0"
|
|
38
38
|
},
|
|
39
39
|
"files": [
|
|
40
40
|
"lib/"
|
|
@@ -49,5 +49,5 @@
|
|
|
49
49
|
"static site",
|
|
50
50
|
"web publishing"
|
|
51
51
|
],
|
|
52
|
-
"gitHead": "
|
|
52
|
+
"gitHead": "5cd3f9cc70622e465cb44daf1aa2035ed5a35f54"
|
|
53
53
|
}
|