@gesslar/bedoc 1.6.1 → 1.9.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,6 +1,6 @@
1
1
  {
2
2
  "name": "@gesslar/bedoc",
3
- "version": "1.6.1",
3
+ "version": "1.9.0",
4
4
  "description": "Pluggable documentation engine for any language and format",
5
5
  "publisher": "gesslar",
6
6
  "author": "gesslar",
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,7 +72,6 @@ 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
  })
@@ -7,10 +7,12 @@ export default class ActionManager {
7
7
  #log
8
8
  #debug
9
9
  #file
10
+ #variables
10
11
 
11
- constructor(actionDefinition, logger) {
12
+ constructor({actionDefinition, logger, variables}) {
12
13
  this.#log = logger
13
14
  this.#debug = this.#log.newDebug()
15
+ this.#variables = variables
14
16
 
15
17
  this.#initialize(actionDefinition)
16
18
  }
@@ -65,6 +67,10 @@ export default class ActionManager {
65
67
  return this.#log
66
68
  }
67
69
 
70
+ get variables() {
71
+ return this.#variables
72
+ }
73
+
68
74
  async #setupAction() {
69
75
  const setup = this.action?.setup
70
76
 
@@ -90,7 +96,11 @@ export default class ActionManager {
90
96
  return
91
97
 
92
98
  await this.hookManager.setup.call(
93
- this.hookManager.hooks, {parent: this.action, log: this.#log}
99
+ this.hookManager.hooks, {
100
+ action: this.action,
101
+ variables: this.#variables,
102
+ log: this.#log
103
+ }
94
104
  )
95
105
  }
96
106
 
@@ -145,7 +145,7 @@ export default class Configuration {
145
145
  }
146
146
  }
147
147
 
148
- #mapEntryOptions({options, source}) {
148
+ #mapEntryOptions({options = {}, source}) {
149
149
  // CLI already has done all the work via commander
150
150
  if(source === Environment.CLI)
151
151
  return options
@@ -162,15 +162,6 @@ export default class Configuration {
162
162
  if(!options.basePath)
163
163
  options.basePath = {value: dir, source}
164
164
 
165
- // Inject packageJson if not available
166
- if(!options.packageJson) {
167
- const jsonFile = composeFilename(dir, "package.json")
168
- if(fileExists(jsonFile)) {
169
- const jsonObj = loadJson(jsonFile)
170
- options.packageJson = {value: jsonObj, source}
171
- }
172
- }
173
-
174
165
  // Add defaults which are missing
175
166
  for(const [key, param] of Object.entries(ConfigurationParameters)) {
176
167
  if(options[key] === undefined && param.default !== undefined)
@@ -226,19 +217,28 @@ export default class Configuration {
226
217
  allOptions.push({source: "environment", options: environmentVariables})
227
218
 
228
219
  const packageJson = entryOptions?.packageJson
229
- if(packageJson?.bedoc)
230
- 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
+ }
231
231
 
232
232
  // Then the config file, if the options specified a config file
233
233
  const useConfig =
234
234
  entryOptions?.config ||
235
- packageJson?.bedoc?.config ||
235
+ packageJson?.config ||
236
236
  environmentVariables?.config
237
237
 
238
238
  if(useConfig) {
239
239
  const configFile =
240
- packageJson?.bedoc?.config
241
- ? resolveFilename(packageJson?.bedoc?.config)
240
+ packageJson?.config
241
+ ? resolveFilename(packageJson?.config)
242
242
  : entryOptions.config?.value
243
243
  ? resolveFilename(entryOptions.config.value)
244
244
  : null
@@ -246,9 +246,22 @@ export default class Configuration {
246
246
  if(!configFile)
247
247
  throw new Error("No config file specified")
248
248
 
249
- const config = loadJson(configFile)
249
+ const configObject = loadJson(configFile)
250
+ const subConfigName =
251
+ entryOptions?.sub ||
252
+ packageJson?.sub ||
253
+ environmentVariables?.sub
254
+
255
+ // If we didn't specify a subconfiguration, let's just remove
256
+ // it so it doesn't pollute anything.
257
+ if(!subConfigName)
258
+ delete configObject.sub
259
+
260
+ const finalConfig = subConfigName?.value
261
+ ? this.#resolveSubconfigs(configObject, subConfigName.value)
262
+ : configObject
250
263
 
251
- allOptions.push({source: "config", options: config})
264
+ allOptions.push({source: "config", options: finalConfig})
252
265
  }
253
266
 
254
267
  allOptions.push({source: "entry", options: entryOptions})
@@ -256,6 +269,21 @@ export default class Configuration {
256
269
  return allOptions
257
270
  }
258
271
 
272
+ #resolveSubconfigs(configObject, subConfigName) {
273
+ const subConfig = configObject.sub?.find(sub => sub.name === subConfigName)
274
+
275
+ if(!subConfig)
276
+ throw new Error(`No such subconfiguration \`${subConfigName}\``)
277
+
278
+ // We don't need this anymore
279
+ delete subConfig.name
280
+
281
+ for(const [key,val] of Object.entries(subConfig))
282
+ configObject[key] = val
283
+
284
+ return configObject
285
+ }
286
+
259
287
  /**
260
288
  * Get environment variables
261
289
  *
@@ -294,7 +322,8 @@ export default class Configuration {
294
322
  )
295
323
  const optionsOnly = nonEntryOptions.map(option => option.options)
296
324
  const mergedOptions = optionsOnly.reduce((acc, options) => {
297
- for(const [key, value] of Object.entries(options)) acc[key] = value
325
+ for(const [key, value] of Object.entries(options))
326
+ acc[key] = value
298
327
 
299
328
  return acc
300
329
  }, {})
@@ -120,6 +120,14 @@ const ConfigurationParameters = Object.freeze({
120
120
  mustExist: true,
121
121
  },
122
122
  },
123
+ sub: {
124
+ short: "s",
125
+ param: "name",
126
+ description: "Specify a subconfiguration",
127
+ type: newTypeSpec("string"),
128
+ required: false,
129
+ dependent: "config",
130
+ },
123
131
  debug: {
124
132
  short: "d",
125
133
  description: "Enable debug mode",
@@ -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
@@ -91,14 +91,18 @@ export default class Core {
91
91
 
92
92
  // Adding to instance
93
93
  instance.actions = {}
94
+ const {variables} = validConfig
94
95
  const managers = {print: PrintManager, parse: ParseManager}
95
- for(const [, value] of Object.entries(finalActions)) {
96
- const {action: actionType} = value.action.meta
96
+ for(const [, actionDefinition] of Object.entries(finalActions)) {
97
+ const {action: actionType} = actionDefinition.action.meta
97
98
 
98
99
  debug("Attaching %o action to instance", 2, actionType)
99
- instance.actions[actionType] = new managers[actionType](
100
- value, instance.logger
101
- )
100
+ instance.actions[actionType] =
101
+ new managers[actionType] ({
102
+ actionDefinition,
103
+ logger: instance.logger,
104
+ variables
105
+ })
102
106
 
103
107
  if(validConfig.hooks) {
104
108
  const hookManager = await HookManager.new({
@@ -7,7 +7,7 @@ import * as ActionUtil from "./util/ActionUtil.js"
7
7
  import * as DataUtil from "./util/DataUtil.js"
8
8
  import {composeDirectory,directoryExists} from "./util/FDUtil.js"
9
9
 
10
- const {ls,resolveFilename,getFiles} = FDUtil
10
+ const {ls,fileExists,composeFilename,getFiles} = FDUtil
11
11
  const {actionTypes, actionMetaRequirements, loadJson} = ActionUtil
12
12
  const {isType} = DataUtil
13
13
 
@@ -40,7 +40,7 @@ export default class Discovery {
40
40
  const options = this.core.options ?? {}
41
41
 
42
42
  if(options?.mockPath) {
43
- debug("Discovering mock actions in `%s`", 1, options.mockPath)
43
+ debug("Discovering mock actions in `%s`", 2, options.mockPath)
44
44
 
45
45
  bucket.push(
46
46
  ...(await getFiles([
@@ -49,9 +49,10 @@ export default class Discovery {
49
49
  ])),
50
50
  )
51
51
  } else {
52
- debug("Mock path not set, discovering actions in node_modules", 1)
52
+ debug("Mock path not set, discovering actions in node_modules", 2)
53
53
 
54
54
  debug("Looking for actions in project's package.json", 2)
55
+
55
56
  if(this.core.packageJson?.modules) {
56
57
  const actions = this.core.packageJson?.modules
57
58
 
@@ -66,30 +67,46 @@ export default class Discovery {
66
67
  debug("No actions found in project's package.json", 2)
67
68
  }
68
69
 
69
- debug("Looking for actions in node_modules (global and locally installed", 2)
70
+ debug("Looking for actions in node_modules (global and locally installed)", 2)
70
71
  const directories = [
71
- "./node_modules",
72
+ execSync("npm root").toString().trim(),
72
73
  execSync("npm root -g").toString().trim(),
73
- ]
74
+ ].filter(Boolean)
75
+
76
+ const nodeModulesDirs = directories
77
+ .map(composeDirectory)
78
+ .filter(directoryExists)
74
79
 
75
80
  debug("Found %o directories to search for actions", 2, directories.length)
76
81
  debug("Directories to search for actions: %o", 3, directories)
77
82
 
78
- const moduleDirectories = directories
79
- .map(composeDirectory)
80
- .filter(directoryExists)
81
- for(const moduleDirectory of moduleDirectories) {
82
- const {directories: dirs} = await ls(moduleDirectory.absolutePath)
83
+ for(const nodeModulesDir of nodeModulesDirs) {
84
+ const dirsToSearch = []
85
+ const {directories: moduleDirs} = await ls(nodeModulesDir.absolutePath)
83
86
 
84
- debug("Found %o directories in `%s`", 2,
85
- dirs.length, moduleDirectory.absolutePath
86
- )
87
+ debug("Found %o directories in %o", 2, moduleDirs.length, nodeModulesDir.absolutePath)
88
+
89
+ // Handle scoped packages (e.g., @bedoc/something)
90
+ const scopedDirs = moduleDirs.filter(d => d.name.startsWith("@"))
87
91
 
88
- const bedocDirs = dirs.filter(d => d.name.startsWith("bedoc-"))
89
- debug("Found %o bedoc directories under %s", 2, bedocDirs.length, moduleDirectory.absolutePath)
92
+ dirsToSearch.push(...moduleDirs)
90
93
 
91
- const exports = bedocDirs.map(d => this.#getModuleExports(d))
92
- debug("Found %o module exports under %s", 2, exports.length, moduleDirectory.absolutePath)
94
+ // If we find a scope (e.g., "@bedoc"), look inside it for bedoc modules
95
+ for(const scopedDir of scopedDirs) {
96
+ const {directories: scopedPackages} = await ls(scopedDir.absolutePath)
97
+
98
+ debug("Found %o directories under scoped package %o", 2, directories.length, scopedDir.name)
99
+
100
+ dirsToSearch.push(...scopedPackages)
101
+ }
102
+
103
+ debug("Found %o directories to search for actions", 2, dirsToSearch.length)
104
+
105
+ const exports = dirsToSearch
106
+ .filter(d => !d.name.startsWith("."))
107
+ .map(d => this.#getModuleExports(d))
108
+
109
+ debug("Found %o module exports under %o", 2, exports.length, nodeModulesDir.absolutePath)
93
110
 
94
111
  bucket.push(...exports.flat())
95
112
  }
@@ -108,18 +125,27 @@ export default class Discovery {
108
125
  */
109
126
  #getModuleExports(dirMap) {
110
127
  const debug = this.#debug
111
- debug("Getting module exports from `%s`", 3, dirMap.absolutePath)
128
+ debug("Getting module exports from %o", 3, dirMap.absolutePath)
129
+
130
+ const packageJsonFile = composeFilename(dirMap, "package.json")
131
+ if(!fileExists(packageJsonFile))
132
+ return []
112
133
 
113
- const packageJsonFile = resolveFilename("package.json", dirMap)
114
- debug("Loading package.json from `%s`", 3, packageJsonFile.absolutePath)
134
+ debug("Loading package.json from %o", 3, packageJsonFile.absolutePath)
115
135
 
116
136
  const packageJson = loadJson(packageJsonFile)
117
- debug("Loaded package.json from `%s`", 3, packageJsonFile.absolutePath)
137
+ debug("Loaded package.json from %o", 3, packageJsonFile.absolutePath)
118
138
 
119
139
  const bedocPackageJsonModules = packageJson.bedoc?.modules ?? []
120
- const bedocModuleFiles = bedocPackageJsonModules.map(file =>
121
- resolveFilename(file, dirMap)
122
- )
140
+
141
+ debug("Discovered %o published modules", 2, bedocPackageJsonModules.length)
142
+ debug("Published modules %o", 3, bedocPackageJsonModules)
143
+
144
+ const bedocModuleFiles = bedocPackageJsonModules
145
+ .map(m => composeFilename(dirMap, m))
146
+ .filter(m => fileExists(m))
147
+
148
+ debug("Composed modules %o", 3, bedocModuleFiles)
123
149
 
124
150
  return bedocModuleFiles
125
151
  }
@@ -225,7 +251,7 @@ export default class Discovery {
225
251
  JSON.stringify(moduleFile, null, 2) + "\n" +
226
252
  JSON.stringify(action, null, 2))
227
253
 
228
- debug("Checking action `%s`", 2, metaAction)
254
+ debug("Checking action %o", 2, metaAction)
229
255
 
230
256
  const isValid = this.#validMeta(metaAction, {action, contract})
231
257
 
@@ -246,7 +272,7 @@ export default class Discovery {
246
272
 
247
273
  for(const actionType of actionTypes) {
248
274
  const total = resultActions[actionType].length
249
- debug("Found %o `%o` actions", 2, total, actionType)
275
+ debug("Found %o %o actions", 2, total, actionType)
250
276
  }
251
277
 
252
278
  const total = Object.keys(resultActions).reduce((acc, curr) => {
@@ -280,7 +306,7 @@ export default class Discovery {
280
306
 
281
307
  // First let's check if we wanted something specific
282
308
  if(validatedConfig[config]) {
283
- debug("Checking for specific `%s` action", 3, actionType)
309
+ debug("Checking for specific %o action", 3, actionType)
284
310
  const found = actions[actionType].find(
285
311
  a => a.file.specificType.includes(actionType)
286
312
  )
@@ -294,7 +320,7 @@ export default class Discovery {
294
320
  }
295
321
 
296
322
  // Hmm! We didn't find anything specific. Let's check the criterion
297
- debug("Checking for `%s` actions with criterion `%s`", 3, actionType, criterion)
323
+ debug("Checking for %o actions with criterion %o", 3, actionType, criterion)
298
324
  debug("Validated config to check against: %o", 3, validatedConfig)
299
325
  const found = actions[actionType].filter(a => {
300
326
  debug("Meta criterion value: %o", 4, a.action.meta[criterion])
@@ -341,7 +367,7 @@ export default class Discovery {
341
367
  */
342
368
  #validMeta(actionType, toValidate) {
343
369
  const debug = this.#debug
344
- debug("Checking meta requirements for `%s`", 3, actionType)
370
+ debug("Checking meta requirements for %o", 3, actionType)
345
371
 
346
372
  const requirements = actionMetaRequirements[actionType]
347
373
  if(!requirements)