adapt-authoring-core 1.3.0 → 1.3.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/.github/workflows/new.yml +7 -11
- package/adapt-authoring.json +2 -2
- package/docs/folder-structure.md +105 -0
- package/docs/hooks.md +214 -0
- package/docs/plugins/binscripts.js +33 -30
- package/docs/plugins/coremodules.js +9 -8
- package/docs/plugins/licensing.js +8 -4
- package/lib/App.js +1 -2
- package/lib/Utils.js +0 -1
- package/package.json +3 -3
- package/.github/workflows/labelled_prs.yml +0 -16
- package/docs/data-folder.md +0 -4
- package/docs/temp-folder.md +0 -12
|
@@ -1,19 +1,15 @@
|
|
|
1
|
-
|
|
1
|
+
# Calls the org-level reusable workflow to add PRs to the TODO Board
|
|
2
|
+
|
|
3
|
+
name: Add PR to Project
|
|
2
4
|
|
|
3
5
|
on:
|
|
4
|
-
issues:
|
|
5
|
-
types:
|
|
6
|
-
- opened
|
|
7
6
|
pull_request:
|
|
8
7
|
types:
|
|
9
8
|
- opened
|
|
9
|
+
- reopened
|
|
10
10
|
|
|
11
11
|
jobs:
|
|
12
12
|
add-to-project:
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
- uses: actions/add-to-project@v0.1.0
|
|
17
|
-
with:
|
|
18
|
-
project-url: https://github.com/orgs/adapt-security/projects/5
|
|
19
|
-
github-token: ${{ secrets.PROJECTS_SECRET }}
|
|
13
|
+
uses: adapt-security/.github/.github/workflows/new.yml@main
|
|
14
|
+
secrets:
|
|
15
|
+
PROJECTS_SECRET: ${{ secrets.PROJECTS_SECRET }}
|
package/adapt-authoring.json
CHANGED
|
@@ -7,10 +7,10 @@
|
|
|
7
7
|
"manualPages": {
|
|
8
8
|
"binscripts.md": "reference",
|
|
9
9
|
"coremodules.md": "reference",
|
|
10
|
-
"data-folder.md": "basics",
|
|
11
10
|
"customising.md": "advanced",
|
|
11
|
+
"folder-structure.md": "getting-started",
|
|
12
|
+
"hooks.md": "basics",
|
|
12
13
|
"licensing.md": "reference",
|
|
13
|
-
"temp-folder.md": "basics",
|
|
14
14
|
"writing-a-module.md": "basics",
|
|
15
15
|
"writing-core-code.md": "contributing"
|
|
16
16
|
},
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# Folder structure
|
|
2
|
+
|
|
3
|
+
This guide explains the folder structure of an Adapt authoring tool installation.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
A typical installation has the following structure:
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
adapt-authoring/
|
|
11
|
+
├── APP_DATA/ # Application runtime data
|
|
12
|
+
│ ├── data/ # Persistent files
|
|
13
|
+
│ └── temp/ # Temporary files
|
|
14
|
+
├── bin/ # CLI scripts
|
|
15
|
+
├── conf/ # Environment configuration files
|
|
16
|
+
└── node_modules/ # Installed modules and dependencies
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## `APP_DATA`
|
|
20
|
+
|
|
21
|
+
The `APP_DATA` folder contains all files required to run an Adapt authoring tool instance (with the exception of the source code, which are found in `node_modules`).
|
|
22
|
+
|
|
23
|
+
### `data`
|
|
24
|
+
|
|
25
|
+
The data directory stores persistent application data that must survive restarts and updates. By default this is `APP_DATA/data/`, but can be configured via the `dataDir` config option.
|
|
26
|
+
|
|
27
|
+
> **Warning:** Never modify or delete the data directory while the application is running. Doing so will cause data loss and runtime errors.
|
|
28
|
+
|
|
29
|
+
### Using the data directory
|
|
30
|
+
|
|
31
|
+
Module developers should store any data that must persist between restarts in this directory. You can use the `$DATA` variable in your config schema to automatically populate this value at runtime:
|
|
32
|
+
|
|
33
|
+
```json
|
|
34
|
+
{
|
|
35
|
+
"myDataPath": {
|
|
36
|
+
"type": "string",
|
|
37
|
+
"isDirectory": true,
|
|
38
|
+
"default": "$DATA/mymodule"
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
See the [configuration guide](configuration.md) for more information.
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
### `temp`
|
|
47
|
+
|
|
48
|
+
The temp directory stores temporary files used at runtime. By default this is `APP_DATA/temp/`, but can be configured via `tempDir`. Files in this directory may be removed at any time when the application is stopped.
|
|
49
|
+
|
|
50
|
+
#### Using the temp directory
|
|
51
|
+
|
|
52
|
+
Developers should use the temp directory to store any files which are not needed permanently. Please try to remove any temporary files once they're no longer needed to conserve disk space and reduce the need for manual housekeeping by the server admin.
|
|
53
|
+
|
|
54
|
+
Examples of the kind of data found in the temp directory: generated asset thumbnails, documentation build output, compiled UI app code and file uploads.
|
|
55
|
+
|
|
56
|
+
As with the data directory, there is a custom variable (`$TEMP`) which can be used in config schemas to populate the correct value at runtime:
|
|
57
|
+
|
|
58
|
+
```json
|
|
59
|
+
{
|
|
60
|
+
"myCachePath": {
|
|
61
|
+
"type": "string",
|
|
62
|
+
"isDirectory": true,
|
|
63
|
+
"default": "$TEMP/mymodule-cache"
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Clearing the temp directory
|
|
69
|
+
|
|
70
|
+
It is safe to delete the temp directory when the application is stopped. On restart, the application will recreate any required directories. This can be useful for:
|
|
71
|
+
|
|
72
|
+
- Freeing disk space
|
|
73
|
+
- Preparing for updates
|
|
74
|
+
|
|
75
|
+
> **Warning:** Do not delete the temp directory while the application is running. Users will encounter errors and builds will fail.
|
|
76
|
+
|
|
77
|
+
## `bin`
|
|
78
|
+
|
|
79
|
+
Command-line tools are found in the `bin` folder. Modules can also provide their own CLIs which are available via `npx`. See the [CLI reference](binscripts.md) for available commands.
|
|
80
|
+
|
|
81
|
+
## `docs`
|
|
82
|
+
|
|
83
|
+
The `doc` folder contains documentation pages. These are picked up and compiled by the documentation generation tools when running `at-docgen`. See the [Building the docs](building-docs) for details.
|
|
84
|
+
|
|
85
|
+
## `conf`
|
|
86
|
+
|
|
87
|
+
The `conf/` directory contains configuration files. The application loads the file matching your `NODE_ENV` value (e.g., `production.config.js` when `NODE_ENV=production`). If `NODE_ENV` is not set, it defaults to `production`.
|
|
88
|
+
|
|
89
|
+
See the [configuration guide](configuration.md) for details.
|
|
90
|
+
|
|
91
|
+
## `node_modules`
|
|
92
|
+
|
|
93
|
+
The Adapt authoring tool is built from modular components, each published as an npm package. All modules are installed into the `node_modules/` directory as standard npm dependencies.
|
|
94
|
+
|
|
95
|
+
Core modules follow the naming convention `adapt-authoring-*` and are listed as dependencies in `package.json`, e.g.
|
|
96
|
+
|
|
97
|
+
```json
|
|
98
|
+
{
|
|
99
|
+
"dependencies": {
|
|
100
|
+
"adapt-authoring-auth": "^1.0.0",
|
|
101
|
+
"adapt-authoring-content": "^1.0.0",
|
|
102
|
+
"adapt-authoring-mongodb": "^1.0.0"
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
```
|
package/docs/hooks.md
ADDED
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
# Hooks
|
|
2
|
+
|
|
3
|
+
Hooks allow modules to react to events and extend functionality without modifying core code. They're the primary mechanism for inter-module communication in the Adapt authoring tool.
|
|
4
|
+
|
|
5
|
+
## How hooks work
|
|
6
|
+
|
|
7
|
+
A hook is a point in the code where external observers can run their own functions. When a hook is invoked, all registered observers are called with the same arguments.
|
|
8
|
+
|
|
9
|
+
All hook observers must complete before the operation continues. For example, a document won't be inserted until all `preInsertHook` observers have finished executing.
|
|
10
|
+
|
|
11
|
+
### Mutable vs. non-mutable
|
|
12
|
+
|
|
13
|
+
Hooks can be either **mutable** or **immutable**:
|
|
14
|
+
|
|
15
|
+
- **Immutable**: the _default_ behaviour, observers receive a deep copy of any arguments to ensure that the original data is read-only and prevent unintended modifications. By default, observers are run in parallel (at the same time).
|
|
16
|
+
- **Mutable**: hooks allow modification of param data, and run observers in series (one after another) to ensure modifications are applied in order.
|
|
17
|
+
|
|
18
|
+
## Basic usage
|
|
19
|
+
|
|
20
|
+
### Creating hooks
|
|
21
|
+
|
|
22
|
+
```javascript
|
|
23
|
+
import { Hook } from 'adapt-authoring-core'
|
|
24
|
+
|
|
25
|
+
class MyModule extends AbstractModule {
|
|
26
|
+
async init () {
|
|
27
|
+
// param data will be read only, observers will be run in parallel
|
|
28
|
+
this.myBasicHook = new Hook()
|
|
29
|
+
|
|
30
|
+
// allows param data to be modified, observers run in series
|
|
31
|
+
this.myMutableHook = new Hook({ mutable: true })
|
|
32
|
+
|
|
33
|
+
// force observers to run in series
|
|
34
|
+
this.mySeriesHook = new Hook({ type: Hook.Types.Series })
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async doSomething () {
|
|
38
|
+
// Invoke the hook, passing any relevant data
|
|
39
|
+
await this.myBasicHook.invoke(someData)
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Listening to a hook
|
|
45
|
+
|
|
46
|
+
Use `tap()` to register an observer function.
|
|
47
|
+
|
|
48
|
+
Listeners can be `async`, and a second `scope` parameter can be passed to bind `this`:
|
|
49
|
+
|
|
50
|
+
```javascript
|
|
51
|
+
const content = await this.app.waitForModule('content')
|
|
52
|
+
|
|
53
|
+
content.preInsertHook.tap(async data => {
|
|
54
|
+
data.createdAt = new Date()
|
|
55
|
+
}, this)
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Removing an observer
|
|
59
|
+
|
|
60
|
+
Use `untap()` to remove an observer:
|
|
61
|
+
|
|
62
|
+
```javascript
|
|
63
|
+
const observer = data => console.log(data)
|
|
64
|
+
content.preInsertHook.tap(observer)
|
|
65
|
+
|
|
66
|
+
// Later...
|
|
67
|
+
content.preInsertHook.untap(observer)
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Waiting for a hook
|
|
71
|
+
|
|
72
|
+
Use `onInvoke()` to get a promise that resolves when the hook is next invoked:
|
|
73
|
+
|
|
74
|
+
```javascript
|
|
75
|
+
await server.listeningHook.onInvoke()
|
|
76
|
+
console.log('Server is now listening')
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Error handling
|
|
80
|
+
|
|
81
|
+
If an observer throws an error, the hook stops executing and the error propagates to the caller. For mutable hooks running in series, any observers after the failing one won't be called.
|
|
82
|
+
|
|
83
|
+
```javascript
|
|
84
|
+
content.preInsertHook.tap(data => {
|
|
85
|
+
if (!data.title) {
|
|
86
|
+
throw new Error('Title is required')
|
|
87
|
+
}
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
try {
|
|
91
|
+
await content.insert({ body: 'No title here' })
|
|
92
|
+
} catch (e) {
|
|
93
|
+
console.log(e.message) // 'Title is required'
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Best practices
|
|
98
|
+
|
|
99
|
+
1. **Keep observers focused** — Each observer should do one thing well
|
|
100
|
+
2. **Handle errors gracefully** — Don't let one observer break the entire flow (unless intended)
|
|
101
|
+
3. **Avoid side effects in non-mutable hooks** — They receive copies of data, so modifications won't persist
|
|
102
|
+
4. **Use descriptive names** — Try to name your hooks clearly, and try to follow established patterns (see below for examples)
|
|
103
|
+
6. **Consider execution order** — For mutable hooks, observers run in the order they were registered. Keep this in mind both as the hook creator, and as the hook observer.
|
|
104
|
+
|
|
105
|
+
## Common hooks
|
|
106
|
+
|
|
107
|
+
Below are some commonly used hooks, which you may find useful.
|
|
108
|
+
|
|
109
|
+
| Module | Hook | Description | Parameters | Mutable |
|
|
110
|
+
| ------ | ---- | ----------- | ---------- | :-----: |
|
|
111
|
+
| AbstractModule | `readyHook` | Module has initialised | | No |
|
|
112
|
+
| AbstractApiModule | `requestHook` | API request received | `(req)` | Yes |
|
|
113
|
+
| AbstractApiModule | `preInsertHook` | Before document insert | `(data, options, mongoOptions)` | Yes |
|
|
114
|
+
| AbstractApiModule | `postInsertHook` | After document insert | `(doc)` | No |
|
|
115
|
+
| AbstractApiModule | `preUpdateHook` | Before document update | `(originalDoc, newData, options, mongoOptions)` | Yes |
|
|
116
|
+
| AbstractApiModule | `postUpdateHook` | After document update | `(originalDoc, updatedDoc)` | No |
|
|
117
|
+
| AbstractApiModule | `preDeleteHook` | Before document delete | `(doc, options, mongoOptions)` | No |
|
|
118
|
+
| AbstractApiModule | `postDeleteHook` | After document delete | `(doc)` | No |
|
|
119
|
+
| AbstractApiModule | `accessCheckHook` | Check document access | `(req, doc)` | No |
|
|
120
|
+
| AdaptFrameworkBuild | `preBuildHook` | Before course build starts | | Yes |
|
|
121
|
+
| AdaptFrameworkBuild | `postBuildHook` | After course build completes | | Yes |
|
|
122
|
+
| AdaptFrameworkImport | `preImportHook` | Before course import starts | | No |
|
|
123
|
+
| AdaptFrameworkImport | `postImportHook` | After course import completes | | No |
|
|
124
|
+
|
|
125
|
+
## Practical examples
|
|
126
|
+
|
|
127
|
+
### Adding timestamps
|
|
128
|
+
|
|
129
|
+
```javascript
|
|
130
|
+
async init () {
|
|
131
|
+
await super.init()
|
|
132
|
+
const content = await this.app.waitForModule('content')
|
|
133
|
+
|
|
134
|
+
content.preInsertHook.tap(data => {
|
|
135
|
+
data.createdAt = new Date()
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
content.preUpdateHook.tap((original, newData) => {
|
|
139
|
+
newData.updatedAt = new Date()
|
|
140
|
+
})
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### Enforcing data format
|
|
145
|
+
|
|
146
|
+
```javascript
|
|
147
|
+
async init () {
|
|
148
|
+
await super.init()
|
|
149
|
+
|
|
150
|
+
this.preInsertHook.tap(this.forceLowerCaseEmail)
|
|
151
|
+
this.preUpdateHook.tap(this.forceLowerCaseEmail)
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
forceLowerCaseEmail (data) {
|
|
155
|
+
if (data.email) {
|
|
156
|
+
data.email = data.email.toLowerCase()
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### Access control
|
|
162
|
+
|
|
163
|
+
```javascript
|
|
164
|
+
async init () {
|
|
165
|
+
await super.init()
|
|
166
|
+
const content = await this.app.waitForModule('content')
|
|
167
|
+
|
|
168
|
+
content.accessCheckHook.tap((req, doc) => {
|
|
169
|
+
// Only allow access to own documents
|
|
170
|
+
if (doc.createdBy !== req.auth.user._id.toString()) {
|
|
171
|
+
throw this.app.errors.UNAUTHORISED
|
|
172
|
+
}
|
|
173
|
+
})
|
|
174
|
+
}
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
### Cascading deletes
|
|
178
|
+
|
|
179
|
+
```javascript
|
|
180
|
+
async init () {
|
|
181
|
+
await super.init()
|
|
182
|
+
const assets = await this.app.waitForModule('assets')
|
|
183
|
+
|
|
184
|
+
assets.preDeleteHook.tap(async doc => {
|
|
185
|
+
// Remove all references to this asset
|
|
186
|
+
await this.removeAssetReferences(doc._id)
|
|
187
|
+
})
|
|
188
|
+
}
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### Registering schemas
|
|
192
|
+
|
|
193
|
+
```javascript
|
|
194
|
+
async init () {
|
|
195
|
+
await super.init()
|
|
196
|
+
const jsonschema = await this.app.waitForModule('jsonschema')
|
|
197
|
+
|
|
198
|
+
jsonschema.registerSchemasHook.tap(async () => {
|
|
199
|
+
await jsonschema.registerSchema('/path/to/schema.json')
|
|
200
|
+
})
|
|
201
|
+
}
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
### Waiting for server startup
|
|
205
|
+
|
|
206
|
+
```javascript
|
|
207
|
+
async init () {
|
|
208
|
+
await super.init()
|
|
209
|
+
const server = await this.app.waitForModule('server')
|
|
210
|
+
|
|
211
|
+
await server.listeningHook.onInvoke()
|
|
212
|
+
this.log('info', 'Server is ready, starting background tasks')
|
|
213
|
+
}
|
|
214
|
+
```
|
|
@@ -1,45 +1,48 @@
|
|
|
1
|
-
import fs from 'fs-extra'
|
|
2
|
-
import { parse } from 'comment-parser'
|
|
1
|
+
import fs from 'fs-extra'
|
|
2
|
+
import { parse } from 'comment-parser'
|
|
3
3
|
|
|
4
4
|
export default class BinScripts {
|
|
5
|
-
async run() {
|
|
6
|
-
this.manualFile = 'binscripts.md'
|
|
5
|
+
async run () {
|
|
6
|
+
this.manualFile = 'binscripts.md'
|
|
7
7
|
this.replace = { CONTENT: await this.generateMd() }
|
|
8
8
|
}
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
|
|
10
|
+
async generateMd () {
|
|
11
|
+
const allDeps = await Promise.all(Object.values(this.app.dependencies).map(this.processDep))
|
|
11
12
|
return allDeps
|
|
12
13
|
.reduce((m, d) => d ? m.concat(d) : m, [])
|
|
13
|
-
.sort((a,b) => a.name.localeCompare(b.name))
|
|
14
|
+
.sort((a, b) => a.name.localeCompare(b.name))
|
|
14
15
|
.map(d => this.dataToMd(d))
|
|
15
|
-
.join('\n')
|
|
16
|
+
.join('\n')
|
|
16
17
|
}
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
|
|
19
|
+
async processDep ({ name, bin, rootDir }) {
|
|
20
|
+
if (!bin || typeof bin === 'string') {
|
|
21
|
+
return
|
|
20
22
|
}
|
|
21
23
|
return await Promise.all(Object.entries(bin).map(async ([scriptName, filePath]) => {
|
|
22
|
-
const data = { name: scriptName, description: 'No description provided.', moduleName: name }
|
|
23
|
-
const contents = (await fs.readFile(`${rootDir}/${filePath}`)).toString()
|
|
24
|
-
const match = contents.match(/^#!\/usr\/bin\/env node(\s*)?\/\*\*([\s\S]+?)\*\//)
|
|
25
|
-
if(match) {
|
|
26
|
-
const [{ description, tags }] = parse(match[0])
|
|
27
|
-
const params = tags.reduce((m,t) => {
|
|
28
|
-
if(t.tag === 'param') m.push({ name: t.name, description: t.description })
|
|
29
|
-
return m
|
|
30
|
-
}, [])
|
|
31
|
-
data.description = description
|
|
32
|
-
if(params.length) data.params = params
|
|
24
|
+
const data = { name: scriptName, description: 'No description provided.', moduleName: name }
|
|
25
|
+
const contents = (await fs.readFile(`${rootDir}/${filePath}`)).toString()
|
|
26
|
+
const match = contents.match(/^#!\/usr\/bin\/env node(\s*)?\/\*\*([\s\S]+?)\*\//)
|
|
27
|
+
if (match) {
|
|
28
|
+
const [{ description, tags }] = parse(match[0])
|
|
29
|
+
const params = tags.reduce((m, t) => {
|
|
30
|
+
if (t.tag === 'param') m.push({ name: t.name, description: t.description })
|
|
31
|
+
return m
|
|
32
|
+
}, [])
|
|
33
|
+
data.description = description
|
|
34
|
+
if (params.length) data.params = params
|
|
33
35
|
}
|
|
34
|
-
return data
|
|
35
|
-
}))
|
|
36
|
+
return data
|
|
37
|
+
}))
|
|
36
38
|
}
|
|
37
|
-
|
|
39
|
+
|
|
40
|
+
dataToMd ({ name, description, moduleName, params }) {
|
|
38
41
|
let content = `<h2 class="script" id="${name}">${name} <span class="module">from ${moduleName}</span></h2>`
|
|
39
|
-
content += `<div class="details"><p class="description">${description}</p
|
|
40
|
-
if(params) {
|
|
41
|
-
content += `<p><b>Params</b><ul>${params.reduce((s,p) => `${s}<li><code>${p.name}</code> ${p.description}</li>`, '')}</ul></p
|
|
42
|
+
content += `<div class="details"><p class="description">${description}</p>`
|
|
43
|
+
if (params) {
|
|
44
|
+
content += `<p><b>Params</b><ul>${params.reduce((s, p) => `${s}<li><code>${p.name}</code> ${p.description}</li>`, '')}</ul></p>`
|
|
42
45
|
}
|
|
43
|
-
return content
|
|
46
|
+
return content
|
|
44
47
|
}
|
|
45
|
-
}
|
|
48
|
+
}
|
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
export default class CoreModules {
|
|
2
|
-
async run() {
|
|
3
|
-
this.manualFile = 'coremodules.md'
|
|
2
|
+
async run () {
|
|
3
|
+
this.manualFile = 'coremodules.md'
|
|
4
4
|
this.replace = {
|
|
5
5
|
VERSION: this.app.pkg.version,
|
|
6
6
|
MODULES: this.generateMd()
|
|
7
|
-
}
|
|
7
|
+
}
|
|
8
8
|
}
|
|
9
|
-
|
|
9
|
+
|
|
10
|
+
generateMd () {
|
|
10
11
|
return Object.keys(this.app.dependencies).sort().reduce((s, name) => {
|
|
11
|
-
const { version, description, homepage } = this.app.dependencies[name]
|
|
12
|
-
return s
|
|
13
|
-
}, '| Name | Version | Description |\n| - | :-: | - |')
|
|
12
|
+
const { version, description, homepage } = this.app.dependencies[name]
|
|
13
|
+
return `${s}\n| ${homepage ? `[${name}](${homepage})` : name} | ${version} | ${description} |`
|
|
14
|
+
}, '| Name | Version | Description |\n| - | :-: | - |')
|
|
14
15
|
}
|
|
15
|
-
}
|
|
16
|
+
}
|
|
@@ -83,17 +83,19 @@ export default class Licensing {
|
|
|
83
83
|
|
|
84
84
|
Object.keys(this.licenses)
|
|
85
85
|
.sort()
|
|
86
|
-
.forEach(l =>
|
|
86
|
+
.forEach(l => {
|
|
87
|
+
md += `| ${l} | ${this.licenses[l].count} |\n`
|
|
88
|
+
})
|
|
87
89
|
|
|
88
90
|
return md
|
|
89
91
|
}
|
|
90
92
|
|
|
91
93
|
async generateLicenseDetailsMd () {
|
|
92
94
|
let md = ''
|
|
93
|
-
Object.entries(this.licenses).forEach(([key, { name,
|
|
95
|
+
Object.entries(this.licenses).forEach(([key, { name, spdxId, description, body, permissions }]) => {
|
|
94
96
|
if (!name) return
|
|
95
97
|
md += '<details>\n'
|
|
96
|
-
md += `<summary>${name} (${
|
|
98
|
+
md += `<summary>${name} (${spdxId})</summary>\n`
|
|
97
99
|
md += `<p>${description}</p>\n`
|
|
98
100
|
md += `<p>This license allows the following:\n<ul>${permissions.map(p => `<li>${this.permissionsMap(p)}</li>`).join('\n')}</ul></p>\n`
|
|
99
101
|
md += '<p>The original license text is as follows:</p>\n'
|
|
@@ -113,7 +115,9 @@ export default class Licensing {
|
|
|
113
115
|
|
|
114
116
|
async generateMd () {
|
|
115
117
|
let md = '<tr><th>Name</th><th>Version</th><th>License</th><th>Description</th></tr>\n'
|
|
116
|
-
this.dependencies.forEach(pkg =>
|
|
118
|
+
this.dependencies.forEach(pkg => {
|
|
119
|
+
md += `<tr><td>${pkg.homepage ? `<a href="${pkg.homepage}" target="_blank">${pkg.name}</a>` : pkg.name}</td><td>${pkg.version}</td><td>${pkg.license}</td><td>${pkg.description}</tr>\n`
|
|
120
|
+
})
|
|
117
121
|
return `<details>\n<summary>Module dependency list</summary>\n<table>${md}</table>\n</details>`
|
|
118
122
|
}
|
|
119
123
|
}
|
package/lib/App.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import AbstractModule from './AbstractModule.js'
|
|
2
2
|
import DependencyLoader from './DependencyLoader.js'
|
|
3
|
-
import { fileURLToPath } from 'url'
|
|
4
3
|
import fs from 'fs'
|
|
5
4
|
import path from 'path'
|
|
6
5
|
import Utils from './Utils.js'
|
|
@@ -95,7 +94,7 @@ class App extends AbstractModule {
|
|
|
95
94
|
branch: gitHead.split('/').pop(),
|
|
96
95
|
commit: fs.readFileSync(path.join(gitRoot, gitHead.split(': ')[1]), 'utf8').trim()
|
|
97
96
|
}
|
|
98
|
-
} catch(e) {
|
|
97
|
+
} catch (e) {
|
|
99
98
|
return {}
|
|
100
99
|
}
|
|
101
100
|
}
|
package/lib/Utils.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "adapt-authoring-core",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.1",
|
|
4
4
|
"description": "A bundle of reusable 'core' functionality",
|
|
5
5
|
"homepage": "https://github.com/adapt-security/adapt-authoring-core",
|
|
6
6
|
"license": "GPL-3.0",
|
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
"main": "index.js",
|
|
9
9
|
"repository": "github:adapt-security/adapt-authoring-core",
|
|
10
10
|
"dependencies": {
|
|
11
|
-
"fs-extra": "11.3.
|
|
12
|
-
"glob": "^
|
|
11
|
+
"fs-extra": "11.3.3",
|
|
12
|
+
"glob": "^13.0.0",
|
|
13
13
|
"lodash": "^4.17.21",
|
|
14
14
|
"minimist": "^1.2.8"
|
|
15
15
|
},
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
name: Add labelled PRs to project
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
pull_request:
|
|
5
|
-
types: [ labeled ]
|
|
6
|
-
|
|
7
|
-
jobs:
|
|
8
|
-
add-to-project:
|
|
9
|
-
if: ${{ github.event.label.name == 'dependencies' }}
|
|
10
|
-
name: Add to main project
|
|
11
|
-
runs-on: ubuntu-latest
|
|
12
|
-
steps:
|
|
13
|
-
- uses: actions/add-to-project@v0.1.0
|
|
14
|
-
with:
|
|
15
|
-
project-url: https://github.com/orgs/adapt-security/projects/5
|
|
16
|
-
github-token: ${{ secrets.PROJECTS_SECRET }}
|
package/docs/data-folder.md
DELETED
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
# The data folder
|
|
2
|
-
The data folder is used to store persistant application data and **should not in any circumstances be modified or removed**. Doing so will result in data loss and generally cause unexpected runtime issues.
|
|
3
|
-
|
|
4
|
-
As a module developer, you should store any data in here which should persist between app restarts. You can make use of the `isDirectory` schema keyword to make configuration simpler for end-users. See the [schemas page](schemas-introduction#isdirectory) for more information.
|
package/docs/temp-folder.md
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
# The temp folder
|
|
2
|
-
The `temp` folder (note: this path will vary depending on your local configuration) is, as the name suggests, a temporary store for files used at runtime by the application. These files can be related to any number of things such as file uploads, course builds, documentation builds and so on.
|
|
3
|
-
|
|
4
|
-
## Using the temp folder
|
|
5
|
-
As a module developer, it is recommended that you store any temporary files needed by your module in here. Please be aware however, that due to its non-permanent nature, any data stored in the temp folder has the potential to be removed at any time. We therefore recommend running relevant checks during the startup of your module, and reinitialise any missing files at that point.
|
|
6
|
-
|
|
7
|
-
It is safe to assume that the temp folder will *not* be removed while the app is running; anyone choosing to do so should expect fatal errors.
|
|
8
|
-
|
|
9
|
-
## Removing the temp folder
|
|
10
|
-
It is perfectly safe to remove the temp folder. You may wish to do this in the event of low disk space, before an update, or even as a regular housekeeping task.
|
|
11
|
-
|
|
12
|
-
> If you do remove the temp folder, this must be done while the app is stopped, or your users may encounter errors while using the app. Restarting the application will ensure that any vital files are reinstated.
|