@antora/content-classifier 3.1.2 → 3.2.0-alpha.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 CHANGED
@@ -9,4 +9,4 @@ Its site generator aggregates documents from versioned content repositories and
9
9
 
10
10
  Copyright (C) 2017-present [OpenDevise Inc.](https://opendevise.com) and the [Antora Project](https://antora.org).
11
11
 
12
- Use of this software is granted under the terms of the [Mozilla Public License Version 2.0](https://www.mozilla.org/en-US/MPL/2.0/) (MPL-2.0).
12
+ Use of this software is granted under the terms of the [Mozilla Public License Version 2.0](https://www.mozilla.org/en-US/MPL/2.0/) (MPL-2.0).
@@ -8,42 +8,58 @@ const collateAsciiDocAttributes = require('@antora/asciidoc-loader/config/collat
8
8
  *
9
9
  * @memberof content-classifier
10
10
  *
11
- * @param {Object} playbook - The configuration object for Antora.
11
+ * @param {Object} playbook - The configuration object for Antora. See ContentCatalog constructor for relevant keys.
12
12
  * @param {Object} playbook.site - Site-related configuration data.
13
13
  * @param {String} playbook.site.startPage - The start page for the site; redirects from base URL.
14
- * @param {Object} playbook.urls - URL settings for the site.
15
- * @param {String} playbook.urls.htmlExtensionStyle - The style to use when computing page URLs.
16
14
  * @param {Object} aggregate - The raw aggregate of virtual file objects to be classified.
17
15
  * @param {Object} [siteAsciiDocConfig={}] - Site-wide AsciiDoc processor configuration options.
18
- * @returns {ContentCatalog} A structured catalog of content components and virtual content files.
16
+ * @param {Function} [onComponentsRegistered] - A function (optionally async) to invoke after components are
17
+ * registered. Must return an instance of ContentCatalog. If async, this function will also return a Promise.
18
+ *
19
+ * @returns {ContentCatalog} A structured catalog of content components, versions, and virtual content files.
19
20
  */
20
- function classifyContent (playbook, aggregate, siteAsciiDocConfig = {}) {
21
- const contentCatalog = new ContentCatalog(playbook)
22
- aggregate
23
- .reduce((accum, componentVersionData) => {
24
- // drop files since they aren't needed to register component version
25
- // drop startPage to defer registration of start page
26
- const { name, version, files, startPage, ...descriptor } = Object.assign({}, componentVersionData, {
27
- asciidoc: resolveAsciiDocConfig(siteAsciiDocConfig, componentVersionData),
28
- })
29
- return new Map(accum).set(
30
- contentCatalog.registerComponentVersion(name, version, descriptor),
31
- componentVersionData
32
- )
33
- }, new Map())
34
- .forEach((componentVersionData, componentVersion) => {
35
- const { name, version } = componentVersion
36
- const { files, nav, startPage } = componentVersionData
37
- componentVersionData.files = undefined // clean up memory
38
- files.forEach((file) => allocateSrc(file, name, version, nav) && contentCatalog.addFile(file))
39
- contentCatalog.registerComponentVersionStartPage(name, componentVersion, startPage)
21
+ function classifyContent (playbook, aggregate, siteAsciiDocConfig = {}, onComponentsRegistered) {
22
+ const siteStartPage = playbook.site.startPage
23
+ let contentCatalog = registerComponentVersions(new ContentCatalog(playbook), aggregate, siteAsciiDocConfig)
24
+ return typeof onComponentsRegistered === 'function' &&
25
+ (contentCatalog = onComponentsRegistered(contentCatalog)) instanceof Promise
26
+ ? contentCatalog.then((contentCatalogValue) => addFilesAndRegisterStartPages(contentCatalogValue, siteStartPage))
27
+ : addFilesAndRegisterStartPages(contentCatalog, siteStartPage)
28
+ }
29
+
30
+ function registerComponentVersions (contentCatalog, aggregate, siteAsciiDocConfig) {
31
+ for (const componentVersionBucket of aggregate) {
32
+ // advance files, nav, and startPage to component version to be used in later phase
33
+ const { name, version, files, nav, startPage, ...data } = Object.assign(componentVersionBucket, {
34
+ asciidoc: resolveAsciiDocConfig(siteAsciiDocConfig, componentVersionBucket),
40
35
  })
41
- contentCatalog.registerSiteStartPage(playbook.site.startPage)
36
+ Object.assign(contentCatalog.registerComponentVersion(name, version, data), { files, nav, startPage })
37
+ }
38
+ return contentCatalog
39
+ }
40
+
41
+ function addFilesAndRegisterStartPages (contentCatalog, siteStartPage) {
42
+ for (const { versions: componentVersions } of contentCatalog.getComponents()) {
43
+ for (const componentVersion of componentVersions) {
44
+ const { name: component, version, files = [], nav, startPage } = componentVersion
45
+ for (let file, i = 0, len = files.length; i < len; i++) {
46
+ allocateSrc((file = files[i]), component, version, nav) && contentCatalog.addFile(file, componentVersion)
47
+ files[i] = undefined // free memory
48
+ }
49
+ contentCatalog.registerComponentVersionStartPage(component, componentVersion, startPage)
50
+ }
51
+ }
52
+ contentCatalog.registerSiteStartPage(siteStartPage)
42
53
  return contentCatalog
43
54
  }
44
55
 
45
56
  function allocateSrc (file, component, version, nav) {
46
- const extname = file.src.extname
57
+ const { extname, family } = file.src
58
+ if (family && family !== 'nav') {
59
+ Object.assign(file.src, { component, version })
60
+ file.src.moduleRootPath ??= calculateRootPath(file.src.relative.split('/').length)
61
+ return true
62
+ }
47
63
  const filepath = file.path
48
64
  const navInfo = nav && getNavInfo(filepath, nav)
49
65
  const pathSegments = filepath.split('/')
@@ -23,17 +23,17 @@ class ContentCatalog {
23
23
  const urls = playbook.urls || {}
24
24
  this.htmlUrlExtensionStyle = urls.htmlExtensionStyle || 'default'
25
25
  this.urlRedirectFacility = urls.redirectFacility || 'static'
26
- this.latestVersionUrlSegment = urls.latestVersionSegment
27
- this.latestPrereleaseVersionUrlSegment = urls.latestPrereleaseVersionSegment
28
- if (this.latestVersionUrlSegment == null && this.latestPrereleaseVersionUrlSegment == null) {
29
- this.latestVersionUrlSegmentStrategy = undefined
26
+ this.latestVersionSegment = urls.latestVersionSegment
27
+ this.latestPrereleaseVersionSegment = urls.latestPrereleaseVersionSegment
28
+ if (this.latestVersionSegment == null && this.latestPrereleaseVersionSegment == null) {
29
+ this.latestVersionSegmentStrategy = undefined
30
30
  } else {
31
- this.latestVersionUrlSegmentStrategy = urls.latestVersionSegmentStrategy || 'replace'
32
- if (this.latestVersionUrlSegmentStrategy === 'redirect:from') {
33
- if (!this.latestVersionUrlSegment) this.latestVersionUrlSegment = undefined
34
- if (!this.latestPrereleaseVersionUrlSegment) {
35
- this.latestPrereleaseVersionUrlSegment = undefined
36
- if (!this.latestVersionUrlSegment) this.latestVersionUrlSegmentStrategy = undefined
31
+ this.latestVersionSegmentStrategy = urls.latestVersionSegmentStrategy || 'replace'
32
+ if (this.latestVersionSegmentStrategy === 'redirect:from') {
33
+ if (!this.latestVersionSegment) this.latestVersionSegment = undefined
34
+ if (!this.latestPrereleaseVersionSegment) {
35
+ this.latestPrereleaseVersionSegment = undefined
36
+ if (!this.latestVersionSegment) this.latestVersionSegmentStrategy = undefined
37
37
  }
38
38
  }
39
39
  }
@@ -42,6 +42,8 @@ class ContentCatalog {
42
42
  /**
43
43
  * Registers a new component version with the content catalog. Also registers the component if it does not yet exist.
44
44
  *
45
+ * Must be followed by a call to registerComponentVersionStartPage to finalize object.
46
+ *
45
47
  * @param {String} name - The name of the component to which this component version belongs.
46
48
  * @param {String} version - The version of the component to register.
47
49
  * @param {Object} [descriptor={}] - The configuration data for the component version.
@@ -58,8 +60,9 @@ class ContentCatalog {
58
60
  * @returns {Object} The constructed component version object.
59
61
  */
60
62
  registerComponentVersion (name, version, descriptor = {}) {
61
- const { asciidoc, displayVersion, prerelease, startPage: startPageSpec, title } = descriptor
63
+ const { asciidoc, displayVersion, prerelease, startPage: startPageSpec, title, versionSegment } = descriptor
62
64
  const componentVersion = { displayVersion: displayVersion || version || 'default', title: title || name, version }
65
+ if (versionSegment != null) componentVersion.versionSegment = versionSegment
63
66
  Object.defineProperty(componentVersion, 'name', { value: name, enumerable: true })
64
67
  if (prerelease) {
65
68
  componentVersion.prerelease = prerelease
@@ -127,14 +130,15 @@ class ContentCatalog {
127
130
  )
128
131
  }
129
132
  if (startPageSpec) {
133
+ // @deprecated use separate call to register start page for component version
130
134
  this.registerComponentVersionStartPage(name, componentVersion, startPageSpec === true ? undefined : startPageSpec)
131
135
  }
132
136
  return componentVersion
133
137
  }
134
138
 
135
- addFile (file) {
139
+ addFile (file, componentVersion) {
136
140
  const src = file.src
137
- let family = src.family
141
+ let { component, version, family } = src
138
142
  let filesForFamily = this[$files].get(family)
139
143
  if (!filesForFamily) this[$files].set(family, (filesForFamily = new Map()))
140
144
  const key = generateKey(src)
@@ -146,7 +150,7 @@ class ContentCatalog {
146
150
  .map((it, idx) => `${idx + 1}: ${getFileLocation(it)}`)
147
151
  .join(LOG_WRAP)
148
152
  if (family === 'nav') {
149
- throw new Error(`Duplicate nav in ${src.version}@${src.component}: ${file.path}${LOG_WRAP}${details}`)
153
+ throw new Error(`Duplicate nav in ${version}@${component}: ${file.path}${LOG_WRAP}${details}`)
150
154
  } else {
151
155
  throw new Error(`Duplicate ${family}: ${generateResourceSpec(src)}${LOG_WRAP}${details}`)
152
156
  }
@@ -162,16 +166,18 @@ class ContentCatalog {
162
166
  file = new File(file)
163
167
  }
164
168
  if (family === 'alias') {
165
- src.mediaType = 'text/asciidoc'
166
169
  file.mediaType = 'text/html'
167
170
  // NOTE: an alias masquerades as the target file
168
171
  family = file.rel.src.family
169
- // QUESTION: should we preserve the mediaType property on file if already defined?
172
+ // NOTE: short circuit in case of splat alias (alias -> alias)
173
+ if (family === 'alias' && (file.pub || {}).splat) return filesForFamily.set(key, file) && file
174
+ src.mediaType = 'text/asciidoc'
170
175
  } else if (!(file.mediaType = src.mediaType) && !('mediaType' in src)) {
176
+ // QUESTION: should we preserve the mediaType property on file if already defined?
171
177
  file.mediaType = src.mediaType = resolveMimeType(src.extname) || (family === 'page' ? 'text/asciidoc' : undefined)
172
178
  }
173
179
  let publishable
174
- let versionSegment
180
+ let activeVersionSegment
175
181
  if (file.out) {
176
182
  publishable = true
177
183
  } else if ('out' in file) {
@@ -181,15 +187,18 @@ class ContentCatalog {
181
187
  ('/' + src.relative).indexOf('/_') < 0
182
188
  ) {
183
189
  publishable = true
184
- versionSegment = computeVersionSegment.call(this, src.component, src.version)
185
- file.out = computeOut(src, family, versionSegment, this.htmlUrlExtensionStyle)
190
+ if (componentVersion == null) componentVersion = this.getComponentVersion(component, version) || { version }
191
+ activeVersionSegment = computeVersionSegment.call(this, componentVersion)
192
+ file.out = computeOut(src, family, activeVersionSegment, this.htmlUrlExtensionStyle)
186
193
  }
187
194
  if (!file.pub && (publishable || family === 'nav')) {
188
- if (versionSegment == null) versionSegment = computeVersionSegment.call(this, src.component, src.version)
189
- file.pub = computePub(src, file.out, family, versionSegment, this.htmlUrlExtensionStyle)
195
+ if (activeVersionSegment == null) {
196
+ if (componentVersion == null) componentVersion = this.getComponentVersion(component, version) || { version }
197
+ activeVersionSegment = computeVersionSegment.call(this, componentVersion)
198
+ }
199
+ file.pub = computePub(src, file.out, family, activeVersionSegment, this.htmlUrlExtensionStyle)
190
200
  }
191
- filesForFamily.set(key, file)
192
- return file
201
+ return filesForFamily.set(key, file) && file
193
202
  }
194
203
 
195
204
  removeFile (file) {
@@ -267,38 +276,48 @@ class ContentCatalog {
267
276
 
268
277
  // TODO add `follow` argument to control whether alias is followed
269
278
  getSiteStartPage () {
270
- return this.getById(ROOT_INDEX_PAGE_ID) || (this.getById(ROOT_INDEX_ALIAS_ID) || {}).rel
279
+ let file
280
+ if ((file = this.getById(ROOT_INDEX_PAGE_ID))) return file
281
+ if ((file = this.getById(ROOT_INDEX_ALIAS_ID))) return file.rel
282
+ const rootComponent = this.getComponent('ROOT')
283
+ if (!rootComponent) return
284
+ const version = rootComponent.versions.find(({ activeVersionSegment }) => activeVersionSegment === '')?.version
285
+ if (!version) return
286
+ if ((file = this.getById(Object.assign({}, ROOT_INDEX_PAGE_ID, { version })))) return file
287
+ if ((file = this.getById(Object.assign({}, ROOT_INDEX_ALIAS_ID, { version })))) return file.rel
271
288
  }
272
289
 
273
290
  registerComponentVersionStartPage (name, componentVersion, startPageSpec = undefined) {
291
+ const component = name
274
292
  let version = componentVersion.version
275
293
  if (version == null) {
276
294
  // QUESTION: should we warn or throw error if component version cannot be found?
277
- if (!(componentVersion = this.getComponentVersion(name, componentVersion))) return
295
+ if (!(componentVersion = this.getComponentVersion(component, componentVersion))) return
278
296
  version = componentVersion.version
279
297
  }
298
+ const activeVersionSegment = computeVersionSegment.call(this, componentVersion)
280
299
  let startPage
281
300
  let startPageSrc
282
- const indexPageId = Object.assign({}, ROOT_INDEX_PAGE_ID, { component: name, version })
301
+ const indexPageId = Object.assign({}, ROOT_INDEX_PAGE_ID, { component, version })
283
302
  if (startPageSpec) {
284
303
  if (
285
304
  (startPage = this.resolvePage(startPageSpec, indexPageId)) &&
286
- (startPageSrc = startPage.src).component === name &&
305
+ (startPageSrc = startPage.src).component === component &&
287
306
  startPageSrc.version === version
288
307
  ) {
289
308
  if (!this.getById(indexPageId)) {
290
- const indexAliasId = Object.assign({}, ROOT_INDEX_ALIAS_ID, { component: name, version })
309
+ const indexAliasId = Object.assign({}, ROOT_INDEX_ALIAS_ID, { component, version })
291
310
  const indexAlias = this.getById(indexAliasId)
292
311
  indexAlias
293
312
  ? indexAlias.synthetic && Object.assign(indexAlias, { rel: startPage })
294
- : this.addFile({ src: indexAliasId, rel: startPage, synthetic: true })
313
+ : this.addFile({ src: indexAliasId, rel: startPage, synthetic: true }, componentVersion)
295
314
  }
296
315
  } else {
297
316
  // TODO pass componentVersion as logObject
298
317
  logger.warn(
299
318
  'Start page specified for %s@%s %s: %s',
300
319
  version,
301
- name,
320
+ component,
302
321
  startPage === false ? 'has invalid syntax' : 'not found',
303
322
  startPageSpec
304
323
  )
@@ -309,25 +328,34 @@ class ContentCatalog {
309
328
  }
310
329
  if (startPage) {
311
330
  componentVersion.url = startPage.pub.url
312
- } else {
331
+ } else if (!componentVersion.url) {
313
332
  // QUESTION: should we warn if the default start page cannot be found?
314
- const versionSegment = computeVersionSegment.call(this, name, version)
315
333
  componentVersion.url = computePub(
316
334
  (startPageSrc = prepareSrc(Object.assign({}, indexPageId, { family: 'page' }))),
317
- computeOut(startPageSrc, startPageSrc.family, versionSegment, this.htmlUrlExtensionStyle),
335
+ computeOut(startPageSrc, startPageSrc.family, activeVersionSegment, this.htmlUrlExtensionStyle),
318
336
  startPageSrc.family,
319
- versionSegment,
337
+ activeVersionSegment,
320
338
  this.htmlUrlExtensionStyle
321
339
  ).url
322
340
  }
323
-
324
- const symbolicVersionAlias = createSymbolicVersionAlias(
325
- name,
326
- version,
327
- computeVersionSegment.call(this, name, version, 'alias'),
328
- this.latestVersionUrlSegmentStrategy
329
- )
330
- if (symbolicVersionAlias) this.addFile(symbolicVersionAlias)
341
+ Object.defineProperties(componentVersion, {
342
+ activeVersionSegment:
343
+ activeVersionSegment === version
344
+ ? { configurable: true, enumerable: false, get: getVersion }
345
+ : { configurable: true, enumerable: false, value: activeVersionSegment },
346
+ files: {
347
+ configurable: true,
348
+ enumerable: false,
349
+ get: getComponentVersionFiles.bind(this, { component, version }),
350
+ },
351
+ startPage: {
352
+ configurable: true,
353
+ enumerable: false,
354
+ get: getComponentVersionStartPage.bind(this, { component, version }),
355
+ },
356
+ })
357
+ addSymbolicVersionAlias.call(this, componentVersion)
358
+ return startPage
331
359
  }
332
360
 
333
361
  registerSiteStartPage (startPageSpec) {
@@ -335,9 +363,9 @@ class ContentCatalog {
335
363
  const rel = this.resolvePage(startPageSpec)
336
364
  if (rel) {
337
365
  if (this.getById(ROOT_INDEX_PAGE_ID)) return
366
+ if (rel.pub.url === (this.htmlUrlExtensionStyle === 'default' ? '/index.html' : '/')) return
338
367
  const rootIndexAlias = this.getById(ROOT_INDEX_ALIAS_ID)
339
368
  if (rootIndexAlias) return rootIndexAlias.synthetic ? Object.assign(rootIndexAlias, { rel }) : undefined
340
- if (rel.pub.url === (this.htmlUrlExtensionStyle === 'default' ? '/index.html' : '/')) return
341
369
  const src = Object.assign({}, ROOT_INDEX_ALIAS_ID)
342
370
  return this.addFile({ src, rel, synthetic: true }, { version: src.version })
343
371
  } else if (rel === false) {
@@ -357,9 +385,14 @@ class ContentCatalog {
357
385
  // QUESTION should we throw an error if alias is invalid?
358
386
  if (!src || (inferredSpec && src.relative === '.adoc')) return
359
387
  const component = this.getComponent(src.component)
388
+ let componentVersion
360
389
  if (component) {
361
390
  // NOTE version is not set when alias specifies a component, but not a version
362
- if (src.version == null) src.version = component.latest.version
391
+ if (src.version == null) {
392
+ src.version = (componentVersion = component.latest).version
393
+ } else {
394
+ componentVersion = this.getComponentVersion(component, src.version)
395
+ }
363
396
  const existingPage = this.getById(src)
364
397
  if (existingPage) {
365
398
  throw new Error(
@@ -384,12 +417,34 @@ class ContentCatalog {
384
417
  )
385
418
  }
386
419
  // NOTE the redirect producer will populate contents when the redirect facility is 'static'
387
- const alias = this.addFile({ src, rel: target })
420
+ const alias = this.addFile({ src, rel: target }, componentVersion)
388
421
  // NOTE record the first alias this target claims as the preferred one
389
422
  if (!target.rel) target.rel = alias
390
423
  return alias
391
424
  }
392
425
 
426
+ /**
427
+ * Adds a splat (directory) alias from the specified version segment in one component to the specified
428
+ * version segment in the same or different component.
429
+ *
430
+ * @returns {File} The virtual file that represents the splat alias.
431
+ */
432
+ addSplatAlias (from, to) {
433
+ if (!from.versionSegment) throw new Error('cannot map splat alias from empty version segment')
434
+ const family = 'alias'
435
+ const baseSrc = { module: 'ROOT', family, relative: '', basename: '', stem: '', extname: '' }
436
+ const basePub = { splat: true }
437
+ const { component: fromComponent = to.component, versionSegment: fromVersionSegment } = from
438
+ const fromSrc = Object.assign({ component: fromComponent, version: fromVersionSegment }, baseSrc)
439
+ const fromPub = Object.assign(computePub(fromSrc, computeOut(fromSrc, family, fromVersionSegment), family), basePub)
440
+ const { component: toComponent, version: toVersion } = to
441
+ const toVersionSegment =
442
+ to.versionSegment ?? this.getComponentVersion(toComponent, toVersion)?.activeVersionSegment ?? toVersion
443
+ const toSrc = Object.assign({ component: toComponent, version: toVersion ?? toVersionSegment }, baseSrc)
444
+ const toPub = Object.assign(computePub(toSrc, computeOut(toSrc, family, toVersionSegment), family), basePub)
445
+ return this.addFile({ pub: fromPub, src: fromSrc, rel: { pub: toPub, src: toSrc } })
446
+ }
447
+
393
448
  /**
394
449
  * Attempts to resolve a string contextual page ID spec to a file in the catalog.
395
450
  *
@@ -477,7 +532,7 @@ function prepareSrc (src) {
477
532
  return update ? Object.assign(src, { basename, extname, stem }) : src
478
533
  }
479
534
 
480
- function computeOut (src, family, version, htmlUrlExtensionStyle) {
535
+ function computeOut (src, family, versionSegment, htmlUrlExtensionStyle) {
481
536
  let { component, module: module_, basename, extname, relative, stem } = src
482
537
  if (component === 'ROOT') component = ''
483
538
  if (module_ === 'ROOT') module_ = ''
@@ -497,7 +552,7 @@ function computeOut (src, family, version, htmlUrlExtensionStyle) {
497
552
  familyPathSegment = '_attachments'
498
553
  }
499
554
 
500
- const modulePath = path.join(component, version, module_)
555
+ const modulePath = path.join(component, versionSegment, module_)
501
556
  const dirname = path.join(modulePath, familyPathSegment, path.dirname(relative), indexifyPathSegment)
502
557
  const path_ = path.join(dirname, basename)
503
558
  const moduleRootPath = path.relative(dirname, modulePath) || '.'
@@ -506,13 +561,13 @@ function computeOut (src, family, version, htmlUrlExtensionStyle) {
506
561
  return { dirname, basename, path: path_, moduleRootPath, rootPath }
507
562
  }
508
563
 
509
- function computePub (src, out, family, version, htmlUrlExtensionStyle) {
564
+ function computePub (src, out, family, versionSegment, htmlUrlExtensionStyle) {
510
565
  const pub = {}
511
566
  let url
512
567
  if (family === 'nav') {
513
568
  const component = src.component || 'ROOT'
514
569
  const urlSegments = component === 'ROOT' ? [] : [component]
515
- if (version) urlSegments.push(version)
570
+ if (versionSegment) urlSegments.push(versionSegment)
516
571
  const module_ = src.module || 'ROOT'
517
572
  if (module_ !== 'ROOT') urlSegments.push(module_)
518
573
  if (urlSegments.length) urlSegments.push('')
@@ -531,71 +586,49 @@ function computePub (src, out, family, version, htmlUrlExtensionStyle) {
531
586
  urlSegments[lastUrlSegmentIdx] = ''
532
587
  }
533
588
  url = '/' + urlSegments.join('/')
534
- } else {
535
- if ((url = '/' + out.path) === '/.') url = '/'
536
- if (family === 'alias' && !src.relative) pub.splat = true
589
+ } else if ((url = '/' + out.path) === '/.') {
590
+ url = '/'
537
591
  }
538
-
539
592
  pub.url = ~url.indexOf(' ') ? url.replace(SPACE_RX, '%20') : url
593
+ return out ? Object.assign(pub, { moduleRootPath: out.moduleRootPath, rootPath: out.rootPath }) : pub
594
+ }
540
595
 
541
- if (out) {
542
- pub.moduleRootPath = out.moduleRootPath
543
- pub.rootPath = out.rootPath
544
- }
545
-
546
- return pub
596
+ function addSymbolicVersionAlias (componentVersion) {
597
+ const { name: component, version } = componentVersion
598
+ const originalVersionSegment = computeVersionSegment.call(this, componentVersion, 'original')
599
+ const symbolicVersionSegment = computeVersionSegment.call(this, componentVersion, 'alias')
600
+ if (symbolicVersionSegment === originalVersionSegment || symbolicVersionSegment == null) return
601
+ const originalVersionSrc = { component, version, versionSegment: originalVersionSegment }
602
+ const symbolicVersionSrc = { component, version, versionSegment: symbolicVersionSegment }
603
+ return this.latestVersionSegmentStrategy === 'redirect:to'
604
+ ? this.addSplatAlias(originalVersionSrc, symbolicVersionSrc)
605
+ : this.addSplatAlias(symbolicVersionSrc, originalVersionSrc)
547
606
  }
548
607
 
549
- function computeVersionSegment (name, version, mode) {
608
+ function computeVersionSegment (componentVersion, mode) {
609
+ const version = componentVersion.version
550
610
  // special designation for master verson is @deprecated; special designation scheduled to be removed in Antora 4
551
- if (mode === 'original') return !version || version === 'master' ? '' : version
552
- const strategy = this.latestVersionUrlSegmentStrategy
553
- if (!version || version === 'master') {
554
- if (mode !== 'alias') return ''
611
+ const normalizedVersion = version && version !== 'master' ? version : ''
612
+ const { versionSegment = normalizedVersion } = componentVersion
613
+ if (mode === 'original') return versionSegment
614
+ const strategy = this.latestVersionSegmentStrategy
615
+ if (!versionSegment) {
616
+ if (!mode) return ''
555
617
  if (strategy === 'redirect:to') return
556
618
  }
557
- if (strategy === 'redirect:to' || strategy === (mode === 'alias' ? 'redirect:from' : 'replace')) {
558
- const component = this.getComponent(name)
559
- const componentVersion = component && this.getComponentVersion(component, version)
560
- if (componentVersion) {
561
- const segment =
619
+ if (strategy === 'redirect:to' || strategy === (mode ? 'redirect:from' : 'replace')) {
620
+ let component
621
+ if ((component = 'name' in componentVersion && this.getComponent(componentVersion.name))) {
622
+ const latestSegment =
562
623
  componentVersion === component.latest
563
- ? this.latestVersionUrlSegment
624
+ ? this.latestVersionSegment
564
625
  : componentVersion === component.latestPrerelease
565
- ? this.latestPrereleaseVersionUrlSegment
626
+ ? this.latestPrereleaseVersionSegment
566
627
  : undefined
567
- return segment == null ? version : segment
628
+ return latestSegment == null ? versionSegment : latestSegment
568
629
  }
569
630
  }
570
- return version
571
- }
572
-
573
- function createSymbolicVersionAlias (component, version, symbolicVersionSegment, strategy) {
574
- if (symbolicVersionSegment == null || symbolicVersionSegment === version) return
575
- const family = 'alias'
576
- const baseVersionAliasSrc = { component, module: 'ROOT', family, relative: '', basename: '', stem: '', extname: '' }
577
- const symbolicVersionAliasSrc = Object.assign({}, baseVersionAliasSrc, { version: symbolicVersionSegment })
578
- const symbolicVersionAlias = {
579
- src: symbolicVersionAliasSrc,
580
- pub: computePub(
581
- symbolicVersionAliasSrc,
582
- computeOut(symbolicVersionAliasSrc, family, symbolicVersionSegment),
583
- family
584
- ),
585
- }
586
- const originalVersionAliasSrc = Object.assign({}, baseVersionAliasSrc, { version })
587
- const originalVersionSegment = computeVersionSegment(component, version, 'original')
588
- const originalVersionAlias = {
589
- src: originalVersionAliasSrc,
590
- pub: computePub(
591
- originalVersionAliasSrc,
592
- computeOut(originalVersionAliasSrc, family, originalVersionSegment),
593
- family
594
- ),
595
- }
596
- return strategy === 'redirect:to'
597
- ? Object.assign(originalVersionAlias, { out: undefined, rel: symbolicVersionAlias })
598
- : Object.assign(symbolicVersionAlias, { out: undefined, rel: originalVersionAlias })
631
+ return versionSegment
599
632
  }
600
633
 
601
634
  function getFileLocation ({ path: path_, src: { abspath, origin } }) {
@@ -607,4 +640,16 @@ function getFileLocation ({ path: path_, src: { abspath, origin } }) {
607
640
  return `${abspath || path.join(startPath, path_)} in ${'worktree' in origin ? worktree || gitdir : url} (${details})`
608
641
  }
609
642
 
643
+ function getComponentVersionFiles (componentVersionId) {
644
+ return this.findBy(componentVersionId)
645
+ }
646
+
647
+ function getComponentVersionStartPage (componentVersionId) {
648
+ return this.resolvePage('index.adoc', componentVersionId)
649
+ }
650
+
651
+ function getVersion () {
652
+ return this.version
653
+ }
654
+
610
655
  module.exports = ContentCatalog
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@antora/content-classifier",
3
- "version": "3.1.2",
3
+ "version": "3.2.0-alpha.2",
4
4
  "description": "Organizes aggregated content into a virtual file catalog for use in an Antora documentation pipeline.",
5
5
  "license": "MPL-2.0",
6
6
  "author": "OpenDevise Inc. (https://opendevise.com)",
@@ -27,8 +27,8 @@
27
27
  "#constants": "./lib/constants.js"
28
28
  },
29
29
  "dependencies": {
30
- "@antora/asciidoc-loader": "3.1.2",
31
- "@antora/logger": "3.1.2",
30
+ "@antora/asciidoc-loader": "3.2.0-alpha.2",
31
+ "@antora/logger": "3.2.0-alpha.2",
32
32
  "mime-types": "~2.1",
33
33
  "vinyl": "~2.2"
34
34
  },