adapt-authoring-core 1.3.0 → 1.3.2

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,19 +1,15 @@
1
- name: Add to main project
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
- name: Add to main project
14
- runs-on: ubuntu-latest
15
- steps:
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 }}
@@ -6,12 +6,17 @@
6
6
  "sourceIndex": "docs/index-backend.md",
7
7
  "manualPages": {
8
8
  "binscripts.md": "reference",
9
+ "contributing.md": "contributing",
9
10
  "coremodules.md": "reference",
10
- "data-folder.md": "basics",
11
- "customising.md": "advanced",
11
+ "customising.md": "development",
12
+ "folder-structure.md": "getting-started",
13
+ "git.md": "contributing",
14
+ "hooks.md": "concepts",
12
15
  "licensing.md": "reference",
13
- "temp-folder.md": "basics",
14
- "writing-a-module.md": "basics",
16
+ "peer-review.md": "contributing",
17
+ "releasing.md": "contributing",
18
+ "run.md": "getting-started",
19
+ "writing-a-module.md": "development",
15
20
  "writing-core-code.md": "contributing"
16
21
  },
17
22
  "manualPlugins": [
@@ -0,0 +1,54 @@
1
+ # Contributing to the project
2
+
3
+ First of all, thank you for showing interest in the Adapt project! We heartily welcome any contribution however big or small. This page outlines a few ways you can get involved.
4
+
5
+ ## 1. Explore the community
6
+
7
+ The Adapt project has a thriving, friendly community who have an incredible knowledge of the inner-workings of Adapt - make as much use of this as you can.
8
+
9
+ ### Say hello
10
+
11
+ If this is your first time contributing, please drop by our [general_chat](https://gitter.im/adaptlearning/general_chat) stream on Gitter and introduce yourself (you'll need a GitHub account to do this) - we love seeing new faces!
12
+
13
+ For chat specifically related to the authoring tool, the [adapt-authoring room](https://gitter.im/adaptlearning/adapt-authoring) is the place to go.
14
+
15
+ ### Head to the community site
16
+
17
+ The community site acts as the project hub; first and foremost providing you with links to all corners of the Adapt project, such as the issue tracker, and source code.
18
+
19
+ At the heart of the community site are the forums, and one of the best ways you can contribute to the project is to share your knowledge of Adapt in here. The only requirement is that you like a chat!
20
+
21
+
22
+ ## 2. Find something to work on
23
+
24
+ The next step to getting involved is to find something to actually work on. Based on your skills and interests, there are a number of different areas that you can help out with.
25
+
26
+ ### Report issues
27
+
28
+ One of the easiest ways to make a meaningful contribution to the project is to submit any bugs you find to the relevant repo:
29
+ - [Adapt Framework](https://github.com/adaptlearning/adapt_framework/issues)
30
+ - [Adapt authoring tool](https://github.com/adaptlearning/adapt-authoring/issues)
31
+
32
+ If you think you've found a bug, check out our guide on [reporting issues](https://github.com/adaptlearning/adapt_framework/wiki/Bugs-and-features), which will give you more information on verifying your bug, as well as what to report, and where.
33
+
34
+ If you can fix a bug you've reported, even better! Check out the next section on what to do in that case.
35
+
36
+ ### Fix issues
37
+
38
+ If you're looking to get involved as a developer, fixing existing issues is a good place to start.
39
+
40
+ We've written a [guide to contributing code](writing-core-code) which will give you more information on finding a bug to work on, how to go about fixing that bug, and what to do once you've written a patch.
41
+
42
+ ### Contribute code
43
+
44
+ If you've been working with Adapt for a while, and are comfortable working with the framework on a larger scale, you may want to contribute to a larger feature-request patch (you can filter these in the issues page of the relevant repo), or [write your own module](writing-a-module).
45
+
46
+ Have a look at our [page dedicated to code contribution](https://github.com/adaptlearning/adapt_framework/wiki/Contributing-code) for more.
47
+
48
+ ### Write documentation
49
+
50
+ If you're a keen writer, we're always looking for more hands to help out writing documentation (no technical knowledge necessary). See our [documentation guide](https://github.com/adaptlearning/adapt_framework/wiki/Writing-documentation) for more.
51
+
52
+ ## 3. Repeat!
53
+
54
+ Hopefully, you've been bitten by the open-source bug by now and want to contribute more. We always look forward to community contribution, no matter how small :grinning:
@@ -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/git.md ADDED
@@ -0,0 +1,41 @@
1
+ # Git process
2
+
3
+ > This article assumes that you understand the [basic concepts of the git version control system](https://help.github.com/articles/good-resources-for-learning-git-and-github/)._
4
+
5
+ ### Overview
6
+
7
+ The authoring tool core repository doesn't contain any code
8
+
9
+ We organise the branches in our repos in a similar way to standard [git flow](https://datasift.github.io/gitflow/IntroducingGitFlow.html), with a few alterations.
10
+
11
+ ### Branching
12
+
13
+ We use the following branches:
14
+
15
+ Name | Description | Persisting
16
+ ---- | ----------- | ----------
17
+ `master` | Contains the stable, released code. | yes
18
+ `release/VERSION_NAME`<br/>*(e.g. `release/v1.0`)* | A release candidate branch. Contains **development** code, and should not be considered stable. Use this code at your own risk! | no
19
+ `issue/TICKET_NAME` <br/>*(e.g. `issue/1024`)* | A self-contained bug-fix/feature. Should be named after a corresponding issue ID. Finished changes should be submitted as a pull request. | no
20
+
21
+ We also apply the following rules:
22
+
23
+ * The `master` branch is the only persisting branch. **All other branches should be deleted post-merge**.
24
+ * The `master` branch contains only thoroughly tested code, and should only ever merge code from a `release` branch.
25
+ * Any `issue` branches should be submitted as a PR to the current `release` branch (**NOT `master` -- unlike standard git flow, we don't allow hotfixes directly into `master`**).
26
+
27
+ ### Gearing up for release
28
+
29
+ We go through the following schedule prior to making a release:
30
+
31
+ 1. The core development team assign a bunch of issues/features to the relevant release.
32
+ 2. A `release` branch is created from the master branch.
33
+ 3. The coders are let loose :dizzy_face::wrench:, and submit their additions as PRs using the [documented process](peer-review).
34
+ 4. Once all work has been done, we call a code freeze :snowflake: to avoid any scope-creep (i.e. only allow bug fixes from this point).
35
+ 5. The release branch goes through our testing process.
36
+ 6. Any bugs found are fixed :innocent:.
37
+ 7. Steps 5. and 6. are :repeat: as necessary.
38
+
39
+ ### Releasing
40
+
41
+ Once the release code has been tested, we merge the release branch into `master`, tag the `master` branch with the release number, and **party**! :tada::balloon::tropical_drink:
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
+ ```
@@ -0,0 +1,51 @@
1
+ # Peer code review
2
+
3
+ All code must go through a peer-approval process before it is merged with any of the collaborator-maintained codebases. This is to ensure that it firstly 'scratches' the intended 'itch', and complies to the guidelines set out by the project.
4
+
5
+ ## The Process
6
+
7
+ When code is ready for review, the below process is followed:
8
+
9
+ 1. Code is submitted for review as a pull request.
10
+ 1. Code is then reviewed by peers using GitHub's [pull-request review](https://help.github.com/articles/about-pull-request-reviews/) feature to leave comments where necessary and give an approval/rejection.
11
+ 1. Code is amended if needed until it satisfies the required number of approvals.
12
+ 1. Provided all unit tests are passing, code is merged.
13
+
14
+ ## The Rules
15
+
16
+ The reviewing process is governed by these rules:
17
+
18
+ * The developer who submits the PR is not allowed to submit a review.
19
+ * If a commit is added to the PR, any previously given reviews are invalidated. Everyone must re-post their verdicts after reviewing the commit.
20
+ * Any new functionality/feature requests uncovered during review of a PR must be separated into new issues/PRs, unless directly related.
21
+ * **For code to be merged, a minimum of three approvals is required. Of these, at least two must have come from a core team member. In the interest of impartiality, it is generally not advised to get all three approvals from a single collaborating company.**
22
+ * The person who merges the PR must also submit their review prior to merging **if their vote is required**. Merging a PR which already has enough approvals does not require the review of the merger themselves.
23
+ * Any review rejections must be accompanied by an adequate explanation of why the PR should not be merged. Without this, the review will be disregarded.
24
+ * Any concerns signalled by a 'rejected' review warrant careful consideration, but no reviewer has veto rights. If the PR receives enough approvals, it can still be merged (provided the other rules outlined on this page are followed).
25
+ 1. Concerns will be resolved in conversation between the submitter of the PR and the reviewer who rejected the PR. When/if satisfied, the reviewer will change verdict to an approval.
26
+ 2. At an impasse, a course of action will be decided by the core development team.
27
+
28
+ ## The Verdict
29
+
30
+ There are two 'statuses' which can be applied to submitted PRs: **approve** and **request changes**. See below for a quick checklist for each:
31
+
32
+ ### Request changes.
33
+ A core developer may reject a PR because any (or all) of the following:
34
+
35
+ - The code fails testing.
36
+ - The code does not meet Adapt standards.
37
+ - The code negatively impacts the application.
38
+ - There are unresolved questions about the intent of the code.
39
+
40
+ ### Approved.
41
+ A core developer will approve an PR because of the following:
42
+
43
+ - The intentions of the submitter are understood.
44
+ - The implementation of the PR is accepted.
45
+ - The submitted code complies with the code-style outlined in the project's [styleguide](https://github.com/adaptlearning/documentation/blob/master/01_cross_workstream/style_guide.md).
46
+ - The code passes all automated tests.
47
+ - No negative issues resulting from the proposed changes are forseen.
48
+
49
+ ## References
50
+
51
+ [SmartBear: 11 Best Practices for Peer Code Review](http://smartbear.com/smartbear/media/pdfs/wp-cc-11-best-practices-of-peer-code-review.pdf)
@@ -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
- async generateMd() {
10
- const allDeps = await Promise.all(Object.values(this.app.dependencies).map(this.processDep));
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
- async processDep({ name, bin, rootDir }) {
18
- if(!bin || typeof bin === 'string') {
19
- return;
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
- dataToMd({ name, description, moduleName, params }) {
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
- generateMd() {
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 += `\n| ${homepage ? `[${name}](${homepage})` : name} | ${version} | ${description} |`;
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 => md += `| ${l} | ${this.licenses[l].count} |\n`)
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, spdx_id, description, body, permissions }]) => {
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} (${spdx_id})</summary>\n`
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 => 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`)
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
  }
@@ -0,0 +1,56 @@
1
+ # Releasing
2
+
3
+ This guide explains how releases are managed for the Adapt authoring tool.
4
+
5
+ ## Overview
6
+
7
+ The project uses two release mechanisms:
8
+
9
+ - **Module releases** — Automated via semantic-release when changes are merged to master
10
+ - **Main repository releases** — Manual, allowing for thorough testing between releases
11
+
12
+ ## Module releases
13
+
14
+ Individual modules (e.g., `adapt-authoring-core`, `adapt-authoring-auth`) are released automatically using [semantic-release](https://semantic-release.gitbook.io/).
15
+
16
+ ### How it works
17
+
18
+ When a pull request is merged to the `master` branch, the release workflow:
19
+
20
+ 1. Analyses commit messages to determine the release type
21
+ 2. Bumps the version in `package.json`
22
+ 3. Generates release notes from commit messages
23
+ 4. Creates a GitHub release
24
+ 5. Publishes the package to npm (via [trusted publishing](https://docs.npmjs.com/generating-provenance-statements))
25
+
26
+ ### Version numbers
27
+
28
+ Releases follow [semantic versioning](https://semver.org/) (major.minor.patch). The version bump is determined by commit message prefixes:
29
+
30
+ | Prefix | Release type | Example |
31
+ | ------ | ------------ | ------- |
32
+ | `Fix:` | Patch (0.0.x) | Bug fixes |
33
+ | `Update:` | Minor (0.x.0) | New features, backwards-compatible changes |
34
+ | `Breaking:` | Major (x.0.0) | Breaking changes |
35
+ | `Docs:`, `Chore:` | No release | Documentation, maintenance |
36
+
37
+ See [writing core code](writing-core-code.md) for detailed commit message guidelines.
38
+
39
+ ### Configuration
40
+
41
+ Each module's release behaviour is configured in `package.json`, with the GitHub Actions workflow defined in `.github/workflows/releases.yml`.
42
+
43
+ ## Main repository releases
44
+
45
+ The main `adapt-authoring` repository is released manually. This allows for a comprehensive testing cycle before each release.
46
+
47
+ ### Release process
48
+
49
+ 1. **Update dependencies** — Bump all `adapt-authoring-*` modules to their latest versions
50
+ 2. **Testing** — Run the full test suite and perform manual testing
51
+ 3. **Version bump** — Update the version in `package.json`
52
+ 4. **Tag and release** — Create a git tag and GitHub release with release notes
53
+
54
+ ### Permissions
55
+
56
+ Only users with collaborator status on the repository can publish releases.
package/docs/run.md ADDED
@@ -0,0 +1,12 @@
1
+ # Running the application
2
+ > You can find developer-specific instructions in the [developer install instructions](install-dev).
3
+
4
+ To run the application, simply use the npm start script:
5
+ ```
6
+ npm start
7
+ ```
8
+
9
+ > Advanced users may also use the `NODE_ENV` environment variable to specify which configuration file is loaded with the application. This value is `production` by default.
10
+ > You can read more about configuring your environment on [this page](configure-environment).
11
+
12
+ The application will take a while to start up on the first boot, as it has various initialisation tasks to perform. Subsequent boots will be much quicker.
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
@@ -63,7 +63,6 @@ class Utils {
63
63
  error = e
64
64
  })
65
65
  task.on('close', exitCode => {
66
- console.log(output);
67
66
  exitCode !== 0 ? reject(App.instance.errors.SPAWN.setData({ error: error ?? output })) : resolve(output)
68
67
  })
69
68
  })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "adapt-authoring-core",
3
- "version": "1.3.0",
3
+ "version": "1.3.2",
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.1",
12
- "glob": "^11.0.0",
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 }}
@@ -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.
@@ -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.