asciidoctor 4.0.0-alpha.4 → 4.0.0-alpha.6

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/index.js CHANGED
@@ -1,2 +1 @@
1
- export { default } from '@asciidoctor/core'
2
1
  export * from '@asciidoctor/core'
package/lib/cli.js CHANGED
@@ -25,120 +25,127 @@ const FAILURE_LEVELS = {
25
25
 
26
26
  const DOT_RELATIVE_RX = new RegExp(`^\\.{1,2}[/${sep.replace('\\', '\\\\')}]`)
27
27
 
28
- const HELP_TEXT = `asciidoctor [options...] files...
28
+ const HELP_PREAMBLE = `asciidoctor [options...] files...
29
29
  Translate the AsciiDoc source file or file(s) into the backend output format (e.g., HTML 5, DocBook 5, etc.)
30
30
  By default, the output is written to a file with the basename of the source file and the appropriate extension
31
31
 
32
- Options:
33
- -b, --backend <backend> set output format backend
34
- -d, --doctype <doctype> document type [article, book, manpage, inline]
35
- -o, --out-file <file> output file; use '' or '-' for STDOUT
36
- -S, --safe-mode <mode> safe mode level [unsafe, safe, server, secure]
37
- -e, --embedded suppress enclosing document structure
38
- -s, --no-header-footer suppress enclosing document structure
39
- -n, --section-numbers auto-number section titles in the HTML backend
40
- -B, --base-dir <dir> base directory containing the document and resources
41
- -D, --destination-dir <dir> destination output directory
42
- --failure-level <level> minimum logging level that triggers non-zero exit code [INFO, WARN, ERROR, FATAL]
43
- -q, --quiet suppress warnings
44
- --trace include backtrace information on errors
45
- -v, --verbose enable verbose mode
46
- -t, --timings enable timings mode
47
- -T, --template-dir <dir> directory with custom converter templates (repeatable)
48
- -E, --template-engine <engine> template engine to use for custom converter templates
49
- -a, --attribute <key[=value]> document attribute to set (repeatable)
50
- -r, --require <library> require the specified library before executing the processor (repeatable)
51
- -V, --version display the version and runtime environment
52
- --help [syntax] show this help; show AsciiDoc syntax overview if topic is 'syntax'`
53
-
54
- function parseCliArgs(argv) {
55
- const args = argv.slice(2)
56
- const { values, positionals } = parseArgs({
57
- args,
58
- allowPositionals: true,
59
- strict: false,
60
- options: {
61
- backend: { type: 'string', short: 'b' },
62
- doctype: { type: 'string', short: 'd' },
63
- 'out-file': { type: 'string', short: 'o' },
64
- 'safe-mode': { type: 'string', short: 'S' },
65
- embedded: { type: 'boolean', short: 'e' },
66
- 'no-header-footer': { type: 'boolean', short: 's' },
67
- 'section-numbers': { type: 'boolean', short: 'n' },
68
- 'base-dir': { type: 'string', short: 'B' },
69
- 'destination-dir': { type: 'string', short: 'D' },
70
- 'failure-level': { type: 'string' },
71
- quiet: { type: 'boolean', short: 'q' },
72
- trace: { type: 'boolean' },
73
- verbose: { type: 'boolean', short: 'v' },
74
- timings: { type: 'boolean', short: 't' },
75
- 'template-dir': { type: 'string', short: 'T', multiple: true },
76
- 'template-engine': { type: 'string', short: 'E' },
77
- attribute: { type: 'string', short: 'a', multiple: true },
78
- require: { type: 'string', short: 'r', multiple: true },
79
- version: { type: 'boolean', short: 'V' },
80
- help: { type: 'boolean' },
81
- },
82
- })
83
- return { values, positionals, args }
84
- }
32
+ Options:`
85
33
 
86
- function buildOptions(values, extraAttrs = []) {
87
- const {
88
- backend,
89
- doctype,
90
- 'safe-mode': safeMode,
91
- embedded,
92
- 'no-header-footer': noHeaderFooter,
93
- 'section-numbers': sectionNumbers,
94
- 'base-dir': baseDir,
95
- 'destination-dir': destinationDir,
96
- 'out-file': outFile,
97
- 'template-dir': templateDir,
98
- 'template-engine': templateEngine,
99
- quiet,
100
- verbose,
101
- timings,
102
- trace,
103
- 'failure-level': failureLevelStr = 'FATAL',
104
- attribute: cliAttributes,
105
- } = values
106
-
107
- const level = failureLevelStr.toUpperCase()
108
- const failureLevel = FAILURE_LEVELS[level] ?? FAILURE_LEVELS.FATAL
109
-
110
- const attributes = [...extraAttrs]
111
- if (sectionNumbers) attributes.push('sectnums')
112
- if (cliAttributes) attributes.push(...cliAttributes)
113
-
114
- const standalone = !(embedded || noHeaderFooter)
115
- const verboseMode = quiet ? 0 : verbose ? 2 : 1
116
-
117
- const options = {
118
- standalone,
119
- safe: safeMode ?? 'unsafe',
120
- verbose: verboseMode,
121
- timings: timings ?? false,
122
- trace: trace ?? false,
123
- attributes,
124
- mkdirs: true,
125
- }
34
+ const HELP_COLUMN = 36
126
35
 
127
- if (backend) options.backend = backend
128
- if (doctype) options.doctype = doctype
129
- if (baseDir != null) options.base_dir = baseDir
130
- if (destinationDir != null) options.to_dir = destinationDir
131
- if (templateDir) options.template_dirs = templateDir
132
- if (templateEngine) options.template_engine = templateEngine
133
-
134
- const toStdout = outFile === '' || outFile === "''" || outFile === '-'
135
- if (toStdout) {
136
- options.to_file = false
137
- } else if (outFile !== undefined) {
138
- options.to_file = outFile
139
- }
36
+ const BASE_OPTION_DEFINITIONS = {
37
+ backend: {
38
+ type: 'string',
39
+ short: 'b',
40
+ describe: 'set output format backend',
41
+ metavar: '<backend>',
42
+ },
43
+ doctype: {
44
+ type: 'string',
45
+ short: 'd',
46
+ describe: 'document type [article, book, manpage, inline]',
47
+ metavar: '<doctype>',
48
+ },
49
+ 'out-file': {
50
+ type: 'string',
51
+ short: 'o',
52
+ describe: "output file; use '' or '-' for STDOUT",
53
+ metavar: '<file>',
54
+ },
55
+ 'safe-mode': {
56
+ type: 'string',
57
+ short: 'S',
58
+ describe: 'safe mode level [unsafe, safe, server, secure]',
59
+ metavar: '<mode>',
60
+ },
61
+ embedded: {
62
+ type: 'boolean',
63
+ short: 'e',
64
+ describe: 'suppress enclosing document structure',
65
+ },
66
+ 'no-header-footer': {
67
+ type: 'boolean',
68
+ short: 's',
69
+ describe: 'suppress enclosing document structure',
70
+ },
71
+ 'section-numbers': {
72
+ type: 'boolean',
73
+ short: 'n',
74
+ describe: 'auto-number section titles in the HTML backend',
75
+ },
76
+ 'base-dir': {
77
+ type: 'string',
78
+ short: 'B',
79
+ describe: 'base directory containing the document and resources',
80
+ metavar: '<dir>',
81
+ },
82
+ 'destination-dir': {
83
+ type: 'string',
84
+ short: 'D',
85
+ describe: 'destination output directory',
86
+ metavar: '<dir>',
87
+ },
88
+ 'failure-level': {
89
+ type: 'string',
90
+ describe:
91
+ 'minimum logging level that triggers non-zero exit code [INFO, WARN, ERROR, FATAL]',
92
+ metavar: '<level>',
93
+ },
94
+ quiet: { type: 'boolean', short: 'q', describe: 'suppress warnings' },
95
+ trace: {
96
+ type: 'boolean',
97
+ describe: 'include backtrace information on errors',
98
+ },
99
+ verbose: { type: 'boolean', short: 'v', describe: 'enable verbose mode' },
100
+ timings: { type: 'boolean', short: 't', describe: 'enable timings mode' },
101
+ 'template-dir': {
102
+ type: 'string',
103
+ short: 'T',
104
+ multiple: true,
105
+ describe: 'directory with custom converter templates (repeatable)',
106
+ metavar: '<dir>',
107
+ },
108
+ 'template-engine': {
109
+ type: 'string',
110
+ short: 'E',
111
+ describe: 'template engine to use for custom converter templates',
112
+ metavar: '<engine>',
113
+ },
114
+ attribute: {
115
+ type: 'string',
116
+ short: 'a',
117
+ multiple: true,
118
+ describe: 'document attribute to set (repeatable)',
119
+ metavar: '<key[=value]>',
120
+ },
121
+ require: {
122
+ type: 'string',
123
+ short: 'r',
124
+ multiple: true,
125
+ describe:
126
+ 'require the specified library before executing the processor (repeatable)',
127
+ metavar: '<library>',
128
+ },
129
+ version: {
130
+ type: 'boolean',
131
+ short: 'V',
132
+ describe: 'display the version and runtime environment',
133
+ },
134
+ help: {
135
+ type: 'boolean',
136
+ describe:
137
+ "show this help; show AsciiDoc syntax overview if topic is 'syntax'",
138
+ metavar: '[syntax]',
139
+ },
140
+ }
140
141
 
141
- return { options, failureLevel, toStdout }
142
+ function buildHelpLine(key, def) {
143
+ const shortPart = def.short ? `-${def.short}, ` : ' '
144
+ const metavar = def.type === 'boolean' ? '' : ` ${def.metavar ?? `<${key}>`}`
145
+ const keyPart = `--${key}${metavar}`
146
+ const left = ` ${shortPart}${keyPart}`
147
+ const padding = Math.max(2, HELP_COLUMN - left.length)
148
+ return `${left}${' '.repeat(padding)}${def.describe ?? ''}`
142
149
  }
143
150
 
144
151
  function requireLibrary(requirePath, cwd = process.cwd()) {
@@ -153,56 +160,246 @@ function requireLibrary(requirePath, cwd = process.cwd()) {
153
160
  return require(requirePath)
154
161
  }
155
162
 
156
- function prepareProcessor(values) {
157
- const requirePaths = values.require
158
- if (!requirePaths) return
159
- for (const requirePath of requirePaths) {
160
- const lib = requireLibrary(requirePath)
161
- if (lib && typeof lib.register === 'function') {
162
- lib.register(Extensions)
163
- }
163
+ /**
164
+ * Definition of a CLI option used by {@link Options#addOption}.
165
+ *
166
+ * @typedef {Object} OptionDefinition
167
+ * @property {'string'|'boolean'} type - the value type of the option
168
+ * @property {string} [short] - single character short alias (without -)
169
+ * @property {boolean} [multiple] - whether the option can be repeated
170
+ * @property {string|boolean} [default] - default value when the option is absent
171
+ * @property {string} [describe] - description shown in --help output
172
+ * @property {string} [metavar] - value placeholder shown in --help for string options (e.g. `<theme>`)
173
+ */
174
+
175
+ /**
176
+ * Parses command-line arguments and builds Asciidoctor convert options.
177
+ * Extend this class to add custom options via {@link Options#addOption}.
178
+ */
179
+ export class Options {
180
+ constructor() {
181
+ /** @internal */
182
+ this._definitions = { ...BASE_OPTION_DEFINITIONS }
183
+ /** @type {string[]|null} */
184
+ this.argv = null
185
+ /** @type {Record<string, unknown>} */
186
+ this.values = {}
187
+ /** @type {string[]} */
188
+ this.positionals = []
189
+ /** @type {boolean} */
190
+ this.stdin = false
191
+ /** @type {Object} */
192
+ this.options = {}
193
+ /** @type {number} */
194
+ this.failureLevel = FAILURE_LEVELS.FATAL
164
195
  }
165
- }
166
196
 
167
- function getVersion() {
168
- const pkg = JSON.parse(
169
- readFileSync(join(import.meta.dirname, '..', 'package.json'), 'utf8')
170
- )
171
- return `Asciidoctor.js ${_getVersion()} (Asciidoctor ${getCoreVersion()}) [https://asciidoctor.org]
172
- Runtime Environment (node ${process.version} on ${process.platform})
173
- CLI version ${pkg.version}`
174
- }
197
+ /**
198
+ * Register a custom CLI option.
199
+ * Call this in your subclass constructor before invoking {@link Options#parse}.
200
+ *
201
+ * @param {string} key - the long option name (without --)
202
+ * @param {OptionDefinition} opt - the option definition
203
+ * @returns {this}
204
+ */
205
+ addOption(key, opt) {
206
+ this._definitions[key] = opt
207
+ return this
208
+ }
175
209
 
176
- function readFromStdin() {
177
- return new Promise((resolve, reject) => {
178
- let data = ''
179
- process.stdin.setEncoding('utf8')
180
- process.stdin.on('readable', () => {
181
- const chunk = process.stdin.read()
182
- if (chunk !== null) data += chunk
210
+ /**
211
+ * Parse the command-line arguments.
212
+ * Populates {@link Options#values}, {@link Options#positionals}, {@link Options#stdin},
213
+ * {@link Options#options}, and {@link Options#failureLevel}.
214
+ *
215
+ * @param {string[]} argv - the process arguments (typically `process.argv`)
216
+ * @returns {this}
217
+ */
218
+ parse(argv) {
219
+ this.argv = argv
220
+ const args = argv.slice(2)
221
+ const parseArgsOptions = {}
222
+ for (const [key, def] of Object.entries(this._definitions)) {
223
+ const entry = { type: def.type }
224
+ if (def.short) entry.short = def.short
225
+ if (def.multiple) entry.multiple = def.multiple
226
+ if (def.default !== undefined) entry.default = def.default
227
+ parseArgsOptions[key] = entry
228
+ }
229
+ const { values, positionals } = parseArgs({
230
+ args,
231
+ allowPositionals: true,
232
+ strict: false,
233
+ options: parseArgsOptions,
183
234
  })
184
- process.stdin.on('error', reject)
185
- process.stdin.on('end', () => resolve(data.replace(/\n$/, '')))
186
- })
187
- }
235
+ this.values = values
236
+ this.positionals = positionals
237
+ this.stdin =
238
+ positionals.includes('-') ||
239
+ (positionals.length === 0 && args[args.length - 1] === '-')
240
+ if (this.stdin) {
241
+ this.values['out-file'] = this.values['out-file'] ?? '-'
242
+ }
243
+ const built = this._buildConvertOptions()
244
+ this.options = built.options
245
+ this.failureLevel = built.failureLevel
246
+ return this
247
+ }
248
+
249
+ /** @internal */
250
+ _buildConvertOptions(extraAttrs = []) {
251
+ const {
252
+ backend,
253
+ doctype,
254
+ 'safe-mode': safeMode,
255
+ embedded,
256
+ 'no-header-footer': noHeaderFooter,
257
+ 'section-numbers': sectionNumbers,
258
+ 'base-dir': baseDir,
259
+ 'destination-dir': destinationDir,
260
+ 'out-file': outFile,
261
+ 'template-dir': templateDir,
262
+ 'template-engine': templateEngine,
263
+ quiet,
264
+ verbose,
265
+ timings,
266
+ trace,
267
+ 'failure-level': failureLevelStr = 'FATAL',
268
+ attribute: cliAttributes,
269
+ } = this.values
270
+
271
+ const level = failureLevelStr.toUpperCase()
272
+ const failureLevel = FAILURE_LEVELS[level] ?? FAILURE_LEVELS.FATAL
273
+
274
+ const attributes = [...extraAttrs]
275
+ if (sectionNumbers) attributes.push('sectnums')
276
+ if (cliAttributes) attributes.push(...cliAttributes)
277
+
278
+ const standalone = !(embedded || noHeaderFooter)
279
+ const verboseMode = quiet ? 0 : verbose ? 2 : 1
280
+
281
+ const options = {
282
+ standalone,
283
+ safe: safeMode ?? 'unsafe',
284
+ verbose: verboseMode,
285
+ timings: timings ?? false,
286
+ trace: trace ?? false,
287
+ attributes,
288
+ mkdirs: true,
289
+ }
290
+
291
+ if (backend) options.backend = backend
292
+ if (doctype) options.doctype = doctype
293
+ if (baseDir != null) options.base_dir = baseDir
294
+ if (destinationDir != null) options.to_dir = destinationDir
295
+ if (templateDir) options.template_dirs = templateDir
296
+ if (templateEngine) options.template_engine = templateEngine
297
+
298
+ const toStdout = outFile === '' || outFile === "''" || outFile === '-'
299
+ if (toStdout) {
300
+ options.to_file = false
301
+ } else if (outFile !== undefined) {
302
+ options.to_file = outFile
303
+ }
304
+
305
+ return { options, failureLevel }
306
+ }
188
307
 
189
- async function exit(failureLevel) {
190
- const logger = LoggerManager.getLogger()
191
- const maxSeverity = logger.getMaxSeverity()
192
- const code = maxSeverity !== null && maxSeverity >= failureLevel ? 1 : 0
193
- process.exit(code)
308
+ /**
309
+ * Build the help text from all registered option definitions.
310
+ *
311
+ * @returns {string}
312
+ */
313
+ buildHelpText() {
314
+ const lines = [HELP_PREAMBLE]
315
+ for (const [key, def] of Object.entries(this._definitions)) {
316
+ lines.push(buildHelpLine(key, def))
317
+ }
318
+ return lines.join('\n')
319
+ }
194
320
  }
195
321
 
196
- export async function run(argv = process.argv) {
197
- const { values, positionals, args } = parseCliArgs(argv)
322
+ /**
323
+ * Executes the CLI after options have been parsed.
324
+ * Extend this class to customize the conversion workflow.
325
+ */
326
+ export class Invoker {
327
+ /**
328
+ * @param {Options} options - the parsed options instance
329
+ */
330
+ constructor(options) {
331
+ this.options = options
332
+ }
333
+
334
+ /**
335
+ * Run the CLI: handle `--version`, `--help`, stdin, and file conversion.
336
+ *
337
+ * @returns {Promise<void>}
338
+ */
339
+ async invoke() {
340
+ const { values, positionals, argv, stdin } = this.options
341
+ const args = argv.slice(2)
198
342
 
199
- if (values.version || (values.verbose && args.length === 1)) {
200
- console.log(getVersion())
201
- process.exit(0)
343
+ if (values.version || (values.verbose && args.length === 1)) {
344
+ this.showVersion()
345
+ process.exit(0)
346
+ }
347
+
348
+ if (values.help) {
349
+ this.showHelp(positionals[0])
350
+ process.exit(0)
351
+ }
352
+
353
+ this._prepareProcessor(values)
354
+ const { options, failureLevel } = this.options
355
+
356
+ const logger = LoggerManager.getLogger()
357
+ if (values.quiet) logger.setLevel(3)
358
+ else if (values.verbose) logger.setLevel(0)
359
+
360
+ const files = positionals.filter((f) => f !== '-')
361
+
362
+ if (stdin) {
363
+ await this._convertFromStdin(options)
364
+ } else if (files.length > 0) {
365
+ await this.convertFiles(files, options, values)
366
+ } else {
367
+ this.showHelp()
368
+ process.exit(0)
369
+ }
370
+
371
+ await this._exit(failureLevel)
372
+ }
373
+
374
+ /**
375
+ * Return the version string printed by `--version`.
376
+ * Override to prepend your tool's own version.
377
+ *
378
+ * @returns {string}
379
+ */
380
+ version() {
381
+ const pkg = JSON.parse(
382
+ readFileSync(join(import.meta.dirname, '..', 'package.json'), 'utf8')
383
+ )
384
+ return `Asciidoctor.js ${_getVersion()} (Asciidoctor ${getCoreVersion()}) [https://asciidoctor.org]
385
+ Runtime Environment (node ${process.version} on ${process.platform})
386
+ CLI version ${pkg.version}`
387
+ }
388
+
389
+ /**
390
+ * Print the version string to stdout.
391
+ */
392
+ showVersion() {
393
+ console.log(this.version())
202
394
  }
203
395
 
204
- if (values.help) {
205
- if (positionals[0] === 'syntax') {
396
+ /**
397
+ * Print help to stderr, or the AsciiDoc syntax reference if `topic` is `'syntax'`.
398
+ *
399
+ * @param {string} [topic]
400
+ */
401
+ showHelp(topic) {
402
+ if (topic === 'syntax') {
206
403
  console.log(
207
404
  readFileSync(
208
405
  join(import.meta.dirname, '..', 'data', 'reference', 'syntax.adoc'),
@@ -210,48 +407,85 @@ export async function run(argv = process.argv) {
210
407
  )
211
408
  )
212
409
  } else {
213
- console.error(HELP_TEXT)
410
+ console.error(this.options.buildHelpText())
214
411
  }
215
- process.exit(0)
216
412
  }
217
413
 
218
- prepareProcessor(values)
219
- const { options, failureLevel, toStdout } = buildOptions(values)
220
-
221
- // Configure logger verbosity
222
- const logger = LoggerManager.getLogger()
223
- if (values.quiet)
224
- logger.setLevel(3) // ERROR
225
- else if (values.verbose) logger.setLevel(0) // DEBUG
226
-
227
- const files = positionals.filter((f) => f !== '-')
228
- const isStdin =
229
- positionals.includes('-') ||
230
- (files.length === 0 && args[args.length - 1] === '-')
231
-
232
- if (isStdin) {
233
- const data = await readFromStdin()
234
- const output = await convert(data, { ...options, to_file: false })
235
- process.stdout.write(output)
236
- } else if (files.length > 0) {
414
+ /**
415
+ * Convert the given files.
416
+ * Override this method to implement custom conversion logic (e.g. PDF generation).
417
+ *
418
+ * @param {string[]} files - input files to convert
419
+ * @param {Object} options - Asciidoctor convert options built from the CLI flags
420
+ * @param {Record<string, unknown>} values - all parsed CLI values, including custom options
421
+ * @returns {Promise<void>}
422
+ */
423
+ async convertFiles(files, options, values) {
237
424
  for (const file of files) {
238
425
  if (values.verbose) console.log(`converting file ${file}`)
239
426
  if (values.timings) {
240
427
  const timings = Timings.create()
241
428
  const fileOptions = { ...options, timings }
242
- if (toStdout) fileOptions.to_file = false
243
429
  const result = await convertFile(file, fileOptions)
244
- if (toStdout && typeof result === 'string') process.stdout.write(result)
430
+ if (options.to_file === false && typeof result === 'string')
431
+ process.stdout.write(result)
245
432
  timings.printReport(process.stderr, file)
246
433
  } else {
247
434
  const result = await convertFile(file, options)
248
- if (toStdout && typeof result === 'string') process.stdout.write(result)
435
+ if (options.to_file === false && typeof result === 'string')
436
+ process.stdout.write(result)
249
437
  }
250
438
  }
251
- } else {
252
- console.error(HELP_TEXT)
253
- process.exit(0)
254
439
  }
255
440
 
256
- await exit(failureLevel)
441
+ /** @internal */
442
+ _prepareProcessor(values) {
443
+ const requirePaths = values.require
444
+ if (!requirePaths) return
445
+ for (const requirePath of requirePaths) {
446
+ const lib = requireLibrary(requirePath)
447
+ if (lib && typeof lib.register === 'function') {
448
+ lib.register(Extensions)
449
+ }
450
+ }
451
+ }
452
+
453
+ /** @internal */
454
+ async _convertFromStdin(options) {
455
+ const data = await _readFromStdin()
456
+ const output = await convert(data, { ...options, to_file: false })
457
+ process.stdout.write(output)
458
+ }
459
+
460
+ /** @internal */
461
+ async _exit(failureLevel) {
462
+ const logger = LoggerManager.getLogger()
463
+ const maxSeverity = logger.getMaxSeverity()
464
+ const code = maxSeverity !== null && maxSeverity >= failureLevel ? 1 : 0
465
+ process.exit(code)
466
+ }
467
+ }
468
+
469
+ function _readFromStdin() {
470
+ return new Promise((resolve, reject) => {
471
+ let data = ''
472
+ process.stdin.setEncoding('utf8')
473
+ process.stdin.on('readable', () => {
474
+ const chunk = process.stdin.read()
475
+ if (chunk !== null) data += chunk
476
+ })
477
+ process.stdin.on('error', reject)
478
+ process.stdin.on('end', () => resolve(data.replace(/\n$/, '')))
479
+ })
480
+ }
481
+
482
+ /**
483
+ * Run the CLI.
484
+ * Equivalent to `new Invoker(new Options().parse(argv)).invoke()`.
485
+ *
486
+ * @param {string[]} [argv=process.argv]
487
+ * @returns {Promise<void>}
488
+ */
489
+ export async function run(argv = process.argv) {
490
+ return new Invoker(new Options().parse(argv)).invoke()
257
491
  }
package/package.json CHANGED
@@ -1,12 +1,16 @@
1
1
  {
2
2
  "name": "asciidoctor",
3
- "version": "4.0.0-alpha.4",
3
+ "version": "4.0.0-alpha.6",
4
4
  "description": "A JavaScript AsciiDoc processor powered by Asciidoctor",
5
5
  "type": "module",
6
6
  "main": "index.js",
7
7
  "types": "types",
8
8
  "exports": {
9
- ".": "./index.js"
9
+ ".": "./index.js",
10
+ "./cli": {
11
+ "types": "./types/cli.d.ts",
12
+ "default": "./lib/cli.js"
13
+ }
10
14
  },
11
15
  "bin": {
12
16
  "asciidoctor": "bin/asciidoctor",
@@ -14,6 +18,8 @@
14
18
  },
15
19
  "scripts": {
16
20
  "test": "node --test test/cli.test.js",
21
+ "build:types": "tsc",
22
+ "docs": "typedoc --tsconfig tsconfig.docs.json",
17
23
  "test:deno": "deno test --allow-env --no-check --allow-read --allow-sys --allow-write --allow-run --allow-net test/cli.test.js",
18
24
  "lint": "biome lint bin lib tasks index.js rollup.config.js",
19
25
  "format": "biome format --write bin lib tasks index.js rollup.config.js",
@@ -21,7 +27,7 @@
21
27
  "build:binary": "node tasks/build-sea.js"
22
28
  },
23
29
  "engines": {
24
- "node": ">=24"
30
+ "node": ">=20"
25
31
  },
26
32
  "files": [
27
33
  "bin",
@@ -54,7 +60,7 @@
54
60
  },
55
61
  "homepage": "https://github.com/asciidoctor/asciidoctor.js",
56
62
  "dependencies": {
57
- "@asciidoctor/core": "4.0.0-alpha.4"
63
+ "@asciidoctor/core": "4.0.0-alpha.6"
58
64
  },
59
65
  "devDependencies": {
60
66
  "@biomejs/biome": "^2.4.13",
@@ -67,6 +73,6 @@
67
73
  "pug": "^3.0.3"
68
74
  },
69
75
  "volta": {
70
- "node": "24.15.0"
76
+ "node": "24.16.0"
71
77
  }
72
78
  }
package/types/cli.d.ts ADDED
@@ -0,0 +1,268 @@
1
+ /**
2
+ * Run the CLI.
3
+ * Equivalent to `new Invoker(new Options().parse(argv)).invoke()`.
4
+ *
5
+ * @param {string[]} [argv=process.argv]
6
+ * @returns {Promise<void>}
7
+ */
8
+ export function run(argv?: string[]): Promise<void>;
9
+ /**
10
+ * Definition of a CLI option used by {@link Options#addOption}.
11
+ *
12
+ * @typedef {Object} OptionDefinition
13
+ * @property {'string'|'boolean'} type - the value type of the option
14
+ * @property {string} [short] - single character short alias (without -)
15
+ * @property {boolean} [multiple] - whether the option can be repeated
16
+ * @property {string|boolean} [default] - default value when the option is absent
17
+ * @property {string} [describe] - description shown in --help output
18
+ * @property {string} [metavar] - value placeholder shown in --help for string options (e.g. `<theme>`)
19
+ */
20
+ /**
21
+ * Parses command-line arguments and builds Asciidoctor convert options.
22
+ * Extend this class to add custom options via {@link Options#addOption}.
23
+ */
24
+ export class Options {
25
+ /** @internal */
26
+ _definitions: {
27
+ backend: {
28
+ type: string;
29
+ short: string;
30
+ describe: string;
31
+ metavar: string;
32
+ };
33
+ doctype: {
34
+ type: string;
35
+ short: string;
36
+ describe: string;
37
+ metavar: string;
38
+ };
39
+ 'out-file': {
40
+ type: string;
41
+ short: string;
42
+ describe: string;
43
+ metavar: string;
44
+ };
45
+ 'safe-mode': {
46
+ type: string;
47
+ short: string;
48
+ describe: string;
49
+ metavar: string;
50
+ };
51
+ embedded: {
52
+ type: string;
53
+ short: string;
54
+ describe: string;
55
+ };
56
+ 'no-header-footer': {
57
+ type: string;
58
+ short: string;
59
+ describe: string;
60
+ };
61
+ 'section-numbers': {
62
+ type: string;
63
+ short: string;
64
+ describe: string;
65
+ };
66
+ 'base-dir': {
67
+ type: string;
68
+ short: string;
69
+ describe: string;
70
+ metavar: string;
71
+ };
72
+ 'destination-dir': {
73
+ type: string;
74
+ short: string;
75
+ describe: string;
76
+ metavar: string;
77
+ };
78
+ 'failure-level': {
79
+ type: string;
80
+ describe: string;
81
+ metavar: string;
82
+ };
83
+ quiet: {
84
+ type: string;
85
+ short: string;
86
+ describe: string;
87
+ };
88
+ trace: {
89
+ type: string;
90
+ describe: string;
91
+ };
92
+ verbose: {
93
+ type: string;
94
+ short: string;
95
+ describe: string;
96
+ };
97
+ timings: {
98
+ type: string;
99
+ short: string;
100
+ describe: string;
101
+ };
102
+ 'template-dir': {
103
+ type: string;
104
+ short: string;
105
+ multiple: boolean;
106
+ describe: string;
107
+ metavar: string;
108
+ };
109
+ 'template-engine': {
110
+ type: string;
111
+ short: string;
112
+ describe: string;
113
+ metavar: string;
114
+ };
115
+ attribute: {
116
+ type: string;
117
+ short: string;
118
+ multiple: boolean;
119
+ describe: string;
120
+ metavar: string;
121
+ };
122
+ require: {
123
+ type: string;
124
+ short: string;
125
+ multiple: boolean;
126
+ describe: string;
127
+ metavar: string;
128
+ };
129
+ version: {
130
+ type: string;
131
+ short: string;
132
+ describe: string;
133
+ };
134
+ help: {
135
+ type: string;
136
+ describe: string;
137
+ metavar: string;
138
+ };
139
+ };
140
+ /** @type {string[]|null} */
141
+ argv: string[] | null;
142
+ /** @type {Record<string, unknown>} */
143
+ values: Record<string, unknown>;
144
+ /** @type {string[]} */
145
+ positionals: string[];
146
+ /** @type {boolean} */
147
+ stdin: boolean;
148
+ /** @type {Object} */
149
+ options: any;
150
+ /** @type {number} */
151
+ failureLevel: number;
152
+ /**
153
+ * Register a custom CLI option.
154
+ * Call this in your subclass constructor before invoking {@link Options#parse}.
155
+ *
156
+ * @param {string} key - the long option name (without --)
157
+ * @param {OptionDefinition} opt - the option definition
158
+ * @returns {this}
159
+ */
160
+ addOption(key: string, opt: OptionDefinition): this;
161
+ /**
162
+ * Parse the command-line arguments.
163
+ * Populates {@link Options#values}, {@link Options#positionals}, {@link Options#stdin},
164
+ * {@link Options#options}, and {@link Options#failureLevel}.
165
+ *
166
+ * @param {string[]} argv - the process arguments (typically `process.argv`)
167
+ * @returns {this}
168
+ */
169
+ parse(argv: string[]): this;
170
+ /** @internal */
171
+ _buildConvertOptions(extraAttrs?: any[]): {
172
+ options: {
173
+ standalone: boolean;
174
+ safe: unknown;
175
+ verbose: number;
176
+ timings: unknown;
177
+ trace: unknown;
178
+ attributes: any[];
179
+ mkdirs: boolean;
180
+ };
181
+ failureLevel: any;
182
+ };
183
+ /**
184
+ * Build the help text from all registered option definitions.
185
+ *
186
+ * @returns {string}
187
+ */
188
+ buildHelpText(): string;
189
+ }
190
+ /**
191
+ * Executes the CLI after options have been parsed.
192
+ * Extend this class to customize the conversion workflow.
193
+ */
194
+ export class Invoker {
195
+ /**
196
+ * @param {Options} options - the parsed options instance
197
+ */
198
+ constructor(options: Options);
199
+ options: Options;
200
+ /**
201
+ * Run the CLI: handle `--version`, `--help`, stdin, and file conversion.
202
+ *
203
+ * @returns {Promise<void>}
204
+ */
205
+ invoke(): Promise<void>;
206
+ /**
207
+ * Return the version string printed by `--version`.
208
+ * Override to prepend your tool's own version.
209
+ *
210
+ * @returns {string}
211
+ */
212
+ version(): string;
213
+ /**
214
+ * Print the version string to stdout.
215
+ */
216
+ showVersion(): void;
217
+ /**
218
+ * Print help to stderr, or the AsciiDoc syntax reference if `topic` is `'syntax'`.
219
+ *
220
+ * @param {string} [topic]
221
+ */
222
+ showHelp(topic?: string): void;
223
+ /**
224
+ * Convert the given files.
225
+ * Override this method to implement custom conversion logic (e.g. PDF generation).
226
+ *
227
+ * @param {string[]} files - input files to convert
228
+ * @param {Object} options - Asciidoctor convert options built from the CLI flags
229
+ * @param {Record<string, unknown>} values - all parsed CLI values, including custom options
230
+ * @returns {Promise<void>}
231
+ */
232
+ convertFiles(files: string[], options: any, values: Record<string, unknown>): Promise<void>;
233
+ /** @internal */
234
+ _prepareProcessor(values: any): void;
235
+ /** @internal */
236
+ _convertFromStdin(options: any): Promise<void>;
237
+ /** @internal */
238
+ _exit(failureLevel: any): Promise<void>;
239
+ }
240
+ /**
241
+ * Definition of a CLI option used by {@link Options#addOption}.
242
+ */
243
+ export type OptionDefinition = {
244
+ /**
245
+ * - the value type of the option
246
+ */
247
+ type: "string" | "boolean";
248
+ /**
249
+ * - single character short alias (without -)
250
+ */
251
+ short?: string;
252
+ /**
253
+ * - whether the option can be repeated
254
+ */
255
+ multiple?: boolean;
256
+ /**
257
+ * - default value when the option is absent
258
+ */
259
+ default?: string | boolean;
260
+ /**
261
+ * - description shown in --help output
262
+ */
263
+ describe?: string;
264
+ /**
265
+ * - value placeholder shown in --help for string options (e.g. `<theme>`)
266
+ */
267
+ metavar?: string;
268
+ };
@@ -489,7 +489,7 @@ export namespace Document {
489
489
  import { Footnote } from './footnote.js';
490
490
  import { AttributeEntry } from './attribute_entry.js';
491
491
  import { AbstractBlock } from './abstract_block.js';
492
- import type { Reader } from './reader.js';
492
+ import { Reader } from './reader.js';
493
493
  import { Section } from './section.js';
494
494
  import { Inline } from './inline.js';
495
495
  export { Footnote, AttributeEntry };
package/types/index.d.cts CHANGED
@@ -27,14 +27,14 @@ export function load(input: string | string[] | Buffer, options?: any): Promise<
27
27
  * @returns {Promise<Document>} - the parsed Document
28
28
  */
29
29
  export function loadFile(filename: string, options?: any): Promise<Document>;
30
- export type ProcessorDslInterface = import("./extensions.js").ProcessorDslInterface;
31
- export type DocumentProcessorDslInterface = import("./extensions.js").DocumentProcessorDslInterface;
32
- export type SyntaxProcessorDslInterface = import("./extensions.js").SyntaxProcessorDslInterface;
33
- export type IncludeProcessorDslInterface = import("./extensions.js").IncludeProcessorDslInterface;
34
- export type DocinfoProcessorDslInterface = import("./extensions.js").DocinfoProcessorDslInterface;
35
- export type BlockProcessorDslInterface = import("./extensions.js").BlockProcessorDslInterface;
36
- export type MacroProcessorDslInterface = import("./extensions.js").MacroProcessorDslInterface;
37
- export type InlineMacroProcessorDslInterface = import("./extensions.js").InlineMacroProcessorDslInterface;
30
+ export type { ProcessorDslInterface } from './extensions.js';
31
+ export type { DocumentProcessorDslInterface } from './extensions.js';
32
+ export type { SyntaxProcessorDslInterface } from './extensions.js';
33
+ export type { IncludeProcessorDslInterface } from './extensions.js';
34
+ export type { DocinfoProcessorDslInterface } from './extensions.js';
35
+ export type { BlockProcessorDslInterface } from './extensions.js';
36
+ export type { MacroProcessorDslInterface } from './extensions.js';
37
+ export type { InlineMacroProcessorDslInterface } from './extensions.js';
38
38
  import { Document } from './document.js';
39
39
  import { convert } from './convert.js';
40
40
  import { convertFile } from './convert.js';
package/types/index.d.ts CHANGED
@@ -26,14 +26,14 @@ export function load(input: string | string[] | Buffer, options?: any): Promise<
26
26
  * @returns {Promise<Document>} - the parsed Document
27
27
  */
28
28
  export function loadFile(filename: string, options?: any): Promise<Document>;
29
- export type ProcessorDslInterface = import("./extensions.js").ProcessorDslInterface;
30
- export type DocumentProcessorDslInterface = import("./extensions.js").DocumentProcessorDslInterface;
31
- export type SyntaxProcessorDslInterface = import("./extensions.js").SyntaxProcessorDslInterface;
32
- export type IncludeProcessorDslInterface = import("./extensions.js").IncludeProcessorDslInterface;
33
- export type DocinfoProcessorDslInterface = import("./extensions.js").DocinfoProcessorDslInterface;
34
- export type BlockProcessorDslInterface = import("./extensions.js").BlockProcessorDslInterface;
35
- export type MacroProcessorDslInterface = import("./extensions.js").MacroProcessorDslInterface;
36
- export type InlineMacroProcessorDslInterface = import("./extensions.js").InlineMacroProcessorDslInterface;
29
+ export type { ProcessorDslInterface } from './extensions.js';
30
+ export type { DocumentProcessorDslInterface } from './extensions.js';
31
+ export type { SyntaxProcessorDslInterface } from './extensions.js';
32
+ export type { IncludeProcessorDslInterface } from './extensions.js';
33
+ export type { DocinfoProcessorDslInterface } from './extensions.js';
34
+ export type { BlockProcessorDslInterface } from './extensions.js';
35
+ export type { MacroProcessorDslInterface } from './extensions.js';
36
+ export type { InlineMacroProcessorDslInterface } from './extensions.js';
37
37
  import { Document } from './document.js';
38
38
  import { convert } from './convert.js';
39
39
  import { convertFile } from './convert.js';