@antora/content-aggregator 3.1.9 → 3.1.11

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.
@@ -1,32 +1,32 @@
1
1
  'use strict'
2
2
 
3
3
  const computeOrigin = require('./compute-origin')
4
- const { createHash } = require('crypto')
4
+ const { createHash } = require('node:crypto')
5
5
  const createGitHttpPlugin = require('./git-plugin-http')
6
6
  const decodeUint8Array = require('./decode-uint8-array')
7
7
  const deepClone = require('./deep-clone')
8
- const EventEmitter = require('events')
8
+ const EventEmitter = require('node:events')
9
9
  const expandPath = require('@antora/expand-path-helper')
10
10
  const File = require('./file')
11
11
  const filterRefs = require('./filter-refs')
12
- const fs = require('fs')
12
+ const fs = require('node:fs')
13
13
  const { promises: fsp } = fs
14
14
  const getCacheDir = require('cache-directory')
15
15
  const GitCredentialManagerStore = require('./git-credential-manager-store')
16
16
  const git = require('./git')
17
17
  const { NotFoundError, ObjectTypeError, UnknownTransportError, UrlParseError } = git.Errors
18
18
  const { globStream } = require('fast-glob')
19
- const { inspect } = require('util')
19
+ const { inspect } = require('node:util')
20
20
  const invariably = require('./invariably')
21
21
  const logger = require('./logger')
22
22
  const { makeMatcherRx, versionMatcherOpts: VERSION_MATCHER_OPTS } = require('./matcher')
23
23
  const MultiProgress = require('multi-progress') // calls require('progress') as a peer dependencies
24
- const ospath = require('path')
24
+ const ospath = require('node:path')
25
25
  const { posix: path } = ospath
26
26
  const posixify = require('./posixify')
27
27
  const removeGitSuffix = require('./remove-git-suffix')
28
28
  const { fs: resolvePathGlobsFs, git: resolvePathGlobsGit } = require('./resolve-path-globs')
29
- const { pipeline, Writable } = require('stream')
29
+ const { pipeline, Writable } = require('node:stream')
30
30
  const forEach = (write) => new Writable({ objectMode: true, write })
31
31
  const userRequire = require('@antora/user-require-helper')
32
32
  const yaml = require('js-yaml')
@@ -100,21 +100,22 @@ function aggregateContent (playbook) {
100
100
  const sourcesByUrl = sources.reduce((accum, source) => {
101
101
  return accum.set(source.url, [...(accum.get(source.url) || []), Object.assign({}, sourceDefaults, source)])
102
102
  }, new Map())
103
- const progress = !quiet && createProgress(sourcesByUrl.keys(), process.stdout)
103
+ const progress = quiet ? undefined : createProgress(sourcesByUrl.keys(), process.stdout)
104
104
  const refPatternCache = Object.assign(new Map(), { braces: new Map() })
105
- const loadOpts = { cacheDir, fetch, gitPlugins, progress, startDir, refPatternCache }
105
+ const fetchConfig = { always: fetch, depth: Math.max(0, gitConfig.fetchDepth ?? 1) }
106
+ const loadOpts = { cacheDir, fetch: fetchConfig, gitPlugins, progress, startDir, refPatternCache }
106
107
  return collectFiles(sourcesByUrl, loadOpts, concurrency).then(buildAggregate, (err) => {
107
- progress && progress.terminate()
108
+ progress?.terminate()
108
109
  throw err
109
110
  })
110
111
  })
111
112
  }
112
113
 
113
- async function collectFiles (sourcesByUrl, loadOpts, concurrency, fetchedUrls) {
114
+ async function collectFiles (sourcesByUrl, loadOpts, concurrency, fetchedUrls = []) {
114
115
  const loadTasks = [...sourcesByUrl.entries()].map(([url, sources]) => {
115
116
  const loadOptsForUrl = Object.assign({}, loadOpts)
116
- if (loadOpts.fetch && fetchedUrls && fetchedUrls.length && fetchedUrls.includes(url)) loadOptsForUrl.fetch = false
117
- if (tagsSpecified(sources)) loadOptsForUrl.fetchTags = true
117
+ if (loadOpts.fetch.always && fetchedUrls.length && fetchedUrls.includes(url)) loadOptsForUrl.fetch.always = false
118
+ if (tagsSpecified(sources)) loadOptsForUrl.fetch.tags = true
118
119
  return loadRepository.bind(null, url, loadOptsForUrl, { url, sources })
119
120
  })
120
121
  return gracefulPromiseAllWithLimit(loadTasks, concurrency.fetch).then(([results, rejections]) => {
@@ -124,7 +125,7 @@ async function collectFiles (sourcesByUrl, loadOpts, concurrency, fetchedUrls) {
124
125
  const msg0 = 'An unexpected error occurred while fetching content sources concurrently.'
125
126
  const msg1 = 'Retrying with git.fetch_concurrency value of 1.'
126
127
  logger.warn(rejections[0], msg0 + ' ' + msg1)
127
- const fulfilledUrls = results.map((it) => it && it.repo.url && it.url).filter((it) => it)
128
+ const fulfilledUrls = results.filter((it) => it?.repo.url).map((it) => it.url)
128
129
  return collectFiles(sourcesByUrl, loadOpts, Object.assign(concurrency, { fetch: 1 }), fulfilledUrls)
129
130
  }
130
131
  throw rejections[0]
@@ -163,7 +164,7 @@ async function loadRepository (url, opts, result = {}) {
163
164
  if (~url.indexOf(':') && GIT_URI_DETECTOR_RX.test(url)) {
164
165
  let credentials, displayUrl
165
166
  ;({ displayUrl, url, credentials } = extractCredentials(url))
166
- const { cacheDir, fetch, fetchTags, gitPlugins, progress } = opts
167
+ const { cacheDir, fetch, gitPlugins, progress } = opts
167
168
  dir = ospath.join(cacheDir, generateCloneFolderName(displayUrl))
168
169
  // NOTE the presence of the url property on the repo object implies the repository is remote
169
170
  repo = { cache, dir, fs, gitdir: dir, noCheckout: true, url }
@@ -171,9 +172,9 @@ async function loadRepository (url, opts, result = {}) {
171
172
  const validStateFile = ospath.join(dir, VALID_STATE_FILENAME)
172
173
  try {
173
174
  await fsp.access(validStateFile)
174
- if (fetch) {
175
+ if (fetch.always) {
175
176
  await fsp.unlink(validStateFile)
176
- const fetchOpts = buildFetchOptions(repo, progress, displayUrl, credentials, gitPlugins, fetchTags, 'fetch')
177
+ const fetchOpts = buildFetchOptions(repo, progress, displayUrl, credentials, gitPlugins, fetch, 'fetch')
177
178
  await git
178
179
  .fetch(fetchOpts)
179
180
  .then(() => {
@@ -181,19 +182,19 @@ async function loadRepository (url, opts, result = {}) {
181
182
  return git.setConfig(Object.assign({ path: 'remote.origin.private', value: authStatus }, repo))
182
183
  })
183
184
  .catch((fetchErr) => {
184
- if (fetchOpts.onProgress) fetchOpts.onProgress.finish(fetchErr)
185
+ fetchOpts.onProgress?.finish(fetchErr)
185
186
  if (HTTP_ERROR_CODE_RX.test(fetchErr.code) && fetchErr.data.statusCode === 401) fetchErr.rethrow = true
186
187
  throw fetchErr
187
188
  })
188
189
  .then(() => fsp.writeFile(validStateFile, '').catch(invariably.void))
189
- .then(() => fetchOpts.onProgress && fetchOpts.onProgress.finish())
190
+ .then(() => fetchOpts.onProgress?.finish())
190
191
  } else {
191
192
  authStatus = await git.getConfig(Object.assign({ path: 'remote.origin.private' }, repo))
192
193
  }
193
194
  } catch (gitErr) {
194
195
  await fsp['rm' in fsp ? 'rm' : 'rmdir'](dir, { recursive: true, force: true })
195
196
  if (gitErr.rethrow) throw transformGitCloneError(gitErr, displayUrl)
196
- const fetchOpts = buildFetchOptions(repo, progress, displayUrl, credentials, gitPlugins, fetchTags, 'clone')
197
+ const fetchOpts = buildFetchOptions(repo, progress, displayUrl, credentials, gitPlugins, fetch, 'clone')
197
198
  await git
198
199
  .clone(fetchOpts)
199
200
  .then(() => git.resolveRef(Object.assign({ ref: 'HEAD', depth: 1 }, repo)))
@@ -202,16 +203,23 @@ async function loadRepository (url, opts, result = {}) {
202
203
  return git.setConfig(Object.assign({ path: 'remote.origin.private', value: authStatus }, repo))
203
204
  })
204
205
  .catch((cloneErr) => {
205
- if (fetchOpts.onProgress) fetchOpts.onProgress.finish(cloneErr)
206
+ fetchOpts.onProgress?.finish(cloneErr)
206
207
  const authRequested = credentialManager.status({ url }) === 'requested'
207
208
  throw transformGitCloneError(cloneErr, displayUrl, authRequested)
208
209
  })
209
210
  .then(() => fsp.writeFile(validStateFile, '').catch(invariably.void))
210
- .then(() => fetchOpts.onProgress && fetchOpts.onProgress.finish())
211
+ .then(() => fetchOpts.onProgress?.finish())
211
212
  }
212
213
  } else if (await isDirectory((dir = expandPath(url, { dot: opts.startDir })))) {
213
- const gitdir = ospath.join(dir, '.git')
214
- repo = (await isDirectory(gitdir)) ? { cache, dir, fs, gitdir } : { cache, dir, fs, gitdir: dir, noCheckout: true }
214
+ const dotgit = ospath.join(dir, '.git')
215
+ const dotgitStat = await fsp.stat(dotgit).catch(() => ({ isFile: invariably.false, isDirectory: invariably.false }))
216
+ if (dotgitStat.isDirectory()) {
217
+ repo = { cache, dir, fs, gitdir: dotgit }
218
+ } else if (dotgitStat.isFile()) {
219
+ repo = await resolveRepositoryFromWorktree({ cache, dir, fs, gitdir: dotgit })
220
+ } else {
221
+ repo = { cache, dir, fs, gitdir: dir, noCheckout: true }
222
+ }
215
223
  try {
216
224
  await git.resolveRef(Object.assign({ ref: 'HEAD', depth: 1 }, repo))
217
225
  } catch {
@@ -236,11 +244,9 @@ function extractCredentials (url) {
236
244
  // NOTE if only username is present, assume it's an oauth token and set password to empty string
237
245
  const credentials = username ? { username, password: password || '' } : {}
238
246
  return { displayUrl, url, credentials }
239
- } else if (url.startsWith('git@')) {
240
- return { displayUrl: url, url: 'https://' + url.substr(4).replace(':', '/') }
241
- } else {
242
- return { displayUrl: url, url }
243
247
  }
248
+ if (url.startsWith('git@')) return { displayUrl: url, url: 'https://' + url.substr(4).replace(':', '/') }
249
+ return { displayUrl: url, url }
244
250
  }
245
251
 
246
252
  async function selectStartPathsForRepository (repo, authStatus, sources) {
@@ -248,14 +254,27 @@ async function selectStartPathsForRepository (repo, authStatus, sources) {
248
254
  const originUrls = {}
249
255
  for (const source of sources) {
250
256
  const { version, editUrl } = source
251
- // NOTE if repository is managed (has a url property), we can assume the remote name is origin
252
- // TODO if the repo has no remotes, then remoteName should be undefined
253
- const remoteName = repo.url ? 'origin' : source.remote || 'origin'
254
- const originUrl = repo.url || (originUrls[remoteName] ||= await resolveRemoteUrl(repo, remoteName))
257
+ let remoteName, originUrl
258
+ if (repo.url) {
259
+ remoteName = 'origin' // NOTE if repository is managed (has url property), we can assume remote name is origin
260
+ originUrl = repo.url
261
+ } else {
262
+ remoteName = source.remote || 'origin'
263
+ originUrl =
264
+ remoteName in originUrls
265
+ ? originUrls[remoteName]
266
+ : (originUrls[remoteName] = await resolveRemoteUrl(repo, remoteName))
267
+ if (!originUrl) {
268
+ remoteName = undefined
269
+ if ((originUrl = posixify ? 'file:///' + posixify(repo.dir) : 'file://' + repo.dir).indexOf(' ')) {
270
+ originUrl = originUrl.replace(SPACE_RX, '%20')
271
+ }
272
+ }
273
+ }
255
274
  const refs = await selectReferences(source, repo, remoteName)
256
275
  if (refs.length) {
257
276
  for (const ref of refs) {
258
- for (const startPath of await selectStartPaths(source, repo, remoteName, ref)) {
277
+ for (const startPath of await selectStartPaths(source, repo, ref)) {
259
278
  startPaths.push({ startPath, ref, originUrl, editUrl, version })
260
279
  }
261
280
  }
@@ -274,7 +293,7 @@ async function selectStartPathsForRepository (repo, authStatus, sources) {
274
293
 
275
294
  // QUESTION should we resolve HEAD to a ref eagerly to avoid having to do a match on it?
276
295
  async function selectReferences (source, repo, remote) {
277
- let { branches: branchPatterns, tags: tagPatterns, worktrees: worktreePatterns = '.' } = source
296
+ let { branches: branchPatterns, tags: tagPatterns, worktrees: worktreePatterns } = source
278
297
  const managed = 'url' in repo
279
298
  const isBare = managed || repo.noCheckout
280
299
  const patternCache = repo.cache[REF_PATTERN_CACHE_KEY]
@@ -294,7 +313,16 @@ async function selectReferences (source, repo, remote) {
294
313
  }
295
314
  }
296
315
  }
297
- if (!branchPatterns) return [...refs.values()]
316
+ if (
317
+ !branchPatterns ||
318
+ !(branchPatterns = Array.isArray(branchPatterns)
319
+ ? branchPatterns.map((pattern) => String(pattern))
320
+ : splitRefPatterns(String(branchPatterns))).length
321
+ ) {
322
+ return [...refs.values()]
323
+ }
324
+ const worktreeName = repo.worktreeName // possibly switch to worktree property ({ name, dir}) in future
325
+ if (worktreeName) branchPatterns = branchPatterns.map((it) => (it === 'HEAD' ? 'HEAD@' + worktreeName : it))
298
326
  if (worktreePatterns) {
299
327
  if (worktreePatterns === '.') {
300
328
  worktreePatterns = ['.']
@@ -304,14 +332,14 @@ async function selectReferences (source, repo, remote) {
304
332
  worktreePatterns = Array.isArray(worktreePatterns)
305
333
  ? worktreePatterns.map((pattern) => String(pattern))
306
334
  : splitRefPatterns(String(worktreePatterns))
335
+ if (worktreeName) worktreePatterns = worktreePatterns.map((it) => (it === '@' ? worktreeName : it))
307
336
  }
308
337
  } else {
309
- worktreePatterns = []
338
+ worktreePatterns = worktreePatterns === undefined ? [worktreeName || '.'] : []
310
339
  }
311
- const branchPatternsString = String(branchPatterns)
312
- if (branchPatternsString === 'HEAD' || branchPatternsString === '.') {
313
- const currentBranch = await getCurrentBranchName(repo, remote)
314
- if (currentBranch) {
340
+ let currentBranch
341
+ if (branchPatterns.length === 1 && (branchPatterns[0] === 'HEAD' || branchPatterns[0] === '.')) {
342
+ if ((currentBranch = await getCurrentBranchName(repo, remote).then((branch) => branch ?? false))) {
315
343
  branchPatterns = [currentBranch]
316
344
  } else if (isBare) {
317
345
  return [...refs.values()]
@@ -321,17 +349,11 @@ async function selectReferences (source, repo, remote) {
321
349
  refs.set('HEAD', { shortname: 'HEAD', fullname: 'HEAD', type: 'branch', detached: true, head })
322
350
  return [...refs.values()]
323
351
  }
324
- } else if (
325
- (branchPatterns = Array.isArray(branchPatterns)
326
- ? branchPatterns.map((pattern) => String(pattern))
327
- : splitRefPatterns(branchPatternsString)).length
328
- ) {
352
+ } else {
329
353
  let headBranchIdx
330
354
  // NOTE we can assume at least two entries if HEAD or . are present
331
355
  if (~(headBranchIdx = branchPatterns.indexOf('HEAD')) || ~(headBranchIdx = branchPatterns.indexOf('.'))) {
332
- const currentBranch = await getCurrentBranchName(repo, remote)
333
- if (currentBranch) {
334
- // NOTE ignore if current branch is already in list
356
+ if ((currentBranch = await getCurrentBranchName(repo, remote).then((branch) => branch ?? false))) {
335
357
  if (~branchPatterns.indexOf(currentBranch)) {
336
358
  branchPatterns.splice(headBranchIdx, 1)
337
359
  } else {
@@ -350,11 +372,11 @@ async function selectReferences (source, repo, remote) {
350
372
  branchPatterns.splice(headBranchIdx, 1)
351
373
  }
352
374
  }
353
- } else {
354
- return [...refs.values()]
355
375
  }
356
376
  // NOTE isomorphic-git includes HEAD in list of remote branches (see https://isomorphic-git.org/docs/listBranches)
357
- const remoteBranches = (await git.listBranches(Object.assign({ remote }, repo))).filter((it) => it !== 'HEAD')
377
+ const remoteBranches = remote
378
+ ? (await git.listBranches(Object.assign({ remote }, repo))).filter((it) => it !== 'HEAD')
379
+ : []
358
380
  if (remoteBranches.length) {
359
381
  for (const shortname of filterRefs(remoteBranches, branchPatterns, patternCache)) {
360
382
  const fullname = 'remotes/' + remote + '/' + shortname
@@ -363,11 +385,27 @@ async function selectReferences (source, repo, remote) {
363
385
  }
364
386
  // NOTE only consider local branches if repo has a worktree or there are no remote tracking branches
365
387
  if (!isBare) {
366
- const localBranches = await git.listBranches(repo)
388
+ const localBranches = await git.listBranches(repo).then((branches) => {
389
+ if (branches.length) return branches
390
+ if (currentBranch == null) return getCurrentBranchName(repo).then((branch) => (branch ? [branch] : []))
391
+ return currentBranch ? [currentBranch] : []
392
+ })
367
393
  if (localBranches.length) {
368
394
  const worktrees = await findWorktrees(repo, worktreePatterns)
369
- for (const shortname of filterRefs(localBranches, branchPatterns, patternCache)) {
370
- const head = worktrees.get(shortname) || noWorktree
395
+ let onMatch
396
+ if ((worktreePatterns.join('') || '.') !== '.') {
397
+ const symbolicNames = new Map()
398
+ worktrees.forEach(({ name, symbolicName = 'HEAD@' + name }, shortname) => {
399
+ localBranches.push(symbolicName)
400
+ symbolicNames.set(symbolicName, shortname)
401
+ })
402
+ onMatch = (candidate, { pattern }) => {
403
+ const shortname = symbolicNames.get(candidate)
404
+ return shortname ? (pattern.startsWith('HEAD@') ? shortname : undefined) : candidate
405
+ }
406
+ }
407
+ for (const shortname of filterRefs(localBranches, branchPatterns, patternCache, onMatch)) {
408
+ const head = (worktrees.get(shortname) || { head: noWorktree }).head
371
409
  refs.set(shortname, { shortname, fullname: 'heads/' + shortname, type: 'branch', head })
372
410
  }
373
411
  }
@@ -387,18 +425,16 @@ async function selectReferences (source, repo, remote) {
387
425
  * Returns the current branch name or undefined if the HEAD is detached.
388
426
  */
389
427
  function getCurrentBranchName (repo, remote) {
390
- let refPromise
391
- if (repo.noCheckout) {
392
- refPromise = git
393
- .resolveRef(Object.assign({ ref: 'refs/remotes/' + remote + '/HEAD', depth: 2 }, repo))
394
- .catch(() => git.resolveRef(Object.assign({ ref: 'HEAD', depth: 2 }, repo)))
395
- } else {
396
- refPromise = git.resolveRef(Object.assign({ ref: 'HEAD', depth: 2 }, repo))
397
- }
398
- return refPromise.then((ref) => (ref.startsWith('refs/') ? ref.replace(SHORTEN_REF_RX, '') : undefined))
428
+ return (
429
+ remote && repo.noCheckout
430
+ ? git
431
+ .resolveRef(Object.assign({ ref: 'refs/remotes/' + remote + '/HEAD', depth: 2 }, repo))
432
+ .catch(() => git.resolveRef(Object.assign({ ref: 'HEAD', depth: 2 }, repo)))
433
+ : git.resolveRef(Object.assign({ ref: 'HEAD', depth: 2 }, repo))
434
+ ).then((ref) => (ref.startsWith('refs/') ? ref.replace(SHORTEN_REF_RX, '') : undefined))
399
435
  }
400
436
 
401
- async function selectStartPaths (source, repo, remoteName, ref) {
437
+ async function selectStartPaths (source, repo, ref) {
402
438
  const url = repo.url
403
439
  const displayUrl = url || repo.dir
404
440
  const worktreePath = ref.head
@@ -440,7 +476,7 @@ function collectFilesFromStartPath (startPath, repo, authStatus, ref, originUrl,
440
476
  return (worktreePath ? readFilesFromWorktree(origin) : readFilesFromGitTree(repo, ref.oid, startPath))
441
477
  .then((files) => {
442
478
  const batch = deepClone((origin.descriptor = loadComponentDescriptor(files, ref, version)))
443
- if ('nav' in batch) batch.nav.origin = origin
479
+ if ('nav' in batch && Array.isArray(batch.nav)) batch.nav.origin = origin
444
480
  batch.files = files.map((file) => assignFileProperties(file, origin))
445
481
  batch.origins = [origin]
446
482
  return batch
@@ -525,9 +561,9 @@ function readFilesFromGitTree (repo, oid, startPath) {
525
561
  Object.assign(root, { dirname: '' })
526
562
  return startPath
527
563
  ? getGitTreeAtStartPath(repo, oid, startPath).then((start) => {
528
- Object.assign(start, { dirname: startPath })
529
- return srcGitTree(repo, root, start)
530
- })
564
+ Object.assign(start, { dirname: startPath })
565
+ return srcGitTree(repo, root, start)
566
+ })
531
567
  : srcGitTree(repo, root)
532
568
  })
533
569
  }
@@ -582,7 +618,8 @@ function visitGitTree (emitter, repo, root, filter, convert, parent, dirname = '
582
618
  (target) => {
583
619
  if (target.type === 'tree') {
584
620
  return visitGitTree(emitter, repo, root, filter, convert, target, vfilePath, target.following)
585
- } else if (target.type === 'blob' && filterVerdict === true && (mode = FILE_MODES[target.mode])) {
621
+ }
622
+ if (target.type === 'blob' && filterVerdict === true && (mode = FILE_MODES[target.mode])) {
586
623
  return convert(Object.assign({ mode, oid: target.oid, path: vfilePath }, repo)).then((result) =>
587
624
  emitter.emit('entry', result)
588
625
  )
@@ -654,11 +691,11 @@ function readGitObjectAtPath (repo, root, parent, pathSegments, following) {
654
691
  if (entry.path === firstPathSegment) {
655
692
  return entry.type === 'tree'
656
693
  ? git.readTree(Object.assign({ oid: entry.oid }, repo)).then((subtree) => {
657
- Object.assign(subtree, { dirname: path.join(parent.dirname, entry.path) })
658
- return (pathSegments = pathSegments.slice(1)).length
659
- ? readGitObjectAtPath(repo, root, subtree, pathSegments, following)
660
- : Object.assign(subtree, { type: 'tree', following }) // Q: should this create copy?
661
- })
694
+ Object.assign(subtree, { dirname: path.join(parent.dirname, entry.path) })
695
+ return (pathSegments = pathSegments.slice(1)).length
696
+ ? readGitObjectAtPath(repo, root, subtree, pathSegments, following)
697
+ : Object.assign(subtree, { type: 'tree', following }) // Q: should this create copy?
698
+ })
662
699
  : entry.mode === SYMLINK_FILE_MODE
663
700
  ? readGitSymlink(repo, root, parent, entry, following)
664
701
  : Promise.resolve(entry)
@@ -712,18 +749,18 @@ function loadComponentDescriptor (files, ref, version) {
712
749
  if (!version) {
713
750
  if (version === undefined) throw new Error(`${COMPONENT_DESC_FILENAME} is missing a version`)
714
751
  if (version === false) throw new Error(`${COMPONENT_DESC_FILENAME} has an invalid version`)
715
- version = '' + (typeof version === 'number' ? version : '')
752
+ version = typeof version === 'number' ? '' + version : ''
716
753
  } else if (version === true) {
717
754
  version = ref.shortname.replace(PATH_SEPARATOR_RX, '-')
718
755
  } else if (version.constructor === Object) {
719
756
  const refname = ref.shortname
720
757
  let matched
721
758
  if (refname in version) {
722
- matched = version[refname]
759
+ matched = '' + (version[refname] ?? '')
723
760
  } else if (
724
761
  !Object.entries(version).some(([pattern, replacement]) => {
725
- const result = refname.replace(makeMatcherRx(pattern, VERSION_MATCHER_OPTS), '\0' + replacement)
726
- if (result === refname) return false
762
+ const result = refname.replace(makeMatcherRx(pattern, VERSION_MATCHER_OPTS), '\0' + (replacement ?? ''))
763
+ if (result === refname) return false // no match
727
764
  matched = result.substr(1)
728
765
  return true
729
766
  })
@@ -738,7 +775,7 @@ function loadComponentDescriptor (files, ref, version) {
738
775
  throw new Error(`version in ${COMPONENT_DESC_FILENAME} cannot have path segments: ${version}`)
739
776
  }
740
777
  data.version = version
741
- return camelCaseKeys(data, ['asciidoc'])
778
+ return camelCaseKeys(data, ['asciidoc', 'ext'])
742
779
  }
743
780
 
744
781
  function assignFileProperties (file, origin) {
@@ -755,18 +792,20 @@ function assignFileProperties (file, origin) {
755
792
  return file
756
793
  }
757
794
 
758
- function buildFetchOptions (repo, progress, displayUrl, credentialsFromUrl, gitPlugins, fetchTags, operation) {
795
+ function buildFetchOptions (repo, progress, displayUrl, credentialsFromUrl, gitPlugins, fetch, operation) {
759
796
  const { credentialManager, http, urlRouter } = gitPlugins
797
+ const corsProxy = false
798
+ const depth = fetch.depth || undefined
760
799
  const onAuth = resolveCredentials.bind(credentialManager, new Map().set(undefined, credentialsFromUrl))
761
800
  const onAuthFailure = onAuth
762
801
  const onAuthSuccess = (url) => credentialManager.approved({ url })
763
- const opts = Object.assign({ corsProxy: false, depth: 1, http, onAuth, onAuthFailure, onAuthSuccess }, repo)
802
+ const opts = Object.assign({ corsProxy, depth, http, onAuth, onAuthFailure, onAuthSuccess }, repo)
764
803
  if (urlRouter) opts.url = urlRouter.ensureGitSuffix(opts.url)
765
804
  if (progress) opts.onProgress = createProgressListener(progress, displayUrl, operation)
766
805
  if (operation === 'fetch') {
767
806
  opts.prune = true
768
- if (fetchTags) opts.tags = opts.pruneTags = true
769
- } else if (!fetchTags) {
807
+ if (fetch.tags) opts.tags = opts.pruneTags = true
808
+ } else if (!fetch.tags) {
770
809
  opts.noTags = true
771
810
  }
772
811
  return opts
@@ -861,7 +900,11 @@ function resolveCredentials (credentialsFromUrlHolder, url, auth) {
861
900
  }
862
901
 
863
902
  function identifyAuthStatus (credentialManager, credentials, url) {
864
- return credentials ? 'auth-embedded' : credentialManager.status({ url }) ? 'auth-required' : undefined
903
+ const status = credentialManager.status({ url })
904
+ if (credentials) {
905
+ return typeof status === 'string' && status.startsWith('requested,') ? 'auth-required' : 'auth-embedded'
906
+ }
907
+ if (status != null) return 'auth-required'
865
908
  }
866
909
 
867
910
  /**
@@ -870,7 +913,7 @@ function identifyAuthStatus (credentialManager, credentials, url) {
870
913
  * The purpose of this function is generate a safe, unique folder name for the cloned
871
914
  * repository that gets stored in the cache directory.
872
915
  *
873
- * The generated folder name follows the pattern: <basename>-<sha1>-<version>.git
916
+ * The generated folder name follows the pattern: <basename>-<sha1-of-normalized-url>.git
874
917
  *
875
918
  * @param {String} url - The repository URL to convert.
876
919
  * @returns {String} The generated folder name.
@@ -886,21 +929,18 @@ function generateCloneFolderName (url) {
886
929
  *
887
930
  * @param {Repository} repo - The repository on which to operate.
888
931
  * @param {String} remoteName - The name of the remote to resolve.
889
- * @returns {String} The URL of the specified remote, if defined, or the file URI to the local repository.
932
+ * @returns {String} The URL of the specified remote, if defined
890
933
  */
891
934
  function resolveRemoteUrl (repo, remoteName) {
892
935
  return git.getConfig(Object.assign({ path: 'remote.' + remoteName + '.url' }, repo)).then((url) => {
893
- if (url) {
894
- if (url.startsWith('https://') || url.startsWith('http://')) {
895
- return ~url.indexOf('@') ? url.replace(URL_AUTH_CLEANER_RX, '$1') : url
896
- } else if (url.startsWith('git@')) {
897
- return 'https://' + url.substr(4).replace(':', '/')
898
- } else if (url.startsWith('ssh://')) {
899
- return 'https://' + url.substr(url.indexOf('@') + 1 || 6).replace(URL_PORT_CLEANER_RX, '$1')
900
- }
936
+ if (!url) return
937
+ if (url.startsWith('https://') || url.startsWith('http://')) {
938
+ return ~url.indexOf('@') ? url.replace(URL_AUTH_CLEANER_RX, '$1') : url
939
+ }
940
+ if (url.startsWith('git@')) return 'https://' + url.substr(4).replace(':', '/')
941
+ if (url.startsWith('ssh://')) {
942
+ return 'https://' + url.substr(url.indexOf('@') + 1 || 6).replace(URL_PORT_CLEANER_RX, '$1')
901
943
  }
902
- url = posixify ? 'file:///' + posixify(repo.dir) : 'file://' + repo.dir
903
- return ~url.indexOf(' ') ? url.replace(SPACE_RX, '%20') : url
904
944
  })
905
945
  }
906
946
 
@@ -928,7 +968,7 @@ function loadGitPlugins (gitConfig, networkConfig, startDir) {
928
968
  if (typeof credentialManager.configure === 'function') {
929
969
  credentialManager.configure({ config: gitConfig.credentials, startDir })
930
970
  }
931
- if (typeof credentialManager.status !== 'function') Object.assign(credentialManager, { status () {} })
971
+ if (typeof credentialManager.status !== 'function') Object.assign(credentialManager, { status: invariably.void })
932
972
  } else {
933
973
  credentialManager = new GitCredentialManagerStore().configure({ config: gitConfig.credentials, startDir })
934
974
  }
@@ -1018,39 +1058,50 @@ function coerceToString (value) {
1018
1058
  return value == null ? '' : String(value)
1019
1059
  }
1020
1060
 
1061
+ function resolveRepositoryFromWorktree (repo) {
1062
+ return fsp
1063
+ .readFile(repo.gitdir, 'utf8')
1064
+ .then((contents) => contents.substr(8).trimEnd())
1065
+ .then((worktreeGitdir) =>
1066
+ fsp.readFile(ospath.join(worktreeGitdir, 'commondir'), 'utf8').then(
1067
+ (contents) => {
1068
+ const gitdir = ospath.join(worktreeGitdir, contents.trimEnd())
1069
+ const dir = ospath.basename(gitdir) === '.git' ? ospath.dirname(gitdir) : gitdir
1070
+ return Object.assign(repo, { dir, gitdir, worktreeName: ospath.basename(worktreeGitdir) })
1071
+ },
1072
+ () => repo
1073
+ )
1074
+ )
1075
+ }
1076
+
1021
1077
  function findWorktrees (repo, patterns) {
1022
1078
  if (!patterns.length) return new Map()
1023
- const linkedOnly = patterns[0] === '.' ? !(patterns = patterns.slice(1)) : true
1024
- let worktreesDir
1079
+ const mainWorktree =
1080
+ patterns[0] === '.' && (patterns = patterns.slice(1))
1081
+ ? getCurrentBranchName(repo).then((branch) => branch && [branch, { head: repo.dir, name: '.' }])
1082
+ : Promise.resolve()
1083
+ const worktreesDir = patterns.length ? ospath.join(repo.dir, '.git', 'worktrees') : undefined
1025
1084
  const patternCache = repo.cache[REF_PATTERN_CACHE_KEY]
1026
1085
  return (
1027
- patterns.length
1086
+ worktreesDir
1028
1087
  ? fsp
1029
- .readdir((worktreesDir = ospath.join(repo.dir, '.git', 'worktrees')))
1030
- .then((worktreeNames) => filterRefs(worktreeNames, patterns, patternCache), invariably.emptyArray)
1031
- .then((worktreeNames) =>
1032
- worktreeNames.length
1033
- ? Promise.all(
1088
+ .readdir(worktreesDir)
1089
+ .then((worktreeNames) => filterRefs(worktreeNames, patterns, patternCache), invariably.emptyArray)
1090
+ .then((worktreeNames) =>
1091
+ Promise.all(
1034
1092
  worktreeNames.map((worktreeName) => {
1035
1093
  const gitdir = ospath.resolve(worktreesDir, worktreeName)
1036
- // NOTE uses name of worktree as branch name if HEAD is detached
1037
- return git
1038
- .currentBranch(Object.assign({}, repo, { gitdir }))
1039
- .then((branch = worktreeName) =>
1040
- fsp
1041
- .readFile(ospath.join(gitdir, 'gitdir'), 'utf8')
1042
- .then((contents) => ({ branch, dir: ospath.dirname(contents.trimEnd()) }))
1043
- )
1094
+ // NOTE branch name defaults to worktree name if HEAD is detached
1095
+ return getCurrentBranchName(Object.assign({}, repo, { gitdir })).then((branch = worktreeName) =>
1096
+ fsp
1097
+ .readFile(ospath.join(gitdir, 'gitdir'), 'utf8')
1098
+ .then((contents) => [branch, { head: ospath.dirname(contents.trimEnd()), name: worktreeName }])
1099
+ )
1044
1100
  })
1045
- ).then((entries) => entries.reduce((accum, it) => accum.set(it.branch, it.dir), new Map()))
1046
- : new Map()
1047
- )
1048
- : Promise.resolve(new Map())
1049
- ).then((worktrees) =>
1050
- linkedOnly
1051
- ? worktrees
1052
- : git.currentBranch(repo).then((branch) => (branch ? worktrees.set(branch, repo.dir) : worktrees))
1053
- )
1101
+ )
1102
+ )
1103
+ : Promise.resolve()
1104
+ ).then((entries = []) => mainWorktree.then((entry) => new Map(entry ? entries.push(entry) && entries : entries)))
1054
1105
  }
1055
1106
 
1056
1107
  async function gracefulPromiseAllWithLimit (tasks, limit = Infinity) {
@@ -1,10 +1,10 @@
1
1
  'use strict'
2
2
 
3
- const { posix: path } = require('path')
3
+ const { posix: path } = require('node:path')
4
4
  const posixify = require('./posixify')
5
5
  const removeGitSuffix = require('./remove-git-suffix')
6
6
 
7
- const EDIT_URL_TEMPLATE_VAR_RX = /\{(web_url|ref(?:hash|name|type)|path)\}/g
7
+ const EDIT_URL_TEMPLATE_VAR_RX = /\{(web_url|ref(?:hash|name|type)?|path)\}/g
8
8
  const HOSTED_GIT_REPO_RX = /^(?:https?:\/\/|.+@)(git(?:hub|lab)\.com|bitbucket\.org|pagure\.io)[/:](.+?)(?:\.git)?$/
9
9
 
10
10
  function computeOrigin (url, authStatus, gitdir, ref, startPath, worktreePath = undefined, editUrl = true) {
@@ -24,7 +24,7 @@ function computeOrigin (url, authStatus, gitdir, ref, startPath, worktreePath =
24
24
  if (authStatus) origin.private = authStatus
25
25
  if (url) origin.webUrl = removeGitSuffix(url)
26
26
  if (editUrl === true) {
27
- const match = url && url.match(HOSTED_GIT_REPO_RX)
27
+ const match = url?.match(HOSTED_GIT_REPO_RX)
28
28
  if (match) {
29
29
  const host = match[1]
30
30
  let action = 'blob'
@@ -33,14 +33,16 @@ function computeOrigin (url, authStatus, gitdir, ref, startPath, worktreePath =
33
33
  category = 'f'
34
34
  } else if (host === 'bitbucket.org') {
35
35
  action = 'src'
36
- } else if (reftype === 'branch') {
37
- action = 'edit'
36
+ } else {
37
+ if (reftype === 'branch') action = 'edit'
38
+ if (host.startsWith('gitlab.')) action = '-/' + action
38
39
  }
39
40
  origin.editUrlPattern = 'https://' + path.join(match[1], match[2], action, refname, category, startPath, '%s')
40
41
  }
41
42
  } else if (editUrl) {
42
43
  const vars = {
43
44
  path: () => (startPath ? path.join(startPath, '%s') : '%s'),
45
+ ref: () => 'refs/' + (reftype === 'branch' ? 'heads' : reftype) + '/' + refname,
44
46
  refhash: () => refhash,
45
47
  reftype: () => reftype,
46
48
  refname: () => refname,
@@ -3,4 +3,4 @@
3
3
  module.exports = (({ StringDecoder }) => {
4
4
  const decoder = new StringDecoder()
5
5
  return decoder.write.bind(decoder)
6
- })(require('string_decoder'))
6
+ })(require('node:string_decoder'))
@@ -4,19 +4,20 @@ const { makeMatcherRx, refMatcherOpts: getMatcherOpts, MATCH_ALL_RX } = require(
4
4
 
5
5
  function compileRx (pattern, opts) {
6
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)
7
+ const rx =
8
+ pattern.charAt() === '!' // we handle negate ourselves
9
+ ? Object.defineProperty(makeMatcherRx((pattern = pattern.substr(1)), opts), 'negated', { value: true })
10
+ : makeMatcherRx(pattern, opts)
11
+ return Object.defineProperty(rx, 'pattern', { value: pattern })
10
12
  }
11
13
 
12
- function createMatcher (patterns, cache, opts) {
14
+ function createMatcher (patterns, cache = Object.assign(new Map(), { braces: new Map() })) {
13
15
  const rxs = patterns.map(
14
- (pattern) =>
15
- cache.get(pattern) || cache.set(pattern, compileRx(pattern, opts || (opts = getMatcherOpts(cache)))).get(pattern)
16
+ (pattern) => cache.get(pattern) || cache.set(pattern, compileRx(pattern, getMatcherOpts(cache))).get(pattern)
16
17
  )
17
18
  if (rxs[0].negated) rxs.unshift(MATCH_ALL_RX)
18
- return (candidate) => {
19
- let matched
19
+ return (candidate, onMatch) => {
20
+ let matched, symbolic
20
21
  for (const rx of rxs) {
21
22
  let voteIfMatched = true
22
23
  if (matched) {
@@ -25,16 +26,22 @@ function createMatcher (patterns, cache, opts) {
25
26
  } else if (rx.negated) {
26
27
  continue
27
28
  }
28
- if (rx.test(candidate)) matched = voteIfMatched
29
+ if (rx.test(candidate) || (symbolic && rx.test(symbolic) && (candidate = symbolic))) {
30
+ if (onMatch) {
31
+ if (!(matched = onMatch(candidate, rx))) continue
32
+ ;[symbolic, candidate] = [candidate, matched]
33
+ }
34
+ matched = voteIfMatched && candidate
35
+ }
29
36
  }
30
37
  return matched
31
38
  }
32
39
  }
33
40
 
34
- function filterRefs (candidates, patterns, cache = Object.assign(new Map(), { braces: new Map() })) {
35
- const isMatch = createMatcher(patterns, cache)
41
+ function filterRefs (candidates, patterns, cache, onMatch) {
42
+ const match = createMatcher(patterns, cache)
36
43
  return candidates.reduce((accum, candidate) => {
37
- if (isMatch(candidate)) accum.push(candidate)
44
+ if ((candidate = match(candidate, onMatch))) accum.push(candidate)
38
45
  return accum
39
46
  }, [])
40
47
  }
@@ -1,10 +1,10 @@
1
1
  'use strict'
2
2
 
3
- const { homedir } = require('os')
3
+ const { homedir } = require('node:os')
4
4
  const expandPath = require('@antora/expand-path-helper')
5
- const { promises: fsp } = require('fs')
5
+ const { promises: fsp } = require('node:fs')
6
6
  const invariably = require('./invariably')
7
- const ospath = require('path')
7
+ const ospath = require('node:path')
8
8
 
9
9
  class GitCredentialManagerStore {
10
10
  configure ({ config, startDir }) {
@@ -86,11 +86,11 @@ class GitCredentialManagerStore {
86
86
  }
87
87
 
88
88
  async approved ({ url }) {
89
- this.urls[url] = 'approved'
89
+ this.urls[url] = (url in this.urls ? this.urls[url] + ',' : '') + 'approved'
90
90
  }
91
91
 
92
92
  async rejected ({ url, auth }) {
93
- this.urls[url] = 'rejected'
93
+ this.urls[url] = (url in this.urls ? this.urls[url] + ',' : '') + 'rejected'
94
94
  const statusCode = 401
95
95
  const statusMessage = 'HTTP Basic: Access Denied'
96
96
  const err = new Error(`HTTP Error: ${statusCode} ${statusMessage}`)
package/lib/git.js CHANGED
@@ -1,3 +1,10 @@
1
1
  'use strict'
2
2
 
3
- module.exports = require('isomorphic-git')
3
+ const zlib = require('node:zlib')
4
+ const { promisify } = require('node:util')
5
+
6
+ module.exports = ((pakoModuleId) => {
7
+ const git = require('isomorphic-git')
8
+ require(pakoModuleId).inflate = promisify(zlib.inflate)
9
+ return git
10
+ })('pako')
package/lib/matcher.js CHANGED
@@ -17,7 +17,7 @@ const BASE_OPTS = {
17
17
  }
18
18
 
19
19
  module.exports = {
20
- MATCH_ALL_RX: { test: () => true },
20
+ MATCH_ALL_RX: Object.defineProperty({ test: () => true }, 'pattern', { value: '*' }),
21
21
  expandBraces,
22
22
  makeMatcherRx: makeRe,
23
23
  pathMatcherOpts: Object.assign({}, BASE_OPTS, { dot: false }),
package/lib/posixify.js CHANGED
@@ -1,3 +1,3 @@
1
1
  'use strict'
2
2
 
3
- module.exports = require('path').sep === '\\' ? (p) => p.replace(/\\/g, '/') : undefined
3
+ module.exports = require('node:path').sep === '\\' ? (p) => p.replace(/\\/g, '/') : undefined
@@ -1,7 +1,7 @@
1
1
  'use strict'
2
2
 
3
3
  const deepFlatten = require('./deep-flatten')
4
- const { promises: fsp } = require('fs')
4
+ const { promises: fsp } = require('node:fs')
5
5
  const git = require('./git')
6
6
  const invariably = require('./invariably')
7
7
  const { expandBraces, makeMatcherRx, pathMatcherOpts: MATCHER_OPTS } = require('./matcher')
@@ -16,11 +16,11 @@ function resolvePathGlobs (base, patterns, listDirents, retrievePath, tree = { p
16
16
  if (resolvedPaths.length) {
17
17
  const rx = makeMatcherRx(pattern.substr(1), MATCHER_OPTS)
18
18
  return resolvedPaths.filter((it) => !rx.test(it))
19
- } else {
20
- return resolvedPaths
21
19
  }
20
+ return resolvedPaths
22
21
  })
23
- } else if (RX_MAGIC_DETECTOR.test(pattern)) {
22
+ }
23
+ if (RX_MAGIC_DETECTOR.test(pattern)) {
24
24
  return glob(base, pattern.split('/'), listDirents, retrievePath, tree).then((nestedPaths) =>
25
25
  paths.then((resolvedPaths) => [...resolvedPaths, ...nestedPaths])
26
26
  )
@@ -62,35 +62,36 @@ async function glob (base, patternSegments, listDirents, retrievePath, { oid, pa
62
62
  dirent.isDirectory() && isMatch(dirent.name)
63
63
  ? patternSegments.length
64
64
  ? glob(base, patternSegments, listDirents, retrievePath, {
65
- oid: dirent.oid,
66
- path: joinPath(path, dirent.name),
67
- globbed: true,
68
- })
65
+ oid: dirent.oid,
66
+ path: joinPath(path, dirent.name),
67
+ globbed: true,
68
+ })
69
69
  : joinPath(path, dirent.name)
70
70
  : []
71
71
  )
72
72
  )
73
73
  )
74
74
  return explicit ? [...[...explicit].map((it) => joinPath(path, it)), ...discovered] : discovered
75
- } else {
76
- const [magicBase, nextSegment] = extractMagicBase(patternSegments, patternSegment)
77
- patternSegment = magicBase
78
- if (nextSegment) {
79
- const obj = await retrievePath(base, { oid, path }, patternSegment)
80
- if (obj) {
81
- return glob(base, patternSegments, listDirents, retrievePath, {
82
- oid: obj.oid,
83
- path: joinPath(path, patternSegment),
84
- })
85
- } else if ((patternSegment += '/' + patternSegments.join('/')).indexOf('{')) {
86
- return expandBraces(patternSegment).map((it) => joinPath(path, it))
87
- }
88
- return [joinPath(path, patternSegment)]
89
- } else if (globbed) {
90
- return (await retrievePath(base, { oid, path }, patternSegment)) ? [joinPath(path, patternSegment)] : []
75
+ }
76
+ const [magicBase, nextSegment] = extractMagicBase(patternSegments, patternSegment)
77
+ patternSegment = magicBase
78
+ if (nextSegment) {
79
+ const obj = await retrievePath(base, { oid, path }, patternSegment)
80
+ if (obj) {
81
+ return glob(base, patternSegments, listDirents, retrievePath, {
82
+ oid: obj.oid,
83
+ path: joinPath(path, patternSegment),
84
+ })
85
+ }
86
+ if ((patternSegment += '/' + patternSegments.join('/')).indexOf('{')) {
87
+ return expandBraces(patternSegment).map((it) => joinPath(path, it))
91
88
  }
92
89
  return [joinPath(path, patternSegment)]
93
90
  }
91
+ if (globbed) {
92
+ return (await retrievePath(base, { oid, path }, patternSegment)) ? [joinPath(path, patternSegment)] : []
93
+ }
94
+ return [joinPath(path, patternSegment)]
94
95
  }
95
96
 
96
97
  function extractMagicBase (patternSegments, base) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@antora/content-aggregator",
3
- "version": "3.1.9",
3
+ "version": "3.1.11",
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)",
@@ -11,7 +11,11 @@
11
11
  "Balachandran Sivakumar <balachandran@balachandran.org>"
12
12
  ],
13
13
  "homepage": "https://antora.org",
14
- "repository": "gitlab:antora/antora",
14
+ "repository": {
15
+ "type": "git",
16
+ "url": "git+https://gitlab.com/antora/antora.git",
17
+ "directory": "packages/content-aggregator"
18
+ },
15
19
  "bugs": {
16
20
  "url": "https://gitlab.com/antora/antora/issues"
17
21
  },
@@ -28,9 +32,9 @@
28
32
  "#constants": "./lib/constants.js"
29
33
  },
30
34
  "dependencies": {
31
- "@antora/expand-path-helper": "~2.0",
32
- "@antora/logger": "3.1.9",
33
- "@antora/user-require-helper": "~2.0",
35
+ "@antora/expand-path-helper": "~3.0",
36
+ "@antora/logger": "3.1.11",
37
+ "@antora/user-require-helper": "~3.0",
34
38
  "braces": "~3.0",
35
39
  "cache-directory": "~2.0",
36
40
  "fast-glob": "~3.3",
@@ -45,7 +49,7 @@
45
49
  "vinyl": "~3.0"
46
50
  },
47
51
  "engines": {
48
- "node": ">=16.0.0"
52
+ "node": ">=18.0.0"
49
53
  },
50
54
  "files": [
51
55
  "lib/"
@@ -62,7 +66,7 @@
62
66
  ],
63
67
  "scripts": {
64
68
  "test": "_mocha",
65
- "prepublishOnly": "npx -y downdoc --prepublish",
66
- "postpublish": "npx -y downdoc --postpublish"
69
+ "prepublishOnly": "npx -y downdoc@latest --prepublish",
70
+ "postpublish": "npx -y downdoc@latest --postpublish"
67
71
  }
68
72
  }