@antora/content-classifier 3.1.14 → 3.2.0-alpha.10
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 +82 -43
- package/lib/content-catalog.js +177 -125
- package/lib/util/summarize-file-location.js +1 -1
- package/package.json +8 -7
package/lib/classify-content.js
CHANGED
|
@@ -4,64 +4,95 @@ const ContentCatalog = require('./content-catalog')
|
|
|
4
4
|
const collateAsciiDocAttributes = require('@antora/asciidoc-loader/config/collate-asciidoc-attributes')
|
|
5
5
|
const logger = require('./logger')
|
|
6
6
|
const summarizeFileLocation = require('./util/summarize-file-location')
|
|
7
|
+
const families = { attachments: true, examples: true, images: true, pages: true, partials: true }
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* Organizes the raw aggregate of virtual files into a {ContentCatalog}.
|
|
10
11
|
*
|
|
11
12
|
* @memberof content-classifier
|
|
12
13
|
*
|
|
13
|
-
* @param {Object} playbook - The configuration object for Antora.
|
|
14
|
+
* @param {Object} playbook - The configuration object for Antora. See ContentCatalog constructor for relevant keys.
|
|
14
15
|
* @param {Object} playbook.site - Site-related configuration data.
|
|
15
16
|
* @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
17
|
* @param {Object} aggregate - The raw aggregate of virtual file objects to be classified.
|
|
19
18
|
* @param {Object} [siteAsciiDocConfig={}] - Site-wide AsciiDoc processor configuration options.
|
|
20
|
-
* @
|
|
19
|
+
* @param {Function} [onComponentsRegistered] - A function (optionally async) to invoke after components are
|
|
20
|
+
* registered. Must return an instance of ContentCatalog. If async, this function will also return a Promise.
|
|
21
|
+
*
|
|
22
|
+
* @returns {ContentCatalog} A structured catalog of content components, versions, and virtual content files.
|
|
21
23
|
*/
|
|
22
|
-
function classifyContent (playbook, aggregate, siteAsciiDocConfig = {}) {
|
|
23
|
-
const
|
|
24
|
-
aggregate
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
24
|
+
function classifyContent (playbook, aggregate, siteAsciiDocConfig = {}, onComponentsRegistered = undefined) {
|
|
25
|
+
const siteStartPage = playbook.site.startPage
|
|
26
|
+
let contentCatalog = registerComponentVersions(new ContentCatalog(playbook), aggregate, siteAsciiDocConfig)
|
|
27
|
+
return typeof onComponentsRegistered === 'function' &&
|
|
28
|
+
(contentCatalog = onComponentsRegistered(contentCatalog)) instanceof Promise
|
|
29
|
+
? contentCatalog.then((contentCatalogValue) => addFilesAndRegisterStartPages(contentCatalogValue, siteStartPage))
|
|
30
|
+
: addFilesAndRegisterStartPages(contentCatalog, siteStartPage)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function registerComponentVersions (contentCatalog, aggregate, siteAsciiDocConfig) {
|
|
34
|
+
for (const componentVersionBucket of aggregate) {
|
|
35
|
+
// advance files, nav, and startPage to component version to be used in later phase
|
|
36
|
+
const { name, version, files, nav, startPage, ...data } = Object.assign(componentVersionBucket, {
|
|
37
|
+
asciidoc: resolveAsciiDocConfig(siteAsciiDocConfig, componentVersionBucket),
|
|
38
|
+
})
|
|
39
|
+
Object.assign(contentCatalog.registerComponentVersion(name, version, data), { files, nav, startPage })
|
|
40
|
+
}
|
|
41
|
+
return contentCatalog
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function addFilesAndRegisterStartPages (contentCatalog, siteStartPage) {
|
|
45
|
+
for (const { versions: componentVersions } of contentCatalog.getComponents()) {
|
|
46
|
+
for (const componentVersion of componentVersions) {
|
|
47
|
+
const { name: component, version, files = [], nav, startPage } = componentVersion
|
|
39
48
|
const navResolved = nav && (nav.resolved = new Set())
|
|
40
|
-
|
|
41
|
-
|
|
49
|
+
const rootFiles = []
|
|
50
|
+
for (let file, i = 0; (file = files[i]); i++) {
|
|
51
|
+
files[i] = undefined // free memory
|
|
52
|
+
const outcome = allocateSrc(file, component, version, nav)
|
|
53
|
+
if (outcome) {
|
|
54
|
+
contentCatalog.addFile(file, componentVersion)
|
|
55
|
+
} else if (outcome == null) {
|
|
56
|
+
rootFiles.push(file)
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
if (rootFiles.length) {
|
|
60
|
+
for (let file, i = 0; (file = rootFiles[i]); i++) {
|
|
61
|
+
if (file.src.origin.hasModulesDir) continue
|
|
62
|
+
allocateSrc(file, component, version, null, true) && contentCatalog.addFile(file, componentVersion)
|
|
63
|
+
}
|
|
64
|
+
}
|
|
42
65
|
if (navResolved && nav.length > navResolved.size && new Set(nav).size > navResolved.size) {
|
|
43
66
|
const loc = summarizeFileLocation({ path: 'antora.yml', src: { origin: nav.origin } })
|
|
44
67
|
for (const filepath of nav) {
|
|
45
68
|
if (navResolved.has(filepath)) continue
|
|
46
|
-
logger.warn('Could not resolve nav entry for %s@%s defined in %s: %s', version,
|
|
69
|
+
logger.warn('Could not resolve nav entry for %s@%s defined in %s: %s', version, component, loc, filepath)
|
|
47
70
|
}
|
|
48
71
|
}
|
|
49
|
-
contentCatalog.registerComponentVersionStartPage(
|
|
50
|
-
}
|
|
51
|
-
|
|
72
|
+
contentCatalog.registerComponentVersionStartPage(component, componentVersion, startPage)
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
contentCatalog.registerSiteStartPage(siteStartPage)
|
|
52
76
|
return contentCatalog
|
|
53
77
|
}
|
|
54
78
|
|
|
55
|
-
function allocateSrc (file, component, version, nav) {
|
|
56
|
-
const extname = file.src
|
|
79
|
+
function allocateSrc (file, component, version, nav, implicitRoot) {
|
|
80
|
+
const { extname, family, origin } = file.src
|
|
81
|
+
if (family && family !== 'nav') {
|
|
82
|
+
Object.assign(file.src, { component, version })
|
|
83
|
+
file.src.moduleRootPath ??= calculateRootPath(file.src.relative.split('/').length)
|
|
84
|
+
return true
|
|
85
|
+
}
|
|
57
86
|
const filepath = file.path
|
|
58
|
-
const pathSegments = filepath.split('/')
|
|
87
|
+
const pathSegments = implicitRoot ? ['modules', 'ROOT'].concat(filepath.split('/')) : filepath.split('/')
|
|
88
|
+
const inModules = pathSegments[0] === 'modules'
|
|
89
|
+
if (!implicitRoot && origin) origin.hasModulesDir ||= inModules
|
|
59
90
|
let navInfo
|
|
60
91
|
if (nav && (navInfo = getNavInfo(filepath, nav))) {
|
|
61
|
-
if (extname !== '.adoc') return // ignore file
|
|
92
|
+
if (extname !== '.adoc') return false // ignore file
|
|
62
93
|
file.nav = navInfo
|
|
63
94
|
file.src.family = 'nav'
|
|
64
|
-
if (
|
|
95
|
+
if (inModules && pathSegments.length > 2) {
|
|
65
96
|
file.src.module = pathSegments[1]
|
|
66
97
|
// relative to modules/<module>
|
|
67
98
|
file.src.relative = pathSegments.slice(2).join('/')
|
|
@@ -70,7 +101,7 @@ function allocateSrc (file, component, version, nav) {
|
|
|
70
101
|
// relative to content source root
|
|
71
102
|
file.src.relative = filepath
|
|
72
103
|
}
|
|
73
|
-
} else if (
|
|
104
|
+
} else if (inModules) {
|
|
74
105
|
let familyFolder = pathSegments[2]
|
|
75
106
|
switch (familyFolder) {
|
|
76
107
|
case 'pages':
|
|
@@ -84,29 +115,29 @@ function allocateSrc (file, component, version, nav) {
|
|
|
84
115
|
// relative to modules/<module>/pages
|
|
85
116
|
file.src.relative = pathSegments.slice(3).join('/')
|
|
86
117
|
} else {
|
|
87
|
-
return // ignore file
|
|
118
|
+
return false // ignore file
|
|
88
119
|
}
|
|
89
120
|
break
|
|
90
121
|
case 'assets':
|
|
91
122
|
switch ((familyFolder = pathSegments[3])) {
|
|
92
123
|
case 'attachments':
|
|
93
124
|
case 'images':
|
|
94
|
-
if (!extname) return // ignore file
|
|
125
|
+
if (!extname && familyFolder === 'images') return false // ignore file
|
|
95
126
|
file.src.family = familyFolder.substr(0, familyFolder.length - 1)
|
|
96
127
|
// relative to modules/<module>/assets/<family>s
|
|
97
128
|
file.src.relative = pathSegments.slice(4).join('/')
|
|
98
129
|
break
|
|
99
130
|
default:
|
|
100
|
-
return // ignore file
|
|
131
|
+
return false // ignore file
|
|
101
132
|
}
|
|
102
133
|
break
|
|
103
|
-
case 'attachments':
|
|
104
134
|
case 'images':
|
|
105
|
-
if (!extname) return
|
|
135
|
+
if (!extname) return false
|
|
106
136
|
file.src.family = familyFolder.substr(0, familyFolder.length - 1)
|
|
107
137
|
// relative to modules/<module>/<family>s
|
|
108
138
|
file.src.relative = pathSegments.slice(3).join('/')
|
|
109
139
|
break
|
|
140
|
+
case 'attachments':
|
|
110
141
|
case 'examples':
|
|
111
142
|
case 'partials':
|
|
112
143
|
file.src.family = familyFolder.substr(0, familyFolder.length - 1)
|
|
@@ -114,12 +145,14 @@ function allocateSrc (file, component, version, nav) {
|
|
|
114
145
|
file.src.relative = pathSegments.slice(3).join('/')
|
|
115
146
|
break
|
|
116
147
|
default:
|
|
117
|
-
return // ignore file
|
|
148
|
+
return false // ignore file
|
|
118
149
|
}
|
|
119
150
|
file.src.module = pathSegments[1]
|
|
120
151
|
file.src.moduleRootPath = calculateRootPath(pathSegments.length - 3)
|
|
152
|
+
} else if (origin && pathSegments[0] in families) {
|
|
153
|
+
return // defer file
|
|
121
154
|
} else {
|
|
122
|
-
return // ignore file
|
|
155
|
+
return false // ignore file
|
|
123
156
|
}
|
|
124
157
|
file.src.component = component
|
|
125
158
|
file.src.version = version
|
|
@@ -140,13 +173,19 @@ function getNavInfo (filepath, nav) {
|
|
|
140
173
|
if (~index) return nav.resolved.add(filepath) && { index }
|
|
141
174
|
}
|
|
142
175
|
|
|
143
|
-
function resolveAsciiDocConfig (siteAsciiDocConfig, { asciidoc, origins = [] }) {
|
|
176
|
+
function resolveAsciiDocConfig (siteAsciiDocConfig, { name, version, asciidoc, origins = [] }) {
|
|
144
177
|
const scopedAttributes = asciidoc?.attributes
|
|
145
178
|
if (scopedAttributes) {
|
|
146
|
-
const initial = siteAsciiDocConfig.attributes
|
|
179
|
+
const initial = Object.assign({}, siteAsciiDocConfig.attributes)
|
|
180
|
+
initial['antora-component-name'] = name
|
|
181
|
+
initial['antora-component-version'] = version
|
|
147
182
|
const mdc = { file: { path: 'antora.yml', origin: origins[origins.length - 1] } }
|
|
148
183
|
const attributes = collateAsciiDocAttributes(scopedAttributes, { initial, mdc, merge: true })
|
|
149
|
-
if (attributes !== initial)
|
|
184
|
+
if (attributes !== initial) {
|
|
185
|
+
delete attributes['antora-component-name']
|
|
186
|
+
delete attributes['antora-component-version']
|
|
187
|
+
return Object.assign({}, siteAsciiDocConfig, { attributes })
|
|
188
|
+
}
|
|
150
189
|
}
|
|
151
190
|
return siteAsciiDocConfig
|
|
152
191
|
}
|
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,14 @@ 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:
|
|
63
|
-
const componentVersion = {
|
|
63
|
+
const { asciidoc, displayVersion, prerelease, startPage: startPageRef, title, versionSegment, origins } = descriptor
|
|
64
|
+
const componentVersion = {
|
|
65
|
+
displayVersion: displayVersion || version || 'default',
|
|
66
|
+
title: title || name,
|
|
67
|
+
version,
|
|
68
|
+
origins: new Set(origins && typeof origins[Symbol.iterator] === 'function' ? origins : []),
|
|
69
|
+
}
|
|
70
|
+
if (versionSegment != null) componentVersion.versionSegment = versionSegment
|
|
64
71
|
Object.defineProperty(componentVersion, 'name', { value: name, enumerable: true })
|
|
65
72
|
if (prerelease) {
|
|
66
73
|
componentVersion.prerelease = prerelease
|
|
@@ -128,15 +135,16 @@ class ContentCatalog {
|
|
|
128
135
|
)
|
|
129
136
|
)
|
|
130
137
|
}
|
|
131
|
-
if (
|
|
132
|
-
|
|
138
|
+
if (startPageRef) {
|
|
139
|
+
// @deprecated use separate call to register start page for component version
|
|
140
|
+
this.registerComponentVersionStartPage(name, componentVersion, startPageRef === true ? undefined : startPageRef)
|
|
133
141
|
}
|
|
134
142
|
return componentVersion
|
|
135
143
|
}
|
|
136
144
|
|
|
137
|
-
addFile (file) {
|
|
145
|
+
addFile (file, componentVersion) {
|
|
138
146
|
const src = file.src
|
|
139
|
-
let family = src
|
|
147
|
+
let { component, version, family } = src
|
|
140
148
|
let filesForFamily = this[$files].get(family)
|
|
141
149
|
if (!filesForFamily) this[$files].set(family, (filesForFamily = new Map()))
|
|
142
150
|
const key = generateKey(src)
|
|
@@ -148,48 +156,60 @@ class ContentCatalog {
|
|
|
148
156
|
.map((it, idx) => `${idx + 1}: ${summarizeFileLocation(it)}`)
|
|
149
157
|
.join(LOG_WRAP)
|
|
150
158
|
if (family === 'nav') {
|
|
151
|
-
throw new Error(`Duplicate nav in ${
|
|
159
|
+
throw new Error(`Duplicate nav file: ${file.path} in ${version}@${component}${LOG_WRAP}${details}`)
|
|
152
160
|
}
|
|
153
161
|
throw new Error(`Duplicate ${family}: ${generateResourceSpec(src)}${LOG_WRAP}${details}`)
|
|
154
162
|
}
|
|
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
|
|
163
|
+
// NOTE: assume that if the file is not a vinyl object, the src likely needs to be prepared
|
|
157
164
|
// 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)
|
|
165
|
+
// an alternate approach would be to call prepareSrc if the path property is not set
|
|
160
166
|
if (!File.isVinyl(file)) {
|
|
161
167
|
prepareSrc(src)
|
|
162
168
|
file = new File(file)
|
|
163
169
|
}
|
|
164
170
|
if (family === 'alias') {
|
|
165
|
-
src.mediaType = 'text/asciidoc'
|
|
166
171
|
file.mediaType = 'text/html'
|
|
167
|
-
// NOTE:
|
|
168
|
-
|
|
169
|
-
//
|
|
172
|
+
// NOTE: short circuit in case of splat alias (alias -> alias)
|
|
173
|
+
if (file.rel.src.family === 'alias' && file.pub?.splat) return filesForFamily.set(key, file) && file
|
|
174
|
+
// NOTE the effective family of an alias is a page, which redirects to the target file
|
|
175
|
+
family = 'page'
|
|
176
|
+
src.mediaType = 'text/asciidoc'
|
|
170
177
|
} else if (!(file.mediaType = src.mediaType) && !('mediaType' in src)) {
|
|
171
|
-
|
|
178
|
+
// QUESTION: should we preserve the mediaType property on file if already defined?
|
|
179
|
+
file.mediaType = src.mediaType = src.extname
|
|
180
|
+
? resolveMimeType(src.extname) || (family === 'page' ? 'text/asciidoc' : undefined)
|
|
181
|
+
: 'text/plain'
|
|
172
182
|
}
|
|
173
183
|
let publishable
|
|
174
|
-
let
|
|
184
|
+
let activeVersionSegment
|
|
175
185
|
if (file.out) {
|
|
176
186
|
publishable = true
|
|
177
187
|
} else if ('out' in file) {
|
|
178
188
|
delete file.out
|
|
179
189
|
} else if (
|
|
190
|
+
!file.private &&
|
|
180
191
|
(family === 'page' || family === 'image' || family === 'attachment') &&
|
|
181
|
-
('/' + src.relative).indexOf('/_') < 0
|
|
192
|
+
(file.private === false || ('/' + src.relative).indexOf('/_') < 0)
|
|
182
193
|
) {
|
|
183
194
|
publishable = true
|
|
184
|
-
|
|
185
|
-
|
|
195
|
+
if (componentVersion == null) componentVersion = this.getComponentVersion(component, version) || { version }
|
|
196
|
+
activeVersionSegment =
|
|
197
|
+
'activeVersionSegment' in componentVersion
|
|
198
|
+
? componentVersion.activeVersionSegment
|
|
199
|
+
: computeVersionSegment.call(this, componentVersion)
|
|
200
|
+
file.out = computeOut(src, family, activeVersionSegment, this.htmlUrlExtensionStyle)
|
|
186
201
|
}
|
|
187
202
|
if (!file.pub && (publishable || family === 'nav')) {
|
|
188
|
-
if (
|
|
189
|
-
|
|
203
|
+
if (activeVersionSegment == null) {
|
|
204
|
+
if (componentVersion == null) componentVersion = this.getComponentVersion(component, version) || { version }
|
|
205
|
+
activeVersionSegment =
|
|
206
|
+
'activeVersionSegment' in componentVersion
|
|
207
|
+
? componentVersion.activeVersionSegment
|
|
208
|
+
: computeVersionSegment.call(this, componentVersion)
|
|
209
|
+
}
|
|
210
|
+
file.pub = computePub(src, file.out, family, activeVersionSegment, this.htmlUrlExtensionStyle)
|
|
190
211
|
}
|
|
191
|
-
filesForFamily.set(key, file)
|
|
192
|
-
return file
|
|
212
|
+
return filesForFamily.set(key, file) && file
|
|
193
213
|
}
|
|
194
214
|
|
|
195
215
|
removeFile (file) {
|
|
@@ -266,38 +286,48 @@ class ContentCatalog {
|
|
|
266
286
|
|
|
267
287
|
// TODO add `follow` argument to control whether alias is followed
|
|
268
288
|
getSiteStartPage () {
|
|
269
|
-
|
|
289
|
+
let file
|
|
290
|
+
if ((file = this.getById(ROOT_INDEX_PAGE_ID))) return file
|
|
291
|
+
if ((file = this.getById(ROOT_INDEX_ALIAS_ID))) return file.rel
|
|
292
|
+
const rootComponent = this.getComponent('ROOT')
|
|
293
|
+
if (!rootComponent) return
|
|
294
|
+
const version = rootComponent.versions.find(({ activeVersionSegment }) => activeVersionSegment === '')?.version
|
|
295
|
+
if (!version) return
|
|
296
|
+
if ((file = this.getById(Object.assign({}, ROOT_INDEX_PAGE_ID, { version })))) return file
|
|
297
|
+
if ((file = this.getById(Object.assign({}, ROOT_INDEX_ALIAS_ID, { version })))) return file.rel
|
|
270
298
|
}
|
|
271
299
|
|
|
272
300
|
registerComponentVersionStartPage (name, componentVersion, startPageSpec = undefined) {
|
|
301
|
+
const component = name
|
|
273
302
|
let version = componentVersion.version
|
|
274
303
|
if (version == null) {
|
|
275
304
|
// QUESTION: should we warn or throw error if component version cannot be found?
|
|
276
|
-
if (!(componentVersion = this.getComponentVersion(
|
|
305
|
+
if (!(componentVersion = this.getComponentVersion(component, componentVersion))) return
|
|
277
306
|
version = componentVersion.version
|
|
278
307
|
}
|
|
308
|
+
const activeVersionSegment = computeVersionSegment.call(this, componentVersion)
|
|
279
309
|
let startPage
|
|
280
310
|
let startPageSrc
|
|
281
|
-
const indexPageId = Object.assign({}, ROOT_INDEX_PAGE_ID, { component
|
|
311
|
+
const indexPageId = Object.assign({}, ROOT_INDEX_PAGE_ID, { component, version })
|
|
282
312
|
if (startPageSpec) {
|
|
283
313
|
if (
|
|
284
314
|
(startPage = this.resolvePage(startPageSpec, indexPageId)) &&
|
|
285
|
-
(startPageSrc = startPage.src).component ===
|
|
315
|
+
(startPageSrc = startPage.src).component === component &&
|
|
286
316
|
startPageSrc.version === version
|
|
287
317
|
) {
|
|
288
318
|
if (!this.getById(indexPageId)) {
|
|
289
|
-
const indexAliasId = Object.assign({}, ROOT_INDEX_ALIAS_ID, { component
|
|
319
|
+
const indexAliasId = Object.assign({}, ROOT_INDEX_ALIAS_ID, { component, version })
|
|
290
320
|
const indexAlias = this.getById(indexAliasId)
|
|
291
321
|
indexAlias
|
|
292
322
|
? indexAlias.synthetic && Object.assign(indexAlias, { rel: startPage })
|
|
293
|
-
: this.addFile({ src: indexAliasId, rel: startPage, synthetic: true })
|
|
323
|
+
: this.addFile({ src: indexAliasId, rel: startPage, synthetic: true }, componentVersion)
|
|
294
324
|
}
|
|
295
325
|
} else {
|
|
296
326
|
// TODO pass componentVersion as logObject
|
|
297
327
|
logger.warn(
|
|
298
328
|
'Start page specified for %s@%s %s: %s',
|
|
299
329
|
version,
|
|
300
|
-
|
|
330
|
+
component,
|
|
301
331
|
startPage === false ? 'has invalid syntax' : 'not found',
|
|
302
332
|
startPageSpec
|
|
303
333
|
)
|
|
@@ -308,25 +338,34 @@ class ContentCatalog {
|
|
|
308
338
|
}
|
|
309
339
|
if (startPage) {
|
|
310
340
|
componentVersion.url = startPage.pub.url
|
|
311
|
-
} else {
|
|
341
|
+
} else if (!componentVersion.url) {
|
|
312
342
|
// QUESTION: should we warn if the default start page cannot be found?
|
|
313
|
-
const versionSegment = computeVersionSegment.call(this, name, version)
|
|
314
343
|
componentVersion.url = computePub(
|
|
315
344
|
(startPageSrc = prepareSrc(Object.assign({}, indexPageId, { family: 'page' }))),
|
|
316
|
-
computeOut(startPageSrc, startPageSrc.family,
|
|
345
|
+
computeOut(startPageSrc, startPageSrc.family, activeVersionSegment, this.htmlUrlExtensionStyle),
|
|
317
346
|
startPageSrc.family,
|
|
318
|
-
|
|
347
|
+
activeVersionSegment,
|
|
319
348
|
this.htmlUrlExtensionStyle
|
|
320
349
|
).url
|
|
321
350
|
}
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
351
|
+
Object.defineProperties(componentVersion, {
|
|
352
|
+
activeVersionSegment:
|
|
353
|
+
activeVersionSegment === version
|
|
354
|
+
? { configurable: true, enumerable: false, get: getVersion }
|
|
355
|
+
: { configurable: true, enumerable: false, value: activeVersionSegment },
|
|
356
|
+
files: {
|
|
357
|
+
configurable: true,
|
|
358
|
+
enumerable: false,
|
|
359
|
+
get: getComponentVersionFiles.bind(this, { component, version }),
|
|
360
|
+
},
|
|
361
|
+
startPage: {
|
|
362
|
+
configurable: true,
|
|
363
|
+
enumerable: false,
|
|
364
|
+
get: getComponentVersionStartPage.bind(this, { component, version }),
|
|
365
|
+
},
|
|
366
|
+
})
|
|
367
|
+
addSymbolicVersionAlias.call(this, componentVersion)
|
|
368
|
+
return startPage
|
|
330
369
|
}
|
|
331
370
|
|
|
332
371
|
registerSiteStartPage (startPageSpec) {
|
|
@@ -334,9 +373,9 @@ class ContentCatalog {
|
|
|
334
373
|
const rel = this.resolvePage(startPageSpec)
|
|
335
374
|
if (rel) {
|
|
336
375
|
if (this.getById(ROOT_INDEX_PAGE_ID)) return
|
|
376
|
+
if (rel.pub.url === (this.htmlUrlExtensionStyle === 'default' ? '/index.html' : '/')) return
|
|
337
377
|
const rootIndexAlias = this.getById(ROOT_INDEX_ALIAS_ID)
|
|
338
378
|
if (rootIndexAlias) return rootIndexAlias.synthetic ? Object.assign(rootIndexAlias, { rel }) : undefined
|
|
339
|
-
if (rel.pub.url === (this.htmlUrlExtensionStyle === 'default' ? '/index.html' : '/')) return
|
|
340
379
|
const src = Object.assign({}, ROOT_INDEX_ALIAS_ID)
|
|
341
380
|
return this.addFile({ src, rel, synthetic: true }, { version: src.version })
|
|
342
381
|
}
|
|
@@ -357,9 +396,14 @@ class ContentCatalog {
|
|
|
357
396
|
// QUESTION should we throw an error if alias is invalid?
|
|
358
397
|
if (!src || (inferredSpec && src.relative === '.adoc')) return
|
|
359
398
|
const component = this.getComponent(src.component)
|
|
399
|
+
let componentVersion
|
|
360
400
|
if (component) {
|
|
361
401
|
// NOTE version is not set when alias specifies a component, but not a version
|
|
362
|
-
if (src.version == null)
|
|
402
|
+
if (src.version == null) {
|
|
403
|
+
src.version = (componentVersion = component.latest).version
|
|
404
|
+
} else {
|
|
405
|
+
componentVersion = this.getComponentVersion(component, src.version)
|
|
406
|
+
}
|
|
363
407
|
const existingPage = this.getById(src)
|
|
364
408
|
if (existingPage) {
|
|
365
409
|
throw new Error(
|
|
@@ -384,12 +428,34 @@ class ContentCatalog {
|
|
|
384
428
|
)
|
|
385
429
|
}
|
|
386
430
|
// NOTE the redirect producer will populate contents when the redirect facility is 'static'
|
|
387
|
-
const alias = this.addFile({ src, rel: target })
|
|
431
|
+
const alias = this.addFile({ src, rel: target }, componentVersion)
|
|
388
432
|
// NOTE record the first alias this target claims as the preferred one
|
|
389
433
|
if (!target.rel) target.rel = alias
|
|
390
434
|
return alias
|
|
391
435
|
}
|
|
392
436
|
|
|
437
|
+
/**
|
|
438
|
+
* Adds a splat (directory) alias from the specified version segment in one component to the specified
|
|
439
|
+
* version segment in the same or different component.
|
|
440
|
+
*
|
|
441
|
+
* @returns {File} The virtual file that represents the splat alias.
|
|
442
|
+
*/
|
|
443
|
+
addSplatAlias (from, to) {
|
|
444
|
+
if (!from.versionSegment) throw new Error('cannot map splat alias from empty version segment')
|
|
445
|
+
const family = 'alias'
|
|
446
|
+
const baseSrc = { module: 'ROOT', family, relative: '', basename: '', stem: '', extname: '' }
|
|
447
|
+
const basePub = { splat: true }
|
|
448
|
+
const { component: fromComponent = to.component, versionSegment: fromVersionSegment } = from
|
|
449
|
+
const fromSrc = Object.assign({ component: fromComponent, version: fromVersionSegment }, baseSrc)
|
|
450
|
+
const fromPub = Object.assign(computePub(fromSrc, computeOut(fromSrc, family, fromVersionSegment), family), basePub)
|
|
451
|
+
const { component: toComponent, version: toVersion } = to
|
|
452
|
+
const toVersionSegment =
|
|
453
|
+
to.versionSegment ?? this.getComponentVersion(toComponent, toVersion)?.activeVersionSegment ?? toVersion
|
|
454
|
+
const toSrc = Object.assign({ component: toComponent, version: toVersion ?? toVersionSegment }, baseSrc)
|
|
455
|
+
const toPub = Object.assign(computePub(toSrc, computeOut(toSrc, family, toVersionSegment), family), basePub)
|
|
456
|
+
return this.addFile({ pub: fromPub, src: fromSrc, rel: { pub: toPub, src: toSrc } })
|
|
457
|
+
}
|
|
458
|
+
|
|
393
459
|
/**
|
|
394
460
|
* Attempts to resolve a page reference within the given context to a page in the catalog.
|
|
395
461
|
*
|
|
@@ -474,32 +540,28 @@ function generateResourceSpec ({ component, version, module: module_, family, re
|
|
|
474
540
|
|
|
475
541
|
function prepareSrc (src) {
|
|
476
542
|
let { basename, extname, stem } = src
|
|
477
|
-
let update
|
|
478
543
|
if (basename == null) {
|
|
479
|
-
|
|
480
|
-
basename = path.basename(src.relative)
|
|
544
|
+
basename = src.basename = path.basename(src.relative)
|
|
481
545
|
}
|
|
482
546
|
if (stem == null) {
|
|
483
|
-
update = true
|
|
484
547
|
if (extname == null) {
|
|
485
548
|
if (~(extname = basename.lastIndexOf('.'))) {
|
|
486
|
-
stem = basename.substr(0, extname)
|
|
487
|
-
extname = basename.substr(extname)
|
|
549
|
+
src.stem = basename.substr(0, extname)
|
|
550
|
+
src.extname = basename.substr(extname)
|
|
488
551
|
} else {
|
|
489
|
-
stem = basename
|
|
490
|
-
extname = ''
|
|
552
|
+
src.stem = basename
|
|
553
|
+
src.extname = ''
|
|
491
554
|
}
|
|
492
555
|
} else {
|
|
493
|
-
stem = basename.substr(0, basename.length - extname.length)
|
|
556
|
+
src.stem = basename.substr(0, basename.length - extname.length)
|
|
494
557
|
}
|
|
495
558
|
} else if (extname == null) {
|
|
496
|
-
|
|
497
|
-
extname = basename.substr(stem.length)
|
|
559
|
+
src.extname = basename.substr(stem.length)
|
|
498
560
|
}
|
|
499
|
-
return
|
|
561
|
+
return src
|
|
500
562
|
}
|
|
501
563
|
|
|
502
|
-
function computeOut (src, family,
|
|
564
|
+
function computeOut (src, family, versionSegment, htmlUrlExtensionStyle) {
|
|
503
565
|
let { component, module: module_, basename, extname, relative, stem } = src
|
|
504
566
|
if (component === 'ROOT') component = ''
|
|
505
567
|
if (module_ === 'ROOT') module_ = ''
|
|
@@ -519,7 +581,7 @@ function computeOut (src, family, version, htmlUrlExtensionStyle) {
|
|
|
519
581
|
familyPathSegment = '_attachments'
|
|
520
582
|
}
|
|
521
583
|
|
|
522
|
-
const modulePath = path.join(component,
|
|
584
|
+
const modulePath = path.join(component, versionSegment, module_)
|
|
523
585
|
const dirname = path.join(modulePath, familyPathSegment, path.dirname(relative), indexifyPathSegment)
|
|
524
586
|
const path_ = path.join(dirname, basename)
|
|
525
587
|
const moduleRootPath = path.relative(dirname, modulePath) || '.'
|
|
@@ -528,13 +590,13 @@ function computeOut (src, family, version, htmlUrlExtensionStyle) {
|
|
|
528
590
|
return { dirname, basename, path: path_, moduleRootPath, rootPath }
|
|
529
591
|
}
|
|
530
592
|
|
|
531
|
-
function computePub (src, out, family,
|
|
593
|
+
function computePub (src, out, family, versionSegment, htmlUrlExtensionStyle) {
|
|
532
594
|
const pub = {}
|
|
533
595
|
let url
|
|
534
596
|
if (family === 'nav') {
|
|
535
597
|
const component = src.component || 'ROOT'
|
|
536
598
|
const urlSegments = component === 'ROOT' ? [] : [component]
|
|
537
|
-
if (
|
|
599
|
+
if (versionSegment) urlSegments.push(versionSegment)
|
|
538
600
|
const module_ = src.module || 'ROOT'
|
|
539
601
|
if (module_ !== 'ROOT') urlSegments.push(module_)
|
|
540
602
|
if (urlSegments.length) urlSegments.push('')
|
|
@@ -553,71 +615,61 @@ function computePub (src, out, family, version, htmlUrlExtensionStyle) {
|
|
|
553
615
|
urlSegments[lastUrlSegmentIdx] = ''
|
|
554
616
|
}
|
|
555
617
|
url = '/' + urlSegments.join('/')
|
|
556
|
-
} else {
|
|
557
|
-
|
|
558
|
-
if (family === 'alias' && !src.relative) pub.splat = true
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
pub.url = ~url.indexOf(' ') ? url.replace(SPACE_RX, '%20') : url
|
|
562
|
-
|
|
563
|
-
if (out) {
|
|
564
|
-
pub.moduleRootPath = out.moduleRootPath
|
|
565
|
-
pub.rootPath = out.rootPath
|
|
618
|
+
} else if ((url = '/' + out.path) === '/.') {
|
|
619
|
+
url = '/'
|
|
566
620
|
}
|
|
621
|
+
pub.url = ~url.indexOf(' ') ? url.replaceAll(' ', '%20') : url
|
|
622
|
+
return out ? Object.assign(pub, { moduleRootPath: out.moduleRootPath, rootPath: out.rootPath }) : pub
|
|
623
|
+
}
|
|
567
624
|
|
|
568
|
-
|
|
625
|
+
function addSymbolicVersionAlias (componentVersion) {
|
|
626
|
+
const { name: component, version } = componentVersion
|
|
627
|
+
const originalVersionSegment = computeVersionSegment.call(this, componentVersion, 'original')
|
|
628
|
+
const symbolicVersionSegment = computeVersionSegment.call(this, componentVersion, 'alias')
|
|
629
|
+
if (symbolicVersionSegment === originalVersionSegment || symbolicVersionSegment == null) return
|
|
630
|
+
const originalVersionSrc = { component, version, versionSegment: originalVersionSegment }
|
|
631
|
+
const symbolicVersionSrc = { component, version, versionSegment: symbolicVersionSegment }
|
|
632
|
+
return this.latestVersionSegmentStrategy === 'redirect:to'
|
|
633
|
+
? this.addSplatAlias(originalVersionSrc, symbolicVersionSrc)
|
|
634
|
+
: this.addSplatAlias(symbolicVersionSrc, originalVersionSrc)
|
|
569
635
|
}
|
|
570
636
|
|
|
571
|
-
function computeVersionSegment (
|
|
637
|
+
function computeVersionSegment (componentVersion, mode) {
|
|
638
|
+
const version = componentVersion.version
|
|
572
639
|
// special designation for master version is @deprecated; special designation scheduled to be removed in Antora 4
|
|
573
|
-
|
|
574
|
-
const
|
|
575
|
-
if (
|
|
576
|
-
|
|
640
|
+
const normalizedVersion = version && version !== 'master' ? version : ''
|
|
641
|
+
const { versionSegment = normalizedVersion } = componentVersion
|
|
642
|
+
if (mode === 'original') return versionSegment
|
|
643
|
+
const strategy = this.latestVersionSegmentStrategy
|
|
644
|
+
if (!versionSegment) {
|
|
645
|
+
if (!mode) return ''
|
|
577
646
|
if (strategy === 'redirect:to') return
|
|
578
647
|
}
|
|
579
|
-
if (strategy === 'redirect:to' || strategy === (mode
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
const segment =
|
|
648
|
+
if (strategy === 'redirect:to' || strategy === (mode ? 'redirect:from' : 'replace')) {
|
|
649
|
+
let component
|
|
650
|
+
if ((component = 'name' in componentVersion && this.getComponent(componentVersion.name))) {
|
|
651
|
+
const latestSegment =
|
|
584
652
|
componentVersion === component.latest
|
|
585
|
-
? this.
|
|
653
|
+
? this.latestVersionSegment
|
|
586
654
|
: componentVersion === component.latestPrerelease
|
|
587
|
-
? this.
|
|
655
|
+
? this.latestPrereleaseVersionSegment
|
|
588
656
|
: undefined
|
|
589
|
-
return
|
|
657
|
+
return latestSegment == null ? versionSegment : latestSegment
|
|
590
658
|
}
|
|
591
659
|
}
|
|
592
|
-
return
|
|
660
|
+
return versionSegment
|
|
593
661
|
}
|
|
594
662
|
|
|
595
|
-
function
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
family
|
|
606
|
-
),
|
|
607
|
-
}
|
|
608
|
-
const originalVersionAliasSrc = Object.assign({}, baseVersionAliasSrc, { version })
|
|
609
|
-
const originalVersionSegment = computeVersionSegment(component, version, 'original')
|
|
610
|
-
const originalVersionAlias = {
|
|
611
|
-
src: originalVersionAliasSrc,
|
|
612
|
-
pub: computePub(
|
|
613
|
-
originalVersionAliasSrc,
|
|
614
|
-
computeOut(originalVersionAliasSrc, family, originalVersionSegment),
|
|
615
|
-
family
|
|
616
|
-
),
|
|
617
|
-
}
|
|
618
|
-
return strategy === 'redirect:to'
|
|
619
|
-
? Object.assign(originalVersionAlias, { out: undefined, rel: symbolicVersionAlias })
|
|
620
|
-
: Object.assign(symbolicVersionAlias, { out: undefined, rel: originalVersionAlias })
|
|
663
|
+
function getComponentVersionFiles (componentVersionId) {
|
|
664
|
+
return this.findBy(componentVersionId)
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
function getComponentVersionStartPage (componentVersionId) {
|
|
668
|
+
return this.resolvePage('index.adoc', componentVersionId)
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
function getVersion () {
|
|
672
|
+
return this.version
|
|
621
673
|
}
|
|
622
674
|
|
|
623
675
|
module.exports = ContentCatalog
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@antora/content-classifier",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.2.0-alpha.10",
|
|
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.
|
|
34
|
-
"@antora/logger": "3.
|
|
34
|
+
"@antora/asciidoc-loader": "3.2.0-alpha.10",
|
|
35
|
+
"@antora/logger": "3.2.0-alpha.10",
|
|
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
|
}
|