adapt-authoring-docs 1.3.2 → 1.4.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.
@@ -1,4 +1,4 @@
1
- name: Standard.js formatting check
1
+ name: Lint
2
2
  on: push
3
3
  jobs:
4
4
  default:
@@ -9,4 +9,4 @@ jobs:
9
9
  with:
10
10
  node-version: 'lts/*'
11
11
  - run: npm install
12
- - run: npx standard
12
+ - run: npx standard
package/bin/docgen.js CHANGED
@@ -2,7 +2,8 @@
2
2
  /**
3
3
  * Generates documentation for the installed modules.
4
4
  */
5
- import { App } from 'adapt-authoring-core'
5
+ import { readJson } from 'adapt-authoring-core'
6
+ import { loadDependencies, loadConfigDefaults, loadSchemas, loadErrors, buildRouterTree, buildPermissions } from '../lib/docsData.js'
6
7
  import docsify from '../docsify/docsify.js'
7
8
  import fs from 'fs/promises'
8
9
  import jsdoc3 from '../jsdoc3/jsdoc3.js'
@@ -11,10 +12,13 @@ import swagger from '../swagger/swagger.js'
11
12
 
12
13
  const DEBUG = process.argv.includes('--verbose')
13
14
 
14
- process.env.NODE_ENV ??= 'production'
15
- process.env.ADAPT_AUTHORING_LOGGER__mute = !DEBUG
15
+ function getArg (name) {
16
+ const idx = process.argv.indexOf(name)
17
+ return idx !== -1 && idx + 1 < process.argv.length ? process.argv[idx + 1] : undefined
18
+ }
19
+
20
+ const rootDir = path.resolve(getArg('--rootDir') || process.cwd())
16
21
 
17
- const app = App.instance
18
22
  let outputdir
19
23
 
20
24
  const defaultPages = { // populated in cacheConfigs
@@ -27,10 +31,10 @@ const defaultPages = { // populated in cacheConfigs
27
31
  * Documentation for a module can be enabled in:
28
32
  * package.json > adapt_authoring.documentation.enable
29
33
  */
30
- function cacheConfigs () {
34
+ function cacheConfigs (appData) {
31
35
  const cache = []
32
- const excludes = app.pkg.documentation.excludes ?? []
33
- Object.values(app.dependencies).forEach(dep => {
36
+ const excludes = appData.pkg.documentation.excludes ?? []
37
+ Object.values(appData.dependencies).forEach(dep => {
34
38
  const c = dep.documentation
35
39
 
36
40
  let omitMsg
@@ -49,16 +53,16 @@ function cacheConfigs () {
49
53
  ...c,
50
54
  name: dep.name,
51
55
  version: dep.version,
52
- module: !!app.dependencyloader.instances[dep.name],
56
+ module: dep.module !== false,
53
57
  rootDir: dep.rootDir,
54
58
  includes: c.includes || {}
55
59
  })
56
60
  })
57
61
  cache.push({
58
- ...app.pkg.documentation,
62
+ ...appData.pkg.documentation,
59
63
  enable: true,
60
64
  name: 'adapt-authoring',
61
- rootDir: app.rootDir,
65
+ rootDir: appData.rootDir,
62
66
  includes: {}
63
67
  })
64
68
  return cache
@@ -71,19 +75,31 @@ async function copyRootFiles () {
71
75
  }
72
76
 
73
77
  async function docs () {
74
- console.log(`Generating documentation for ${app.pkg.name}@${app.pkg.version} ${DEBUG ? ' :: DEBUG' : ''}`)
78
+ const dependencies = await loadDependencies(rootDir)
79
+ const pkg = { ...await readJson(path.join(rootDir, 'package.json')), ...await readJson(path.join(rootDir, 'adapt-authoring.json')) }
80
+ const config = await loadConfigDefaults(dependencies)
81
+ const schemas = await loadSchemas(dependencies)
82
+ const errors = await loadErrors(dependencies)
83
+ const routerTree = await buildRouterTree(dependencies)
84
+ const permissions = buildPermissions(routerTree)
75
85
 
76
- try {
77
- await app.onReady()
78
- } catch (e) {
79
- console.log(`App failed to start, cannot continue.\n${e}`)
80
- process.exit(1)
86
+ const appData = {
87
+ rootDir,
88
+ pkg,
89
+ dependencies,
90
+ config,
91
+ errors,
92
+ schemas,
93
+ routerTree,
94
+ permissions
81
95
  }
82
- const config = await app.waitForModule('config')
96
+
97
+ console.log(`Generating documentation for ${appData.pkg.name}@${appData.pkg.version} ${DEBUG ? ' :: DEBUG' : ''}`)
98
+
83
99
  const { name } = JSON.parse(await fs.readFile(new URL('../package.json', import.meta.url)))
84
- outputdir = path.resolve(process.cwd(), config.get(`${name}.outputDir`))
100
+ outputdir = path.resolve(process.cwd(), getArg('--outputDir') || appData.config.get(`${name}.outputDir`))
85
101
 
86
- const cachedConfigs = cacheConfigs()
102
+ const cachedConfigs = cacheConfigs(appData)
87
103
 
88
104
  console.log('\nThis might take a minute or two...\n')
89
105
 
@@ -91,9 +107,9 @@ async function docs () {
91
107
  await fs.rm(outputdir, { recursive: true, force: true })
92
108
  await fs.mkdir(outputdir)
93
109
  await copyRootFiles()
94
- await jsdoc3(app, cachedConfigs, outputdir, defaultPages)
95
- await docsify(app, cachedConfigs, outputdir, defaultPages)
96
- await swagger(app, cachedConfigs, outputdir)
110
+ await jsdoc3(appData, cachedConfigs, outputdir, defaultPages)
111
+ await docsify(appData, cachedConfigs, outputdir, defaultPages)
112
+ await swagger(appData, cachedConfigs, outputdir)
97
113
  } catch (e) {
98
114
  console.log(e)
99
115
  process.exit(1)
package/bin/docserve.js CHANGED
@@ -1,51 +1,39 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * Generates an HTTP server for viewing the local copy of the documentation (note these must be build first with `at-docgen`)
3
+ * Generates an HTTP server for viewing the local copy of the documentation (note these must be built first with `at-docgen`)
4
4
  */
5
- import { App } from 'adapt-authoring-core'
6
5
  import { spawn } from 'child_process'
7
6
  import http from 'http-server'
8
7
  import path from 'path'
9
- /*
10
- function getMime (filePath) {
11
- const ext = path.parse(filePath).ext
12
- return {
13
- '.ico': 'image/x-icon',
14
- '.html': 'text/html',
15
- '.js': 'text/javascript',
16
- '.json': 'application/json',
17
- '.css': 'text/css',
18
- '.png': 'image/png',
19
- '.jpg': 'image/jpeg',
20
- '.svg': 'image/svg+xml',
21
- '.pdf': 'application/pdf',
22
- '.doc': 'application/msword'
23
- }[ext] || 'text/plain'
8
+
9
+ function getArg (name) {
10
+ const idx = process.argv.indexOf(name)
11
+ return idx !== -1 && idx + 1 < process.argv.length ? process.argv[idx + 1] : undefined
24
12
  }
25
- */
26
- process.env.NODE_ENV ??= 'production'
27
- process.env.ADAPT_AUTHORING_LOGGER__mute = true
28
13
 
29
- console.log('Starting app, please wait\n')
30
- // TODO remove need to start app
31
- App.instance.onReady().then(async app => {
32
- console.log('App started\n');
33
- (await app.waitForModule('server')).close() // close connections so we can still run the app separately
14
+ async function getOutputDir () {
15
+ const arg = getArg('--outputDir')
16
+ if (arg) return path.resolve(arg)
17
+ const { loadDependencies, loadConfigDefaults } = await import('../lib/docsData.js')
18
+ const rootDir = path.resolve(getArg('--rootDir') || process.cwd())
19
+ const dependencies = await loadDependencies(rootDir)
20
+ const config = await loadConfigDefaults(dependencies)
21
+ return path.resolve(config.get('adapt-authoring-docs.outputDir'))
22
+ }
34
23
 
35
- const ROOT = path.resolve(app.config.get('adapt-authoring-docs.outputDir'))
36
- const PORT = 9000
37
- const OPEN = process.argv.some(a => a === '--open')
24
+ const ROOT = await getOutputDir()
25
+ const PORT = Number(getArg('--port')) || 9000
26
+ const OPEN = process.argv.some(a => a === '--open')
38
27
 
39
- const server = http.createServer({ root: ROOT, cache: -1 })
28
+ const server = http.createServer({ root: ROOT, cache: -1 })
40
29
 
41
- server.listen(PORT, () => {
42
- const url = `http://localhost:${PORT}`
43
- console.log(`Docs hosted at ${url}`)
30
+ server.listen(PORT, () => {
31
+ const url = `http://localhost:${PORT}`
32
+ console.log(`Docs hosted at ${url}`)
44
33
 
45
- if (OPEN) {
46
- const command = process.platform === 'darwin' ? 'open' : process.platform === 'win32' ? 'start' : 'xdg-open'
47
- spawn(`${command} ${url}`, { shell: true })
48
- .on('error', e => console.log('spawn error', e))
49
- }
50
- })
34
+ if (OPEN) {
35
+ const command = process.platform === 'darwin' ? 'open' : process.platform === 'win32' ? 'start' : 'xdg-open'
36
+ spawn(`${command} ${url}`, { shell: true })
37
+ .on('error', e => console.log('spawn error', e))
38
+ }
51
39
  })
@@ -20,9 +20,9 @@ function generateSectionTitle (sectionName) {
20
20
  /**
21
21
  * Copies all doc files ready for the generator
22
22
  */
23
- export default async function docsify (app, configs, outputdir, defaultPages) {
23
+ export default async function docsify (appData, configs, outputdir, defaultPages) {
24
24
  const dir = path.resolve(outputdir, 'manual')
25
- const sectionsConf = app.config.get('adapt-authoring-docs.manualSections')
25
+ const sectionsConf = appData.config.get('adapt-authoring-docs.manualSections')
26
26
  const defaultSection = Object.entries(sectionsConf).find(([, data]) => data.default)?.[0]
27
27
  /**
28
28
  * init docsify folder
@@ -39,7 +39,7 @@ export default async function docsify (app, configs, outputdir, defaultPages) {
39
39
  try {
40
40
  const wrapper = await new DocsifyPluginWrapper({
41
41
  ...c,
42
- app,
42
+ app: appData,
43
43
  docsRootDir: outputdir,
44
44
  pluginEntry: path.resolve(c.rootDir, p),
45
45
  outputDir: dir
package/jsdoc3/jsdoc3.js CHANGED
@@ -14,7 +14,7 @@ function resolvePath (relativePath) {
14
14
  return fileURLToPath(new URL(relativePath, import.meta.url))
15
15
  }
16
16
 
17
- async function writeConfig (app, outputdir, indexFile) {
17
+ async function writeConfig (appData, outputdir, indexFile) {
18
18
  return fs.writeFile(configPath, JSON.stringify({
19
19
  source: {
20
20
  include: getSourceIncludes(indexFile)
@@ -25,7 +25,7 @@ async function writeConfig (app, outputdir, indexFile) {
25
25
  search: true,
26
26
  static: true,
27
27
  menu: {
28
- [`<img class="logo" src="assets/logo-outline-colour.png" />Adapt authoring tool back-end API documentation<br><span class="version">v${app.pkg.version}</span>`]: {
28
+ [`<img class="logo" src="assets/logo-outline-colour.png" />Adapt authoring tool back-end API documentation<br><span class="version">v${appData.pkg.version}</span>`]: {
29
29
  class: 'menu-title'
30
30
  },
31
31
  'Documentation home': {
@@ -60,7 +60,7 @@ async function writeConfig (app, outputdir, indexFile) {
60
60
  meta: {
61
61
  title: 'Adapt authoring tool UI documentation',
62
62
  description: 'Adapt authoring tool UI documentation',
63
- keyword: `v${app.pkg.version}`
63
+ keyword: `v${appData.pkg.version}`
64
64
  },
65
65
  scripts: [
66
66
  'styles/adapt.css',
@@ -88,10 +88,10 @@ function getSourceIncludes (indexFile) {
88
88
  return includes
89
89
  }
90
90
 
91
- export default async function jsdoc3 (app, configs, outputdir, defaultPages) {
91
+ export default async function jsdoc3 (appData, configs, outputdir, defaultPages) {
92
92
  cachedConfigs = configs
93
93
  const dir = `${outputdir}/backend`
94
- await writeConfig(app, dir, defaultPages.sourceIndex)
94
+ await writeConfig(appData, dir, defaultPages.sourceIndex)
95
95
  try {
96
96
  await execPromise(`npx jsdoc -c ${configPath}`)
97
97
  } catch (e) {
@@ -0,0 +1,298 @@
1
+ import { loadDependencyFiles, readJson } from 'adapt-authoring-core'
2
+ import { glob } from 'glob'
3
+ import os from 'os'
4
+ import path from 'path'
5
+ import { pathToRegexp } from 'path-to-regexp'
6
+
7
+ /**
8
+ * Globs node_modules for adapt-authoring.json files, merges each with its
9
+ * package.json and returns a { [name]: config } map.
10
+ * @param {string} rootDir Absolute path to the app root
11
+ * @returns {Promise<Object>}
12
+ */
13
+ export async function loadDependencies (rootDir) {
14
+ const files = await glob('node_modules/**/adapt-authoring.json', {
15
+ cwd: rootDir,
16
+ absolute: true
17
+ })
18
+ const seen = new Set()
19
+ const deps = {}
20
+ const sorted = files.sort((a, b) => a.length - b.length)
21
+ for (const file of sorted) {
22
+ const dir = path.dirname(file)
23
+ try {
24
+ const pkg = await readJson(path.join(dir, 'package.json'))
25
+ if (seen.has(pkg.name)) continue
26
+ seen.add(pkg.name)
27
+ const meta = await readJson(file)
28
+ deps[pkg.name] = { ...pkg, ...meta, rootDir: dir }
29
+ } catch {
30
+ // skip modules with unreadable config
31
+ }
32
+ }
33
+ return deps
34
+ }
35
+
36
+ /**
37
+ * Reads conf/config.schema.json from each dependency, extracts default values,
38
+ * and returns a { get(key) } object. Resolves $TEMP to os.tmpdir().
39
+ * @param {Object} dependencies Dependencies map from loadDependencies
40
+ * @returns {Promise<Object>}
41
+ */
42
+ export async function loadConfigDefaults (dependencies) {
43
+ const allConfigs = await loadDependencyFiles('conf/config.schema.json', {
44
+ parse: true,
45
+ dependencies
46
+ })
47
+ const defaults = {}
48
+ for (const [depName, [schema]] of Object.entries(allConfigs)) {
49
+ const props = schema.properties || {}
50
+ const modDefaults = {}
51
+ for (const [key, prop] of Object.entries(props)) {
52
+ if (prop.default !== undefined) modDefaults[key] = prop.default
53
+ }
54
+ if (Object.keys(modDefaults).length) defaults[depName] = modDefaults
55
+ }
56
+ return {
57
+ get: (key) => {
58
+ const dotIdx = key.indexOf('.')
59
+ if (dotIdx === -1) return defaults[key]
60
+ const modName = key.slice(0, dotIdx)
61
+ const propKey = key.slice(dotIdx + 1)
62
+ const val = defaults[modName]?.[propKey]
63
+ if (typeof val === 'string') return val.replace('$TEMP', os.tmpdir())
64
+ return val
65
+ }
66
+ }
67
+ }
68
+
69
+ /**
70
+ * Loads and merges all schema/*.schema.json files across dependencies.
71
+ * @param {Object} dependencies Dependencies map from loadDependencies
72
+ * @returns {Promise<Object>} { schemas, raw, getSchema }
73
+ */
74
+ export async function loadSchemas (dependencies) {
75
+ const allSchemas = await loadDependencyFiles('schema/*.schema.json', {
76
+ parse: true,
77
+ dependencies
78
+ })
79
+ const schemaMap = {}
80
+ const rawMap = {}
81
+ for (const [, arr] of Object.entries(allSchemas)) {
82
+ for (const schema of arr) {
83
+ const name = schema.$anchor || schema.$id || 'unknown'
84
+ schemaMap[name] = true
85
+ rawMap[name] = schema
86
+ }
87
+ }
88
+ return {
89
+ schemas: schemaMap,
90
+ raw: rawMap,
91
+ getSchema: async (name) => ({ built: rawMap[name] || {} })
92
+ }
93
+ }
94
+
95
+ /**
96
+ * Loads and merges all errors/*.json files across dependencies.
97
+ * @param {Object} dependencies Dependencies map from loadDependencies
98
+ * @returns {Promise<Object>} Merged error map
99
+ */
100
+ export async function loadErrors (dependencies) {
101
+ const allErrors = await loadDependencyFiles('errors/*.json', {
102
+ parse: true,
103
+ dependencies
104
+ })
105
+ const merged = {}
106
+ for (const arr of Object.values(allErrors)) {
107
+ for (const obj of arr) Object.assign(merged, obj)
108
+ }
109
+ // Match the AdaptError shape that plugins expect:
110
+ // { code, statusCode, meta: { description, data } }
111
+ return Object.entries(merged)
112
+ .sort()
113
+ .reduce((m, [code, { description, statusCode, data }]) => {
114
+ const meta = { description }
115
+ if (data) meta.data = data
116
+ m[code] = { code, statusCode, meta }
117
+ return m
118
+ }, {})
119
+ }
120
+
121
+ /**
122
+ * Reads routes.json files from dependencies and assembles an Express-like
123
+ * { path, routes, childRouters } tree rooted at /api.
124
+ * @param {Object} dependencies Dependencies map from loadDependencies
125
+ * @returns {Promise<Object>} Router tree
126
+ */
127
+ export async function buildRouterTree (dependencies) {
128
+ const apiRouter = { path: '/api', routes: [], childRouters: [] }
129
+ let authRouter = null
130
+ let apiDefaultRoutes = null
131
+ let authDefaultRoutes = null
132
+
133
+ const authDep = dependencies['adapt-authoring-auth']
134
+ const apiDep = dependencies['adapt-authoring-api']
135
+
136
+ if (apiDep) {
137
+ try {
138
+ apiDefaultRoutes = await readJson(path.join(apiDep.rootDir, 'lib', 'default-routes.json'))
139
+ } catch { /* no defaults */ }
140
+ }
141
+ if (authDep) {
142
+ try {
143
+ authDefaultRoutes = await readJson(path.join(authDep.rootDir, 'lib', 'default-routes.json'))
144
+ } catch { /* no defaults */ }
145
+ try {
146
+ const coreAuthConfig = await readJson(path.join(authDep.rootDir, 'lib', 'routes.json'))
147
+ authRouter = {
148
+ path: '/api/auth',
149
+ routes: (coreAuthConfig.routes || []).map(r => staticRouteEntry(r)),
150
+ childRouters: []
151
+ }
152
+ apiRouter.childRouters.push(authRouter)
153
+ } catch { /* no auth routes */ }
154
+ }
155
+
156
+ const allRoutes = await loadDependencyFiles('routes.json', {
157
+ parse: true,
158
+ dependencies
159
+ })
160
+ for (const [depName, [config]] of Object.entries(allRoutes)) {
161
+ if (dependencies[depName].module === false) continue
162
+ if (config.type !== undefined) {
163
+ addAuthTypeRouter(config, authRouter, authDefaultRoutes)
164
+ } else if (config.root) {
165
+ addApiRouter(config, apiRouter, apiDefaultRoutes)
166
+ }
167
+ }
168
+ return apiRouter
169
+ }
170
+
171
+ /**
172
+ * Walks the router tree and builds a permission store with
173
+ * { get: [], post: [], ... } arrays of [regexp, scopes] tuples.
174
+ * @param {Object} routerTree Router tree from buildRouterTree
175
+ * @returns {Object} Permission store
176
+ */
177
+ export function buildPermissions (routerTree) {
178
+ const store = { get: [], post: [], put: [], patch: [], delete: [] }
179
+ walkRouterPermissions(routerTree, store)
180
+ return store
181
+ }
182
+
183
+ // ------- internal helpers -------
184
+
185
+ function addApiRouter (config, parentRouter, defaultRoutes) {
186
+ const root = config.root
187
+ const scope = config.permissionsScope || root
188
+ const schemaName = config.schemaName ?? root
189
+ const collectionName = config.collectionName ?? root
190
+
191
+ let routes = config.routes || []
192
+
193
+ if (config.useDefaultRoutes !== false && defaultRoutes) {
194
+ routes = mergeWithDefaults(routes, defaultRoutes.routes || [])
195
+ }
196
+
197
+ /* eslint-disable no-template-curly-in-string */
198
+ const replacements = {
199
+ '${scope}': scope,
200
+ '${schemaName}': schemaName,
201
+ '${collectionName}': collectionName
202
+ }
203
+ /* eslint-enable no-template-curly-in-string */
204
+
205
+ const childRouter = {
206
+ path: `/api/${root}`,
207
+ routes: routes.map(r => staticRouteEntry(replacePlaceholders(r, replacements))),
208
+ childRouters: []
209
+ }
210
+ parentRouter.childRouters.push(childRouter)
211
+ }
212
+
213
+ function addAuthTypeRouter (config, authRouter, defaultRoutes) {
214
+ if (!authRouter) return
215
+ const type = config.type
216
+ let routes = config.routes || []
217
+
218
+ if (defaultRoutes) {
219
+ routes = mergeWithDefaults(routes, defaultRoutes.routes || [])
220
+ }
221
+
222
+ const childRouter = {
223
+ path: `/api/auth/${type}`,
224
+ routes: routes.map(r => staticRouteEntry(r)),
225
+ childRouters: []
226
+ }
227
+ authRouter.childRouters.push(childRouter)
228
+ }
229
+
230
+ function mergeWithDefaults (customRoutes, defaultRoutes) {
231
+ const overrides = new Map(
232
+ customRoutes.filter(r => r.override).map(r => [r.route, r])
233
+ )
234
+ const matched = new Set()
235
+ const mergedDefaults = defaultRoutes.map(d => {
236
+ const o = overrides.get(d.route)
237
+ if (!o) return d
238
+ matched.add(d.route)
239
+ const { override, ...rest } = o
240
+ return { ...d, ...rest, handlers: { ...d.handlers, ...rest.handlers } }
241
+ })
242
+ const remaining = customRoutes.filter(r => !r.override || !matched.has(r.route))
243
+ return [...mergedDefaults, ...remaining]
244
+ }
245
+
246
+ function staticRouteEntry (routeDef) {
247
+ const handlers = {}
248
+ if (routeDef.handlers) {
249
+ for (const method of Object.keys(routeDef.handlers)) {
250
+ handlers[method] = () => {}
251
+ }
252
+ }
253
+ return {
254
+ route: routeDef.route,
255
+ handlers,
256
+ meta: routeDef.meta || {},
257
+ internal: routeDef.internal || false,
258
+ permissions: routeDef.permissions || {}
259
+ }
260
+ }
261
+
262
+ function walkRouterPermissions (router, store) {
263
+ for (const routeDef of router.routes) {
264
+ const fullRoute = `${router.path}${routeDef.route !== '/' ? routeDef.route : ''}`
265
+ for (const [method, scopes] of Object.entries(routeDef.permissions || {})) {
266
+ if (scopes === null) continue
267
+ const m = method.toLowerCase()
268
+ if (!store[m]) continue
269
+ try {
270
+ const { regexp } = pathToRegexp(fullRoute)
271
+ store[m].push([regexp, scopes])
272
+ } catch {
273
+ // skip routes that can't be compiled (unusual patterns)
274
+ }
275
+ }
276
+ }
277
+ for (const child of router.childRouters) {
278
+ walkRouterPermissions(child, store)
279
+ }
280
+ }
281
+
282
+ function replacePlaceholders (obj, replacements) {
283
+ if (typeof obj === 'string') {
284
+ return Object.entries(replacements).reduce(
285
+ (s, [k, v]) => v != null ? s.replaceAll(k, v) : s,
286
+ obj
287
+ )
288
+ }
289
+ if (Array.isArray(obj)) {
290
+ return obj.map(item => replacePlaceholders(item, replacements))
291
+ }
292
+ if (obj && typeof obj === 'object' && obj.constructor === Object) {
293
+ return Object.fromEntries(
294
+ Object.entries(obj).map(([k, v]) => [k, replacePlaceholders(v, replacements)])
295
+ )
296
+ }
297
+ return obj
298
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "adapt-authoring-docs",
3
- "version": "1.3.2",
3
+ "version": "1.4.0",
4
4
  "description": "Tools for auto-generating documentation for the Adapt authoring tool",
5
5
  "homepage": "https://github.com/adapt-security/adapt-authoring-docs",
6
6
  "license": "GPL-3.0",
@@ -14,6 +14,7 @@
14
14
  "test": "node --test 'tests/**/*.spec.js'"
15
15
  },
16
16
  "dependencies": {
17
+ "adapt-authoring-core": "^2.0.0",
17
18
  "comment-parser": "^1.4.1",
18
19
  "docdash": "^2.0.2",
19
20
  "docsify-cli": "^4.4.4",
@@ -21,16 +22,9 @@
21
22
  "glob": "^13.0.0",
22
23
  "http-server": "^14.1.1",
23
24
  "jsdoc": "^4.0.3",
25
+ "path-to-regexp": "^8.0.0",
24
26
  "swagger-ui": "^5.17.14"
25
27
  },
26
- "peerDependencies": {
27
- "adapt-authoring-core": "^1.7.0"
28
- },
29
- "peerDependenciesMeta": {
30
- "adapt-authoring-core": {
31
- "optional": true
32
- }
33
- },
34
28
  "devDependencies": {
35
29
  "@semantic-release/git": "^10.0.1",
36
30
  "conventional-changelog-eslint": "^6.0.0",
@@ -8,14 +8,12 @@ function resolvePath (relativePath) {
8
8
  /**
9
9
  *
10
10
  */
11
- export default async function swagger (app, configs, outputdir) {
12
- const server = await app.waitForModule('server')
13
- await app.onReady()
11
+ export default async function swagger (appData, configs, outputdir) {
14
12
  const spec = {
15
13
  openapi: '3.0.3',
16
- info: { version: app.pkg.version },
17
- components: { schemas: await generateSchemaSpec(app) },
18
- paths: generatePathSpec(app, server.api)
14
+ info: { version: appData.pkg.version },
15
+ components: { schemas: await generateSchemaSpec(appData.schemas) },
16
+ paths: generatePathSpec(appData.permissions, appData.routerTree)
19
17
  }
20
18
  // generate UI
21
19
  const dir = path.resolve(outputdir, 'rest')
@@ -38,8 +36,7 @@ export default async function swagger (app, configs, outputdir) {
38
36
  ])
39
37
  }
40
38
 
41
- async function generateSchemaSpec (app) {
42
- const jsonschema = await app.waitForModule('jsonschema')
39
+ async function generateSchemaSpec (jsonschema) {
43
40
  const schemas = {}
44
41
  await Promise.all(Object.keys(jsonschema.schemas).map(async s => {
45
42
  schemas[s] = sanitiseSchema((await jsonschema.getSchema(s)).built)
@@ -57,8 +54,7 @@ function sanitiseSchema (schema) {
57
54
  return schema
58
55
  }
59
56
 
60
- function generatePathSpec (app, router, paths = {}) {
61
- const perms = app.dependencyloader.instances['adapt-authoring-auth'].permissions.routes
57
+ function generatePathSpec (permissions, router, paths = {}) {
62
58
  router.routes.forEach(r => {
63
59
  const parameters = r.route.split('/').filter(r => r.startsWith(':')).map(r => {
64
60
  return {
@@ -70,7 +66,7 @@ function generatePathSpec (app, router, paths = {}) {
70
66
  const route = `${router.path}${r.route !== '/' ? r.route : ''}`
71
67
  paths[route] = Object.keys(r.handlers).reduce((memo, method) => {
72
68
  const meta = r.meta?.[method] || {}
73
- const scopes = perms[method].find(p => route.match(p[0]))?.[1] || []
69
+ const scopes = permissions[method].find(p => route.match(p[0]))?.[1] || []
74
70
  let description = r.internal ? 'ROUTE IS ONLY ACCESSIBLE FROM LOCALHOST.<br/><br/>' : ''
75
71
  description += scopes.length
76
72
  ? `Required scopes: ${scopes.map(s => `<span>${s}</span>`).join(' ')}`
@@ -90,7 +86,7 @@ function generatePathSpec (app, router, paths = {}) {
90
86
  }, {})
91
87
  })
92
88
  if (router.childRouters.length) {
93
- router.childRouters.forEach(childRouter => generatePathSpec(app, childRouter, paths))
89
+ router.childRouters.forEach(childRouter => generatePathSpec(permissions, childRouter, paths))
94
90
  }
95
91
  return Object.keys(paths).sort().reduce((m, k) => Object.assign(m, { [k]: paths[k] }), {})
96
92
  }