@antora/content-classifier 3.1.10 → 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.
- package/lib/classify-content.js +53 -31
- package/lib/content-catalog.js +196 -130
- package/lib/util/summarize-file-location.js +1 -1
- package/package.json +8 -7
package/lib/classify-content.js
CHANGED
|
@@ -10,50 +10,66 @@ const summarizeFileLocation = require('./util/summarize-file-location')
|
|
|
10
10
|
*
|
|
11
11
|
* @memberof content-classifier
|
|
12
12
|
*
|
|
13
|
-
* @param {Object} playbook - The configuration object for Antora.
|
|
13
|
+
* @param {Object} playbook - The configuration object for Antora. See ContentCatalog constructor for relevant keys.
|
|
14
14
|
* @param {Object} playbook.site - Site-related configuration data.
|
|
15
15
|
* @param {String} playbook.site.startPage - The start page for the site; redirects from base URL.
|
|
16
|
-
* @param {Object} playbook.urls - URL settings for the site.
|
|
17
|
-
* @param {String} playbook.urls.htmlExtensionStyle - The style to use when computing page URLs.
|
|
18
16
|
* @param {Object} aggregate - The raw aggregate of virtual file objects to be classified.
|
|
19
17
|
* @param {Object} [siteAsciiDocConfig={}] - Site-wide AsciiDoc processor configuration options.
|
|
20
|
-
* @
|
|
18
|
+
* @param {Function} [onComponentsRegistered] - A function (optionally async) to invoke after components are
|
|
19
|
+
* registered. Must return an instance of ContentCatalog. If async, this function will also return a Promise.
|
|
20
|
+
*
|
|
21
|
+
* @returns {ContentCatalog} A structured catalog of content components, versions, and virtual content files.
|
|
21
22
|
*/
|
|
22
|
-
function classifyContent (playbook, aggregate, siteAsciiDocConfig = {}) {
|
|
23
|
-
const
|
|
24
|
-
aggregate
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
23
|
+
function classifyContent (playbook, aggregate, siteAsciiDocConfig = {}, onComponentsRegistered = undefined) {
|
|
24
|
+
const siteStartPage = playbook.site.startPage
|
|
25
|
+
let contentCatalog = registerComponentVersions(new ContentCatalog(playbook), aggregate, siteAsciiDocConfig)
|
|
26
|
+
return typeof onComponentsRegistered === 'function' &&
|
|
27
|
+
(contentCatalog = onComponentsRegistered(contentCatalog)) instanceof Promise
|
|
28
|
+
? contentCatalog.then((contentCatalogValue) => addFilesAndRegisterStartPages(contentCatalogValue, siteStartPage))
|
|
29
|
+
: addFilesAndRegisterStartPages(contentCatalog, siteStartPage)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function registerComponentVersions (contentCatalog, aggregate, siteAsciiDocConfig) {
|
|
33
|
+
for (const componentVersionBucket of aggregate) {
|
|
34
|
+
// advance files, nav, and startPage to component version to be used in later phase
|
|
35
|
+
const { name, version, files, nav, startPage, ...data } = Object.assign(componentVersionBucket, {
|
|
36
|
+
asciidoc: resolveAsciiDocConfig(siteAsciiDocConfig, componentVersionBucket),
|
|
37
|
+
})
|
|
38
|
+
Object.assign(contentCatalog.registerComponentVersion(name, version, data), { files, nav, startPage })
|
|
39
|
+
}
|
|
40
|
+
return contentCatalog
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function addFilesAndRegisterStartPages (contentCatalog, siteStartPage) {
|
|
44
|
+
for (const { versions: componentVersions } of contentCatalog.getComponents()) {
|
|
45
|
+
for (const componentVersion of componentVersions) {
|
|
46
|
+
const { name: component, version, files = [], nav, startPage } = componentVersion
|
|
39
47
|
const navResolved = nav && (nav.resolved = new Set())
|
|
40
|
-
|
|
41
|
-
|
|
48
|
+
for (let file, i = 0, len = files.length; i < len; i++) {
|
|
49
|
+
allocateSrc((file = files[i]), component, version, nav) && contentCatalog.addFile(file, componentVersion)
|
|
50
|
+
files[i] = undefined // free memory
|
|
51
|
+
}
|
|
42
52
|
if (navResolved && nav.length > navResolved.size && new Set(nav).size > navResolved.size) {
|
|
43
53
|
const loc = summarizeFileLocation({ path: 'antora.yml', src: { origin: nav.origin } })
|
|
44
54
|
for (const filepath of nav) {
|
|
45
55
|
if (navResolved.has(filepath)) continue
|
|
46
|
-
logger.warn('Could not resolve nav entry for %s@%s defined in %s: %s', version,
|
|
56
|
+
logger.warn('Could not resolve nav entry for %s@%s defined in %s: %s', version, component, loc, filepath)
|
|
47
57
|
}
|
|
48
58
|
}
|
|
49
|
-
contentCatalog.registerComponentVersionStartPage(
|
|
50
|
-
}
|
|
51
|
-
|
|
59
|
+
contentCatalog.registerComponentVersionStartPage(component, componentVersion, startPage)
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
contentCatalog.registerSiteStartPage(siteStartPage)
|
|
52
63
|
return contentCatalog
|
|
53
64
|
}
|
|
54
65
|
|
|
55
66
|
function allocateSrc (file, component, version, nav) {
|
|
56
|
-
const extname = file.src
|
|
67
|
+
const { extname, family } = file.src
|
|
68
|
+
if (family && family !== 'nav') {
|
|
69
|
+
Object.assign(file.src, { component, version })
|
|
70
|
+
file.src.moduleRootPath ??= calculateRootPath(file.src.relative.split('/').length)
|
|
71
|
+
return true
|
|
72
|
+
}
|
|
57
73
|
const filepath = file.path
|
|
58
74
|
const pathSegments = filepath.split('/')
|
|
59
75
|
let navInfo
|
|
@@ -140,13 +156,19 @@ function getNavInfo (filepath, nav) {
|
|
|
140
156
|
if (~index) return nav.resolved.add(filepath) && { index }
|
|
141
157
|
}
|
|
142
158
|
|
|
143
|
-
function resolveAsciiDocConfig (siteAsciiDocConfig, { asciidoc, origins = [] }) {
|
|
159
|
+
function resolveAsciiDocConfig (siteAsciiDocConfig, { name, version, asciidoc, origins = [] }) {
|
|
144
160
|
const scopedAttributes = asciidoc?.attributes
|
|
145
161
|
if (scopedAttributes) {
|
|
146
|
-
const initial = siteAsciiDocConfig.attributes
|
|
162
|
+
const initial = Object.assign({}, siteAsciiDocConfig.attributes)
|
|
163
|
+
initial['antora-component-name'] = name
|
|
164
|
+
initial['antora-component-version'] = version
|
|
147
165
|
const mdc = { file: { path: 'antora.yml', origin: origins[origins.length - 1] } }
|
|
148
166
|
const attributes = collateAsciiDocAttributes(scopedAttributes, { initial, mdc, merge: true })
|
|
149
|
-
if (attributes !== initial)
|
|
167
|
+
if (attributes !== initial) {
|
|
168
|
+
delete attributes['antora-component-name']
|
|
169
|
+
delete attributes['antora-component-version']
|
|
170
|
+
return Object.assign({}, siteAsciiDocConfig, { attributes })
|
|
171
|
+
}
|
|
150
172
|
}
|
|
151
173
|
return siteAsciiDocConfig
|
|
152
174
|
}
|
package/lib/content-catalog.js
CHANGED
|
@@ -5,13 +5,12 @@ const invariably = { void: () => undefined }
|
|
|
5
5
|
const logger = require('./logger')
|
|
6
6
|
const { lookup: resolveMimeType } = require('./mime-types-with-asciidoc')
|
|
7
7
|
const parseResourceId = require('./util/parse-resource-id')
|
|
8
|
-
const { posix: path } = require('path')
|
|
8
|
+
const { posix: path } = require('node:path')
|
|
9
9
|
const resolveResource = require('./util/resolve-resource')
|
|
10
10
|
const summarizeFileLocation = require('./util/summarize-file-location')
|
|
11
11
|
const versionCompare = require('./util/version-compare-desc')
|
|
12
12
|
|
|
13
13
|
const { ROOT_INDEX_ALIAS_ID, ROOT_INDEX_PAGE_ID } = require('./constants')
|
|
14
|
-
const SPACE_RX = / /g
|
|
15
14
|
const LOG_WRAP = '\n '
|
|
16
15
|
|
|
17
16
|
const $components = Symbol('components')
|
|
@@ -24,17 +23,17 @@ class ContentCatalog {
|
|
|
24
23
|
const urls = playbook.urls || {}
|
|
25
24
|
this.htmlUrlExtensionStyle = urls.htmlExtensionStyle || 'default'
|
|
26
25
|
this.urlRedirectFacility = urls.redirectFacility || 'static'
|
|
27
|
-
this.
|
|
28
|
-
this.
|
|
29
|
-
if (this.
|
|
30
|
-
this.
|
|
26
|
+
this.latestVersionSegment = urls.latestVersionSegment
|
|
27
|
+
this.latestPrereleaseVersionSegment = urls.latestPrereleaseVersionSegment
|
|
28
|
+
if (this.latestVersionSegment == null && this.latestPrereleaseVersionSegment == null) {
|
|
29
|
+
this.latestVersionSegmentStrategy = undefined
|
|
31
30
|
} else {
|
|
32
|
-
this.
|
|
33
|
-
if (this.
|
|
34
|
-
if (!this.
|
|
35
|
-
if (!this.
|
|
36
|
-
this.
|
|
37
|
-
if (!this.
|
|
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
|
|
38
37
|
}
|
|
39
38
|
}
|
|
40
39
|
}
|
|
@@ -43,6 +42,8 @@ class ContentCatalog {
|
|
|
43
42
|
/**
|
|
44
43
|
* Registers a new component version with the content catalog. Also registers the component if it does not yet exist.
|
|
45
44
|
*
|
|
45
|
+
* Must be followed by a call to registerComponentVersionStartPage to finalize object.
|
|
46
|
+
*
|
|
46
47
|
* @param {String} name - The name of the component to which this component version belongs.
|
|
47
48
|
* @param {String} version - The version of the component to register.
|
|
48
49
|
* @param {Object} [descriptor={}] - The configuration data for the component version.
|
|
@@ -59,8 +60,9 @@ class ContentCatalog {
|
|
|
59
60
|
* @returns {Object} The constructed component version object.
|
|
60
61
|
*/
|
|
61
62
|
registerComponentVersion (name, version, descriptor = {}) {
|
|
62
|
-
const { asciidoc, displayVersion, prerelease, startPage: startPageSpec, title } = descriptor
|
|
63
|
+
const { asciidoc, displayVersion, prerelease, startPage: startPageSpec, title, versionSegment } = descriptor
|
|
63
64
|
const componentVersion = { displayVersion: displayVersion || version || 'default', title: title || name, version }
|
|
65
|
+
if (versionSegment != null) componentVersion.versionSegment = versionSegment
|
|
64
66
|
Object.defineProperty(componentVersion, 'name', { value: name, enumerable: true })
|
|
65
67
|
if (prerelease) {
|
|
66
68
|
componentVersion.prerelease = prerelease
|
|
@@ -129,14 +131,15 @@ class ContentCatalog {
|
|
|
129
131
|
)
|
|
130
132
|
}
|
|
131
133
|
if (startPageSpec) {
|
|
134
|
+
// @deprecated use separate call to register start page for component version
|
|
132
135
|
this.registerComponentVersionStartPage(name, componentVersion, startPageSpec === true ? undefined : startPageSpec)
|
|
133
136
|
}
|
|
134
137
|
return componentVersion
|
|
135
138
|
}
|
|
136
139
|
|
|
137
|
-
addFile (file) {
|
|
140
|
+
addFile (file, componentVersion) {
|
|
138
141
|
const src = file.src
|
|
139
|
-
let family = src
|
|
142
|
+
let { component, version, family } = src
|
|
140
143
|
let filesForFamily = this[$files].get(family)
|
|
141
144
|
if (!filesForFamily) this[$files].set(family, (filesForFamily = new Map()))
|
|
142
145
|
const key = generateKey(src)
|
|
@@ -148,30 +151,30 @@ class ContentCatalog {
|
|
|
148
151
|
.map((it, idx) => `${idx + 1}: ${summarizeFileLocation(it)}`)
|
|
149
152
|
.join(LOG_WRAP)
|
|
150
153
|
if (family === 'nav') {
|
|
151
|
-
throw new Error(`Duplicate nav in ${
|
|
154
|
+
throw new Error(`Duplicate nav file: ${file.path} in ${version}@${component}${LOG_WRAP}${details}`)
|
|
152
155
|
}
|
|
153
156
|
throw new Error(`Duplicate ${family}: ${generateResourceSpec(src)}${LOG_WRAP}${details}`)
|
|
154
157
|
}
|
|
155
|
-
// NOTE: if the
|
|
156
|
-
// another option is to assume that if the file is not a vinyl object, the src likely needs to be prepared
|
|
158
|
+
// NOTE: assume that if the file is not a vinyl object, the src likely needs to be prepared
|
|
157
159
|
// a vinyl object is one indication the file was created and prepared by the content aggregator
|
|
158
|
-
//if
|
|
159
|
-
//if (!File.isVinyl(file)) file = new File(file)
|
|
160
|
+
// an alternate approach would be to call prepareSrc if the path property is not set
|
|
160
161
|
if (!File.isVinyl(file)) {
|
|
161
162
|
prepareSrc(src)
|
|
162
163
|
file = new File(file)
|
|
163
164
|
}
|
|
164
165
|
if (family === 'alias') {
|
|
165
|
-
src.mediaType = 'text/asciidoc'
|
|
166
166
|
file.mediaType = 'text/html'
|
|
167
167
|
// NOTE: an alias masquerades as the target file
|
|
168
168
|
family = file.rel.src.family
|
|
169
|
-
//
|
|
169
|
+
// NOTE: short circuit in case of splat alias (alias -> alias)
|
|
170
|
+
if (family === 'alias' && file.pub?.splat) return filesForFamily.set(key, file) && file
|
|
171
|
+
src.mediaType = 'text/asciidoc'
|
|
170
172
|
} else if (!(file.mediaType = src.mediaType) && !('mediaType' in src)) {
|
|
173
|
+
// QUESTION: should we preserve the mediaType property on file if already defined?
|
|
171
174
|
file.mediaType = src.mediaType = resolveMimeType(src.extname) || (family === 'page' ? 'text/asciidoc' : undefined)
|
|
172
175
|
}
|
|
173
176
|
let publishable
|
|
174
|
-
let
|
|
177
|
+
let activeVersionSegment
|
|
175
178
|
if (file.out) {
|
|
176
179
|
publishable = true
|
|
177
180
|
} else if ('out' in file) {
|
|
@@ -181,15 +184,24 @@ class ContentCatalog {
|
|
|
181
184
|
('/' + src.relative).indexOf('/_') < 0
|
|
182
185
|
) {
|
|
183
186
|
publishable = true
|
|
184
|
-
|
|
185
|
-
|
|
187
|
+
if (componentVersion == null) componentVersion = this.getComponentVersion(component, version) || { version }
|
|
188
|
+
activeVersionSegment =
|
|
189
|
+
'activeVersionSegment' in componentVersion
|
|
190
|
+
? componentVersion.activeVersionSegment
|
|
191
|
+
: 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 (
|
|
189
|
-
|
|
195
|
+
if (activeVersionSegment == null) {
|
|
196
|
+
if (componentVersion == null) componentVersion = this.getComponentVersion(component, version) || { version }
|
|
197
|
+
activeVersionSegment =
|
|
198
|
+
'activeVersionSegment' in componentVersion
|
|
199
|
+
? componentVersion.activeVersionSegment
|
|
200
|
+
: computeVersionSegment.call(this, componentVersion)
|
|
201
|
+
}
|
|
202
|
+
file.pub = computePub(src, file.out, family, activeVersionSegment, this.htmlUrlExtensionStyle)
|
|
190
203
|
}
|
|
191
|
-
filesForFamily.set(key, file)
|
|
192
|
-
return file
|
|
204
|
+
return filesForFamily.set(key, file) && file
|
|
193
205
|
}
|
|
194
206
|
|
|
195
207
|
removeFile (file) {
|
|
@@ -266,38 +278,48 @@ class ContentCatalog {
|
|
|
266
278
|
|
|
267
279
|
// TODO add `follow` argument to control whether alias is followed
|
|
268
280
|
getSiteStartPage () {
|
|
269
|
-
|
|
281
|
+
let file
|
|
282
|
+
if ((file = this.getById(ROOT_INDEX_PAGE_ID))) return file
|
|
283
|
+
if ((file = this.getById(ROOT_INDEX_ALIAS_ID))) return file.rel
|
|
284
|
+
const rootComponent = this.getComponent('ROOT')
|
|
285
|
+
if (!rootComponent) return
|
|
286
|
+
const version = rootComponent.versions.find(({ activeVersionSegment }) => activeVersionSegment === '')?.version
|
|
287
|
+
if (!version) return
|
|
288
|
+
if ((file = this.getById(Object.assign({}, ROOT_INDEX_PAGE_ID, { version })))) return file
|
|
289
|
+
if ((file = this.getById(Object.assign({}, ROOT_INDEX_ALIAS_ID, { version })))) return file.rel
|
|
270
290
|
}
|
|
271
291
|
|
|
272
292
|
registerComponentVersionStartPage (name, componentVersion, startPageSpec = undefined) {
|
|
293
|
+
const component = name
|
|
273
294
|
let version = componentVersion.version
|
|
274
295
|
if (version == null) {
|
|
275
296
|
// QUESTION: should we warn or throw error if component version cannot be found?
|
|
276
|
-
if (!(componentVersion = this.getComponentVersion(
|
|
297
|
+
if (!(componentVersion = this.getComponentVersion(component, componentVersion))) return
|
|
277
298
|
version = componentVersion.version
|
|
278
299
|
}
|
|
300
|
+
const activeVersionSegment = computeVersionSegment.call(this, componentVersion)
|
|
279
301
|
let startPage
|
|
280
302
|
let startPageSrc
|
|
281
|
-
const indexPageId = Object.assign({}, ROOT_INDEX_PAGE_ID, { component
|
|
303
|
+
const indexPageId = Object.assign({}, ROOT_INDEX_PAGE_ID, { component, version })
|
|
282
304
|
if (startPageSpec) {
|
|
283
305
|
if (
|
|
284
306
|
(startPage = this.resolvePage(startPageSpec, indexPageId)) &&
|
|
285
|
-
(startPageSrc = startPage.src).component ===
|
|
307
|
+
(startPageSrc = startPage.src).component === component &&
|
|
286
308
|
startPageSrc.version === version
|
|
287
309
|
) {
|
|
288
310
|
if (!this.getById(indexPageId)) {
|
|
289
|
-
const indexAliasId = Object.assign({}, ROOT_INDEX_ALIAS_ID, { component
|
|
311
|
+
const indexAliasId = Object.assign({}, ROOT_INDEX_ALIAS_ID, { component, version })
|
|
290
312
|
const indexAlias = this.getById(indexAliasId)
|
|
291
313
|
indexAlias
|
|
292
314
|
? indexAlias.synthetic && Object.assign(indexAlias, { rel: startPage })
|
|
293
|
-
: this.addFile({ src: indexAliasId, rel: startPage, synthetic: true })
|
|
315
|
+
: this.addFile({ src: indexAliasId, rel: startPage, synthetic: true }, componentVersion)
|
|
294
316
|
}
|
|
295
317
|
} else {
|
|
296
318
|
// TODO pass componentVersion as logObject
|
|
297
319
|
logger.warn(
|
|
298
320
|
'Start page specified for %s@%s %s: %s',
|
|
299
321
|
version,
|
|
300
|
-
|
|
322
|
+
component,
|
|
301
323
|
startPage === false ? 'has invalid syntax' : 'not found',
|
|
302
324
|
startPageSpec
|
|
303
325
|
)
|
|
@@ -308,25 +330,34 @@ class ContentCatalog {
|
|
|
308
330
|
}
|
|
309
331
|
if (startPage) {
|
|
310
332
|
componentVersion.url = startPage.pub.url
|
|
311
|
-
} else {
|
|
333
|
+
} else if (!componentVersion.url) {
|
|
312
334
|
// QUESTION: should we warn if the default start page cannot be found?
|
|
313
|
-
const versionSegment = computeVersionSegment.call(this, name, version)
|
|
314
335
|
componentVersion.url = computePub(
|
|
315
336
|
(startPageSrc = prepareSrc(Object.assign({}, indexPageId, { family: 'page' }))),
|
|
316
|
-
computeOut(startPageSrc, startPageSrc.family,
|
|
337
|
+
computeOut(startPageSrc, startPageSrc.family, activeVersionSegment, this.htmlUrlExtensionStyle),
|
|
317
338
|
startPageSrc.family,
|
|
318
|
-
|
|
339
|
+
activeVersionSegment,
|
|
319
340
|
this.htmlUrlExtensionStyle
|
|
320
341
|
).url
|
|
321
342
|
}
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
343
|
+
Object.defineProperties(componentVersion, {
|
|
344
|
+
activeVersionSegment:
|
|
345
|
+
activeVersionSegment === version
|
|
346
|
+
? { configurable: true, enumerable: false, get: getVersion }
|
|
347
|
+
: { configurable: true, enumerable: false, value: activeVersionSegment },
|
|
348
|
+
files: {
|
|
349
|
+
configurable: true,
|
|
350
|
+
enumerable: false,
|
|
351
|
+
get: getComponentVersionFiles.bind(this, { component, version }),
|
|
352
|
+
},
|
|
353
|
+
startPage: {
|
|
354
|
+
configurable: true,
|
|
355
|
+
enumerable: false,
|
|
356
|
+
get: getComponentVersionStartPage.bind(this, { component, version }),
|
|
357
|
+
},
|
|
358
|
+
})
|
|
359
|
+
addSymbolicVersionAlias.call(this, componentVersion)
|
|
360
|
+
return startPage
|
|
330
361
|
}
|
|
331
362
|
|
|
332
363
|
registerSiteStartPage (startPageSpec) {
|
|
@@ -334,9 +365,9 @@ class ContentCatalog {
|
|
|
334
365
|
const rel = this.resolvePage(startPageSpec)
|
|
335
366
|
if (rel) {
|
|
336
367
|
if (this.getById(ROOT_INDEX_PAGE_ID)) return
|
|
368
|
+
if (rel.pub.url === (this.htmlUrlExtensionStyle === 'default' ? '/index.html' : '/')) return
|
|
337
369
|
const rootIndexAlias = this.getById(ROOT_INDEX_ALIAS_ID)
|
|
338
370
|
if (rootIndexAlias) return rootIndexAlias.synthetic ? Object.assign(rootIndexAlias, { rel }) : undefined
|
|
339
|
-
if (rel.pub.url === (this.htmlUrlExtensionStyle === 'default' ? '/index.html' : '/')) return
|
|
340
371
|
const src = Object.assign({}, ROOT_INDEX_ALIAS_ID)
|
|
341
372
|
return this.addFile({ src, rel, synthetic: true }, { version: src.version })
|
|
342
373
|
}
|
|
@@ -357,9 +388,14 @@ class ContentCatalog {
|
|
|
357
388
|
// QUESTION should we throw an error if alias is invalid?
|
|
358
389
|
if (!src || (inferredSpec && src.relative === '.adoc')) return
|
|
359
390
|
const component = this.getComponent(src.component)
|
|
391
|
+
let componentVersion
|
|
360
392
|
if (component) {
|
|
361
393
|
// NOTE version is not set when alias specifies a component, but not a version
|
|
362
|
-
if (src.version == null)
|
|
394
|
+
if (src.version == null) {
|
|
395
|
+
src.version = (componentVersion = component.latest).version
|
|
396
|
+
} else {
|
|
397
|
+
componentVersion = this.getComponentVersion(component, src.version)
|
|
398
|
+
}
|
|
363
399
|
const existingPage = this.getById(src)
|
|
364
400
|
if (existingPage) {
|
|
365
401
|
throw new Error(
|
|
@@ -384,33 +420,77 @@ class ContentCatalog {
|
|
|
384
420
|
)
|
|
385
421
|
}
|
|
386
422
|
// NOTE the redirect producer will populate contents when the redirect facility is 'static'
|
|
387
|
-
const alias = this.addFile({ src, rel: target })
|
|
423
|
+
const alias = this.addFile({ src, rel: target }, componentVersion)
|
|
388
424
|
// NOTE record the first alias this target claims as the preferred one
|
|
389
425
|
if (!target.rel) target.rel = alias
|
|
390
426
|
return alias
|
|
391
427
|
}
|
|
392
428
|
|
|
393
429
|
/**
|
|
394
|
-
*
|
|
430
|
+
* Adds a splat (directory) alias from the specified version segment in one component to the specified
|
|
431
|
+
* version segment in the same or different component.
|
|
432
|
+
*
|
|
433
|
+
* @returns {File} The virtual file that represents the splat alias.
|
|
434
|
+
*/
|
|
435
|
+
addSplatAlias (from, to) {
|
|
436
|
+
if (!from.versionSegment) throw new Error('cannot map splat alias from empty version segment')
|
|
437
|
+
const family = 'alias'
|
|
438
|
+
const baseSrc = { module: 'ROOT', family, relative: '', basename: '', stem: '', extname: '' }
|
|
439
|
+
const basePub = { splat: true }
|
|
440
|
+
const { component: fromComponent = to.component, versionSegment: fromVersionSegment } = from
|
|
441
|
+
const fromSrc = Object.assign({ component: fromComponent, version: fromVersionSegment }, baseSrc)
|
|
442
|
+
const fromPub = Object.assign(computePub(fromSrc, computeOut(fromSrc, family, fromVersionSegment), family), basePub)
|
|
443
|
+
const { component: toComponent, version: toVersion } = to
|
|
444
|
+
const toVersionSegment =
|
|
445
|
+
to.versionSegment ?? this.getComponentVersion(toComponent, toVersion)?.activeVersionSegment ?? toVersion
|
|
446
|
+
const toSrc = Object.assign({ component: toComponent, version: toVersion ?? toVersionSegment }, baseSrc)
|
|
447
|
+
const toPub = Object.assign(computePub(toSrc, computeOut(toSrc, family, toVersionSegment), family), basePub)
|
|
448
|
+
return this.addFile({ pub: fromPub, src: fromSrc, rel: { pub: toPub, src: toSrc } })
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
/**
|
|
452
|
+
* Attempts to resolve a page reference within the given context to a page in the catalog.
|
|
395
453
|
*
|
|
396
|
-
* Parses the specified
|
|
397
|
-
*
|
|
398
|
-
*
|
|
399
|
-
* the search is attempted again for an "alias". If neither a page or alias can be resolved, the
|
|
400
|
-
* function returns undefined. If the spec does not match the page ID syntax, this function throws
|
|
401
|
-
* an error.
|
|
454
|
+
* Parses the specified page reference (i.e., page ID spec) into a partial page ID, expands it
|
|
455
|
+
* using the provided context, then attempts to locate a file in the page family with that page ID
|
|
456
|
+
* in this catalog. The family segment is optional.
|
|
402
457
|
*
|
|
403
|
-
*
|
|
404
|
-
*
|
|
405
|
-
*
|
|
458
|
+
* If a component is specified, but no version, the latest version of the component stored in the
|
|
459
|
+
* catalog is used. If a page cannot be resolved, the search is attempted again for an "alias". If
|
|
460
|
+
* neither a page or alias can be resolved, the function returns undefined. If the syntax of the
|
|
461
|
+
* reference is invalid, this function throws an error.
|
|
406
462
|
*
|
|
407
|
-
* @
|
|
408
|
-
*
|
|
463
|
+
* @param {String} spec - The contextual page reference (e.g., version@component:module:topic/page.adoc).
|
|
464
|
+
* @param {Object} [context={}] - The context to use to qualify the page reference.
|
|
465
|
+
*
|
|
466
|
+
* @returns {File} The virtual file to which the contextual page reference resolves, or undefined
|
|
467
|
+
* if the file cannot be resolved.
|
|
409
468
|
*/
|
|
410
469
|
resolvePage (spec, context = {}) {
|
|
411
470
|
return this.resolveResource(spec, context, 'page', ['page'])
|
|
412
471
|
}
|
|
413
472
|
|
|
473
|
+
/**
|
|
474
|
+
* Attempts to resolve a resource reference within the given context to a file in the catalog.
|
|
475
|
+
*
|
|
476
|
+
* Parses the specified resource reference (i.e., resource ID spec) into a partial resource ID,
|
|
477
|
+
* expands it using the provided context, then attempts to locate a file with that resource ID in
|
|
478
|
+
* this catalog.
|
|
479
|
+
*
|
|
480
|
+
* If a component is specified, but no version, the latest version of the component stored in the
|
|
481
|
+
* catalog is used. If a defaultFamily is not specified, the family must be specified either by
|
|
482
|
+
* the reference or the context. If permittedFamilies are stated, the family must resolve to a
|
|
483
|
+
* family in this list. If a file cannot be resolved, the function returns undefined. If the
|
|
484
|
+
* syntax of the reference is invalid, this function throws an error.
|
|
485
|
+
*
|
|
486
|
+
* @param {String} spec - The contextual resource reference (e.g., version@component:module:image$topic/image.png).
|
|
487
|
+
* @param {Object} [context={}] - The context to use to qualify the resource reference.
|
|
488
|
+
* @param {String} [defaultFamily=undefined] - The default family to use if one is not provided.
|
|
489
|
+
* @param {Array<String>} [permittedFamilies=undefined] - A list of families that are permitted.
|
|
490
|
+
*
|
|
491
|
+
* @returns {File} The virtual file to which the contextual resource reference resolves, or
|
|
492
|
+
* undefined if the file cannot be resolved.
|
|
493
|
+
*/
|
|
414
494
|
resolveResource (spec, context = {}, defaultFamily = undefined, permittedFamilies = undefined) {
|
|
415
495
|
return resolveResource(spec, this, context, defaultFamily, permittedFamilies)
|
|
416
496
|
}
|
|
@@ -452,32 +532,28 @@ function generateResourceSpec ({ component, version, module: module_, family, re
|
|
|
452
532
|
|
|
453
533
|
function prepareSrc (src) {
|
|
454
534
|
let { basename, extname, stem } = src
|
|
455
|
-
let update
|
|
456
535
|
if (basename == null) {
|
|
457
|
-
|
|
458
|
-
basename = path.basename(src.relative)
|
|
536
|
+
basename = src.basename = path.basename(src.relative)
|
|
459
537
|
}
|
|
460
538
|
if (stem == null) {
|
|
461
|
-
update = true
|
|
462
539
|
if (extname == null) {
|
|
463
540
|
if (~(extname = basename.lastIndexOf('.'))) {
|
|
464
|
-
stem = basename.substr(0, extname)
|
|
465
|
-
extname = basename.substr(extname)
|
|
541
|
+
src.stem = basename.substr(0, extname)
|
|
542
|
+
src.extname = basename.substr(extname)
|
|
466
543
|
} else {
|
|
467
|
-
stem = basename
|
|
468
|
-
extname = ''
|
|
544
|
+
src.stem = basename
|
|
545
|
+
src.extname = ''
|
|
469
546
|
}
|
|
470
547
|
} else {
|
|
471
|
-
stem = basename.substr(0, basename.length - extname.length)
|
|
548
|
+
src.stem = basename.substr(0, basename.length - extname.length)
|
|
472
549
|
}
|
|
473
550
|
} else if (extname == null) {
|
|
474
|
-
|
|
475
|
-
extname = basename.substr(stem.length)
|
|
551
|
+
src.extname = basename.substr(stem.length)
|
|
476
552
|
}
|
|
477
|
-
return
|
|
553
|
+
return src
|
|
478
554
|
}
|
|
479
555
|
|
|
480
|
-
function computeOut (src, family,
|
|
556
|
+
function computeOut (src, family, versionSegment, htmlUrlExtensionStyle) {
|
|
481
557
|
let { component, module: module_, basename, extname, relative, stem } = src
|
|
482
558
|
if (component === 'ROOT') component = ''
|
|
483
559
|
if (module_ === 'ROOT') module_ = ''
|
|
@@ -497,7 +573,7 @@ function computeOut (src, family, version, htmlUrlExtensionStyle) {
|
|
|
497
573
|
familyPathSegment = '_attachments'
|
|
498
574
|
}
|
|
499
575
|
|
|
500
|
-
const modulePath = path.join(component,
|
|
576
|
+
const modulePath = path.join(component, versionSegment, module_)
|
|
501
577
|
const dirname = path.join(modulePath, familyPathSegment, path.dirname(relative), indexifyPathSegment)
|
|
502
578
|
const path_ = path.join(dirname, basename)
|
|
503
579
|
const moduleRootPath = path.relative(dirname, modulePath) || '.'
|
|
@@ -506,13 +582,13 @@ function computeOut (src, family, version, htmlUrlExtensionStyle) {
|
|
|
506
582
|
return { dirname, basename, path: path_, moduleRootPath, rootPath }
|
|
507
583
|
}
|
|
508
584
|
|
|
509
|
-
function computePub (src, out, family,
|
|
585
|
+
function computePub (src, out, family, versionSegment, htmlUrlExtensionStyle) {
|
|
510
586
|
const pub = {}
|
|
511
587
|
let url
|
|
512
588
|
if (family === 'nav') {
|
|
513
589
|
const component = src.component || 'ROOT'
|
|
514
590
|
const urlSegments = component === 'ROOT' ? [] : [component]
|
|
515
|
-
if (
|
|
591
|
+
if (versionSegment) urlSegments.push(versionSegment)
|
|
516
592
|
const module_ = src.module || 'ROOT'
|
|
517
593
|
if (module_ !== 'ROOT') urlSegments.push(module_)
|
|
518
594
|
if (urlSegments.length) urlSegments.push('')
|
|
@@ -531,71 +607,61 @@ function computePub (src, out, family, version, htmlUrlExtensionStyle) {
|
|
|
531
607
|
urlSegments[lastUrlSegmentIdx] = ''
|
|
532
608
|
}
|
|
533
609
|
url = '/' + urlSegments.join('/')
|
|
534
|
-
} else {
|
|
535
|
-
|
|
536
|
-
if (family === 'alias' && !src.relative) pub.splat = true
|
|
537
|
-
}
|
|
538
|
-
|
|
539
|
-
pub.url = ~url.indexOf(' ') ? url.replace(SPACE_RX, '%20') : url
|
|
540
|
-
|
|
541
|
-
if (out) {
|
|
542
|
-
pub.moduleRootPath = out.moduleRootPath
|
|
543
|
-
pub.rootPath = out.rootPath
|
|
610
|
+
} else if ((url = '/' + out.path) === '/.') {
|
|
611
|
+
url = '/'
|
|
544
612
|
}
|
|
613
|
+
pub.url = ~url.indexOf(' ') ? url.replaceAll(' ', '%20') : url
|
|
614
|
+
return out ? Object.assign(pub, { moduleRootPath: out.moduleRootPath, rootPath: out.rootPath }) : pub
|
|
615
|
+
}
|
|
545
616
|
|
|
546
|
-
|
|
617
|
+
function addSymbolicVersionAlias (componentVersion) {
|
|
618
|
+
const { name: component, version } = componentVersion
|
|
619
|
+
const originalVersionSegment = computeVersionSegment.call(this, componentVersion, 'original')
|
|
620
|
+
const symbolicVersionSegment = computeVersionSegment.call(this, componentVersion, 'alias')
|
|
621
|
+
if (symbolicVersionSegment === originalVersionSegment || symbolicVersionSegment == null) return
|
|
622
|
+
const originalVersionSrc = { component, version, versionSegment: originalVersionSegment }
|
|
623
|
+
const symbolicVersionSrc = { component, version, versionSegment: symbolicVersionSegment }
|
|
624
|
+
return this.latestVersionSegmentStrategy === 'redirect:to'
|
|
625
|
+
? this.addSplatAlias(originalVersionSrc, symbolicVersionSrc)
|
|
626
|
+
: this.addSplatAlias(symbolicVersionSrc, originalVersionSrc)
|
|
547
627
|
}
|
|
548
628
|
|
|
549
|
-
function computeVersionSegment (
|
|
629
|
+
function computeVersionSegment (componentVersion, mode) {
|
|
630
|
+
const version = componentVersion.version
|
|
550
631
|
// special designation for master version is @deprecated; special designation scheduled to be removed in Antora 4
|
|
551
|
-
|
|
552
|
-
const
|
|
553
|
-
if (
|
|
554
|
-
|
|
632
|
+
const normalizedVersion = version && version !== 'master' ? version : ''
|
|
633
|
+
const { versionSegment = normalizedVersion } = componentVersion
|
|
634
|
+
if (mode === 'original') return versionSegment
|
|
635
|
+
const strategy = this.latestVersionSegmentStrategy
|
|
636
|
+
if (!versionSegment) {
|
|
637
|
+
if (!mode) return ''
|
|
555
638
|
if (strategy === 'redirect:to') return
|
|
556
639
|
}
|
|
557
|
-
if (strategy === 'redirect:to' || strategy === (mode
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
const segment =
|
|
640
|
+
if (strategy === 'redirect:to' || strategy === (mode ? 'redirect:from' : 'replace')) {
|
|
641
|
+
let component
|
|
642
|
+
if ((component = 'name' in componentVersion && this.getComponent(componentVersion.name))) {
|
|
643
|
+
const latestSegment =
|
|
562
644
|
componentVersion === component.latest
|
|
563
|
-
? this.
|
|
645
|
+
? this.latestVersionSegment
|
|
564
646
|
: componentVersion === component.latestPrerelease
|
|
565
|
-
? this.
|
|
647
|
+
? this.latestPrereleaseVersionSegment
|
|
566
648
|
: undefined
|
|
567
|
-
return
|
|
649
|
+
return latestSegment == null ? versionSegment : latestSegment
|
|
568
650
|
}
|
|
569
651
|
}
|
|
570
|
-
return
|
|
652
|
+
return versionSegment
|
|
571
653
|
}
|
|
572
654
|
|
|
573
|
-
function
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
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 })
|
|
655
|
+
function getComponentVersionFiles (componentVersionId) {
|
|
656
|
+
return this.findBy(componentVersionId)
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
function getComponentVersionStartPage (componentVersionId) {
|
|
660
|
+
return this.resolvePage('index.adoc', componentVersionId)
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
function getVersion () {
|
|
664
|
+
return this.version
|
|
599
665
|
}
|
|
600
666
|
|
|
601
667
|
module.exports = ContentCatalog
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@antora/content-classifier",
|
|
3
|
-
"version": "3.1.
|
|
3
|
+
"version": "3.1.11",
|
|
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)",
|
|
@@ -12,7 +12,8 @@
|
|
|
12
12
|
"homepage": "https://antora.org",
|
|
13
13
|
"repository": {
|
|
14
14
|
"type": "git",
|
|
15
|
-
"url": "git+https://gitlab.com/antora/antora.git"
|
|
15
|
+
"url": "git+https://gitlab.com/antora/antora.git",
|
|
16
|
+
"directory": "packages/content-classifier"
|
|
16
17
|
},
|
|
17
18
|
"bugs": {
|
|
18
19
|
"url": "https://gitlab.com/antora/antora/issues"
|
|
@@ -30,13 +31,13 @@
|
|
|
30
31
|
"#constants": "./lib/constants.js"
|
|
31
32
|
},
|
|
32
33
|
"dependencies": {
|
|
33
|
-
"@antora/asciidoc-loader": "3.1.
|
|
34
|
-
"@antora/logger": "3.1.
|
|
34
|
+
"@antora/asciidoc-loader": "3.1.11",
|
|
35
|
+
"@antora/logger": "3.1.11",
|
|
35
36
|
"mime-types": "~2.1",
|
|
36
37
|
"vinyl": "~3.0"
|
|
37
38
|
},
|
|
38
39
|
"engines": {
|
|
39
|
-
"node": ">=
|
|
40
|
+
"node": ">=18.0.0"
|
|
40
41
|
},
|
|
41
42
|
"files": [
|
|
42
43
|
"lib/"
|
|
@@ -51,7 +52,7 @@
|
|
|
51
52
|
],
|
|
52
53
|
"scripts": {
|
|
53
54
|
"test": "_mocha",
|
|
54
|
-
"prepublishOnly": "npx -y downdoc --prepublish",
|
|
55
|
-
"postpublish": "npx -y downdoc --postpublish"
|
|
55
|
+
"prepublishOnly": "npx -y downdoc@latest --prepublish",
|
|
56
|
+
"postpublish": "npx -y downdoc@latest --postpublish"
|
|
56
57
|
}
|
|
57
58
|
}
|