@gesslar/bedoc 1.6.0 → 1.7.0

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/package.json CHANGED
@@ -1,8 +1,10 @@
1
1
  {
2
2
  "name": "@gesslar/bedoc",
3
- "version": "1.6.0",
3
+ "version": "1.7.0",
4
4
  "description": "Pluggable documentation engine for any language and format",
5
5
  "publisher": "gesslar",
6
+ "author": "gesslar",
7
+ "license": "Unlicense",
6
8
  "main": "./src/core/Core.js",
7
9
  "repository": {
8
10
  "type": "git",
@@ -63,19 +65,5 @@
63
65
  "language",
64
66
  "format",
65
67
  "hooks"
66
- ],
67
- "author": "gesslar",
68
- "license": "Unlicense",
69
- "contributes": {
70
- "commands": [
71
- {
72
- "command": "vscode-bedoc.generateDocs",
73
- "title": "BeDoc: Generate Documentation"
74
- }
75
- ]
76
- },
77
- "extensionKind": [
78
- "workspace",
79
- "ui"
80
68
  ]
81
69
  }
package/src/cli.js CHANGED
@@ -3,6 +3,7 @@
3
3
  import {program} from "commander"
4
4
  import console from "node:console"
5
5
  import process from "node:process"
6
+ import {fileURLToPath,URL} from "node:url"
6
7
 
7
8
  import BeDoc, {Environment} from "./core/Core.js"
8
9
  import {ConfigurationParameters} from "./core/ConfigurationParameters.js"
@@ -10,18 +11,21 @@ import {ConfigurationParameters} from "./core/ConfigurationParameters.js"
10
11
  import * as ActionUtil from "./core/util/ActionUtil.js"
11
12
  import * as FDUtil from "./core/util/FDUtil.js"
12
13
 
13
- const {loadPackageJson} = ActionUtil
14
- const {resolveDirectory} = FDUtil
14
+ const {loadJson} = ActionUtil
15
+ const {resolveFilename,resolveDirectory} = FDUtil
15
16
 
16
17
  // Main entry point
17
18
  ;(async() => {
18
19
  try {
19
20
  // Get package info
20
21
  const basePath = resolveDirectory(process.cwd())
21
- const packageJson = loadPackageJson(basePath)
22
+ const thisPath = resolveDirectory(fileURLToPath(new URL("..", import.meta.url)))
23
+ const bedocPackageJson = loadJson(resolveFilename("package.json", thisPath))
22
24
 
23
25
  // Setup program
24
- program.name(packageJson.name).description(packageJson.description)
26
+ program
27
+ .name(bedocPackageJson.name)
28
+ .description(bedocPackageJson.description)
25
29
 
26
30
  // Build CLI
27
31
  for(const [name, parameter] of Object.entries(ConfigurationParameters)) {
@@ -42,7 +46,7 @@ const {resolveDirectory} = FDUtil
42
46
 
43
47
  // Add version option last
44
48
  program.version(
45
- packageJson.version,
49
+ bedocPackageJson.version,
46
50
  "-v, --version",
47
51
  "Output the version number",
48
52
  )
@@ -68,10 +72,10 @@ const {resolveDirectory} = FDUtil
68
72
  options: {
69
73
  ...optionsWithSources,
70
74
  basePath: {value: basePath, source: "cli"},
71
- packageJson: {value: packageJson, source: "cli"},
72
75
  },
73
76
  source: Environment.CLI
74
77
  })
78
+
75
79
  const filesToProcess = bedoc.options.input.map(f => f.absolutePath)
76
80
  const result = await bedoc.processFiles(filesToProcess)
77
81
  const errored = result.errored
@@ -42,7 +42,9 @@ export default class Configuration {
42
42
  )
43
43
 
44
44
  const allOptions = this.#findAllOptions(options)
45
+
45
46
  Object.assign(finalOptions, await this.#mergeOptions(allOptions))
47
+
46
48
  this.#fixOptionValues(finalOptions)
47
49
 
48
50
  // Priority keys are those which must be processed first. They are
@@ -79,6 +81,12 @@ export default class Configuration {
79
81
  )
80
82
  }
81
83
 
84
+ // Check for mandatory values
85
+ for(const [key, {required}] of Object.entries(ConfigurationParameters)) {
86
+ if(required && !orderedSections.find(s => s.key === key))
87
+ throw new SyntaxError(`Missing mandatory key \`${key}\``)
88
+ }
89
+
82
90
  for(const section of orderedSections) {
83
91
  const {key} = section
84
92
 
@@ -137,7 +145,7 @@ export default class Configuration {
137
145
  }
138
146
  }
139
147
 
140
- #mapEntryOptions({options, source}) {
148
+ #mapEntryOptions({options = {}, source}) {
141
149
  // CLI already has done all the work via commander
142
150
  if(source === Environment.CLI)
143
151
  return options
@@ -154,15 +162,6 @@ export default class Configuration {
154
162
  if(!options.basePath)
155
163
  options.basePath = {value: dir, source}
156
164
 
157
- // Inject packageJson if not available
158
- if(!options.packageJson) {
159
- const jsonFile = composeFilename(dir, "package.json")
160
- if(fileExists(jsonFile)) {
161
- const jsonObj = loadJson(jsonFile)
162
- options.packageJson = {value: jsonObj, source}
163
- }
164
- }
165
-
166
165
  // Add defaults which are missing
167
166
  for(const [key, param] of Object.entries(ConfigurationParameters)) {
168
167
  if(options[key] === undefined && param.default !== undefined)
@@ -213,25 +212,33 @@ export default class Configuration {
213
212
  */
214
213
  #findAllOptions(entryOptions) {
215
214
  const allOptions = []
216
-
217
215
  const environmentVariables = this.#getEnvironmentVariables()
218
216
  if(environmentVariables)
219
217
  allOptions.push({source: "environment", options: environmentVariables})
220
218
 
221
219
  const packageJson = entryOptions?.packageJson
222
- if(packageJson?.bedoc)
223
- allOptions.push({source: "packageJson", options: packageJson.bedoc})
220
+ if(packageJson) {
221
+ allOptions.push({source: "packageJson", options: packageJson})
222
+ } else {
223
+ const packageJsonFile = composeFilename(process.cwd(), "package.json")
224
+ if(fileExists(packageJsonFile)) {
225
+ const packageJson = loadJson(packageJsonFile)
226
+
227
+ if(packageJson.bedoc)
228
+ allOptions.push({source: "packageJson", options: packageJson.bedoc})
229
+ }
230
+ }
224
231
 
225
232
  // Then the config file, if the options specified a config file
226
233
  const useConfig =
227
234
  entryOptions?.config ||
228
- packageJson?.bedoc?.config ||
235
+ packageJson?.config ||
229
236
  environmentVariables?.config
230
237
 
231
238
  if(useConfig) {
232
239
  const configFile =
233
- packageJson?.bedoc?.config
234
- ? resolveFilename(packageJson?.bedoc?.config)
240
+ packageJson?.config
241
+ ? resolveFilename(packageJson?.config)
235
242
  : entryOptions.config?.value
236
243
  ? resolveFilename(entryOptions.config.value)
237
244
  : null
@@ -287,7 +294,8 @@ export default class Configuration {
287
294
  )
288
295
  const optionsOnly = nonEntryOptions.map(option => option.options)
289
296
  const mergedOptions = optionsOnly.reduce((acc, options) => {
290
- for(const [key, value] of Object.entries(options)) acc[key] = value
297
+ for(const [key, value] of Object.entries(options))
298
+ acc[key] = value
291
299
 
292
300
  return acc
293
301
  }, {})
@@ -6,7 +6,7 @@ const {readFile, writeFile, composeFilename} = FDUtil
6
6
 
7
7
  export default class Conveyor {
8
8
  #succeeded = []
9
- #warned = []
9
+ #warned = []
10
10
  #errored = []
11
11
 
12
12
  constructor(parse, print, logger, output) {
@@ -83,11 +83,11 @@ export default class Conveyor {
83
83
  const {parse, print} = this
84
84
 
85
85
  try {
86
- debug("Processing file: `%s`", 2, file.path)
86
+ debug("Processing file: %o", 2, file.path)
87
87
 
88
88
  // Step 1: Read file
89
89
  const fileContent = readFile(file)
90
- debug("Read file content `%s` (%d bytes)", 2, file.path, fileContent.length)
90
+ debug("Read file content %o (%o bytes)", 2, file.path, fileContent.length)
91
91
 
92
92
  // Step 2: Parse file
93
93
  const parseResult = await parse.runAction({
@@ -98,12 +98,12 @@ export default class Conveyor {
98
98
  return parseResult
99
99
 
100
100
  if(parseResult.status === "warning")
101
- debug("Parsed file successfully, but with warnings: `%s`", 2, file.path)
101
+ debug("Parsed file successfully, but with warnings: %o", 2, file.path)
102
102
  else
103
- debug("Parsed file successfully: `%s`", 2, file.path)
103
+ debug("Parsed file successfully: %o", 2, file.path)
104
104
 
105
105
  if(!parseResult.result) {
106
- const mess = format("No content found in `%s`. No file written.", file.path)
106
+ const mess = format("No content found in %o. No file written.", file.path)
107
107
  return {status: "warning", file, warning: mess}
108
108
  }
109
109
 
@@ -115,7 +115,7 @@ export default class Conveyor {
115
115
  if(printResult.status === "error")
116
116
  return printResult
117
117
 
118
- debug("Printed file successfully: `%s`", 2, file.path)
118
+ debug("Printed file successfully: %o", 2, file.path)
119
119
 
120
120
  // Step 4: Write output
121
121
  const {status: printStatus, destFile, destContent} = printResult
@@ -129,7 +129,7 @@ export default class Conveyor {
129
129
  if(isNullish(destFile) || isNullish(destContent))
130
130
  return {
131
131
  status: "warning",
132
- warning: format("No content or destination file for %s", file.path)
132
+ warning: format("No content or destination file for %o", file.path)
133
133
  }
134
134
 
135
135
  break
@@ -137,14 +137,21 @@ export default class Conveyor {
137
137
  throw new Error(`Invalid status received from printing ${file.module}`)
138
138
  }
139
139
 
140
- const writeResult = await this.#writeOutput(destFile, destContent)
140
+ if(this.output) {
141
+ const writeResult = await this.#writeOutput(destFile, destContent)
141
142
 
142
- if(writeResult.status === "success")
143
- debug("Wrote output for: `%s` (%d bytes)", 2, file.path, destContent.length)
144
- else
145
- debug("Error writing output for: `%s`", 2, file.path)
143
+ if(writeResult.status === "success")
144
+ debug("Wrote output %o (%o bytes)", 2, writeResult.file.path, destContent.length)
145
+ else
146
+ debug("Error writing output for: `%s`", 2, file.path)
147
+
148
+ return writeResult
149
+ }
150
+
151
+ debug("Output not specified. Writing skipped.", 2)
152
+
153
+ return {status: "success"}
146
154
 
147
- return writeResult
148
155
  } catch(error) {
149
156
  return {status: "error", file, error}
150
157
  }
@@ -158,8 +165,12 @@ export default class Conveyor {
158
165
  * @returns {Promise<object>} - Resolves when the file is written.
159
166
  */
160
167
  async #writeOutput(destFile, destContent) {
168
+ const debug = this.logger.newDebug()
169
+
161
170
  const destFileMap = composeFilename(this.output.path, destFile)
162
171
 
172
+ debug("Writing output to %o => %o", 2, destFile, destFileMap.absolutePath)
173
+
163
174
  try {
164
175
  writeFile(destFileMap, destContent)
165
176
 
package/src/core/Core.js CHANGED
@@ -45,11 +45,9 @@ export default class Core {
45
45
  debug("Creating new BeDoc instance with options: `%o`", 2, validConfig)
46
46
 
47
47
  const discovery = new Discovery(instance)
48
- const {printer: validPrint, parser: validParse} = validConfig
49
-
50
48
  const actionDefs = await discovery.discoverActions({
51
- print: validPrint,
52
- parse: validParse
49
+ print: validConfig.printer,
50
+ parse: validConfig.parser
53
51
  })
54
52
 
55
53
  const validCrit = discovery.satisfyCriteria(actionDefs, validConfig)
@@ -97,7 +95,7 @@ export default class Core {
97
95
  for(const [, value] of Object.entries(finalActions)) {
98
96
  const {action: actionType} = value.action.meta
99
97
 
100
- debug("Attaching `%o` action to instance", 2, actionType)
98
+ debug("Attaching %o action to instance", 2, actionType)
101
99
  instance.actions[actionType] = new managers[actionType](
102
100
  value, instance.logger
103
101
  )
@@ -34,11 +34,13 @@ export default class Discovery {
34
34
 
35
35
  debug("Discovering actions", 2)
36
36
 
37
+ debug("Specific modules provided: %o", 2, specific)
38
+
37
39
  const bucket = []
38
40
  const options = this.core.options ?? {}
39
41
 
40
42
  if(options?.mockPath) {
41
- debug("Discovering mock actions in `%s`", 1, options.mockPath)
43
+ debug("Discovering mock actions in `%s`", 2, options.mockPath)
42
44
 
43
45
  bucket.push(
44
46
  ...(await getFiles([
@@ -47,13 +49,13 @@ export default class Discovery {
47
49
  ])),
48
50
  )
49
51
  } else {
50
- debug("Mock path not set, discovering actions in node_modules", 1)
52
+ debug("Mock path not set, discovering actions in node_modules", 2)
51
53
 
52
54
  debug("Looking for actions in project's package.json", 2)
53
55
  if(this.core.packageJson?.modules) {
54
56
  const actions = this.core.packageJson?.modules
55
57
 
56
- debug("Found %d actions in package.json", 3, actions)
58
+ debug("Found %o actions in package.json", 3, actions)
57
59
  debug("Actions found in package.json action in package.json: %o", 3, actions)
58
60
 
59
61
  if(actions && typeof(actions) === "object")
@@ -70,7 +72,7 @@ export default class Discovery {
70
72
  execSync("npm root -g").toString().trim(),
71
73
  ]
72
74
 
73
- debug("Found %d directories to search for actions", 2, directories.length)
75
+ debug("Found %o directories to search for actions", 2, directories.length)
74
76
  debug("Directories to search for actions: %o", 3, directories)
75
77
 
76
78
  const moduleDirectories = directories
@@ -79,15 +81,15 @@ export default class Discovery {
79
81
  for(const moduleDirectory of moduleDirectories) {
80
82
  const {directories: dirs} = await ls(moduleDirectory.absolutePath)
81
83
 
82
- debug("Found %d directories in `%s`", 2,
84
+ debug("Found %o directories in `%s`", 2,
83
85
  dirs.length, moduleDirectory.absolutePath
84
86
  )
85
87
 
86
88
  const bedocDirs = dirs.filter(d => d.name.startsWith("bedoc-"))
87
- debug("Found %d bedoc directories under %s", 2, bedocDirs.length, moduleDirectory.absolutePath)
89
+ debug("Found %o bedoc directories under %s", 2, bedocDirs.length, moduleDirectory.absolutePath)
88
90
 
89
91
  const exports = bedocDirs.map(d => this.#getModuleExports(d))
90
- debug("Found %d module exports under %s", 2, exports.length, moduleDirectory.absolutePath)
92
+ debug("Found %o module exports under %s", 2, exports.length, moduleDirectory.absolutePath)
91
93
 
92
94
  bucket.push(...exports.flat())
93
95
  }
@@ -127,34 +129,35 @@ export default class Discovery {
127
129
  * respective contracts.
128
130
  *
129
131
  * @param {object[]} moduleFiles The module file objects to process
130
- * @param {object} specific The specific actions to load
132
+ * @param {object} specificModules The specific modules to load
131
133
  * @returns {Promise<object>} The discovered action
132
134
  */
133
- async #loadActionsAndContracts(moduleFiles, specific) {
135
+ async #loadActionsAndContracts(moduleFiles, specificModules) {
134
136
  const debug = this.#debug
135
137
 
136
138
  debug("Loading actions and contracts", 2)
137
139
  debug("Loading %d module files", 2, moduleFiles.length)
138
- debug("Specific actions to load: %o", 2, specific)
140
+ debug("Specific modules to load: %o", 2, specificModules)
139
141
 
140
142
  const resultActions = {}
141
143
  actionTypes.forEach(actionType => (resultActions[actionType] = []))
142
144
 
143
145
  // Tag the specific actions to load, so we can filter them later
144
- for(const [type, file] of Object.entries(specific)) {
146
+ for(const [type, file] of Object.entries(specificModules)) {
145
147
  if(file) {
146
- debug("Tagging specific action `%s` as `%s`", 3, file.absolutePath, type)
147
- file.specificType = type
148
+ debug("Tagging specific module `%s` as `%s`", 3, file.absolutePath, type)
149
+ file.specificType = file.specificType || []
150
+ file.specificType.push(type)
148
151
  }
149
152
  }
150
153
 
151
154
  const toLoad = [
152
155
  ...moduleFiles,
153
- ...Object.values(specific).filter(Boolean),
156
+ ...Object.values(specificModules).filter(Boolean),
154
157
  ]
155
158
 
156
- debug("Loading %d combined actions", 2, toLoad.length)
157
- debug("Actions to load: %o", 3, toLoad)
159
+ debug("Loading %d discovered modules", 2, toLoad.length)
160
+ debug("Modules to load: %o", 3, toLoad)
158
161
 
159
162
  const loadedActions = []
160
163
  for(const file of toLoad) {
@@ -170,19 +173,21 @@ export default class Discovery {
170
173
  }
171
174
 
172
175
  debug("Loaded %d actions", 2, loadedActions.length)
176
+ debug("Loaded actions", 3, loadedActions)
173
177
 
174
- const filtered = []
178
+ const filteredActions = []
175
179
  for(const actionType of actionTypes) {
176
- const file = specific[actionType]
180
+ const module = specificModules[actionType]
177
181
  const matchingActions = []
178
- if(file) {
179
- debug("Filtering actions for specific `%s`", 2, actionType)
182
+ if(module) {
183
+ debug("Filtering actions for specific: %o", 2, actionType)
180
184
  const found = loadedActions.find(
181
- e => e.file.absolutePath === file.absolutePath
185
+ e => e.file.specificType?.includes(actionType) &&
186
+ e.action.meta?.action === actionType
182
187
  )
183
188
 
184
189
  if(!found)
185
- throw new Error(`Could not find specific action: ${file.absolutePath}`)
190
+ throw new Error(`Could not find specific action: ${module.absolutePath}`)
186
191
 
187
192
  matchingActions.push(found)
188
193
  } else {
@@ -198,15 +203,17 @@ export default class Discovery {
198
203
  matchingActions.length, actionType
199
204
  )
200
205
 
201
- filtered.push(...matchingActions)
206
+ filteredActions.push(...matchingActions)
202
207
  }
203
208
 
204
- debug("Filtered %d actions", 2, filtered.length)
209
+ debug("Filtered %d actions", 2, filteredActions.length)
210
+ debug("Filtered actions %o", 3, filteredActions)
205
211
 
206
212
  // Now check the metas for validity
207
- for(const e of filtered) {
208
- const {action, contract, file: moduleFile} = e
213
+ for(const filtered of filteredActions) {
214
+ const {action, contract, file: moduleFile} = filtered
209
215
  const meta = action.meta
216
+
210
217
  if(!meta)
211
218
  throw new TypeError("Action has no meta object:\n" +
212
219
  JSON.stringify(moduleFile, null, 2) + "\n" +
@@ -222,7 +229,7 @@ export default class Discovery {
222
229
 
223
230
  const isValid = this.#validMeta(metaAction, {action, contract})
224
231
 
225
- debug("Action `%o` in `%s` is %s", 3,
232
+ debug("Meta in action %o in %o is %o", 3,
226
233
  metaAction, moduleFile.module, isValid ? "valid" : "invalid"
227
234
  )
228
235
 
@@ -239,7 +246,7 @@ export default class Discovery {
239
246
 
240
247
  for(const actionType of actionTypes) {
241
248
  const total = resultActions[actionType].length
242
- debug("Found %d `%s` actions", 2, total, actionType)
249
+ debug("Found %o `%o` actions", 2, total, actionType)
243
250
  }
244
251
 
245
252
  const total = Object.keys(resultActions).reduce((acc, curr) => {
@@ -255,6 +262,9 @@ export default class Discovery {
255
262
 
256
263
  satisfyCriteria(actions, validatedConfig) {
257
264
  const debug = this.#debug
265
+
266
+ debug("Available actions to check %o", 3, actions)
267
+
258
268
  const satisfied = {parse: [], print: []}
259
269
  const toMatch = {
260
270
  parse: {criterion: "language", config: "parser"},
@@ -272,10 +282,10 @@ export default class Discovery {
272
282
  if(validatedConfig[config]) {
273
283
  debug("Checking for specific `%s` action", 3, actionType)
274
284
  const found = actions[actionType].find(
275
- a => a.file.specificType === actionType
285
+ a => a.file.specificType.includes(actionType)
276
286
  )
277
287
  if(found) {
278
- debug("Found specific `%s` action", 3, actionType)
288
+ debug("Found specific %o action", 3, actionType)
279
289
  satisfied[actionType].push(found)
280
290
  continue
281
291
  }
@@ -283,7 +293,6 @@ export default class Discovery {
283
293
  debug("No specific `%s` action found", 3, actionType)
284
294
  }
285
295
 
286
-
287
296
  // Hmm! We didn't find anything specific. Let's check the criterion
288
297
  debug("Checking for `%s` actions with criterion `%s`", 3, actionType, criterion)
289
298
  debug("Validated config to check against: %o", 3, validatedConfig)
@@ -293,7 +302,7 @@ export default class Discovery {
293
302
  return a.action.meta[criterion] === validatedConfig[criterion]
294
303
  })
295
304
 
296
- debug("Found %d `%s` actions with criterion `%s`", 3,
305
+ debug("Found %o %o actions with criterion %o", 3,
297
306
  found.length, actionType, criterion
298
307
  )
299
308