@antora/assembler 1.0.0-beta.9 → 1.0.0-rc.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.
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ autoload :JSON, 'json'
4
+
5
+ class JsonlFormatter < Asciidoctor::Logger::BasicFormatter
6
+ def call severity, time, progname, msg
7
+ entry = { 'time' => time.utc.to_i, 'level' => severity.downcase, 'name' => progname }
8
+ if ::String === msg
9
+ entry['msg'] = msg
10
+ else
11
+ loc = msg[:source_location]
12
+ entry['file'] = { 'path' => loc.file, 'line' => loc.lineno }
13
+ entry['msg'] = msg[:text]
14
+ end
15
+ (JSON.generate entry) + ?\n
16
+ end
17
+ end
18
+
19
+ proc do
20
+ logger = Asciidoctor::LoggerManager.logger
21
+ logger.formatter = JsonlFormatter.new
22
+ unless (progname = ::File.basename $PROGRAM_NAME).empty?
23
+ logger.progname = progname
24
+ end
25
+ end.call
@@ -1,99 +1,116 @@
1
1
  'use strict'
2
2
 
3
- const computeOut = require('./util/compute-out')
4
- const fs = require('node:fs')
5
- const { promises: fsp } = fs
3
+ const compileConversionAttributes = require('./compile-conversion-attributes')
4
+ const fsp = require('node:fs/promises')
6
5
  const LazyReadable = require('./util/lazy-readable')
7
6
  const loadConfig = require('./load-config')
7
+ const logCommand = require('./log-command')
8
8
  const ospath = require('node:path')
9
9
  const { posix: path } = ospath
10
10
  const produceAssemblyFiles = require('./produce-assembly-files')
11
11
  const PromiseQueue = require('./util/promise-queue')
12
- const { stringify: toJSON } = JSON
12
+ const runCommand = require('@antora/run-command-helper')
13
13
 
14
- const invariably = { new: () => ({}), void: () => undefined }
14
+ const invariably = { new: () => ({}), null: () => null, void: () => undefined }
15
15
  const PACKAGE_NAME = require('../package.json').name
16
16
  const NEWLINE_RX = /(?:\r?\n)+/
17
17
 
18
- async function assembleContent (playbook, contentCatalog, converter, { configSource, navigationCatalog }) {
19
- const assemblerConfig = await loadConfig(playbook, configSource)
20
- if (!assemblerConfig) return [] // TODO consider removing and doing this another way
18
+ async function assembleContent (playbook, contentCatalog, converter, providers) {
19
+ const { configSource, navigationCatalog, componentVersionFilter } = providers
20
+ const {
21
+ convert = converter,
22
+ getDefaultCommand,
23
+ extname: targetExtname = '',
24
+ backend: targetBackend = targetExtname.substring(1),
25
+ format: targetFormat = targetBackend,
26
+ embedReferenceStyle = 'relative',
27
+ mediaType: targetMediaType,
28
+ loggerName = `${PACKAGE_NAME} [${targetBackend}-exporter]`,
29
+ xmlCompliant,
30
+ } = converter ?? {}
31
+ const assemblerConfig = await loadConfig.call(this, configSource, playbook, targetBackend ? '-' + targetBackend : '')
32
+ if (assemblerConfig.enabled === false || !contentCatalog.publishableFamilies?.has('export')) return []
21
33
  const context = isBound(this)
22
34
  ? this
23
35
  : {
24
36
  getFunctions: invariably.new,
25
37
  getLogger: invariably.void,
26
38
  getVariables: invariably.new,
39
+ require,
27
40
  }
28
41
  const generatorFunctions = context.getFunctions()
29
- const { loadAsciiDoc = require('@antora/asciidoc-loader') } = generatorFunctions
30
- const {
31
- convert = converter,
32
- getDefaultCommand,
33
- extname: targetExtname = '',
34
- backend: targetBackend = targetExtname.slice(1),
35
- embedReferenceStyle = 'relative',
36
- mediaType: targetMediaType,
37
- loggerName = PACKAGE_NAME,
38
- } = converter ?? {}
42
+ const { loadAsciiDoc = context.require('@antora/asciidoc-loader') } = generatorFunctions
39
43
  const { assembly: assemblyConfig, build: buildConfig } = assemblerConfig
40
44
  assemblyConfig.embedReferenceStyle = embedReferenceStyle
41
- const { profile = targetBackend } = assemblyConfig
42
- const intrinsicAttributes = { 'loader-assembler': '' }
43
- buildConfig.cwd ??= process.cwd()
45
+ if (xmlCompliant) assemblyConfig.xmlIds ??= true
46
+ const profile = (assemblyConfig.profile ??= targetBackend)
47
+ const intrinsicAttributes = { 'loader-assembler': '', 'assembler-root-level': assemblyConfig.rootLevel }
48
+ const cwd = (buildConfig.cwd ??= process.cwd())
44
49
  if (profile) {
45
- buildConfig.dir ??= ospath.join(playbook.dir ?? process.cwd(), `build/assembler-${profile}`)
50
+ buildConfig.dir ??= ospath.join(playbook.dir ?? process.cwd(), `build/assembler/${profile}`)
46
51
  intrinsicAttributes[`assembler-profile-${profile}`] = ''
47
52
  intrinsicAttributes['assembler-profile'] = profile
48
- if (targetBackend) {
49
- intrinsicAttributes[`assembler-backend-${targetBackend}`] = ''
50
- intrinsicAttributes['assembler-backend'] = targetBackend
51
- }
52
53
  } else {
53
- buildConfig.dir ??= ospath.join(playbook.dir ?? process.cwd(), 'build/assembler')
54
+ buildConfig.dir ??= ospath.join(playbook.dir ?? process.cwd(), 'build/assembler/_')
55
+ }
56
+ if (targetBackend) {
57
+ intrinsicAttributes[`assembler-backend-${targetBackend}`] = ''
58
+ intrinsicAttributes['assembler-backend'] = targetBackend
59
+ }
60
+ if (targetFormat) {
61
+ intrinsicAttributes[`assembler-format-${targetFormat}`] = ''
62
+ intrinsicAttributes['assembler-format'] = targetFormat
54
63
  }
55
64
  if (targetExtname) {
56
- const targetFiletype = targetExtname.slice(1)
65
+ const targetFiletype = targetExtname.substring(1)
57
66
  intrinsicAttributes[`assembler-filetype-${targetFiletype}`] = ''
58
67
  intrinsicAttributes['assembler-filetype'] = targetFiletype
59
68
  }
60
- Object.assign(assemblerConfig.asciidoc.attributes, intrinsicAttributes)
69
+ Object.assign(assemblyConfig.attributes, intrinsicAttributes)
61
70
  const assemblyFiles = produceAssemblyFiles(
62
71
  loadAsciiDoc,
63
72
  contentCatalog,
64
73
  assemblerConfig,
65
- createResolveAssemblyModel(context, contentCatalog, assemblyConfig, intrinsicAttributes, navigationCatalog)
74
+ generateSelectAssemblerProfile(context, contentCatalog, assemblyConfig, intrinsicAttributes, navigationCatalog),
75
+ typeof componentVersionFilter === 'function' ? componentVersionFilter : undefined
66
76
  )
67
77
  if (!(assemblyFiles.length && typeof convert === 'function')) return assemblyFiles
68
- if (buildConfig.command == null && typeof getDefaultCommand === 'function') {
69
- buildConfig.command = await getDefaultCommand(buildConfig.cwd)
70
- }
71
- const { publishSite: publishFiles = require('@antora/site-publisher') } = generatorFunctions
78
+ buildConfig.command ??= typeof getDefaultCommand === 'function' ? await getDefaultCommand(cwd, playbook) : null
79
+ const { publishSite: publishFiles = context.require('@antora/site-publisher') } = generatorFunctions
72
80
  await prepareWorkspace(publishFiles, assemblyFiles, buildConfig)
81
+ const helpers = { runCommand }
82
+ Object.defineProperty(helpers, 'logger', {
83
+ get: () => context?.getLogger(loggerName),
84
+ })
85
+ Object.defineProperty(helpers, 'logCommand', {
86
+ get: () => logCommand.bind(null, helpers.logger),
87
+ })
73
88
  const boundConvert = convert.bind(context)
89
+ const boundResolveFileOrContents = resolveFileOrContents.bind(context)
74
90
  return new PromiseQueue({ concurrency: buildConfig.processLimit })
75
91
  .add(
76
92
  assemblyFiles.map((doc) => async () => {
77
93
  const relativeToOutput = embedReferenceStyle === 'output-relative'
78
- const convertAttributes = prepareConvertAttributes(doc, targetExtname, relativeToOutput, buildConfig)
79
- if (buildConfig.mkdirs) await fsp.mkdir(convertAttributes.outdir, { recursive: true, force: true })
80
- return boundConvert(doc, convertAttributes, buildConfig).then((result) => {
81
- const fileOrContents = resolveFileOrContents.call(context, result, convertAttributes, buildConfig, loggerName)
82
- return coerceToExportFormat(doc, targetBackend, targetExtname, targetMediaType, fileOrContents)
83
- })
94
+ const attributes = await compileConversionAttributes(doc, targetExtname, assemblerConfig, relativeToOutput)
95
+ return boundConvert(doc, attributes, buildConfig, helpers).then((result) =>
96
+ boundResolveFileOrContents(result, attributes, buildConfig, loggerName).then((fileOrContents) =>
97
+ coerceToExportFormat(doc, targetBackend, targetExtname, targetMediaType, fileOrContents)
98
+ )
99
+ )
84
100
  })
85
101
  )
86
102
  .toPromise()
87
103
  .then((files) => {
88
- if (!buildConfig.publish) {
104
+ const { publish, qualifyExports } = buildConfig
105
+ if (!publish) {
89
106
  for (const file of files) delete file.assembler.assembled
90
107
  return files
91
108
  }
92
- const qualifyExports = buildConfig.qualifyExports
93
109
  return files.map((file) => {
94
- const pages = file.assembler.assembled.pages
110
+ const pages = file.isNull() ? undefined : file.assembler.assembled.pages
95
111
  delete file.assembler.assembled
96
- file = contentCatalog.addFile(Object.assign(file, { out: computeOut.call(contentCatalog, file.src) }))
112
+ if (!pages) return file
113
+ file = contentCatalog.addFile(file)
97
114
  const extname = file.extname
98
115
  const download = file.assembler.downloadStem + extname
99
116
  if (qualifyExports) {
@@ -103,8 +120,15 @@ async function assembleContent (playbook, contentCatalog, converter, { configSou
103
120
  }
104
121
  pages.forEach((fragment, page) => {
105
122
  const assemblerMeta = (page.assembler ??= {})
106
- ;(assemblerMeta.exports ??= []).push({ fragment, file })
107
- const extnameProp = extname.slice(1)
123
+ const exports = (assemblerMeta.exports ??= [])
124
+ const exportEntry = { fragment, file }
125
+ if (fragment === pages.rootPageFragment) exportEntry.root = true
126
+ const insertIdx = exports.findIndex(
127
+ ({ file: candidate }) =>
128
+ !(candidate.src.component === page.src.component && candidate.src.version === page.src.version)
129
+ )
130
+ ~insertIdx ? exports.splice(insertIdx, 0, exportEntry) : exports.push(exportEntry)
131
+ const extnameProp = extname.substring(1)
108
132
  if (extnameProp in assemblerMeta) return
109
133
  Object.defineProperty(assemblerMeta, extnameProp, {
110
134
  configurable: true,
@@ -117,106 +141,63 @@ async function assembleContent (playbook, contentCatalog, converter, { configSou
117
141
  })
118
142
  }
119
143
 
120
- function createResolveAssemblyModel (context, contentCatalog, common, intrinsicAttributes, navigationCatalog) {
144
+ /**
145
+ * Generates a function that selects the active assembler profile, initializes an assembly model using
146
+ * the keys from the profile as well as any inherited shared keys, builds the navigation for the assembly, and
147
+ * returns the initialized assembly model. The assembly model is further populated after the call to this function.
148
+ */
149
+ function generateSelectAssemblerProfile (context, contentCatalog, baseModel, intrinsicAttributes, navigationCatalog) {
121
150
  const logger = context.getLogger?.(PACKAGE_NAME)
122
151
  const { assemblerProfiles } = context.getVariables()
123
152
  if (!assemblerProfiles) {
124
153
  return (componentVersion) => {
125
154
  const navigation =
126
155
  navigationCatalog?.getNavigation(componentVersion.name, componentVersion.version) ?? componentVersion.navigation
127
- return Object.assign({ logger }, common, { navigation })
156
+ return Object.assign({}, baseModel, { attributes: Object.assign({}, baseModel.attributes), navigation, logger })
128
157
  }
129
158
  }
130
159
  const boundSendToLog = sendToLog.bind(logger)
131
- const { buildNavigation = require('@antora/navigation-builder') } = context.getFunctions()
160
+ const { buildAlternateNavigation = context.require('@antora/navigation-builder').buildAlternateNavigation } =
161
+ context.getFunctions()
132
162
  return (componentVersion) => {
133
163
  const componentVersionProfiles = assemblerProfiles.get(componentVersion.version + '@' + componentVersion.name)
134
- const overrides =
135
- componentVersionProfiles?.get(intrinsicAttributes['assembler-profile']) ?? componentVersionProfiles?.get() ?? {}
136
- const { navFiles, messages } = overrides
137
- const model = Object.assign({ logger }, common, overrides)
164
+ const overrides = componentVersionProfiles?.get(baseModel.profile) ?? componentVersionProfiles?.get()
165
+ const navigation = navigationCatalog?.getNavigation(componentVersion.name, componentVersion.version)
166
+ const attributes = Object.assign({}, baseModel.attributes)
167
+ const model = Object.assign({}, baseModel, overrides, { attributes, logger })
168
+ if (!overrides) return Object.assign(model, { navigation: navigation || componentVersion.navigation })
169
+ if ('rootLevel' in overrides) attributes['assembler-root-level'] = overrides.rootLevel
138
170
  delete model.navFiles
139
171
  delete model.messages
140
- const navigationOverride = navigationCatalog?.getNavigation(componentVersion.name, componentVersion.version)
141
- if (navigationOverride) return Object.assign(model, { navigation: navigationOverride })
142
- messages?.forEach(boundSendToLog)
143
- model.navigation = componentVersion.navigation
144
- if (!navFiles) return model
172
+ if (overrides.attributes) {
173
+ for (const [name, val] of Object.entries(overrides.attributes)) {
174
+ if (!(name in intrinsicAttributes)) attributes[name] = val
175
+ }
176
+ }
177
+ if (navigation) return Object.assign(model, { navigation })
178
+ overrides.messages?.forEach(boundSendToLog)
179
+ const navFiles = overrides.navFiles
180
+ if (!navFiles) return Object.assign(model, { navigation: componentVersion.navigation })
145
181
  if (!navFiles.length) return Object.assign(model, { navigation: [] })
146
- const navigation_ = model.navigation
147
- const asciidoc_ = componentVersion.asciidoc
148
- componentVersion.asciidoc = Object.assign({}, asciidoc_, {
149
- attributes: Object.assign({}, asciidoc_.attributes, intrinsicAttributes),
182
+ model.navigation = buildAlternateNavigation(contentCatalog, componentVersion, navFiles, {
183
+ attributes: model.attributes,
150
184
  })
151
- buildNavigation(
152
- new Proxy(contentCatalog, {
153
- get (target, property) {
154
- const method = target[property]
155
- if (property !== 'findBy') return method
156
- return (criteria) => (toJSON(criteria) === '{"family":"nav"}' ? navFiles : method.call(target, criteria))
157
- },
158
- })
159
- )
160
- model.navigation = componentVersion.navigation
161
- Object.assign(componentVersion, { asciidoc: asciidoc_, navigation: navigation_ })
162
185
  return model
163
186
  }
164
187
  }
165
188
 
166
- function prepareConvertAttributes (doc, targetExtname, relativeToOutput, buildConfig) {
167
- const {
168
- asciidoc: { attributes: docAttributes } = { attributes: {} },
169
- extname: docfilesuffix,
170
- path: reldocfile,
171
- src: { family, relative },
172
- } = doc
173
- const { cwd = process.cwd(), dir = cwd } = buildConfig
174
- const docname = family + '$' + relative.slice(0, relative.length - docfilesuffix.length)
175
- const docfile = ospath.join(dir, reldocfile)
176
- const outdir = ospath.dirname(docfile)
177
- const docdir = relativeToOutput ? dir : outdir
178
- const imagesdir = ''
179
- const outfile = docfile.slice(0, docfile.length - docfilesuffix.length) + targetExtname
180
- const attributes = Object.assign({}, docAttributes, {
181
- docdir,
182
- docfile,
183
- docfilesuffix,
184
- 'docname@': docname,
185
- imagesdir,
186
- outdir,
187
- outfile,
188
- outfilesuffix: targetExtname,
189
- toArgs (optionFlag, command) {
190
- const padCharRef = process.platform === 'win32' && command.startsWith('bundle exec ')
191
- const args = []
192
- for (let [name, val] of Object.entries(this)) {
193
- if (val) {
194
- val = `${name}=${padCharRef && typeof val.charAt === 'function' && val.charAt() === '&' ? ' ' : ''}${val}`
195
- } else if (val === '') {
196
- val = name
197
- } else {
198
- val = `!${name}${val === false ? '@' : ''}`
199
- }
200
- args.push(optionFlag, val)
201
- }
202
- return args
203
- },
204
- })
205
- return Object.defineProperty(attributes, 'toArgs', { enumerable: false })
206
- }
207
-
208
- function coerceToExportFormat (originalFile, targetBackend, targetExtname, targetMediaType, fileOrContents) {
189
+ function coerceToExportFormat (assemblyFile, targetBackend, targetExtname, targetMediaType, fileOrContents) {
209
190
  const file =
210
191
  fileOrContents == null || Buffer.isBuffer(fileOrContents) || typeof fileOrContents.pipe === 'function'
211
- ? Object.assign(originalFile, { contents: fileOrContents })
192
+ ? Object.assign(assemblyFile, { contents: fileOrContents })
212
193
  : fileOrContents
213
194
  ;(file.assembler ??= {}).backend = targetBackend
214
195
  if (file.extname === targetExtname) return file
215
- const sourcePath = file.path
216
- const sourceExtname = file.extname
217
- const relativeWithoutExtname = file.src.relative.slice(0, file.src.relative.length - sourceExtname.length)
218
- const newPath = sourcePath.slice(0, sourcePath.length - sourceExtname.length) + targetExtname
219
- Object.assign(file, { mediaType: (file.src.mediaType = targetMediaType), path: newPath })
196
+ const { path: sourcePath, extname: sourceExtname } = file
197
+ const relativeWithoutExtname = file.src.relative.substring(0, file.src.relative.length - sourceExtname.length)
198
+ const newPath = sourcePath.substring(0, sourcePath.length - sourceExtname.length) + targetExtname
199
+ Object.assign(file, { mediaType: (file.src.mediaType = targetMediaType), path: (file.src.path = newPath) })
200
+ delete file.src.abspath
220
201
  file.src.basename = path.basename((file.src.relative = relativeWithoutExtname + (file.src.extname = targetExtname)))
221
202
  return file
222
203
  }
@@ -237,41 +218,66 @@ function prepareWorkspace (publishFiles, assemblyFiles, buildConfig) {
237
218
  outPaths.add(asset.out.path)
238
219
  }
239
220
  }
240
- if (keepSource) files.push(...assemblyFiles.map((file) => Object.assign(file, { out: { path: file.path } })))
241
- return publishFiles({ output: { clean, dir } }, { getFiles: () => files })
221
+ if (keepSource) {
222
+ for (const file of assemblyFiles) {
223
+ file.out = { path: file.path }
224
+ file.src.contents = file.contents
225
+ file.src.abspath = ospath.join(dir, file.path)
226
+ files.push(file)
227
+ }
228
+ }
229
+ return publishFiles({ output: { clean, dir } }, { getFiles: () => files }).then(() => {
230
+ if (keepSource) {
231
+ for (const file of assemblyFiles) delete file.out
232
+ }
233
+ })
242
234
  }
243
235
 
244
- function resolveFileOrContents (convertResult, convertAttributes, buildConfig, loggerName) {
236
+ function resolveFileOrContents (convertResult, attributes, buildConfig, loggerName) {
245
237
  let fileOrContents
246
238
  if (convertResult?.status != null) {
247
- fileOrContents = 'file' in convertResult ? convertResult.file : convertResult.contents
239
+ if ('file' in convertResult) {
240
+ fileOrContents = convertResult.file
241
+ } else if ('contents' in convertResult) {
242
+ fileOrContents = convertResult.contents
243
+ } else if (buildConfig.outputOptionFlag === false) {
244
+ fileOrContents = convertResult.stdout
245
+ }
248
246
  let logger, match
249
- if (buildConfig.stderrSink === 'log' && convertResult.stderr.length && (logger = this.getLogger(loggerName))) {
250
- const docfile = convertAttributes.docfile
247
+ if (buildConfig.stderrSink === 'log' && convertResult.stderr?.length && (logger = this.getLogger(loggerName))) {
248
+ const docfile = attributes.docfile
251
249
  const command = buildConfig.command
250
+ const file = { path: docfile }
252
251
  const stderr = convertResult.stderr.toString().trimEnd()
252
+ const additionalLines = ['Additional stderr lines from command:']
253
253
  stderr.split(NEWLINE_RX).forEach((line) => {
254
- const ctx = { command, file: { path: docfile } }
254
+ const ctx = { command, file }
255
255
  if (line.charAt() === '{' && line.charAt(line.length - 1) === '}') {
256
256
  const entry = JSON.parse(line)
257
257
  if (entry.name) ctx.program = entry.name
258
258
  if (entry.file?.line) ctx.line = entry.file.line
259
259
  logger[entry.level](ctx, entry.msg)
260
- } else if ((match = /^(.+):(\d+): warning: (.+)/.exec(line))) {
260
+ } else if ((match = /^([^:]+):(\d+): warning: (.+)/.exec(line))) {
261
261
  const [, scriptPath, lineno, msg] = match
262
262
  ctx.stack = [{ file: { path: scriptPath }, line: parseInt(lineno, 10) }]
263
263
  logger.warn(ctx, msg)
264
+ } else if ((match = /^asciidoctor: ([A-Z]+): (?:[^:]+: line (\d+): )?(.+)/.exec(line))) {
265
+ // NOTE we don't care about the filename in the message since it can only be docfile
266
+ const [, level, lineno, msg] = match
267
+ if (lineno) ctx.line = parseInt(lineno, 10)
268
+ logger[level === 'WARNING' ? 'warn' : level.toLowerCase()](ctx, msg)
264
269
  } else {
265
- logger.info(ctx, line)
270
+ additionalLines.push(line)
266
271
  }
267
272
  })
273
+ if (additionalLines.length > 1) logger.info({ command, file }, additionalLines.join('\n'))
268
274
  }
269
275
  } else if (convertResult !== undefined) {
270
- return convertResult
276
+ return Promise.resolve(convertResult)
271
277
  }
272
- return fileOrContents === undefined
273
- ? new LazyReadable(() => fs.createReadStream(convertAttributes.outfile))
274
- : fileOrContents
278
+ if (fileOrContents !== undefined) return Promise.resolve(fileOrContents)
279
+ const outfile = attributes.outfile
280
+ return fsp.access(outfile).then(() => new LazyReadable(outfile), invariably.null)
275
281
  }
276
282
 
277
283
  function isBound (obj) {
@@ -0,0 +1,76 @@
1
+ 'use strict'
2
+
3
+ const fsp = require('node:fs/promises')
4
+ const ospath = require('node:path')
5
+
6
+ async function compileConversionAttributes (doc, targetExtname, assemblerConfig, relativeToOutput = false) {
7
+ const {
8
+ asciidoc: { attributes: docAttributes } = { attributes: {} },
9
+ extname: docfilesuffix,
10
+ path: reldocfile,
11
+ src: { family, relative },
12
+ } = doc
13
+ const { cwd = process.cwd(), dir = cwd, mkdirs } = assemblerConfig.build
14
+ const docname = family + '$' + relative.substring(0, relative.length - docfilesuffix.length)
15
+ const docfile = (doc.src.path = ospath.join(dir, reldocfile))
16
+ const outdir = ospath.dirname(docfile)
17
+ const outfile = docfile.substring(0, docfile.length - docfilesuffix.length) + targetExtname
18
+ const docdir = relativeToOutput ? dir : outdir
19
+ //const imagesoutdir = ospath.join(outdir, '..', '_images/generated')
20
+ const imagesoutdir = outdir
21
+ const attributes = Object.assign({ revdate: `${assemblerConfig.assembly.revdate}@` }, docAttributes, {
22
+ docdir,
23
+ docfile,
24
+ docfilesuffix,
25
+ docname,
26
+ imagesdir: '',
27
+ imagesoutdir,
28
+ outdir,
29
+ outfile,
30
+ outfilesuffix: targetExtname,
31
+ toArgs (attributeOptionFlag, outputOptionFlag) {
32
+ const args = []
33
+ let requireAsciidoctorLogAdapter
34
+ for (const [name, val] of Object.entries(this)) {
35
+ let optionValue
36
+ if (name === 'asciidoctor-log-integration') {
37
+ if ((val ?? false) !== false) requireAsciidoctorLogAdapter = val
38
+ continue
39
+ } else if ((val ?? false) === false) {
40
+ optionValue = `${name}!${val === false ? '=@' : ''}`
41
+ } else {
42
+ optionValue = val === '' ? name : name + '=' + val
43
+ }
44
+ args.push(attributeOptionFlag, optionValue)
45
+ }
46
+ if (requireAsciidoctorLogAdapter) args.push('-r', requireAsciidoctorLogAdapter)
47
+ if (outputOptionFlag) args.push(outputOptionFlag, this.outfile)
48
+ return args
49
+ },
50
+ })
51
+ Object.defineProperty(attributes, 'outfilesuffix', {
52
+ get () {
53
+ return ospath.extname(this.outfile)
54
+ },
55
+ set (value) {
56
+ const outfile = this.outfile
57
+ const extname = ospath.extname(outfile)
58
+ if (extname && outfile.endsWith(extname)) this.outfile = outfile.substring(0, outfile.length - extname.length)
59
+ this.outfile += value
60
+ },
61
+ })
62
+ Object.defineProperty(attributes, 'toArgs', { enumerable: false })
63
+ if (mkdirs) await fsp.mkdir(outdir, { recursive: true, force: true })
64
+ if (attributes['asciidoctor-log-integration'] != null) {
65
+ const scriptSourcePath = require.resolve('#asciidoctor-log-adapter')
66
+ let scriptTargetPath = scriptSourcePath
67
+ if (attributes['asciidoctor-log-integration'] === 'copy-to-build-dir') {
68
+ scriptTargetPath = ospath.join(dir, 'asciidoctor-log-adapter.rb')
69
+ await fsp.cp(scriptSourcePath, scriptTargetPath, { force: true, recursive: true })
70
+ }
71
+ attributes['asciidoctor-log-integration'] = scriptTargetPath
72
+ }
73
+ return attributes
74
+ }
75
+
76
+ module.exports = compileConversionAttributes
package/lib/configure.js CHANGED
@@ -1,34 +1,61 @@
1
1
  'use strict'
2
2
 
3
+ const { ASSEMBLY_KEYS } = require('./constants')
4
+
3
5
  function configure (context, ...args) {
4
6
  internalConfigure.apply(context, args)
5
7
  }
6
8
 
7
9
  function internalConfigure (converter, config = {}, providers = {}) {
8
- this.once('componentsRegistered', ({ contentCatalog, assemblerProfiles }) => {
9
- if (assemblerProfiles) return
10
- this.updateVariables({ assemblerProfiles: getAssemblerProfiles(contentCatalog) })
11
- })
10
+ if (!this.listeners('beforeProcess').includes(enableKeepSource)) {
11
+ this.once('componentsRegistered', ({ contentCatalog, assemblerProfiles }) => {
12
+ contentCatalog.publishableFamilies.add('export')
13
+ if (!assemblerProfiles) this.updateVariables({ assemblerProfiles: getAssemblerProfiles(contentCatalog) })
14
+ })
12
15
 
13
- this.once('beforeProcess', ({ siteAsciiDocConfig }) => {
14
- siteAsciiDocConfig.keepSource = true
15
- })
16
+ this.once('beforeProcess', enableKeepSource)
17
+ }
16
18
 
17
19
  this.once('navigationBuilt', async ({ playbook, contentCatalog }) => {
18
- const { assembleContent = require('./assemble-content'), ...assembleContentConfig } = providers
19
- assembleContentConfig.configSource ??= config.configFile
20
- await assembleContent.call(this, playbook, contentCatalog, converter, assembleContentConfig)
20
+ const { assembleContent = require('./assemble-content'), ...assembleContentProviders } = providers
21
+ if (config.configSource?.constructor === Object) {
22
+ assembleContentProviders.configSource = config.configSource
23
+ await assembleContent.call(this, playbook, contentCatalog, converter, assembleContentProviders)
24
+ } else {
25
+ const singleConfig = !('configFiles' in config)
26
+ const configFiles = singleConfig ? config.configFile : config.configFiles
27
+ for (const configSource of Array.isArray(configFiles) ? configFiles : [configFiles]) {
28
+ const assembleContentProvidersWithConfigSource = Object.assign({}, assembleContentProviders, { configSource })
29
+ await assembleContent.call(this, playbook, contentCatalog, converter, assembleContentProvidersWithConfigSource)
30
+ if (singleConfig) break
31
+ }
32
+ }
21
33
  })
22
34
  }
23
35
 
36
+ function enableKeepSource ({ siteAsciiDocConfig }) {
37
+ if (siteAsciiDocConfig.keepSource instanceof Boolean) return
38
+ siteAsciiDocConfig.keepSource = Object.assign(new Boolean(true), { oldValue: siteAsciiDocConfig.keepSource })
39
+ this.once('navigationBuilt', restoreKeepSource)
40
+ }
41
+
42
+ function restoreKeepSource ({ siteAsciiDocConfig, contentCatalog }) {
43
+ if (!(siteAsciiDocConfig.keepSource instanceof Boolean)) return
44
+ if ((siteAsciiDocConfig.keepSource = siteAsciiDocConfig.keepSource.oldValue)) return
45
+ contentCatalog.getPages((page) => delete page.src.contents)
46
+ }
47
+
24
48
  function getAssemblerProfiles (contentCatalog, assemblerProfiles = new Map()) {
25
49
  contentCatalog.getComponents().forEach((component) => {
26
50
  component.versions.forEach((componentVersion) => {
27
- const source = componentVersion.nav?.origin
51
+ const source =
52
+ componentVersion.nav?.origin ??
53
+ [...(componentVersion.origins ?? [])].find((it) => it.descriptor?.ext?.assembler)
28
54
  const assemblerConfig = getAssemblerConfigFromDescriptor(source?.descriptor)
29
55
  if (!assemblerConfig) return
30
- const componentVersionRef = `${componentVersion.version}@${componentVersion.name}`
31
- const filesByPath = componentVersion.files.reduce((accum, it) => accum.set(it.path, it), new Map())
56
+ const { name: componentName, version, files } = componentVersion
57
+ const componentVersionKey = `${version}@${componentName}`
58
+ const filesByPath = files.reduce((accum, it) => accum.set(it.path, it), new Map())
32
59
  const componentVersionProfiles = assemblerConfig.reduce((accum, entry) => {
33
60
  const data = {}
34
61
  const profile = entry.profile ?? undefined
@@ -37,14 +64,15 @@ function getAssemblerProfiles (contentCatalog, assemblerProfiles = new Map()) {
37
64
  Object.entries(entry).forEach(([key, val]) => {
38
65
  if (key === 'profile' || key === 'nav') return
39
66
  const camelKey = key.toLowerCase().replace(/[_-]([a-z0-9])/g, (_, l, idx) => (idx ? l.toUpperCase() : l))
40
- data[camelKey] = val
67
+ if (ASSEMBLY_KEYS.includes(camelKey)) data[camelKey] = val
41
68
  })
42
69
  if (nav) {
43
70
  data.navFiles = (nav.length ? [...new Set(nav)] : nav).reduce((navFiles, path_) => {
44
71
  const navFile = filesByPath.get(path_)
45
72
  if (navFile) {
46
- navFiles.push(initNavFile(navFile, component.name, componentVersion.version, navFiles.length))
73
+ navFiles.push(initNavFile(navFile, componentName, version, navFiles.length))
47
74
  } else {
75
+ const componentVersionRef = `${version === 'master' ? '' : version}@${componentName}`
48
76
  ;(data.messages ??= []).push([
49
77
  'warn',
50
78
  { source },
@@ -58,7 +86,7 @@ function getAssemblerProfiles (contentCatalog, assemblerProfiles = new Map()) {
58
86
  }
59
87
  return accum.set(profile, data)
60
88
  }, new Map())
61
- assemblerProfiles.set(componentVersionRef, componentVersionProfiles)
89
+ assemblerProfiles.set(componentVersionKey, componentVersionProfiles)
62
90
  })
63
91
  })
64
92
  return assemblerProfiles
@@ -0,0 +1,24 @@
1
+ 'use strict'
2
+
3
+ module.exports = Object.freeze({
4
+ ASSEMBLY_KEYS: [
5
+ 'attributes',
6
+ 'doctype',
7
+ 'dropExplicitXrefText',
8
+ 'linkReferenceStyle',
9
+ 'insertStartPage',
10
+ 'profile',
11
+ 'rootLevel',
12
+ 'rootPageStyle',
13
+ 'sectionMergeStrategy',
14
+ 'xmlIds',
15
+ ],
16
+ LEGACY_ASSEMBLY_KEYS: [
17
+ 'dropExplicitXrefText',
18
+ 'linkReferenceStyle',
19
+ 'insertStartPage',
20
+ 'rootLevel',
21
+ 'sectionMergeStrategy',
22
+ ],
23
+ CAMEL_CASE_STOP_KEYS: ['asciidoc.attributes', 'assembly.attributes'],
24
+ })