adapt-authoring-core 2.1.4 → 2.2.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.
@@ -88,51 +88,51 @@ We use [semantic-release](https://semantic-release.gitbook.io/) to automate rele
88
88
  | Prefix | Release type | Use for |
89
89
  |--------|--------------|---------|
90
90
  | `Fix:` | Patch (0.0.x) | Bug fixes |
91
- | `Update:` | Minor (0.x.0) | New features, backwards-compatible changes |
91
+ | `Update:` | Minor (0.x.0) | Backwards-compatible enhancements to existing features |
92
+ | `New:` | Minor (0.x.0) | New features |
92
93
  | `Breaking:` | Major (x.0.0) | Breaking changes |
93
94
  | `Docs:` | No release | Documentation only |
94
- | `Chore:` | No release | Maintenance, refactoring |
95
+ | `Build:` | No release | Build process changes |
96
+ | `Upgrade:` | Varies | Dependency upgrades |
97
+ | `Chore:` | No release | Maintenance, refactoring, tests |
95
98
 
96
99
  ### Format
97
100
 
98
101
  ```
99
- Prefix: Short description
102
+ Prefix: Short description (fixes #1234)
103
+ ```
100
104
 
101
- Longer explanation if needed. Wrap at 72 characters.
105
+ Use `(fixes #1234)` when the commit fully resolves an issue, or `(refs #1234)` for partial progress. Keep the first line under 72 characters.
102
106
 
103
- Closes #1234
104
- ```
107
+ Add a longer explanation on subsequent lines if needed:
105
108
 
106
- ### Examples
109
+ ```
110
+ Prefix: Short description (fixes #1234)
107
111
 
112
+ Longer explanation if needed. Wrap at 72 characters.
108
113
  ```
109
- Fix: Prevent crash when uploading empty file
110
114
 
111
- The upload handler now validates file size before processing,
112
- returning a 400 error for empty files.
115
+ ### Examples
113
116
 
114
- Closes #1234
115
117
  ```
116
-
118
+ Fix: Prevent crash when uploading empty file (fixes #1234)
117
119
  ```
118
- Update: Add bulk delete endpoint for assets
119
120
 
120
- Closes #5678
121
+ ```
122
+ Update: Add bulk delete endpoint for assets (fixes #5678)
121
123
  ```
122
124
 
123
125
  ```
124
- Breaking: Remove deprecated /api/v1 endpoints
126
+ Breaking: Remove deprecated /api/v1 endpoints (fixes #9012)
125
127
 
126
128
  The v1 API has been removed. All clients should migrate to /api.
127
-
128
- Closes #9012
129
129
  ```
130
130
 
131
131
  ### Tips
132
132
 
133
133
  - Use the imperative mood ("Add feature" not "Added feature")
134
134
  - Keep the first line under 72 characters
135
- - Reference the issue number with `Closes #1234` to auto-close it when merged
135
+ - Reference the issue in the first line with `(fixes #N)` or `(refs #N)`
136
136
  - For breaking changes, explain what users need to do to migrate
137
137
 
138
138
  ## Submitting a pull request
@@ -80,7 +80,7 @@ Command-line tools are found in the `bin` folder. Modules can also provide their
80
80
 
81
81
  ## `docs`
82
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.
83
+ The `docs` 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
84
 
85
85
  ## `conf`
86
86
 
package/docs/hooks.md CHANGED
@@ -12,7 +12,7 @@ All hook observers must complete before the operation continues. For example, a
12
12
 
13
13
  Hooks can be either **mutable** or **immutable**:
14
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).
15
+ - **Immutable**: the _default_ behaviour. Observers are run in parallel (at the same time). When running in series, observers receive a deep copy of arguments to prevent unintended modifications.
16
16
  - **Mutable**: hooks allow modification of param data, and run observers in series (one after another) to ensure modifications are applied in order.
17
17
 
18
18
  ## Basic usage
@@ -119,8 +119,8 @@ Below are some commonly used hooks, which you may find useful.
119
119
  | AbstractApiModule | `accessCheckHook` | Check document access | `(req, doc)` | No |
120
120
  | AdaptFrameworkBuild | `preBuildHook` | Before course build starts | | Yes |
121
121
  | AdaptFrameworkBuild | `postBuildHook` | After course build completes | | Yes |
122
- | AdaptFrameworkImport | `preImportHook` | Before course import starts | | No |
123
- | AdaptFrameworkImport | `postImportHook` | After course import completes | | No |
122
+ | AdaptFrameworkModule | `preImportHook` | Before course import starts | | Yes |
123
+ | AdaptFrameworkModule | `postImportHook` | After course import completes | | No |
124
124
 
125
125
  ## Practical examples
126
126
 
@@ -23,21 +23,19 @@ The below is what we recommend, and is the approach taken by the the core dev te
23
23
  | `conf` | Folder | All config files go here (in `.schema.json` format) |
24
24
  | `docs` | Folder | Documentation files go here (in `.md` format) |
25
25
  | `lib` | Folder | All `.js` code should go here |
26
- | `test` | Folder | Test scripts go here (in `*.spec.js`) |
26
+ | `tests` | Folder | Test scripts go here (in `*.spec.js`) |
27
27
  | `index.js` | File | Contains all of the exports for your module |
28
28
  | `adapt-authoring.json` | File | Adapt-specific metadata file used when initialising the app |
29
29
  | `package.json` | File | npm configuration file |
30
+ | `routes.json` | File | _(Optional)_ Declarative route definitions for modules that expose HTTP endpoints. See [Handling server requests](server-requests.md) and [Authentication and permissions](auth-permissions.md) for details. |
30
31
 
31
32
  ##### A note on exports:
32
- If your module needs to export more than just your main module class, you must make sure your module class uses the key `Module` in order to be loaded by DependencyLoader. For example:
33
-
34
- ```
35
- export default {
36
- Module: MyModuleClass,
37
- utils: MyUtilsClass,
38
- // ...other exports
39
- };
33
+ Your module class must be the **default export** this is what `DependencyLoader` imports. For additional exports, use named exports:
40
34
 
35
+ ```javascript
36
+ // index.js
37
+ export { default } from './lib/MyModule.js'
38
+ export { MyUtilsClass } from './lib/utils.js'
41
39
  ```
42
40
 
43
41
  ## 2. Set up your package.json
@@ -81,7 +79,7 @@ If you need to wait for another module to initialise before you can continue, th
81
79
  See below for an example:
82
80
 
83
81
  ```javascript
84
- const { AbstractModule } = require('adapt-authoring-core');
82
+ import { AbstractModule } from 'adapt-authoring-core'
85
83
 
86
84
  export default class MyModule extends AbstractModule {
87
85
  /** @override */
@@ -164,7 +164,7 @@ const fixtures = JSON.parse(readFileSync(join(__dirname, 'data', 'fixtures.json'
164
164
 
165
165
  ```json
166
166
  "scripts": {
167
- "test": "node --test tests/"
167
+ "test": "node --test 'tests/*.spec.js'"
168
168
  }
169
169
  ```
170
170
 
package/index.js CHANGED
@@ -2,4 +2,4 @@ export { default as AbstractModule } from './lib/AbstractModule.js'
2
2
  export { default as App } from './lib/App.js'
3
3
  export { default as DependencyLoader } from './lib/DependencyLoader.js'
4
4
  export { default as Hook } from './lib/Hook.js'
5
- export { metadataFileName, packageFileName, isObject, getArgs, spawn, readJson, writeJson, toBoolean, ensureDir, escapeRegExp, stringifyValues } from './lib/Utils.js'
5
+ export { metadataFileName, packageFileName, isObject, getArgs, spawn, readJson, writeJson, toBoolean, ensureDir, escapeRegExp, stringifyValues, loadDependencyFiles } from './lib/Utils.js'
package/lib/Utils.js CHANGED
@@ -8,3 +8,4 @@ export { toBoolean } from './utils/toBoolean.js'
8
8
  export { ensureDir } from './utils/ensureDir.js'
9
9
  export { escapeRegExp } from './utils/escapeRegExp.js'
10
10
  export { stringifyValues } from './utils/stringifyValues.js'
11
+ export { loadDependencyFiles } from './utils/loadDependencyFiles.js'
@@ -0,0 +1,25 @@
1
+ import fs from 'fs/promises'
2
+ import { glob } from 'glob'
3
+ import App from '../App.js'
4
+
5
+ /**
6
+ * Scans files matching a glob pattern across all module dependency directories.
7
+ * @param {string} pattern - Glob pattern to match files (e.g. 'errors/*.json')
8
+ * @param {Object} [options] - Options
9
+ * @param {boolean} [options.parse=false] - If true, reads and parses each matched file as JSON
10
+ * @param {Object} [options.dependencies] - Custom dependencies map (defaults to App.instance.dependencies)
11
+ * @returns {Promise<Object>} Map of module name to array of file paths (or parsed JSON objects if parse is true)
12
+ */
13
+ export async function loadDependencyFiles (pattern, options = {}) {
14
+ const { parse = false, dependencies = App.instance.dependencies } = options
15
+ const results = {}
16
+ await Promise.all(Object.values(dependencies).map(async dep => {
17
+ const files = await glob(pattern, { cwd: dep.rootDir, absolute: true })
18
+ if (!files.length) return
19
+ results[dep.name] = await Promise.all(files.map(async f => {
20
+ if (parse) return JSON.parse(await fs.readFile(f, 'utf8'))
21
+ return f
22
+ }))
23
+ }))
24
+ return results
25
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "adapt-authoring-core",
3
- "version": "2.1.4",
3
+ "version": "2.2.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",
@@ -0,0 +1,91 @@
1
+ import { describe, it } from 'node:test'
2
+ import assert from 'node:assert/strict'
3
+ import fs from 'fs/promises'
4
+ import path from 'path'
5
+ import os from 'os'
6
+
7
+ import { loadDependencyFiles } from '../lib/utils/loadDependencyFiles.js'
8
+
9
+ async function makeTempDir (suffix) {
10
+ return fs.mkdtemp(path.join(os.tmpdir(), `loadDepFiles-${suffix}-`))
11
+ }
12
+
13
+ describe('loadDependencyFiles()', () => {
14
+ it('should return file paths grouped by module name', async () => {
15
+ const dir = await makeTempDir('paths')
16
+ await fs.writeFile(path.join(dir, 'a.json'), '{"x":1}')
17
+ const deps = { mymod: { name: 'mymod', rootDir: dir } }
18
+ try {
19
+ const result = await loadDependencyFiles('*.json', { dependencies: deps })
20
+ assert.ok(result.mymod, 'should have entry for mymod')
21
+ assert.equal(result.mymod.length, 1)
22
+ assert.ok(result.mymod[0].endsWith('a.json'))
23
+ } finally {
24
+ await fs.rm(dir, { recursive: true })
25
+ }
26
+ })
27
+
28
+ it('should parse JSON when parse option is true', async () => {
29
+ const dir = await makeTempDir('parse')
30
+ await fs.writeFile(path.join(dir, 'b.json'), '{"key":"value"}')
31
+ const deps = { mymod: { name: 'mymod', rootDir: dir } }
32
+ try {
33
+ const result = await loadDependencyFiles('*.json', { parse: true, dependencies: deps })
34
+ assert.ok(result.mymod, 'should have entry for mymod')
35
+ assert.equal(result.mymod.length, 1)
36
+ assert.deepEqual(result.mymod[0], { key: 'value' })
37
+ } finally {
38
+ await fs.rm(dir, { recursive: true })
39
+ }
40
+ })
41
+
42
+ it('should return empty object when no files match', async () => {
43
+ const dir = await makeTempDir('nomatch')
44
+ const deps = { mymod: { name: 'mymod', rootDir: dir } }
45
+ try {
46
+ const result = await loadDependencyFiles('errors/*.json', { dependencies: deps })
47
+ assert.deepEqual(result, {})
48
+ } finally {
49
+ await fs.rm(dir, { recursive: true })
50
+ }
51
+ })
52
+
53
+ it('should group files by module name across multiple deps', async () => {
54
+ const dir1 = await makeTempDir('multi1')
55
+ const dir2 = await makeTempDir('multi2')
56
+ await fs.writeFile(path.join(dir1, 'x.json'), '"a"')
57
+ await fs.writeFile(path.join(dir2, 'y.json'), '"b"')
58
+ const deps = {
59
+ mod1: { name: 'mod1', rootDir: dir1 },
60
+ mod2: { name: 'mod2', rootDir: dir2 }
61
+ }
62
+ try {
63
+ const result = await loadDependencyFiles('*.json', { dependencies: deps })
64
+ assert.ok(result.mod1)
65
+ assert.ok(result.mod2)
66
+ assert.equal(result.mod1.length, 1)
67
+ assert.equal(result.mod2.length, 1)
68
+ } finally {
69
+ await fs.rm(dir1, { recursive: true })
70
+ await fs.rm(dir2, { recursive: true })
71
+ }
72
+ })
73
+
74
+ it('should not include modules with no matching files', async () => {
75
+ const dir1 = await makeTempDir('partial1')
76
+ const dir2 = await makeTempDir('partial2')
77
+ await fs.writeFile(path.join(dir1, 'match.json'), '{}')
78
+ const deps = {
79
+ mod1: { name: 'mod1', rootDir: dir1 },
80
+ mod2: { name: 'mod2', rootDir: dir2 }
81
+ }
82
+ try {
83
+ const result = await loadDependencyFiles('*.json', { dependencies: deps })
84
+ assert.ok(result.mod1)
85
+ assert.equal(result.mod2, undefined)
86
+ } finally {
87
+ await fs.rm(dir1, { recursive: true })
88
+ await fs.rm(dir2, { recursive: true })
89
+ }
90
+ })
91
+ })