@antora/cli 3.0.0-alpha.6 → 3.0.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -3,9 +3,9 @@
3
3
  The command line interface (CLI) for Antora.
4
4
 
5
5
  [Antora](https://antora.org) is a modular static site generator designed for creating documentation sites from AsciiDoc documents.
6
- Its site generator pipeline aggregates documents from versioned content repositories and processes them using [Asciidoctor](https://asciidoctor.org).
6
+ Its site generator aggregates documents from versioned content repositories and processes them using [Asciidoctor](https://asciidoctor.org).
7
7
 
8
- To run Antora, you need both the CLI and a site generator pipeline.
8
+ To run Antora, you need both the CLI and a site generator.
9
9
  Once these packages are installed, you can use the `antora` command to generate your site.
10
10
 
11
11
  ## How to Install
package/lib/cli.js CHANGED
@@ -2,107 +2,158 @@
2
2
 
3
3
  'use strict'
4
4
 
5
+ const buildPlaybook = require('@antora/playbook-builder')
5
6
  const cli = require('./commander')
6
- // Q: can we ask the playbook builder for the config schema?
7
- const configSchema = require('@antora/playbook-builder/lib/config/schema')
8
7
  const convict = require('@antora/playbook-builder/lib/solitary-convict')
9
- const { finalizeLogger } = require('@antora/logger')
8
+ const { configureLogger, getLogger, finalizeLogger } = require('@antora/logger')
10
9
  const ospath = require('path')
10
+ const userRequire = require('@antora/user-require-helper')
11
11
 
12
- const DOT_RELATIVE_RX = new RegExp(`^\\.{1,2}[/${ospath.sep.replace('/', '').replace('\\', '\\\\')}]`)
13
12
  const { version: VERSION } = require('../package.json')
14
13
 
15
14
  async function run (argv = process.argv) {
16
- const result = cli.parse(argv.length < 3 ? [...argv, 'help'] : argv)
17
- /* istanbul ignore else */
18
- if (cli._promise) await cli._promise
19
- return result
15
+ const args = argv.slice(2)
16
+ return cli.parseAsync(args.length ? args : ['help'], { from: 'user' })
20
17
  }
21
18
 
22
- function exitWithError (err, showStack, msg = undefined) {
19
+ function exitWithError (err, opts, msg = undefined) {
23
20
  if (!msg) msg = err.message || err
24
- if (showStack) {
25
- let stack
21
+ const name = msg.startsWith('asciidoctor: FAILED: ') ? (msg = msg.slice(21)) && 'asciidoctor' : cli.name()
22
+ const logger = getLogger(null)
23
+ ? getLogger(name)
24
+ : configureLogger({ format: 'pretty', level: opts.silent ? 'silent' : 'fatal', failureLevel: 'fatal' }).get(name)
25
+ if (opts.stacktrace) {
26
+ let loc, stack
26
27
  if ((stack = err.backtrace)) {
27
- msg = [`error: ${msg}`, ...stack.slice(1)].join('\n')
28
+ err = Object.assign(new Error(msg), { stack: ['Error', ...stack.slice(1)].join('\n') })
28
29
  } else if ((stack = err.stack)) {
29
- msg = stack.startsWith(`${err.name}: ${msg}\n`) ? stack : [msg, ...stack.split('\n').slice(1)].join('\n')
30
+ if (err instanceof SyntaxError && stack.includes('\nSyntaxError: ')) {
31
+ ;[loc, stack] = stack.split(/\n+SyntaxError: [^\n]+/)
32
+ err = Object.assign(new SyntaxError(msg), { stack: stack.replace('\n', `SyntaxError\n at ${loc}\n`) })
33
+ } else if (stack.startsWith(`${err.name}: ${msg}`)) {
34
+ stack = stack.replace(`${err.name}: ${msg}`, '').replace(/^\n/, '')
35
+ err = Object.assign(new err.constructor(msg), { stack: stack ? `${err.name}\n${stack}` : undefined })
36
+ }
30
37
  } else {
31
- msg = `error: ${msg} (no stack)`
38
+ err = Object.assign(new Error(msg), { stack: undefined })
32
39
  }
33
- console.error(msg)
40
+ if ({}.propertyIsEnumerable.call(err, 'name')) Object.defineProperty(err, 'name', { enumerable: false })
41
+ err.stack = `Cause: ${err.stack || '(no stacktrace)'}`
42
+ logger.fatal(err, msg)
34
43
  } else {
35
- console.error(`error: ${msg}\nAdd the --stacktrace option to see the cause.`)
44
+ logger.fatal(msg + '\nAdd the --stacktrace option to see the cause of the error.')
36
45
  }
37
- process.exit(1)
46
+ return exit()
38
47
  }
39
48
 
40
- function requireLibraries (requirePaths) {
41
- if (requirePaths) requirePaths.forEach((requirePath) => requireLibrary(requirePath))
49
+ function exit () {
50
+ return finalizeLogger().then((failOnExit) => process.exit(failOnExit ? 1 : process.exitCode))
42
51
  }
43
52
 
44
- function requireLibrary (requirePath, cwd = process.cwd()) {
45
- if (requirePath.charAt() === '.' && DOT_RELATIVE_RX.test(requirePath)) {
46
- // NOTE require resolves a dot-relative path relative to current file; resolve relative to cwd instead
47
- requirePath = ospath.resolve(requirePath)
48
- } else if (!ospath.isAbsolute(requirePath)) {
49
- // NOTE appending node_modules prevents require from looking elsewhere before looking in these paths
50
- const paths = [cwd, ospath.dirname(__dirname)].map((start) => ospath.join(start, 'node_modules'))
51
- requirePath = require.resolve(requirePath, { paths })
52
- }
53
- return require(requirePath)
53
+ function getTTYColumns () {
54
+ return process.env.COLUMNS || process.stdout.columns || 80
55
+ }
56
+
57
+ function outputError (str, write) {
58
+ write(str.replace(/^error: /, cli.name() + ': '))
54
59
  }
55
60
 
56
61
  cli
62
+ .allowExcessArguments(false)
63
+ .configureOutput({ getOutHelpWidth: getTTYColumns, getErrHelpWidth: getTTYColumns, outputError })
64
+ .storeOptionsAsProperties()
57
65
  .name('antora')
58
- .version(VERSION, '-v, --version', 'Output the version number.')
66
+ .version(
67
+ {
68
+ toString () {
69
+ const generator = cli._findCommand('generate').getOptionValue('generator')
70
+ const buffer = ['@antora/cli: ' + VERSION]
71
+ let generatorVersion
72
+ const generatorPackageJson = generator + '/package.json'
73
+ try {
74
+ generatorVersion = require(generatorPackageJson).version
75
+ } catch {
76
+ try {
77
+ generatorVersion = require(require.resolve(generatorPackageJson, { paths: [''] })).version
78
+ } catch {}
79
+ }
80
+ buffer.push(generator + ': ' + (generatorVersion || 'not installed'))
81
+ return buffer.join('\n')
82
+ },
83
+ },
84
+ '-v, --version',
85
+ 'Output the version of the CLI and default site generator.'
86
+ )
59
87
  .description('A modular, multi-repository documentation site generator for AsciiDoc.')
60
88
  .usage('[options] [[command] [args]]')
61
89
  .helpOption('-h, --help', 'Output usage information.')
62
- .option('-r, --require <library>', 'Require library (aka node module) or script before executing command.')
63
- .on('option:require', (requirePath) => (cli.requirePaths = [...(cli.requirePaths || []), requirePath]))
90
+ .addHelpText('after', () => {
91
+ const name = cli.name()
92
+ return cli
93
+ .createHelp()
94
+ .wrap(
95
+ ` \nRun '${name} <command> --help' to see options and examples for a command (e.g., ${name} generate --help).`,
96
+ getTTYColumns(),
97
+ 0
98
+ )
99
+ })
100
+ .option('-r, --require <library>', 'Require library (aka node module) or script path before executing command.')
101
+ .on('option:require', (requireRequest) => (cli.requireRequests = cli.requireRequests || []).push(requireRequest))
64
102
  .option('--stacktrace', 'Print the stacktrace to the console if the application fails.')
65
103
 
66
104
  cli
67
105
  .command('generate <playbook>', { isDefault: true })
68
- .description('Generate a documentation site specified in <playbook>.')
69
- .optionsFromConvict(convict(configSchema), { exclude: 'playbook' })
70
- .option('--generator <library>', 'The site generator library.', '@antora/site-generator-default')
71
- .action(async (playbookFile, command) => {
106
+ .description('Generate a documentation site as specified by <playbook>.')
107
+ .optionsFromConvict(convict(buildPlaybook.defaultSchema), { exclude: 'playbook' })
108
+ .trackOptions()
109
+ .action(async (playbookFile, options, command) => {
110
+ const errorOpts = { stacktrace: cli.stacktrace, silent: command.silent }
111
+ const playbookDir = ospath.resolve(playbookFile, '..')
112
+ const userRequireContext = { dot: playbookDir, paths: [playbookDir, __dirname] }
113
+ if (cli.requireRequests) {
114
+ try {
115
+ cli.requireRequests.forEach((requireRequest) => userRequire(requireRequest, userRequireContext))
116
+ } catch (err) {
117
+ return exitWithError(err, errorOpts)
118
+ }
119
+ }
120
+ const args = command.optionArgs.concat('--playbook', playbookFile)
121
+ let playbook
72
122
  try {
73
- requireLibraries(cli.requirePaths)
123
+ playbook = buildPlaybook(args, process.env, buildPlaybook.defaultSchema, (config) => {
124
+ try {
125
+ configureLogger(config.getModel('runtime.log'), playbookDir)
126
+ } catch {}
127
+ })
74
128
  } catch (err) {
75
- exitWithError(err, cli.stacktrace)
129
+ return exitWithError(err, errorOpts)
76
130
  }
77
- const generator = command.generator
131
+ const generator = playbook.antora.generator
78
132
  let generateSite
79
133
  try {
80
- generateSite = requireLibrary(generator, ospath.resolve(playbookFile, '..'))
134
+ generateSite =
135
+ (generateSite = userRequire(generator, userRequireContext)).length === 1
136
+ ? generateSite.bind(null, playbook)
137
+ : generateSite.bind(null, args, process.env)
81
138
  } catch (err) {
82
139
  let msg = 'Generator not found or failed to load.'
83
140
  if (generator && generator.charAt() !== '.') msg += ` Try installing the '${generator}' package.`
84
- exitWithError(err, cli.stacktrace, msg)
141
+ return exitWithError(err, errorOpts, msg)
85
142
  }
86
- const args = cli.rawArgs.slice(cli.rawArgs.indexOf(command.name()) + 1)
87
- args.splice(args.indexOf(playbookFile), 0, '--playbook')
88
- // TODO support passing a preloaded convict config as third option; gets new args and env
89
- cli._promise = generateSite(args, process.env)
90
- .then(finalizeLogger)
91
- .then((failOnExit) => process.exit(failOnExit ? 1 : process.exitCode))
92
- .catch((err) => finalizeLogger().then(() => exitWithError(err, cli.stacktrace)))
143
+ return generateSite()
144
+ .then(exit)
145
+ .catch((err) => exitWithError(err, errorOpts))
93
146
  })
94
147
  .options.sort((a, b) => a.long.localeCompare(b.long))
95
148
 
96
- cli.command('help [command]', { hidden: true }).action((name, command) => {
149
+ cli.command('help [command]', { hidden: true }).action((name, options, command) => {
97
150
  if (name) {
98
- const helpCommand = cli.commands.find((candidate) => candidate.name() === name)
151
+ const helpCommand = cli._findCommand(name)
99
152
  if (helpCommand) {
100
153
  helpCommand.help()
101
154
  } else {
102
- console.error(
103
- `'${name}' is not a valid command in ${cli.name()}. See '${cli.name()} --help' for a list of commands.`
104
- )
105
- process.exit(1)
155
+ const message = `error: unknown command '${name}'. See '${cli.name()} --help' for a list of commands.`
156
+ cli._displayError(1, 'commander.unknownCommand', message)
106
157
  }
107
158
  } else {
108
159
  cli.help()
@@ -111,10 +162,4 @@ cli.command('help [command]', { hidden: true }).action((name, command) => {
111
162
 
112
163
  cli.command('version', { hidden: true }).action(() => cli.emit('option:version'))
113
164
 
114
- cli.on('--help', () => {
115
- console.log(
116
- `\nRun '${cli.name()} <command> --help' to see options and examples for a command (e.g., ${cli.name()} generate --help).`
117
- )
118
- })
119
-
120
165
  module.exports = run
@@ -5,8 +5,15 @@ const { Command } = require('commander')
5
5
  Command.prototype.optionsFromConvict = function (convictConfig, opts = {}) {
6
6
  let exclude = opts.exclude
7
7
  if (exclude && !Array.isArray(exclude)) exclude = [exclude]
8
- getOptions(convictConfig).forEach((option) => {
9
- if (!(exclude && exclude.includes(option.name))) this.option(option.form, option.description, option.default)
8
+ getOptions(convictConfig).forEach(({ name, form, description, default: default_, choices }) => {
9
+ if (exclude && exclude.includes(name)) return
10
+ this.addOption(
11
+ choices
12
+ ? this.createOption(form, description)
13
+ .default(default_, default_)
14
+ .choices(choices)
15
+ : this.createOption(form, description).default(default_, default_)
16
+ )
10
17
  })
11
18
  return this
12
19
  }
@@ -24,15 +31,15 @@ function collectOptions (props, context = undefined) {
24
31
  const { arg, format, default: default_ } = value
25
32
  const option = { name: arg, form: `--${arg}`, description: value.doc, format: format }
26
33
  if (Array.isArray(format)) {
27
- option.form += ' <option>'
28
- option.description += ` (options: ${format.join(', ').replace(/, ([^,]+)$/, ', or $1')})`
34
+ option.form += ' <choice>'
35
+ option.choices = Object.defineProperty(format.slice(), 'map', { value: () => format })
29
36
  } else if (format !== 'boolean') {
30
37
  option.form += ` <${arg.substr(arg.lastIndexOf('-') + 1, arg.length)}>`
31
38
  }
32
39
  if (default_ === null) {
33
40
  //option.mandatory = true
34
41
  option.description += ' (required)'
35
- } else if (default_ && (typeof default_ !== 'object' || default_.toString() !== '[object Object]')) {
42
+ } else if (default_ && default_.constructor !== Object) {
36
43
  option.default = default_
37
44
  }
38
45
  accum.push(option)
@@ -0,0 +1,14 @@
1
+ 'use strict'
2
+
3
+ const { Command } = require('commander')
4
+
5
+ Command.prototype.trackOptions = function () {
6
+ const optionArgs = (this.optionArgs = [])
7
+ for (const eventName of this.eventNames().filter((name) => name.startsWith('option:'))) {
8
+ this.on(eventName, function () {
9
+ optionArgs.push(`--${eventName.slice(7)}`)
10
+ if (arguments.length) optionArgs.push(arguments[0])
11
+ })
12
+ }
13
+ return this
14
+ }
package/lib/commander.js CHANGED
@@ -1,7 +1,7 @@
1
1
  'use strict'
2
2
 
3
3
  const commander = require('commander')
4
- require('./commander/condense-help')
5
4
  require('./commander/options-from-convict')
5
+ require('./commander/track-options')
6
6
 
7
7
  module.exports = commander
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@antora/cli",
3
- "version": "3.0.0-alpha.6",
3
+ "version": "3.0.0-beta.1",
4
4
  "description": "The command line interface for Antora.",
5
5
  "license": "MPL-2.0",
6
6
  "author": "OpenDevise Inc. (https://opendevise.com)",
@@ -18,17 +18,18 @@
18
18
  "antora": "bin/antora"
19
19
  },
20
20
  "dependencies": {
21
- "@antora/logger": "3.0.0-alpha.6",
22
- "@antora/playbook-builder": "3.0.0-alpha.6",
23
- "commander": "~6.2"
21
+ "@antora/logger": "3.0.0-beta.1",
22
+ "@antora/playbook-builder": "3.0.0-beta.1",
23
+ "@antora/user-require-helper": "~2.0",
24
+ "commander": "~8.3"
24
25
  },
25
26
  "devDependencies": {
26
- "@antora/site-publisher": "3.0.0-alpha.6",
27
- "convict": "~6.1",
27
+ "@antora/site-publisher": "3.0.0-beta.1",
28
+ "convict": "~6.2",
28
29
  "kapok-js": "~0.10"
29
30
  },
30
31
  "engines": {
31
- "node": ">=10.17.0"
32
+ "node": ">=12.21.0"
32
33
  },
33
34
  "files": [
34
35
  "bin/",
@@ -42,5 +43,5 @@
42
43
  "static site",
43
44
  "web publishing"
44
45
  ],
45
- "gitHead": "38ec002e88eede3ce5c401a6e226d1a0356945c5"
46
+ "gitHead": "7c5ef1ea93dd489af533c80a936c736013c41769"
46
47
  }
@@ -1,17 +0,0 @@
1
- 'use strict'
2
-
3
- const { Command } = require('commander')
4
- const { helpInformation } = Command.prototype
5
- const { stringify } = JSON
6
-
7
- // TODO include common options when outputting help for a (sub)command
8
- Command.prototype.helpInformation = function () {
9
- const nonTTYColumns = 'columns' in process.stdout ? undefined : (process.stdout.columns = process.env.COLUMNS || 80)
10
- // NOTE override stringify to coerce to string normally
11
- JSON.stringify = (val) => `${val}`
12
- const helpInfo = helpInformation.call(this)
13
- //const helpInfo = helpInformation.call(this).split('\n').filter((line) => !line.includes('--no-')).join('\n')
14
- JSON.stringify = stringify
15
- if (nonTTYColumns) delete process.stdout.columns
16
- return helpInfo
17
- }