@antora/playbook-builder 3.2.0-alpha.8 → 3.2.0-rc.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.
@@ -4,6 +4,7 @@ const convict = require('./solitary-convict')
4
4
  const defaultSchema = require('./config/schema')
5
5
  const fs = require('node:fs')
6
6
  const ospath = require('node:path')
7
+ const yaml = require('js-yaml')
7
8
 
8
9
  /**
9
10
  * Builds a playbook object according to the provided schema from the specified
@@ -28,8 +29,22 @@ const ospath = require('node:path')
28
29
  * marked in the schema as preserve, all keys in the playbook are camelCased.
29
30
  */
30
31
  function buildPlaybook (args = [], env = process.env, schema = defaultSchema, beforeValidate = undefined) {
31
- const config = Object.assign(convict(schema, { args, env }), { getModel })
32
- const playbook = config.has('playbook') && config.get('playbook')
32
+ const parsedArgs = args.length ? parseArgs(args) : {}
33
+ const opts = { args: [], env: {} }
34
+ const config = Object.assign(convict(schema, opts), { getModel, getSchema })
35
+ Object.assign(opts, { args, env })
36
+ let playbook
37
+ if ('playbook' in schema) {
38
+ const playbookEnv = schema.playbook.env
39
+ if (playbookEnv != null) playbook = env[playbookEnv]
40
+ const playbookArg = schema.playbook.arg
41
+ if (playbookArg != null) playbook = parsedArgs[playbookArg] ?? playbook
42
+ if (playbook === undefined) {
43
+ if (config.has('playbook')) playbook = config.get('playbook')
44
+ } else {
45
+ config.set('playbook', playbook)
46
+ }
47
+ }
33
48
  let absPlaybookPath
34
49
  if (playbook) {
35
50
  if (ospath.extname((absPlaybookPath = ospath.resolve(playbook)))) {
@@ -50,10 +65,11 @@ function buildPlaybook (args = [], env = process.env, schema = defaultSchema, be
50
65
  }
51
66
  }
52
67
  try {
53
- if (playbook) {
54
- config.loadFile(absPlaybookPath)
55
- if (playbook !== absPlaybookPath) config.set('playbook', absPlaybookPath)
56
- }
68
+ Object.assign(opts, { args: [] })
69
+ playbook ? config.loadFile(absPlaybookPath) : config.load({})
70
+ importArguments(config, parsedArgs)
71
+ Object.assign(opts, { args })
72
+ if (playbook && playbook !== absPlaybookPath) config.set('playbook', absPlaybookPath)
57
73
  const beforeValidateFromSchema = config._def[Symbol.for('convict.beforeValidate')]
58
74
  if (beforeValidateFromSchema) beforeValidateFromSchema(config)
59
75
  if (beforeValidate) beforeValidate(config)
@@ -82,9 +98,8 @@ function camelCaseKeys (o, stopPaths = [], p = '') {
82
98
  function getModel (name) {
83
99
  let config = this
84
100
  const data = config.get(name)
85
- let schema = config._schema
101
+ const schema = config.getSchema(name)
86
102
  if (name) {
87
- schema = name.split('.').reduce((accum, key) => accum._cvtProperties[key], schema)
88
103
  config = Object.assign(convict(name.split('.').reduce((def, key) => def[key], config._def)), { _instance: data })
89
104
  }
90
105
  config.validate({ allowed: 'strict' })
@@ -97,6 +112,10 @@ function getModel (name) {
97
112
  return model
98
113
  }
99
114
 
115
+ function getSchema (name) {
116
+ return name ? name.split('.').reduce((accum, key) => accum._cvtProperties[key], this._schema) : this._schema
117
+ }
118
+
100
119
  function getStopPaths (schemaProperties, schemaPath = [], stopPaths = []) {
101
120
  for (const [key, { preserve, _cvtProperties }] of Object.entries(schemaProperties)) {
102
121
  if (preserve) {
@@ -110,9 +129,49 @@ function getStopPaths (schemaProperties, schemaPath = [], stopPaths = []) {
110
129
  return stopPaths
111
130
  }
112
131
 
132
+ function importArguments (config, args) {
133
+ for (const [argName, configKey] of Object.entries(config._argv)) {
134
+ const argVal = args[argName]
135
+ if (argVal === undefined) continue
136
+ const argFormat = config.getSchema(configKey).format
137
+ let argValStr = argVal
138
+ if (argFormat === 'map' || argFormat === 'primitive-map') {
139
+ const dumpOpts = { condenseFlow: true, flowLevel: 0, noCompatMode: true, quotingType: '"' }
140
+ argValStr = yaml.dump(Array.isArray(argVal) ? argVal : [argVal], dumpOpts)
141
+ } else if (Array.isArray(argVal)) {
142
+ argValStr = argVal.join(',')
143
+ }
144
+ config.set(configKey, argValStr)
145
+ }
146
+ }
147
+
113
148
  function getDetails (playbook, absPlaybookPath) {
114
149
  if (playbook === absPlaybookPath) return ''
115
150
  return ` (${ospath.isAbsolute(playbook) ? '' : 'cwd: ' + process.cwd() + ', '}playbook: ${playbook})`
116
151
  }
117
152
 
153
+ function parseArgs (args) {
154
+ let currentOptionName
155
+ return Object.assign(
156
+ args.reduce((accum, it) => {
157
+ if (it.startsWith('--') && it !== '--') {
158
+ if (currentOptionName) accum[currentOptionName] = true
159
+ currentOptionName = it.substring(2)
160
+ } else if (currentOptionName) {
161
+ const currentOptionValue = accum[currentOptionName]
162
+ if (currentOptionValue == null || currentOptionValue === true) {
163
+ accum[currentOptionName] = it
164
+ } else if (currentOptionValue.constructor === Array) {
165
+ currentOptionValue.push(it)
166
+ } else {
167
+ accum[currentOptionName] = [currentOptionValue, it]
168
+ }
169
+ currentOptionName = undefined
170
+ }
171
+ return accum
172
+ }, {}),
173
+ currentOptionName ? { [currentOptionName]: true } : undefined
174
+ )
175
+ }
176
+
118
177
  module.exports = Object.assign(buildPlaybook, { defaultSchema })
@@ -86,6 +86,11 @@ module.exports = {
86
86
  format: 'array-or-string',
87
87
  default: undefined,
88
88
  },
89
+ worktrees: {
90
+ doc: 'The default worktrees pattern to use when no specific pattern is provided.',
91
+ format: 'boolean-or-array-or-string',
92
+ default: '.',
93
+ },
89
94
  },
90
95
  ui: {
91
96
  bundle: {
@@ -245,7 +250,7 @@ module.exports = {
245
250
  log: {
246
251
  level: {
247
252
  doc: 'Set the minimum log level of messages that get logged.',
248
- format: ['all', 'debug', 'info', 'warn', 'error', 'fatal', 'silent'],
253
+ format: ['all', 'trace', 'debug', 'info', 'warn', 'error', 'fatal', 'silent'],
249
254
  default: 'warn',
250
255
  arg: 'log-level',
251
256
  env: 'ANTORA_LOG_LEVEL',
@@ -5,9 +5,10 @@ const json = require('json5')
5
5
  const toml = require('@iarna/toml')
6
6
  const yaml = require('js-yaml')
7
7
 
8
- const ARGS_SCANNER_RX = /(?:([^=,]+)|(?==))(?:,|$|=(|("|').*?\3|[^,]+)(?:,|$))/g
9
8
  const PRIMITIVE_TYPES = [Boolean, Number, String]
9
+ const COERCE_SCHEMA = yaml.FAILSAFE_SCHEMA
10
10
  const YAML_SCHEMA = yaml.CORE_SCHEMA.extend({ implicit: [yaml.types.merge] })
11
+ const YAML_PREFIX_RX = new RegExp('!!((?:auto|str|bool|int|float|seq|map)(?= )|null(?=$))(?: |$)')
11
12
 
12
13
  /**
13
14
  * A convict function wrapper that registers custom formats and parsers and
@@ -43,6 +44,14 @@ function registerFormats (convict) {
43
44
  }
44
45
  },
45
46
  })
47
+ convict.addFormat({
48
+ name: 'boolean-or-array-or-string',
49
+ validate: (val) => {
50
+ if (!(val == null || typeof val === 'boolean' || val.constructor === String || Array.isArray(val))) {
51
+ throw new Error('must be a boolean, array, string, or null')
52
+ }
53
+ },
54
+ })
46
55
  convict.addFormat({
47
56
  name: 'map',
48
57
  validate: (val) => {
@@ -51,16 +60,23 @@ function registerFormats (convict) {
51
60
  coerce: (val, config, name) => {
52
61
  if (config == null) return val
53
62
  const accum = config.has(name) ? config.get(name) : {}
54
- let match
55
- const scanner = new RegExp(ARGS_SCANNER_RX)
56
- while ((match = scanner.exec(val))) {
57
- const [, k, v = ''] = match
58
- if (!k) continue
63
+ const entries = yaml.load(val, { schema: COERCE_SCHEMA })
64
+ for (const entry of Array.isArray(entries) ? entries : [entries]) {
65
+ let k = entry
66
+ let v = ''
67
+ const equalsIdx = entry.indexOf('=')
68
+ if (~equalsIdx) {
69
+ if (!(k = entry.substring(0, equalsIdx))) continue
70
+ v = entry.substring(equalsIdx + 1)
71
+ }
59
72
  let parsed = v
60
- if (parsed && parsed !== '-') {
61
- try {
62
- parsed = yaml.load(v, { schema: yaml.CORE_SCHEMA })
63
- } catch {}
73
+ if (v) {
74
+ const match = v.match(YAML_PREFIX_RX)
75
+ if (match) {
76
+ try {
77
+ parsed = yaml.load(match[1] === 'auto' ? v.substring(7) : v, { schema: yaml.CORE_SCHEMA })
78
+ } catch {}
79
+ }
64
80
  }
65
81
  accum[k] = parsed
66
82
  }
@@ -83,20 +99,26 @@ function registerFormats (convict) {
83
99
  coerce: (val, config, name) => {
84
100
  if (config == null) return val
85
101
  const accum = config.has(name) ? config.get(name) : {}
86
- let match
87
- const scanner = new RegExp(ARGS_SCANNER_RX)
88
- while ((match = scanner.exec(val))) {
89
- const [, k, v] = match
90
- if (k) {
91
- let parsed
92
- if (v && v !== '-') {
93
- parsed = yaml.load(v, { schema: yaml.CORE_SCHEMA })
94
- if (parsed && PRIMITIVE_TYPES.indexOf(parsed.constructor) < 0) parsed = v
95
- } else {
96
- parsed = v || ''
102
+ const entries = yaml.load(val, { schema: COERCE_SCHEMA })
103
+ for (const entry of Array.isArray(entries) ? entries : [entries]) {
104
+ let k = entry
105
+ let v = ''
106
+ const equalsIdx = entry.indexOf('=')
107
+ if (~equalsIdx) {
108
+ if (!(k = entry.substring(0, equalsIdx))) continue
109
+ v = entry.substring(equalsIdx + 1)
110
+ }
111
+ let parsed = v
112
+ if (v) {
113
+ const match = v.match(YAML_PREFIX_RX)
114
+ if (match) {
115
+ try {
116
+ parsed = yaml.load(match[1] === 'auto' ? v.substring(7) : v, { schema: yaml.CORE_SCHEMA })
117
+ if (parsed && !~PRIMITIVE_TYPES.indexOf(parsed.constructor)) parsed = v
118
+ } catch {}
97
119
  }
98
- accum[~k.indexOf('-') ? k.replace(/-/g, '_') : k] = parsed
99
120
  }
121
+ accum[~k.indexOf('-') ? k.replace(/-/g, '_') : k] = parsed
100
122
  }
101
123
  return accum
102
124
  },
@@ -108,16 +130,30 @@ function registerFormats (convict) {
108
130
  },
109
131
  coerce: (val, config, name) => {
110
132
  const accum = config?.has(name) ? config.get(name) : []
111
- val.split(',').forEach((v) => {
112
- if (~accum.indexOf(v)) return
113
- const match = accum.find((it) => it.constructor === Object && it.id === v)
114
- if (match) {
115
- if (match.enabled === false) match.enabled = true
133
+ const byId = {}
134
+ const byRequire = {}
135
+ let orderIdx = 0
136
+ const order = new Map()
137
+ for (const [idx, it] of accum.entries()) {
138
+ const ext = it.constructor === Object ? it : (accum[idx] = { require: it })
139
+ byRequire[ext.require] ??= ext
140
+ if ('id' in ext) byId[ext.id] ??= ext
141
+ order.set(it, orderIdx++)
142
+ }
143
+ for (let request of val.split(',')) {
144
+ const enable = request.charAt() === '!' ? (request = request.substring(1)) == null : true
145
+ let match
146
+ if ((match = byId[request] ?? byRequire[request])) {
147
+ if ((match.enabled ?? true) !== enable) match.enabled = enable
116
148
  } else {
117
- accum.push(v)
149
+ accum.push((match = byRequire[request] = { require: request }))
150
+ if (!enable) match.enabled = false
118
151
  }
119
- })
152
+ if ((match.order ?? 'auto') === 'auto') order.set(match, orderIdx++)
153
+ }
120
154
  return accum
155
+ .sort((a, b) => order.get(a) - order.get(b))
156
+ .map((it) => (Object.keys(it).length === 1 && 'require' in it ? it.require : it))
121
157
  },
122
158
  })
123
159
  convict.addFormat({
@@ -166,7 +202,10 @@ function registerFormats (convict) {
166
202
  }
167
203
  if (~parsedUrl.pathname.indexOf('%20')) throw new Error('pathname segment must not contain spaces')
168
204
  },
169
- coerce: (val) => (val.length > 1 && val.charAt(val.length - 1) === '/' ? val.slice(0, -1) : val),
205
+ coerce: (val) => {
206
+ if (!val || val === '~') return null
207
+ return val.length > 1 && val.charAt(val.length - 1) === '/' ? val.substring(0, val.length - 1) : val
208
+ },
170
209
  })
171
210
  }
172
211
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@antora/playbook-builder",
3
- "version": "3.2.0-alpha.8",
3
+ "version": "3.2.0-rc.1",
4
4
  "description": "Builds a playbook object from user input for configuring successive documentation components in an Antora pipeline.",
5
5
  "license": "MPL-2.0",
6
6
  "author": "OpenDevise Inc. (https://opendevise.com)",
@@ -29,11 +29,11 @@
29
29
  "dependencies": {
30
30
  "@iarna/toml": "~2.2",
31
31
  "convict": "~6.2",
32
- "js-yaml": "~4.1",
32
+ "js-yaml": "~4.2",
33
33
  "json5": "~2.2"
34
34
  },
35
35
  "engines": {
36
- "node": ">=18.0.0"
36
+ "node": ">=20.0.0"
37
37
  },
38
38
  "files": [
39
39
  "lib/"
@@ -47,7 +47,7 @@
47
47
  "web publishing"
48
48
  ],
49
49
  "scripts": {
50
- "test": "_mocha",
50
+ "test": "node --test",
51
51
  "prepublishOnly": "npx -y downdoc@latest --prepublish",
52
52
  "postpublish": "npx -y downdoc@latest --postpublish"
53
53
  }