adapt-project 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/releases.yml +32 -0
- package/.github/workflows/standardjs.yml +13 -0
- package/.github/workflows/tests.yml +13 -0
- package/README.md +168 -0
- package/eslint.config.js +10 -0
- package/index.js +13 -0
- package/lib/Data.js +185 -0
- package/lib/Framework.js +295 -0
- package/lib/JSONFile.js +104 -0
- package/lib/JSONFileItem.js +27 -0
- package/lib/Plugins.js +82 -0
- package/lib/Schemas.js +208 -0
- package/lib/Translate.js +495 -0
- package/lib/data/Language.js +301 -0
- package/lib/data/LanguageFile.js +35 -0
- package/lib/plugins/Plugin.js +143 -0
- package/lib/schema/ExtensionSchema.js +26 -0
- package/lib/schema/GlobalsSchema.js +65 -0
- package/lib/schema/ModelSchema.js +45 -0
- package/lib/schema/ModelSchemas.js +41 -0
- package/lib/schema/Schema.js +191 -0
- package/package.json +54 -0
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
on:
|
|
3
|
+
push:
|
|
4
|
+
branches:
|
|
5
|
+
- master
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
release:
|
|
9
|
+
name: Release
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
permissions:
|
|
12
|
+
contents: write # to be able to publish a GitHub release
|
|
13
|
+
issues: write # to be able to comment on released issues
|
|
14
|
+
pull-requests: write # to be able to comment on released pull requests
|
|
15
|
+
id-token: write # to enable use of OIDC for trusted publishing and npm provenance
|
|
16
|
+
steps:
|
|
17
|
+
- name: Checkout
|
|
18
|
+
uses: actions/checkout@v3
|
|
19
|
+
with:
|
|
20
|
+
fetch-depth: 0
|
|
21
|
+
- name: Setup Node.js
|
|
22
|
+
uses: actions/setup-node@v3
|
|
23
|
+
with:
|
|
24
|
+
node-version: 'lts/*'
|
|
25
|
+
- name: Update npm
|
|
26
|
+
run: npm install -g npm@latest
|
|
27
|
+
- name: Install dependencies
|
|
28
|
+
run: npm ci
|
|
29
|
+
- name: Release
|
|
30
|
+
env:
|
|
31
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
32
|
+
run: npx semantic-release
|
package/README.md
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
# adapt-project
|
|
2
|
+
|
|
3
|
+
Node.js library for managing Adapt Learning Framework projects — plugins, schemas, course data, and translations.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install adapt-project
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Overview
|
|
12
|
+
|
|
13
|
+
`adapt-project` provides a programmatic API for working with Adapt Framework project directories. It handles:
|
|
14
|
+
|
|
15
|
+
- **Course data** — Load and manage multilingual content structured as hierarchical JSON (`course > menu/page > article > block > component`)
|
|
16
|
+
- **Schemas** — Discover and validate content models using JSON schemas from plugins
|
|
17
|
+
- **Plugins** — Discover installed plugins from the `src/` directory and read their metadata
|
|
18
|
+
- **Translations** — Export translatable strings and import translated content (CSV, JSON, XLIFF)
|
|
19
|
+
|
|
20
|
+
## Quick Start
|
|
21
|
+
|
|
22
|
+
```js
|
|
23
|
+
const { Framework } = require('adapt-project');
|
|
24
|
+
|
|
25
|
+
const framework = new Framework({
|
|
26
|
+
rootPath: '/path/to/adapt-project'
|
|
27
|
+
}).load();
|
|
28
|
+
|
|
29
|
+
// Load course data
|
|
30
|
+
const data = framework.getData();
|
|
31
|
+
data.languages.forEach(lang => {
|
|
32
|
+
const course = lang.getCourseFileItem();
|
|
33
|
+
console.log(`${lang.name}: ${course.item._id}`);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
// Load schemas
|
|
37
|
+
const schemas = framework.getSchemas();
|
|
38
|
+
|
|
39
|
+
// Discover plugins
|
|
40
|
+
const plugins = framework.getPlugins();
|
|
41
|
+
|
|
42
|
+
// Export translations
|
|
43
|
+
const translate = framework.getTranslate({
|
|
44
|
+
masterLang: 'en',
|
|
45
|
+
format: 'csv'
|
|
46
|
+
});
|
|
47
|
+
await translate.export();
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## API
|
|
51
|
+
|
|
52
|
+
### `Framework`
|
|
53
|
+
|
|
54
|
+
Main entry point representing an Adapt Framework root directory.
|
|
55
|
+
|
|
56
|
+
```js
|
|
57
|
+
const framework = new Framework({
|
|
58
|
+
rootPath: process.cwd(), // Project root
|
|
59
|
+
outputPath: './build/', // Build output directory
|
|
60
|
+
sourcePath: './src/', // Source code directory
|
|
61
|
+
courseDir: 'course', // Course data subdirectory name
|
|
62
|
+
jsonext: 'json', // JSON file extension (json or txt)
|
|
63
|
+
trackingIdType: 'block', // Content type to assign tracking IDs to
|
|
64
|
+
useOutputData: false, // Read from build output instead of source
|
|
65
|
+
includedFilter: () => true,// Plugin filter function
|
|
66
|
+
log: console.log,
|
|
67
|
+
warn: console.warn
|
|
68
|
+
}).load();
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
| Method | Returns | Description |
|
|
72
|
+
|---|---|---|
|
|
73
|
+
| `load()` | `Framework` | Load the project's `package.json` |
|
|
74
|
+
| `getData()` | `Data` | Get course data instance |
|
|
75
|
+
| `getPlugins()` | `Plugins` | Discover and load all plugins |
|
|
76
|
+
| `getSchemas()` | `Schemas` | Load all plugin schemas |
|
|
77
|
+
| `getTranslate(options)` | `Translate` | Get translation import/export instance |
|
|
78
|
+
| `makeIncludeFilter()` | `function` | Build a plugin filter from `config.json` build includes/excludes |
|
|
79
|
+
| `applyGlobalsDefaults()` | `Framework` | Apply schema defaults to `_globals` in course data |
|
|
80
|
+
| `applyScreenSizeDefaults()` | `Framework` | Apply schema defaults to `screenSize` in config |
|
|
81
|
+
|
|
82
|
+
### `Data`
|
|
83
|
+
|
|
84
|
+
Manages the `course/` folder — config, languages, and all content items.
|
|
85
|
+
|
|
86
|
+
| Method | Returns | Description |
|
|
87
|
+
|---|---|---|
|
|
88
|
+
| `load()` | `Data` | Scan and load all language directories |
|
|
89
|
+
| `getLanguage(name)` | `Language` | Get a specific language by folder name |
|
|
90
|
+
| `getConfigFileItem()` | `JSONFileItem` | Get the `config.json` file item |
|
|
91
|
+
| `copyLanguage(from, to)` | `Language` | Duplicate a language folder |
|
|
92
|
+
| `checkIds()` | `Data` | Validate `_id` / `_parentId` structure across all languages |
|
|
93
|
+
| `addTrackingIds()` | `Data` | Auto-assign sequential `_trackingId` values |
|
|
94
|
+
| `removeTrackingIds()` | `Data` | Strip `_trackingId` from all items |
|
|
95
|
+
| `save()` | `Data` | Persist all changes to disk |
|
|
96
|
+
|
|
97
|
+
### `Translate`
|
|
98
|
+
|
|
99
|
+
Export and import translatable strings identified by schema annotations.
|
|
100
|
+
|
|
101
|
+
```js
|
|
102
|
+
const translate = framework.getTranslate({
|
|
103
|
+
masterLang: 'en',
|
|
104
|
+
targetLang: 'fr',
|
|
105
|
+
format: 'csv', // 'csv', 'json', or 'xlf'
|
|
106
|
+
csvDelimiter: ',',
|
|
107
|
+
shouldReplaceExisting: false
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
await translate.export(); // Export master language strings
|
|
111
|
+
await translate.import(); // Import translated strings into target language
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Supported formats:
|
|
115
|
+
- **CSV** — One file per content type, with auto-detected encoding and delimiter
|
|
116
|
+
- **JSON** — Single `export.json` with all translatable strings
|
|
117
|
+
- **XLIFF 1.2** — Single `source.xlf` file
|
|
118
|
+
|
|
119
|
+
## Content Hierarchy
|
|
120
|
+
|
|
121
|
+
Adapt course content follows this structure:
|
|
122
|
+
|
|
123
|
+
```
|
|
124
|
+
course/
|
|
125
|
+
config.json — Global configuration
|
|
126
|
+
en/ — Language folder
|
|
127
|
+
course.json — Course metadata and _globals
|
|
128
|
+
contentObjects.json — Menus and pages
|
|
129
|
+
articles.json — Articles within pages
|
|
130
|
+
blocks.json — Blocks within articles
|
|
131
|
+
components.json — Components within blocks
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Each content item has:
|
|
135
|
+
- `_id` — Unique identifier
|
|
136
|
+
- `_type` — Model type (`course`, `menu`, `page`, `article`, `block`, `component`)
|
|
137
|
+
- `_parentId` — Reference to parent item
|
|
138
|
+
- `_trackingId` — Sequential tracking identifier (auto-generated)
|
|
139
|
+
|
|
140
|
+
## Directory Layout
|
|
141
|
+
|
|
142
|
+
```
|
|
143
|
+
adapt-project/
|
|
144
|
+
index.js — Module exports
|
|
145
|
+
lib/
|
|
146
|
+
Framework.js — Main entry point
|
|
147
|
+
Data.js — Course data management
|
|
148
|
+
Schemas.js — Schema discovery and validation
|
|
149
|
+
Plugins.js — Plugin discovery
|
|
150
|
+
Translate.js — Translation export/import
|
|
151
|
+
JSONFile.js — JSON file I/O
|
|
152
|
+
JSONFileItem.js — JSON sub-item wrapper
|
|
153
|
+
data/
|
|
154
|
+
Language.js — Single language folder
|
|
155
|
+
LanguageFile.js — Single language file
|
|
156
|
+
plugins/
|
|
157
|
+
Plugin.js — Plugin representation
|
|
158
|
+
schema/
|
|
159
|
+
Schema.js — Base schema class
|
|
160
|
+
GlobalsSchema.js — Global config schema
|
|
161
|
+
ModelSchema.js — Content model schema
|
|
162
|
+
ExtensionSchema.js — Model extension schema
|
|
163
|
+
ModelSchemas.js — Schema collection
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
## License
|
|
167
|
+
|
|
168
|
+
GPL-3.0
|
package/eslint.config.js
ADDED
package/index.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import Framework from './lib/Framework.js'
|
|
2
|
+
import Data from './lib/Data.js'
|
|
3
|
+
import Plugins from './lib/Plugins.js'
|
|
4
|
+
import Schemas from './lib/Schemas.js'
|
|
5
|
+
import Translate from './lib/Translate.js'
|
|
6
|
+
|
|
7
|
+
export {
|
|
8
|
+
Framework,
|
|
9
|
+
Data,
|
|
10
|
+
Plugins,
|
|
11
|
+
Schemas,
|
|
12
|
+
Translate
|
|
13
|
+
}
|
package/lib/Data.js
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import path from 'path'
|
|
2
|
+
import fs from 'fs-extra'
|
|
3
|
+
import globs from 'globs'
|
|
4
|
+
import JSONFile from './JSONFile.js'
|
|
5
|
+
import Language from './data/Language.js'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @typedef {import('./Framework')} Framework
|
|
9
|
+
* @typedef {import('./JSONFileItem')} JSONFileItem
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* This class represents the course folder. It contains references to the config.json,
|
|
14
|
+
* all languages, each language file and subsequently each language file item.
|
|
15
|
+
* It is filename agnostic, except for config.[jsonext], such that there are no
|
|
16
|
+
* hard references to the other file names, allowing any filename to be used with the
|
|
17
|
+
* appropriate [jsonext] file extension (usually txt or json).
|
|
18
|
+
* It assumes all language files are located at course/[langName]/*.[jsonext] and
|
|
19
|
+
* the config file is located at course/config.[jsonext].
|
|
20
|
+
* It has _id and _parentId structure checking and _trackingId management included.
|
|
21
|
+
*/
|
|
22
|
+
class Data {
|
|
23
|
+
/**
|
|
24
|
+
* @param {Object} options
|
|
25
|
+
* @param {Framework} options.framework
|
|
26
|
+
* @param {string} options.sourcePath
|
|
27
|
+
* @param {string} options.courseDir
|
|
28
|
+
* @param {string} options.jsonext
|
|
29
|
+
* @param {string} options.trackingIdType
|
|
30
|
+
* @param {function} options.log
|
|
31
|
+
*/
|
|
32
|
+
constructor ({
|
|
33
|
+
framework = null,
|
|
34
|
+
sourcePath = null,
|
|
35
|
+
courseDir = null,
|
|
36
|
+
jsonext = 'json',
|
|
37
|
+
trackingIdType = 'block',
|
|
38
|
+
log = console.log
|
|
39
|
+
} = {}) {
|
|
40
|
+
/** @type {Framework} */
|
|
41
|
+
this.framework = framework
|
|
42
|
+
/** @type {string} */
|
|
43
|
+
this.sourcePath = sourcePath
|
|
44
|
+
/** @type {string} */
|
|
45
|
+
this.courseDir = courseDir
|
|
46
|
+
/** @type {string} */
|
|
47
|
+
this.jsonext = jsonext
|
|
48
|
+
/** @type {string} */
|
|
49
|
+
this.trackingIdType = trackingIdType
|
|
50
|
+
/** @type {function} */
|
|
51
|
+
this.log = log
|
|
52
|
+
/** @type {JSONFile} */
|
|
53
|
+
this.configFile = null
|
|
54
|
+
/** @type {[Language]} */
|
|
55
|
+
this.languages = null
|
|
56
|
+
/** @type {string} */
|
|
57
|
+
this.coursePath = path.join(this.sourcePath, this.courseDir).replace(/\\/g, '/')
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** @returns {Data} */
|
|
61
|
+
load () {
|
|
62
|
+
this.languages = globs.sync(path.join(this.coursePath, '*/')).map(languagePath => {
|
|
63
|
+
const language = new Language({
|
|
64
|
+
framework: this.framework,
|
|
65
|
+
languagePath,
|
|
66
|
+
courseDir: this.courseDir,
|
|
67
|
+
jsonext: this.jsonext,
|
|
68
|
+
trackingIdType: this.trackingIdType,
|
|
69
|
+
log: this.log
|
|
70
|
+
})
|
|
71
|
+
language.load()
|
|
72
|
+
return language
|
|
73
|
+
}).filter(lang => lang.isValid)
|
|
74
|
+
this.configFile = new JSONFile({
|
|
75
|
+
framework: this.framework,
|
|
76
|
+
path: path.join(this.coursePath, `config.${this.jsonext}`),
|
|
77
|
+
jsonext: this.jsonext
|
|
78
|
+
})
|
|
79
|
+
this.configFile.load()
|
|
80
|
+
return this
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** @type {boolean} */
|
|
84
|
+
get hasChanged () {
|
|
85
|
+
return this.languages.some(language => language.hasChanged)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** @type {[string]} */
|
|
89
|
+
get languageNames () {
|
|
90
|
+
return this.languages.map(language => language.name)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Fetch a Language instance by name.
|
|
95
|
+
* @param {string} name
|
|
96
|
+
* @returns {Language}
|
|
97
|
+
*/
|
|
98
|
+
getLanguage (name) {
|
|
99
|
+
const language = this.languages.find(language => language.name === name)
|
|
100
|
+
if (!language) {
|
|
101
|
+
const err = new Error(`Cannot find language '${name}'.`)
|
|
102
|
+
err.number = 10004
|
|
103
|
+
throw err
|
|
104
|
+
}
|
|
105
|
+
return language
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Returns a JSONFileItem representing the course/config.json file object.
|
|
110
|
+
* @returns {JSONFileItem}
|
|
111
|
+
* */
|
|
112
|
+
getConfigFileItem () {
|
|
113
|
+
return this.configFile.firstFileItem
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* @param {string} fromName
|
|
118
|
+
* @param {string} toName
|
|
119
|
+
* @param {boolean} replace
|
|
120
|
+
* @returns {Language}
|
|
121
|
+
*/
|
|
122
|
+
copyLanguage (fromName, toName, replace = false) {
|
|
123
|
+
const fromLang = this.getLanguage(fromName)
|
|
124
|
+
const newPath = (`${fromLang.rootPath}/${toName}/`).replace(/\\/g, '/')
|
|
125
|
+
|
|
126
|
+
if (this.languageNames.includes(toName) && !replace) {
|
|
127
|
+
const err = new Error(`Folder already exists. ${newPath}`)
|
|
128
|
+
err.number = 10003
|
|
129
|
+
throw err
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
let toLang
|
|
133
|
+
if (this.languageNames.includes(toName)) {
|
|
134
|
+
toLang = this.getLanguage(toName)
|
|
135
|
+
} else {
|
|
136
|
+
toLang = new Language({
|
|
137
|
+
framework: this.framework,
|
|
138
|
+
languagePath: newPath,
|
|
139
|
+
courseDir: this.courseDir,
|
|
140
|
+
jsonext: this.jsonext,
|
|
141
|
+
trackingIdType: this.trackingIdType
|
|
142
|
+
})
|
|
143
|
+
this.languages.push(toLang)
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
fs.mkdirpSync(newPath)
|
|
147
|
+
|
|
148
|
+
fromLang.files.forEach(file => {
|
|
149
|
+
const pathParsed = path.parse(file.path.replace(/\\/g, '/'))
|
|
150
|
+
const newLocation = `${newPath}${pathParsed.name}${pathParsed.ext}`
|
|
151
|
+
fs.removeSync(newLocation)
|
|
152
|
+
fs.copyFileSync(file.path, newLocation)
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
toLang.load()
|
|
156
|
+
return toLang
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/** @returns {Data} */
|
|
160
|
+
checkIds () {
|
|
161
|
+
this.languages.forEach(lang => lang.checkIds())
|
|
162
|
+
return this
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/** @returns {Data} */
|
|
166
|
+
addTrackingIds () {
|
|
167
|
+
this.languages.forEach(lang => lang.addTrackingIds())
|
|
168
|
+
return this
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/** @returns {Data} */
|
|
172
|
+
removeTrackingIds () {
|
|
173
|
+
this.languages.forEach(lang => lang.removeTrackingIds())
|
|
174
|
+
return this
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/** @returns {Data} */
|
|
178
|
+
save () {
|
|
179
|
+
this.configFile.save()
|
|
180
|
+
this.languages.forEach(language => language.save())
|
|
181
|
+
return this
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export default Data
|