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.
@@ -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
@@ -0,0 +1,13 @@
1
+ name: Standard.js
2
+ on: push
3
+ jobs:
4
+ default:
5
+ runs-on: ubuntu-latest
6
+ steps:
7
+ - uses: actions/checkout@master
8
+ - uses: actions/setup-node@master
9
+ with:
10
+ node-version: 'lts/*'
11
+ cache: 'npm'
12
+ - run: npm ci
13
+ - run: npx standard
@@ -0,0 +1,13 @@
1
+ name: Tests
2
+ on: push
3
+ jobs:
4
+ default:
5
+ runs-on: ubuntu-latest
6
+ steps:
7
+ - uses: actions/checkout@master
8
+ - uses: actions/setup-node@master
9
+ with:
10
+ node-version: 'lts/*'
11
+ cache: 'npm'
12
+ - run: npm ci
13
+ - run: npm test
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
@@ -0,0 +1,10 @@
1
+ import { FlatCompat } from '@eslint/eslintrc'
2
+
3
+ const compat = new FlatCompat()
4
+
5
+ export default [
6
+ ...compat.extends('standard'),
7
+ {
8
+ ignores: ['node_modules/']
9
+ }
10
+ ]
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