@antora/content-aggregator 3.0.0-alpha.8 → 3.0.0-beta.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 +243 -257
- package/lib/constants.js +3 -14
- package/lib/filter-refs.js +42 -0
- package/lib/git-credential-manager-store.js +5 -10
- package/lib/git-plugin-http.js +1 -1
- package/lib/index.js +1 -1
- package/lib/invariably.js +3 -0
- package/lib/matcher.js +32 -0
- package/lib/resolve-path-globs.js +26 -35
- package/package.json +6 -8
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
|
@@ -7,6 +7,7 @@ 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
|
|
@@ -14,18 +15,17 @@ const getCacheDir = require('cache-directory')
|
|
|
14
15
|
const GitCredentialManagerStore = require('./git-credential-manager-store')
|
|
15
16
|
const git = require('./git')
|
|
16
17
|
const { NotFoundError, ObjectTypeError, UnknownTransportError, UrlParseError } = git.Errors
|
|
17
|
-
const
|
|
18
|
-
const
|
|
19
|
-
const
|
|
18
|
+
const globStream = require('glob-stream')
|
|
19
|
+
const invariably = require('./invariably')
|
|
20
|
+
const { makeMatcherRx, versionMatcherOpts: VERSION_MATCHER_OPTS } = require('./matcher')
|
|
20
21
|
const MultiProgress = require('multi-progress')
|
|
21
22
|
const ospath = require('path')
|
|
22
23
|
const { posix: path } = ospath
|
|
23
24
|
const posixify = ospath.sep === '\\' ? (p) => p.replace(/\\/g, '/') : undefined
|
|
24
25
|
const { fs: resolvePathGlobsFs, git: resolvePathGlobsGit } = require('./resolve-path-globs')
|
|
25
|
-
const { Transform } = require('stream')
|
|
26
|
-
const map = (transform
|
|
26
|
+
const { pipeline, Transform } = require('stream')
|
|
27
|
+
const map = (transform) => new Transform({ objectMode: true, transform })
|
|
27
28
|
const userRequire = require('@antora/user-require-helper')
|
|
28
|
-
const vfs = require('vinyl-fs')
|
|
29
29
|
const yaml = require('js-yaml')
|
|
30
30
|
|
|
31
31
|
const {
|
|
@@ -37,11 +37,10 @@ const {
|
|
|
37
37
|
GIT_CORE,
|
|
38
38
|
GIT_OPERATION_LABEL_LENGTH,
|
|
39
39
|
GIT_PROGRESS_PHASES,
|
|
40
|
-
|
|
40
|
+
REF_PATTERN_CACHE_KEY,
|
|
41
41
|
SYMLINK_FILE_MODE,
|
|
42
42
|
VALID_STATE_FILENAME,
|
|
43
43
|
} = require('./constants')
|
|
44
|
-
|
|
45
44
|
const ANY_SEPARATOR_RX = /[:/]/
|
|
46
45
|
const CSV_RX = /\s*,\s*/
|
|
47
46
|
const VENTILATED_CSV_RX = /\s*,\s+/
|
|
@@ -89,48 +88,70 @@ const URL_PORT_CLEANER_RX = /^([^/]+):[0-9]+(?=\/)/
|
|
|
89
88
|
function aggregateContent (playbook) {
|
|
90
89
|
const startDir = playbook.dir || '.'
|
|
91
90
|
const { branches, editUrl, tags, sources } = playbook.content
|
|
92
|
-
const {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
)
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
new Map()
|
|
102
|
-
)
|
|
91
|
+
const sourceDefaults = { branches, editUrl, tags }
|
|
92
|
+
const { cacheDir: requestedCacheDir, fetch, quiet } = playbook.runtime
|
|
93
|
+
return ensureCacheDir(requestedCacheDir, startDir).then((cacheDir) => {
|
|
94
|
+
const gitConfig = Object.assign({ ensureGitSuffix: true }, playbook.git)
|
|
95
|
+
const gitPlugins = loadGitPlugins(gitConfig, playbook.network || {}, startDir)
|
|
96
|
+
const fetchConcurrency = Math.max(gitConfig.fetchConcurrency || Infinity, 1)
|
|
97
|
+
const sourcesByUrl = sources.reduce((accum, source) => {
|
|
98
|
+
return accum.set(source.url, [...(accum.get(source.url) || []), Object.assign({}, sourceDefaults, source)])
|
|
99
|
+
}, new Map())
|
|
103
100
|
const progress = !quiet && createProgress(sourcesByUrl.keys(), process.stdout)
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
progress,
|
|
111
|
-
fetch,
|
|
112
|
-
startDir,
|
|
113
|
-
}).then(({ repo, authStatus }) =>
|
|
114
|
-
Promise.all(
|
|
115
|
-
sources.map((source) => {
|
|
116
|
-
source = Object.assign({ branches, editUrl, tags }, source)
|
|
117
|
-
// NOTE if repository is managed (has a url property), we can assume the remote name is origin
|
|
118
|
-
// TODO if the repo has no remotes, then remoteName should be undefined
|
|
119
|
-
const remoteName = repo.url ? 'origin' : source.remote || 'origin'
|
|
120
|
-
return collectFilesFromSource(source, repo, remoteName, authStatus)
|
|
121
|
-
})
|
|
122
|
-
)
|
|
123
|
-
)
|
|
124
|
-
)
|
|
125
|
-
)
|
|
126
|
-
.then(buildAggregate)
|
|
127
|
-
.catch((err) => {
|
|
128
|
-
progress && progress.terminate()
|
|
129
|
-
throw err
|
|
130
|
-
})
|
|
101
|
+
const refPatternCache = Object.assign(new Map(), { braces: new Map() })
|
|
102
|
+
const loadOpts = { cacheDir, fetch, gitPlugins, progress, startDir, refPatternCache }
|
|
103
|
+
return collectFiles(sourcesByUrl, loadOpts, fetchConcurrency).then(buildAggregate, (err) => {
|
|
104
|
+
progress && progress.terminate()
|
|
105
|
+
throw err
|
|
106
|
+
})
|
|
131
107
|
})
|
|
132
108
|
}
|
|
133
109
|
|
|
110
|
+
async function collectFiles (sourcesByUrl, loadOpts, concurrency) {
|
|
111
|
+
const tasks = [...sourcesByUrl.entries()].map(([url, sources]) => [
|
|
112
|
+
() => loadRepository(url, Object.assign({ fetchTags: tagsSpecified(sources) }, loadOpts)),
|
|
113
|
+
({ repo, authStatus }) =>
|
|
114
|
+
Promise.all(
|
|
115
|
+
sources.map((source) => {
|
|
116
|
+
// NOTE if repository is managed (has a url property), we can assume the remote name is origin
|
|
117
|
+
// TODO if the repo has no remotes, then remoteName should be undefined
|
|
118
|
+
const remoteName = repo.url ? 'origin' : source.remote || 'origin'
|
|
119
|
+
return collectFilesFromSource(source, repo, remoteName, authStatus)
|
|
120
|
+
})
|
|
121
|
+
),
|
|
122
|
+
])
|
|
123
|
+
let rejection, started
|
|
124
|
+
const startedContinuations = []
|
|
125
|
+
const recordRejection = (err) => {
|
|
126
|
+
rejection = err
|
|
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
|
|
143
|
+
}
|
|
144
|
+
} else {
|
|
145
|
+
started = tasks.map(([primary, continuation], idx) => runTask(primary, continuation, idx))
|
|
146
|
+
}
|
|
147
|
+
return Promise.all(started).then(() =>
|
|
148
|
+
Promise.all(startedContinuations).then((result) => {
|
|
149
|
+
if (rejection) throw rejection
|
|
150
|
+
return result
|
|
151
|
+
})
|
|
152
|
+
)
|
|
153
|
+
}
|
|
154
|
+
|
|
134
155
|
function buildAggregate (componentVersionBuckets) {
|
|
135
156
|
return [
|
|
136
157
|
...flattenDeep(componentVersionBuckets)
|
|
@@ -148,17 +169,15 @@ function buildAggregate (componentVersionBuckets) {
|
|
|
148
169
|
}
|
|
149
170
|
|
|
150
171
|
async function loadRepository (url, opts) {
|
|
151
|
-
let dir
|
|
152
|
-
|
|
153
|
-
let authStatus
|
|
172
|
+
let authStatus, dir, repo
|
|
173
|
+
const cache = { [REF_PATTERN_CACHE_KEY]: opts.refPatternCache }
|
|
154
174
|
if (~url.indexOf(':') && GIT_URI_DETECTOR_RX.test(url)) {
|
|
155
|
-
let displayUrl
|
|
156
|
-
let credentials
|
|
175
|
+
let credentials, displayUrl
|
|
157
176
|
;({ displayUrl, url, credentials } = extractCredentials(url))
|
|
158
177
|
const { cacheDir, fetch, fetchTags, gitPlugins, progress } = opts
|
|
159
178
|
dir = ospath.join(cacheDir, generateCloneFolderName(displayUrl))
|
|
160
179
|
// NOTE the presence of the url property on the repo object implies the repository is remote
|
|
161
|
-
repo = { cache
|
|
180
|
+
repo = { cache, dir, fs, gitdir: dir, noCheckout: true, url }
|
|
162
181
|
const validStateFile = ospath.join(dir, VALID_STATE_FILENAME)
|
|
163
182
|
try {
|
|
164
183
|
await fsp.access(validStateFile)
|
|
@@ -183,7 +202,7 @@ async function loadRepository (url, opts) {
|
|
|
183
202
|
authStatus = await git.getConfig(Object.assign({ path: 'remote.origin.private' }, repo))
|
|
184
203
|
}
|
|
185
204
|
} catch (gitErr) {
|
|
186
|
-
await rmdir(dir)
|
|
205
|
+
await fsp['rm' in fsp ? 'rm' : 'rmdir'](dir, { recursive: true, force: true })
|
|
187
206
|
if (gitErr.rethrow) throw transformGitCloneError(gitErr, displayUrl)
|
|
188
207
|
const fetchOpts = buildFetchOptions(repo, progress, displayUrl, credentials, gitPlugins, fetchTags, 'clone')
|
|
189
208
|
await git
|
|
@@ -194,7 +213,7 @@ async function loadRepository (url, opts) {
|
|
|
194
213
|
authStatus = credentials ? 'auth-embedded' : credentialManager.status({ url }) ? 'auth-required' : undefined
|
|
195
214
|
return git.setConfig(Object.assign({ path: 'remote.origin.private', value: authStatus }, repo))
|
|
196
215
|
})
|
|
197
|
-
.catch(
|
|
216
|
+
.catch((cloneErr) => {
|
|
198
217
|
// FIXME triggering the error handler here causes assertion problems in the test suite
|
|
199
218
|
//if (fetchOpts.onProgress) fetchOpts.onProgress.finish(cloneErr)
|
|
200
219
|
throw transformGitCloneError(cloneErr, displayUrl)
|
|
@@ -204,9 +223,7 @@ async function loadRepository (url, opts) {
|
|
|
204
223
|
}
|
|
205
224
|
} else if (await isDirectory((dir = expandPath(url, { dot: opts.startDir })))) {
|
|
206
225
|
const gitdir = ospath.join(dir, '.git')
|
|
207
|
-
repo = (await isDirectory(gitdir))
|
|
208
|
-
? { cache: {}, dir, fs, gitdir }
|
|
209
|
-
: { cache: {}, dir, fs, gitdir: dir, noCheckout: true }
|
|
226
|
+
repo = (await isDirectory(gitdir)) ? { cache, dir, fs, gitdir } : { cache, dir, fs, gitdir: dir, noCheckout: true }
|
|
210
227
|
try {
|
|
211
228
|
await git.resolveRef(Object.assign({ ref: 'HEAD', depth: 1 }, repo))
|
|
212
229
|
} catch {
|
|
@@ -250,99 +267,102 @@ async function collectFilesFromSource (source, repo, remoteName, authStatus) {
|
|
|
250
267
|
async function selectReferences (source, repo, remote) {
|
|
251
268
|
let { branches: branchPatterns, tags: tagPatterns, worktrees: worktreePatterns = '.' } = source
|
|
252
269
|
const isBare = repo.noCheckout
|
|
270
|
+
const patternCache = repo.cache[REF_PATTERN_CACHE_KEY]
|
|
253
271
|
const noWorktree = repo.url ? undefined : null
|
|
254
272
|
const refs = new Map()
|
|
255
|
-
if (
|
|
256
|
-
tagPatterns
|
|
273
|
+
if (
|
|
274
|
+
tagPatterns &&
|
|
275
|
+
(tagPatterns = Array.isArray(tagPatterns)
|
|
257
276
|
? tagPatterns.map((pattern) => String(pattern))
|
|
258
|
-
: String(tagPatterns).
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
277
|
+
: splitRefPatterns(String(tagPatterns))).length
|
|
278
|
+
) {
|
|
279
|
+
const tags = await git.listTags(repo)
|
|
280
|
+
if (tags.length) {
|
|
281
|
+
for (const shortname of filterRefs(tags, tagPatterns, patternCache)) {
|
|
262
282
|
// NOTE tags are stored using symbol keys to distinguish them from branches
|
|
263
283
|
refs.set(Symbol(shortname), { shortname, fullname: 'tags/' + shortname, type: 'tag', head: noWorktree })
|
|
264
284
|
}
|
|
265
285
|
}
|
|
266
286
|
}
|
|
267
|
-
if (branchPatterns)
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
287
|
+
if (!branchPatterns) return [...refs.values()]
|
|
288
|
+
if (worktreePatterns) {
|
|
289
|
+
if (worktreePatterns === '.') {
|
|
290
|
+
worktreePatterns = ['.']
|
|
291
|
+
} else if (worktreePatterns === true) {
|
|
292
|
+
worktreePatterns = ['.', '*']
|
|
293
|
+
} else {
|
|
294
|
+
worktreePatterns = Array.isArray(worktreePatterns)
|
|
295
|
+
? worktreePatterns.map((pattern) => String(pattern))
|
|
296
|
+
: splitRefPatterns(String(worktreePatterns))
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
const branchPatternsString = String(branchPatterns)
|
|
300
|
+
if (branchPatternsString === 'HEAD' || branchPatternsString === '.') {
|
|
301
|
+
const currentBranch = await getCurrentBranchName(repo, remote)
|
|
302
|
+
if (currentBranch) {
|
|
303
|
+
branchPatterns = [currentBranch]
|
|
304
|
+
} else if (isBare) {
|
|
305
|
+
return [...refs.values()]
|
|
306
|
+
} else {
|
|
307
|
+
// NOTE current branch is undefined when HEAD is detached
|
|
308
|
+
const head = worktreePatterns[0] === '.' ? repo.dir : noWorktree
|
|
309
|
+
refs.set('HEAD', { shortname: 'HEAD', fullname: 'HEAD', type: 'branch', detached: true, head })
|
|
310
|
+
return [...refs.values()]
|
|
278
311
|
}
|
|
279
|
-
|
|
280
|
-
|
|
312
|
+
} else if (
|
|
313
|
+
(branchPatterns = Array.isArray(branchPatterns)
|
|
314
|
+
? branchPatterns.map((pattern) => String(pattern))
|
|
315
|
+
: splitRefPatterns(branchPatternsString)).length
|
|
316
|
+
) {
|
|
317
|
+
let headBranchIdx
|
|
318
|
+
// NOTE we can assume at least two entries if HEAD or . are present
|
|
319
|
+
if (~(headBranchIdx = branchPatterns.indexOf('HEAD')) || ~(headBranchIdx = branchPatterns.indexOf('.'))) {
|
|
281
320
|
const currentBranch = await getCurrentBranchName(repo, remote)
|
|
282
321
|
if (currentBranch) {
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
if (!isBare) {
|
|
286
|
-
// NOTE current branch is undefined when HEAD is detached
|
|
287
|
-
const head = worktreePatterns[0] === '.' ? repo.dir : noWorktree
|
|
288
|
-
refs.set('HEAD', { shortname: 'HEAD', fullname: 'HEAD', type: 'branch', detached: true, head })
|
|
289
|
-
}
|
|
290
|
-
return [...refs.values()]
|
|
291
|
-
}
|
|
292
|
-
} else if (
|
|
293
|
-
(branchPatterns = Array.isArray(branchPatterns)
|
|
294
|
-
? branchPatterns.map((pattern) => String(pattern))
|
|
295
|
-
: branchPatternsString.split(CSV_RX)).length
|
|
296
|
-
) {
|
|
297
|
-
let headBranchIdx
|
|
298
|
-
// NOTE we can assume at least two entries if HEAD or . are present
|
|
299
|
-
if (~(headBranchIdx = branchPatterns.indexOf('HEAD')) || ~(headBranchIdx = branchPatterns.indexOf('.'))) {
|
|
300
|
-
const currentBranch = await getCurrentBranchName(repo, remote)
|
|
301
|
-
if (currentBranch) {
|
|
302
|
-
// NOTE ignore if current branch is already in list
|
|
303
|
-
if (~branchPatterns.indexOf(currentBranch)) {
|
|
304
|
-
branchPatterns.splice(headBranchIdx, 1)
|
|
305
|
-
} else {
|
|
306
|
-
branchPatterns[headBranchIdx] = currentBranch
|
|
307
|
-
}
|
|
308
|
-
} else {
|
|
309
|
-
if (!isBare) {
|
|
310
|
-
let head = noWorktree
|
|
311
|
-
if (worktreePatterns[0] === '.') {
|
|
312
|
-
worktreePatterns = worktreePatterns.slice(1)
|
|
313
|
-
head = repo.dir
|
|
314
|
-
}
|
|
315
|
-
// NOTE current branch is undefined when HEAD is detached
|
|
316
|
-
refs.set('HEAD', { shortname: 'HEAD', fullname: 'HEAD', type: 'branch', detached: true, head })
|
|
317
|
-
}
|
|
322
|
+
// NOTE ignore if current branch is already in list
|
|
323
|
+
if (~branchPatterns.indexOf(currentBranch)) {
|
|
318
324
|
branchPatterns.splice(headBranchIdx, 1)
|
|
325
|
+
} else {
|
|
326
|
+
branchPatterns[headBranchIdx] = currentBranch
|
|
319
327
|
}
|
|
328
|
+
} else if (isBare) {
|
|
329
|
+
branchPatterns.splice(headBranchIdx, 1)
|
|
330
|
+
} else {
|
|
331
|
+
let head = noWorktree
|
|
332
|
+
if (worktreePatterns[0] === '.') {
|
|
333
|
+
worktreePatterns = worktreePatterns.slice(1)
|
|
334
|
+
head = repo.dir
|
|
335
|
+
}
|
|
336
|
+
// NOTE current branch is undefined when HEAD is detached
|
|
337
|
+
refs.set('HEAD', { shortname: 'HEAD', fullname: 'HEAD', type: 'branch', detached: true, head })
|
|
338
|
+
branchPatterns.splice(headBranchIdx, 1)
|
|
320
339
|
}
|
|
321
|
-
} else {
|
|
322
|
-
return [...refs.values()]
|
|
323
340
|
}
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
341
|
+
} else {
|
|
342
|
+
return [...refs.values()]
|
|
343
|
+
}
|
|
344
|
+
// NOTE isomorphic-git includes HEAD in list of remote branches (see https://isomorphic-git.org/docs/listBranches)
|
|
345
|
+
const remoteBranches = (await git.listBranches(Object.assign({ remote }, repo))).filter((it) => it !== 'HEAD')
|
|
346
|
+
if (remoteBranches.length) {
|
|
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 })
|
|
331
350
|
}
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
}
|
|
351
|
+
}
|
|
352
|
+
// NOTE only consider local branches if repo has a worktree or there are no remote tracking branches
|
|
353
|
+
if (!isBare) {
|
|
354
|
+
const localBranches = await git.listBranches(repo)
|
|
355
|
+
if (localBranches.length) {
|
|
356
|
+
const worktrees = await findWorktrees(repo, worktreePatterns)
|
|
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 })
|
|
341
360
|
}
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
361
|
+
}
|
|
362
|
+
} else if (!remoteBranches.length) {
|
|
363
|
+
const localBranches = await git.listBranches(repo)
|
|
364
|
+
if (localBranches.length) {
|
|
365
|
+
for (const shortname of filterRefs(localBranches, branchPatterns, patternCache)) {
|
|
346
366
|
refs.set(shortname, { shortname, fullname: 'heads/' + shortname, type: 'branch', head: noWorktree })
|
|
347
367
|
}
|
|
348
368
|
}
|
|
@@ -417,68 +437,53 @@ function collectFilesFromStartPath (startPath, repo, authStatus, ref, worktreePa
|
|
|
417
437
|
|
|
418
438
|
function readFilesFromWorktree (worktreePath, startPath) {
|
|
419
439
|
const cwd = ospath.join(worktreePath, startPath)
|
|
420
|
-
return fsp
|
|
421
|
-
|
|
422
|
-
|
|
440
|
+
return fsp.stat(cwd).then(
|
|
441
|
+
(startPathStat) => {
|
|
442
|
+
if (!startPathStat.isDirectory()) throw new Error(`the start path '${startPath}' is not a directory`)
|
|
443
|
+
return srcFs(cwd)
|
|
444
|
+
},
|
|
445
|
+
() => {
|
|
423
446
|
throw new Error(`the start path '${startPath}' does not exist`)
|
|
424
|
-
})
|
|
425
|
-
.then((stat) => {
|
|
426
|
-
if (!stat.isDirectory()) throw new Error(`the start path '${startPath}' is not a directory`)
|
|
427
|
-
return new Promise((resolve, reject) =>
|
|
428
|
-
vfs
|
|
429
|
-
.src(CONTENT_SRC_GLOB, Object.assign({ cwd }, CONTENT_SRC_OPTS))
|
|
430
|
-
.on('error', (err) => {
|
|
431
|
-
if (err.code === 'ENOENT' && err.syscall === 'stat') {
|
|
432
|
-
try {
|
|
433
|
-
if (fs.lstatSync(err.path).isSymbolicLink()) {
|
|
434
|
-
err.message = `Broken symbolic link detected at ${ospath.relative(cwd, err.path)}`
|
|
435
|
-
}
|
|
436
|
-
} catch {}
|
|
437
|
-
} else if (err.code === 'ELOOP') {
|
|
438
|
-
err.message = `Symbolic link cycle detected at ${ospath.relative(cwd, err.path)}`
|
|
439
|
-
}
|
|
440
|
-
reject(err)
|
|
441
|
-
})
|
|
442
|
-
.pipe(relativizeFiles())
|
|
443
|
-
.pipe(collectFiles(resolve))
|
|
444
|
-
)
|
|
445
|
-
})
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
/**
|
|
449
|
-
* Transforms the path of every file in the stream to a relative posix path.
|
|
450
|
-
*
|
|
451
|
-
* Applies a mapping function to all files in the stream so they end up with a
|
|
452
|
-
* posixified path relative to the file's base instead of the filesystem root.
|
|
453
|
-
* This mapper also filters out any directories (indicated by file.isNull())
|
|
454
|
-
* that got caught up in the glob.
|
|
455
|
-
*/
|
|
456
|
-
function relativizeFiles () {
|
|
457
|
-
return map((file, enc, next) => {
|
|
458
|
-
if (file.isNull()) {
|
|
459
|
-
next()
|
|
460
|
-
} else {
|
|
461
|
-
next(
|
|
462
|
-
null,
|
|
463
|
-
new File({
|
|
464
|
-
path: posixify ? posixify(file.relative) : file.relative,
|
|
465
|
-
contents: file.contents,
|
|
466
|
-
stat: file.stat,
|
|
467
|
-
src: { abspath: file.path },
|
|
468
|
-
})
|
|
469
|
-
)
|
|
470
447
|
}
|
|
471
|
-
|
|
448
|
+
)
|
|
472
449
|
}
|
|
473
450
|
|
|
474
|
-
function
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
451
|
+
function srcFs (cwd) {
|
|
452
|
+
return new Promise((resolve, reject, cache = {}, files = []) =>
|
|
453
|
+
pipeline(
|
|
454
|
+
globStream(CONTENT_SRC_GLOB, Object.assign({ cache, cwd }, CONTENT_SRC_OPTS)),
|
|
455
|
+
map(({ path: abspathPosix }, _, next) => {
|
|
456
|
+
if (Array.isArray(cache[abspathPosix])) return next() // optimization, but not guaranteed
|
|
457
|
+
const abspath = posixify ? ospath.normalize(abspathPosix) : abspathPosix
|
|
458
|
+
const relpath = abspath.substr(cwd.length + 1)
|
|
459
|
+
symlinkAwareStat(abspath).then(
|
|
460
|
+
(stat) => {
|
|
461
|
+
if (stat.isDirectory()) return next()
|
|
462
|
+
fsp.readFile(abspath).then(
|
|
463
|
+
(contents) => {
|
|
464
|
+
files.push(new File({ path: posixify ? posixify(relpath) : relpath, contents, stat, src: { abspath } }))
|
|
465
|
+
next()
|
|
466
|
+
},
|
|
467
|
+
(readErr) => {
|
|
468
|
+
next(Object.assign(readErr, { message: readErr.message.replace(`'${abspath}'`, relpath) }))
|
|
469
|
+
}
|
|
470
|
+
)
|
|
471
|
+
},
|
|
472
|
+
(statErr) => {
|
|
473
|
+
if (statErr.symlink) {
|
|
474
|
+
statErr.message =
|
|
475
|
+
statErr.code === 'ELOOP'
|
|
476
|
+
? `Symbolic link cycle detected at ${relpath}`
|
|
477
|
+
: `Broken symbolic link detected at ${relpath}`
|
|
478
|
+
} else {
|
|
479
|
+
statErr.message = statErr.message.replace(`'${abspath}'`, relpath)
|
|
480
|
+
}
|
|
481
|
+
next(statErr)
|
|
482
|
+
}
|
|
483
|
+
)
|
|
484
|
+
}),
|
|
485
|
+
(err) => (err ? reject(err) : resolve(files))
|
|
486
|
+
)
|
|
482
487
|
)
|
|
483
488
|
}
|
|
484
489
|
|
|
@@ -493,13 +498,13 @@ function readFilesFromGitTree (repo, oid, startPath) {
|
|
|
493
498
|
}
|
|
494
499
|
|
|
495
500
|
function getGitTreeAtStartPath (repo, oid, startPath) {
|
|
496
|
-
return git
|
|
497
|
-
|
|
498
|
-
|
|
501
|
+
return git.readTree(Object.assign({ oid, filepath: startPath }, repo)).then(
|
|
502
|
+
(result) => Object.assign(result, { dirname: startPath }),
|
|
503
|
+
(err) => {
|
|
499
504
|
const m = err instanceof ObjectTypeError && err.data.expected === 'tree' ? 'is not a directory' : 'does not exist'
|
|
500
505
|
throw new Error(`the start path '${startPath}' ${m}`)
|
|
501
|
-
}
|
|
502
|
-
|
|
506
|
+
}
|
|
507
|
+
)
|
|
503
508
|
}
|
|
504
509
|
|
|
505
510
|
function srcGitTree (repo, root, start) {
|
|
@@ -518,9 +523,11 @@ function createGitTreeWalker (repo, root, filter) {
|
|
|
518
523
|
walk (start) {
|
|
519
524
|
return (
|
|
520
525
|
visitGitTree(this, repo, root, filter, start)
|
|
521
|
-
.then(() => this.emit('end'))
|
|
522
526
|
// NOTE if error is thrown, promises already being resolved won't halt
|
|
523
|
-
.
|
|
527
|
+
.then(
|
|
528
|
+
() => this.emit('end'),
|
|
529
|
+
(err) => this.emit('error', err)
|
|
530
|
+
)
|
|
524
531
|
)
|
|
525
532
|
},
|
|
526
533
|
})
|
|
@@ -543,8 +550,15 @@ function visitGitTree (emitter, repo, root, filter, parent, dirname = '', follow
|
|
|
543
550
|
let mode
|
|
544
551
|
if (entry.mode === SYMLINK_FILE_MODE) {
|
|
545
552
|
reads.push(
|
|
546
|
-
readGitSymlink(repo, root, parent, entry, following)
|
|
547
|
-
|
|
553
|
+
readGitSymlink(repo, root, parent, entry, following).then(
|
|
554
|
+
(target) => {
|
|
555
|
+
if (target.type === 'tree') {
|
|
556
|
+
return visitGitTree(emitter, repo, root, filter, target, vfilePath, new Set(following).add(entry.oid))
|
|
557
|
+
} else if (target.type === 'blob' && filterVerdict === true && (mode = FILE_MODES[target.mode])) {
|
|
558
|
+
emitter.emit('entry', Object.assign({ mode, oid: target.oid, path: vfilePath }, repo))
|
|
559
|
+
}
|
|
560
|
+
},
|
|
561
|
+
(err) => {
|
|
548
562
|
// NOTE this error could be caught after promise chain has already been rejected
|
|
549
563
|
if (err instanceof NotFoundError) {
|
|
550
564
|
err.message = `Broken symbolic link detected at ${vfilePath}`
|
|
@@ -552,14 +566,8 @@ function visitGitTree (emitter, repo, root, filter, parent, dirname = '', follow
|
|
|
552
566
|
err.message = `Symbolic link cycle detected at ${vfilePath}`
|
|
553
567
|
}
|
|
554
568
|
throw err
|
|
555
|
-
}
|
|
556
|
-
|
|
557
|
-
if (target.type === 'tree') {
|
|
558
|
-
return visitGitTree(emitter, repo, root, filter, target, vfilePath, new Set(following).add(entry.oid))
|
|
559
|
-
} else if (target.type === 'blob' && filterVerdict === true && (mode = FILE_MODES[target.mode])) {
|
|
560
|
-
emitter.emit('entry', Object.assign({ mode, oid: target.oid, path: vfilePath }, repo))
|
|
561
|
-
}
|
|
562
|
-
})
|
|
569
|
+
}
|
|
570
|
+
)
|
|
563
571
|
)
|
|
564
572
|
} else if ((mode = FILE_MODES[entry.mode])) {
|
|
565
573
|
emitter.emit('entry', Object.assign({ mode, oid: entry.oid, path: vfilePath }, repo))
|
|
@@ -577,7 +585,7 @@ function readGitSymlink (repo, root, parent, { oid }, following) {
|
|
|
577
585
|
let targetParent
|
|
578
586
|
if (parent.dirname) {
|
|
579
587
|
const dirname = parent.dirname + '/'
|
|
580
|
-
target = path.join(dirname, target)
|
|
588
|
+
target = path.join(dirname, target) // join doesn't remove trailing separator
|
|
581
589
|
if (target.startsWith(dirname)) {
|
|
582
590
|
target = target.substr(dirname.length)
|
|
583
591
|
targetParent = parent
|
|
@@ -585,10 +593,12 @@ function readGitSymlink (repo, root, parent, { oid }, following) {
|
|
|
585
593
|
targetParent = root
|
|
586
594
|
}
|
|
587
595
|
} else {
|
|
588
|
-
target = path.normalize(target)
|
|
596
|
+
target = path.normalize(target) // normalize doesn't remove trailing separator
|
|
589
597
|
targetParent = root
|
|
590
598
|
}
|
|
591
|
-
|
|
599
|
+
const targetSegments = target.split('/')
|
|
600
|
+
if (!targetSegments[targetSegments.length - 1]) targetSegments.pop()
|
|
601
|
+
return readGitObjectAtPath(repo, root, targetParent, targetSegments, following)
|
|
592
602
|
})
|
|
593
603
|
}
|
|
594
604
|
const err = { name: 'SymbolicLinkCycleError', code: 'SymbolicLinkCycleError', oid }
|
|
@@ -664,7 +674,7 @@ function loadComponentDescriptor (files, ref, version) {
|
|
|
664
674
|
matched = version[refname]
|
|
665
675
|
} else if (
|
|
666
676
|
!Object.entries(version).some(([pattern, replacement]) => {
|
|
667
|
-
const result = refname.replace(
|
|
677
|
+
const result = refname.replace(makeMatcherRx(pattern, VERSION_MATCHER_OPTS), '\0' + replacement)
|
|
668
678
|
if (result === refname) return false
|
|
669
679
|
matched = result.substr(1)
|
|
670
680
|
return true
|
|
@@ -891,47 +901,20 @@ function resolveRemoteUrl (repo, remoteName) {
|
|
|
891
901
|
* @return {Boolean} A flag indicating whether the URL matches a directory on the local filesystem.
|
|
892
902
|
*/
|
|
893
903
|
function isDirectory (url) {
|
|
894
|
-
return fsp
|
|
895
|
-
.stat(url)
|
|
896
|
-
.then((stat) => stat.isDirectory())
|
|
897
|
-
.catch(invariably.false)
|
|
904
|
+
return fsp.stat(url).then((stat) => stat.isDirectory(), invariably.false)
|
|
898
905
|
}
|
|
899
906
|
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
return fsp
|
|
906
|
-
.readdir(dir, { withFileTypes: true })
|
|
907
|
-
.then((lst) =>
|
|
908
|
-
Promise.all(
|
|
909
|
-
lst.map((it) =>
|
|
910
|
-
it.isDirectory()
|
|
911
|
-
? rmdir(ospath.join(dir, it.name))
|
|
912
|
-
: fsp.unlink(ospath.join(dir, it.name)).catch((unlinkErr) => {
|
|
913
|
-
if (unlinkErr.code !== 'ENOENT') throw unlinkErr
|
|
914
|
-
})
|
|
915
|
-
)
|
|
916
|
-
)
|
|
917
|
-
)
|
|
918
|
-
.then(() => fsp.rmdir(dir))
|
|
919
|
-
.catch((err) => {
|
|
920
|
-
if (err.code === 'ENOENT') return
|
|
921
|
-
if (err.code === 'ENOTDIR') {
|
|
922
|
-
return fsp.unlink(dir).catch((unlinkErr) => {
|
|
923
|
-
if (unlinkErr.code !== 'ENOENT') throw unlinkErr
|
|
924
|
-
})
|
|
925
|
-
}
|
|
926
|
-
throw err
|
|
907
|
+
function symlinkAwareStat (path_) {
|
|
908
|
+
return fsp.lstat(path_).then((lstat) => {
|
|
909
|
+
if (!lstat.isSymbolicLink()) return lstat
|
|
910
|
+
return fsp.stat(path_).catch((statErr) => {
|
|
911
|
+
throw Object.assign(statErr, { symlink: true })
|
|
927
912
|
})
|
|
913
|
+
})
|
|
928
914
|
}
|
|
929
915
|
|
|
930
|
-
function tagsSpecified (sources
|
|
931
|
-
return
|
|
932
|
-
const tags = source.tags || defaultTags || []
|
|
933
|
-
return Array.isArray(tags) ? tags.length : true
|
|
934
|
-
})
|
|
916
|
+
function tagsSpecified (sources) {
|
|
917
|
+
return sources.some(({ tags }) => tags && (Array.isArray(tags) ? tags.length : true))
|
|
935
918
|
}
|
|
936
919
|
|
|
937
920
|
function loadGitPlugins (gitConfig, networkConfig, startDir) {
|
|
@@ -969,17 +952,16 @@ function ensureCacheDir (preferredCacheDir, startDir) {
|
|
|
969
952
|
? getCacheDir('antora' + (process.env.NODE_ENV === 'test' ? '-test' : '')) || ospath.resolve('.antora/cache')
|
|
970
953
|
: expandPath(preferredCacheDir, { dot: startDir })
|
|
971
954
|
const cacheDir = ospath.join(baseCacheDir, CONTENT_CACHE_FOLDER)
|
|
972
|
-
return fsp
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
.catch((err) => {
|
|
955
|
+
return fsp.mkdir(cacheDir, { recursive: true }).then(
|
|
956
|
+
() => cacheDir,
|
|
957
|
+
(err) => {
|
|
976
958
|
throw Object.assign(err, { message: `Failed to create content cache directory: ${cacheDir}; ${err.message}` })
|
|
977
|
-
}
|
|
959
|
+
}
|
|
960
|
+
)
|
|
978
961
|
}
|
|
979
962
|
|
|
980
963
|
function transformGitCloneError (err, displayUrl) {
|
|
981
|
-
let wrappedMsg
|
|
982
|
-
let trimMessage
|
|
964
|
+
let wrappedMsg, trimMessage
|
|
983
965
|
if (HTTP_ERROR_CODE_RX.test(err.code)) {
|
|
984
966
|
switch (err.data.statusCode) {
|
|
985
967
|
case 401:
|
|
@@ -1010,6 +992,10 @@ function transformGitCloneError (err, displayUrl) {
|
|
|
1010
992
|
return wrappedErr
|
|
1011
993
|
}
|
|
1012
994
|
|
|
995
|
+
function splitRefPatterns (str) {
|
|
996
|
+
return ~str.indexOf('{') ? str.split(VENTILATED_CSV_RX) : str.split(CSV_RX)
|
|
997
|
+
}
|
|
998
|
+
|
|
1013
999
|
function coerceToString (value) {
|
|
1014
1000
|
return value == null ? '' : String(value)
|
|
1015
1001
|
}
|
|
@@ -1022,11 +1008,11 @@ function findWorktrees (repo, patterns) {
|
|
|
1022
1008
|
if (!patterns.length) return new Map()
|
|
1023
1009
|
const linkedOnly = patterns[0] === '.' ? !(patterns = patterns.slice(1)) : true
|
|
1024
1010
|
let worktreesDir
|
|
1011
|
+
const patternCache = repo.cache[REF_PATTERN_CACHE_KEY]
|
|
1025
1012
|
return (patterns.length
|
|
1026
1013
|
? fsp
|
|
1027
1014
|
.readdir((worktreesDir = ospath.join(repo.dir, '.git', 'worktrees')))
|
|
1028
|
-
.
|
|
1029
|
-
.then((worktreeNames) => matcher(worktreeNames, [...patterns]))
|
|
1015
|
+
.then((worktreeNames) => filterRefs(worktreeNames, patterns, patternCache), invariably.emptyArray)
|
|
1030
1016
|
.then((worktreeNames) =>
|
|
1031
1017
|
worktreeNames.length
|
|
1032
1018
|
? Promise.all(
|
package/lib/constants.js
CHANGED
|
@@ -2,25 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
module.exports = Object.freeze({
|
|
4
4
|
COMPONENT_DESC_FILENAME: 'antora.yml',
|
|
5
|
-
CONTENT_CACHE_FOLDER: 'content
|
|
5
|
+
CONTENT_CACHE_FOLDER: 'content',
|
|
6
6
|
CONTENT_SRC_GLOB: '**/*[!~]',
|
|
7
|
-
CONTENT_SRC_OPTS: { follow: true, nomount: true, nosort: true, nounique: true,
|
|
7
|
+
CONTENT_SRC_OPTS: { follow: true, nomount: true, nosort: true, nounique: true, strict: false, uniqueBy: (m) => m },
|
|
8
8
|
FILE_MODES: { 100644: 0o100666 & ~process.umask(), 100755: 0o100777 & ~process.umask() },
|
|
9
9
|
GIT_CORE: 'antora',
|
|
10
10
|
GIT_OPERATION_LABEL_LENGTH: 8,
|
|
11
11
|
GIT_PROGRESS_PHASES: ['Counting objects', 'Compressing objects', 'Receiving objects', 'Resolving deltas'],
|
|
12
|
-
|
|
13
|
-
bash: true,
|
|
14
|
-
debug: false,
|
|
15
|
-
dot: true,
|
|
16
|
-
fastpaths: false,
|
|
17
|
-
lookbehinds: false,
|
|
18
|
-
noextglob: true,
|
|
19
|
-
noglobstar: true,
|
|
20
|
-
nonegate: true,
|
|
21
|
-
noquantifiers: true,
|
|
22
|
-
strictSlashes: true,
|
|
23
|
-
},
|
|
12
|
+
REF_PATTERN_CACHE_KEY: Symbol('RefPatternCache'),
|
|
24
13
|
SYMLINK_FILE_MODE: '120000',
|
|
25
14
|
VALID_STATE_FILENAME: 'valid',
|
|
26
15
|
})
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { makeMatcherRx, refMatcherOpts: getMatcherOpts, MATCH_ALL_RX } = require('./matcher')
|
|
4
|
+
|
|
5
|
+
function compileRx (pattern, opts) {
|
|
6
|
+
if (pattern === '*' || pattern === '**') return MATCH_ALL_RX
|
|
7
|
+
return pattern.charAt() === '!' // do our own negate
|
|
8
|
+
? Object.defineProperty(makeMatcherRx(pattern.substr(1), opts), 'negated', { value: true })
|
|
9
|
+
: makeMatcherRx(pattern, opts)
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function createMatcher (patterns, cache, opts) {
|
|
13
|
+
const rxs = patterns.map(
|
|
14
|
+
(pattern) =>
|
|
15
|
+
cache.get(pattern) || cache.set(pattern, compileRx(pattern, opts || (opts = getMatcherOpts(cache)))).get(pattern)
|
|
16
|
+
)
|
|
17
|
+
if (rxs[0].negated) rxs.unshift(MATCH_ALL_RX)
|
|
18
|
+
return (candidate) => {
|
|
19
|
+
let matched
|
|
20
|
+
for (const rx of rxs) {
|
|
21
|
+
let voteIfMatched = true
|
|
22
|
+
if (matched) {
|
|
23
|
+
if (!rx.negated) continue
|
|
24
|
+
voteIfMatched = false
|
|
25
|
+
} else if (rx.negated) {
|
|
26
|
+
continue
|
|
27
|
+
}
|
|
28
|
+
if (rx.test(candidate)) matched = voteIfMatched
|
|
29
|
+
}
|
|
30
|
+
return matched
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function filterRefs (candidates, patterns, cache = Object.assign(new Map(), { braces: new Map() })) {
|
|
35
|
+
const isMatch = createMatcher(patterns, cache)
|
|
36
|
+
return candidates.reduce((accum, candidate) => {
|
|
37
|
+
if (isMatch(candidate)) accum.push(candidate)
|
|
38
|
+
return accum
|
|
39
|
+
}, [])
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
module.exports = filterRefs
|
|
@@ -3,18 +3,17 @@
|
|
|
3
3
|
const { homedir } = require('os')
|
|
4
4
|
const expandPath = require('@antora/expand-path-helper')
|
|
5
5
|
const { promises: fsp } = require('fs')
|
|
6
|
+
const invariably = require('./invariably')
|
|
6
7
|
const ospath = require('path')
|
|
7
8
|
|
|
8
9
|
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, { dot: 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 {
|
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
|
*/
|
package/lib/matcher.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { compile: bracesToGroup, expand: expandBraces } = require('braces')
|
|
4
|
+
const { makeRe: makeMatcherRx } = require('picomatch')
|
|
5
|
+
|
|
6
|
+
const BASE_OPTS = {
|
|
7
|
+
bash: true,
|
|
8
|
+
dot: true,
|
|
9
|
+
expandRange: (begin, end, step, opts) => bracesToGroup(opts ? `{${begin}..${end}..${step}}` : `{${begin}..${end}}`),
|
|
10
|
+
fastpaths: false,
|
|
11
|
+
nobracket: true,
|
|
12
|
+
noglobstar: true,
|
|
13
|
+
nonegate: true,
|
|
14
|
+
noquantifiers: true,
|
|
15
|
+
regex: false,
|
|
16
|
+
strictSlashes: true,
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
module.exports = {
|
|
20
|
+
MATCH_ALL_RX: { test: () => true },
|
|
21
|
+
expandBraces,
|
|
22
|
+
makeMatcherRx,
|
|
23
|
+
pathMatcherOpts: Object.assign({}, BASE_OPTS, { dot: false }),
|
|
24
|
+
refMatcherOpts: (cache) =>
|
|
25
|
+
Object.assign({}, BASE_OPTS, {
|
|
26
|
+
expandRange: (begin, end, step, opts) => {
|
|
27
|
+
const pattern = opts ? `{${begin}..${end}..${step}}` : `{${begin}..${end}}`
|
|
28
|
+
return cache.braces.get(pattern) || cache.braces.set(pattern, bracesToGroup(pattern)).get(pattern)
|
|
29
|
+
},
|
|
30
|
+
}),
|
|
31
|
+
versionMatcherOpts: Object.assign({}, BASE_OPTS, { nonegate: false }),
|
|
32
|
+
}
|
|
@@ -1,26 +1,21 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const { expand: expandBraces } = require('braces')
|
|
4
3
|
const flattenDeep = require('./flatten-deep')
|
|
5
4
|
const { promises: fsp } = require('fs')
|
|
6
5
|
const git = require('./git')
|
|
7
|
-
const invariably =
|
|
8
|
-
const {
|
|
6
|
+
const invariably = require('./invariably')
|
|
7
|
+
const { expandBraces, makeMatcherRx, pathMatcherOpts: MATCHER_OPTS } = require('./matcher')
|
|
9
8
|
|
|
10
|
-
const
|
|
11
|
-
const RX_MAGIC_DETECTOR = /[*{]/
|
|
12
|
-
const RX_QUESTION_MARK = /\?/g
|
|
13
|
-
const PICOMATCH_OPTS = { nobracket: true, noextglob: true, noglobstar: true, noquantifiers: true }
|
|
14
|
-
const PICOMATCH_NEGATED_OPTS = { nobracket: true, noextglob: true, noquantifiers: true }
|
|
9
|
+
const NON_GLOB_SPECIAL_CHARS_RX = /[.+?^${}()|[\]\\]/g
|
|
10
|
+
const RX_MAGIC_DETECTOR = /[*{(]/
|
|
15
11
|
|
|
16
12
|
function resolvePathGlobs (base, patterns, listDirents, retrievePath, tree = { path: '' }) {
|
|
17
13
|
return patterns.reduce((paths, pattern) => {
|
|
18
14
|
if (pattern.charAt() === '!') {
|
|
19
15
|
return paths.then((resolvedPaths) => {
|
|
20
16
|
if (resolvedPaths.length) {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
return resolvedPaths.filter(rx.test.bind(rx))
|
|
17
|
+
const rx = makeMatcherRx(pattern.substr(1), MATCHER_OPTS)
|
|
18
|
+
return resolvedPaths.filter((it) => !rx.test(it))
|
|
24
19
|
} else {
|
|
25
20
|
return resolvedPaths
|
|
26
21
|
}
|
|
@@ -38,15 +33,14 @@ async function glob (base, patternSegments, listDirents, retrievePath, { oid, pa
|
|
|
38
33
|
let patternSegment = patternSegments[0]
|
|
39
34
|
patternSegments = patternSegments.slice(1)
|
|
40
35
|
if (RX_MAGIC_DETECTOR.test(patternSegment)) {
|
|
41
|
-
let isMatch
|
|
42
|
-
let explicit
|
|
36
|
+
let isMatch, explicit
|
|
43
37
|
if (patternSegment === '*') {
|
|
44
38
|
isMatch = (it) => it.charAt() !== '.'
|
|
39
|
+
} else if (~patternSegment.indexOf('(')) {
|
|
40
|
+
isMatch = (isMatch = makeMatcherRx(patternSegment, MATCHER_OPTS)).test.bind(isMatch)
|
|
45
41
|
} else if (~patternSegment.indexOf('{')) {
|
|
46
42
|
if (globbed) {
|
|
47
|
-
|
|
48
|
-
if (~patternSegment.indexOf('?')) patternSegment = patternSegment.replace(RX_QUESTION_MARK, '\\?')
|
|
49
|
-
isMatch = (isMatch = makePicomatchRx(patternSegment, PICOMATCH_OPTS)).test.bind(isMatch)
|
|
43
|
+
isMatch = (isMatch = makeMatcherRx(patternSegment, MATCHER_OPTS)).test.bind(isMatch)
|
|
50
44
|
} else if (~patternSegment.indexOf('*')) {
|
|
51
45
|
const [wildPatterns, literals] = expandBraces(patternSegment).reduce(
|
|
52
46
|
([wild, literal], it) => (~it.indexOf('*') ? [[...wild, it], literal] : [wild, [...literal, it]]),
|
|
@@ -58,7 +52,7 @@ async function glob (base, patternSegments, listDirents, retrievePath, { oid, pa
|
|
|
58
52
|
return expandBraces(patternSegment).map((it) => joinPath(path, it))
|
|
59
53
|
}
|
|
60
54
|
} else {
|
|
61
|
-
isMatch = (isMatch =
|
|
55
|
+
isMatch = (isMatch = makeSingleMatcherRx(patternSegment)).test.bind(isMatch)
|
|
62
56
|
}
|
|
63
57
|
let dirents = await listDirents(base, oid || path)
|
|
64
58
|
if (explicit) dirents = dirents.filter((dirent) => !explicit.has(dirent.name))
|
|
@@ -90,22 +84,15 @@ async function glob (base, patternSegments, listDirents, retrievePath, { oid, pa
|
|
|
90
84
|
})
|
|
91
85
|
} else if ((patternSegment += '/' + patternSegments.join('/')).indexOf('{')) {
|
|
92
86
|
return expandBraces(patternSegment).map((it) => joinPath(path, it))
|
|
93
|
-
} else {
|
|
94
|
-
return [joinPath(path, patternSegment)]
|
|
95
87
|
}
|
|
88
|
+
return [joinPath(path, patternSegment)]
|
|
96
89
|
} else if (globbed) {
|
|
97
90
|
return (await retrievePath(base, { oid, path }, patternSegment)) ? [joinPath(path, patternSegment)] : []
|
|
98
|
-
} else {
|
|
99
|
-
return [joinPath(path, patternSegment)]
|
|
100
91
|
}
|
|
92
|
+
return [joinPath(path, patternSegment)]
|
|
101
93
|
}
|
|
102
94
|
}
|
|
103
95
|
|
|
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
96
|
function extractMagicBase (patternSegments, base) {
|
|
110
97
|
let nextSegment
|
|
111
98
|
if (patternSegments.length) {
|
|
@@ -124,22 +111,29 @@ function listDirentsFs (base, path) {
|
|
|
124
111
|
function listDirentsGit (repo, treeOid) {
|
|
125
112
|
return git
|
|
126
113
|
.readTree(Object.assign({ oid: treeOid, filepath: '' }, repo))
|
|
127
|
-
.then(
|
|
128
|
-
|
|
114
|
+
.then(
|
|
115
|
+
({ tree: entries }) =>
|
|
116
|
+
entries.map(({ type, oid, path: name }) => ({ name, oid, isDirectory: invariably[type === 'tree'] })),
|
|
117
|
+
invariably.emptyArray
|
|
129
118
|
)
|
|
130
|
-
.catch(invariably.emptyArray)
|
|
131
119
|
}
|
|
132
120
|
|
|
133
121
|
function makeAlternationMatcherRx (patterns) {
|
|
134
122
|
return new RegExp('^(?:' + patterns.map(patternToRx).join('|') + ')$')
|
|
135
123
|
}
|
|
136
124
|
|
|
137
|
-
function
|
|
125
|
+
function makeSingleMatcherRx (pattern) {
|
|
138
126
|
return new RegExp('^' + patternToRx(pattern) + '$')
|
|
139
127
|
}
|
|
140
128
|
|
|
141
129
|
function patternToRx (pattern) {
|
|
142
|
-
return (
|
|
130
|
+
return (
|
|
131
|
+
(pattern.charAt() === '.' ? '' : '(?!\\.)') +
|
|
132
|
+
pattern
|
|
133
|
+
.replace(NON_GLOB_SPECIAL_CHARS_RX, '\\$&')
|
|
134
|
+
.replace('\\\\*', '\\x2a')
|
|
135
|
+
.replace('*', '.*?')
|
|
136
|
+
)
|
|
143
137
|
}
|
|
144
138
|
|
|
145
139
|
function readdirWithFileTypes (dir) {
|
|
@@ -147,10 +141,7 @@ function readdirWithFileTypes (dir) {
|
|
|
147
141
|
}
|
|
148
142
|
|
|
149
143
|
function retrievePathFs (base, { path }, subpath) {
|
|
150
|
-
return fsp
|
|
151
|
-
.access(base + '/' + joinPath(path, subpath))
|
|
152
|
-
.then(invariably.true)
|
|
153
|
-
.catch(invariably.false)
|
|
144
|
+
return fsp.access(base + '/' + joinPath(path, subpath)).then(invariably.true, invariably.false)
|
|
154
145
|
}
|
|
155
146
|
|
|
156
147
|
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.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)",
|
|
@@ -21,21 +21,19 @@
|
|
|
21
21
|
"@antora/user-require-helper": "~2.0",
|
|
22
22
|
"braces": "~3.0",
|
|
23
23
|
"cache-directory": "~2.0",
|
|
24
|
-
"camelcase-keys": "~
|
|
24
|
+
"camelcase-keys": "~7.0",
|
|
25
25
|
"hpagent": "~0.1.0",
|
|
26
|
-
"isomorphic-git": "~1.
|
|
26
|
+
"isomorphic-git": "~1.10",
|
|
27
27
|
"js-yaml": "~4.1",
|
|
28
|
-
"matcher": "~4.0",
|
|
29
28
|
"multi-progress": "~4.0",
|
|
30
29
|
"picomatch": "~2.3",
|
|
31
30
|
"progress": "~2.0",
|
|
32
31
|
"should-proxy": "~1.0",
|
|
33
32
|
"simple-get": "~4.0",
|
|
34
|
-
"vinyl": "~2.2"
|
|
35
|
-
"vinyl-fs": "~3.0"
|
|
33
|
+
"vinyl": "~2.2"
|
|
36
34
|
},
|
|
37
35
|
"engines": {
|
|
38
|
-
"node": ">=
|
|
36
|
+
"node": ">=12.21.0"
|
|
39
37
|
},
|
|
40
38
|
"files": [
|
|
41
39
|
"lib/"
|
|
@@ -50,5 +48,5 @@
|
|
|
50
48
|
"static site",
|
|
51
49
|
"web publishing"
|
|
52
50
|
],
|
|
53
|
-
"gitHead": "
|
|
51
|
+
"gitHead": "45da95a2e2dea538379d2d9f42013d2208fb86c3"
|
|
54
52
|
}
|