coursecode 0.1.52 → 0.1.54
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/README.md +11 -6
- package/bin/cli.js +1 -0
- package/framework/css/components/loading.css +11 -3
- package/framework/docs/COURSE_AUTHORING_GUIDE.md +8 -6
- package/framework/docs/FRAMEWORK_GUIDE.md +22 -21
- package/framework/docs/USER_GUIDE.md +9 -3
- package/framework/index.html +2 -2
- package/framework/js/app/AppUI.js +2 -1
- package/framework/js/core/event-bus.js +21 -6
- package/framework/js/drivers/lti-driver.js +50 -94
- package/framework/js/drivers/proxy-driver.js +8 -5
- package/framework/js/main.js +7 -18
- package/framework/js/utilities/access-control.js +11 -45
- package/framework/js/utilities/data-reporter.js +16 -3
- package/framework/version.json +26 -50
- package/lib/build-packaging.d.ts +4 -0
- package/lib/build-packaging.js +39 -2
- package/lib/cloud.js +1 -1
- package/lib/course-writer.js +97 -32
- package/lib/manifest/cmi5-manifest.js +19 -10
- package/lib/manifest/lti-tool-config.js +18 -5
- package/lib/manifest/scorm-12-manifest.js +11 -5
- package/lib/manifest/scorm-2004-manifest.js +16 -9
- package/lib/manifest/xml-utils.js +14 -0
- package/lib/preview-routes-api.js +16 -7
- package/lib/preview-server.js +49 -28
- package/lib/project-utils.js +34 -0
- package/lib/proxy-templates/proxy.html +7 -3
- package/lib/proxy-templates/scorm-bridge.js +15 -9
- package/lib/stub-player.js +19 -2
- package/lib/token.js +26 -29
- package/lib/upgrade.js +76 -2
- package/package.json +32 -25
- package/template/course/course-config.js +9 -7
- package/template/package.json +13 -4
- package/template/vite.config.js +80 -27
package/lib/upgrade.js
CHANGED
|
@@ -113,6 +113,46 @@ export function addMissingRuntimeDependencies(projectPkg, templatePkg) {
|
|
|
113
113
|
return added;
|
|
114
114
|
}
|
|
115
115
|
|
|
116
|
+
const OBSOLETE_MANAGED_DEV_DEPENDENCIES = ['@vitejs/plugin-legacy'];
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Synchronize the build tooling that accompanies a replaced template config.
|
|
120
|
+
* This only runs with `coursecode upgrade --configs`, keeping the default
|
|
121
|
+
* framework-only upgrade from changing a project's custom build stack.
|
|
122
|
+
*/
|
|
123
|
+
export function syncManagedBuildTooling(projectPkg, templatePkg) {
|
|
124
|
+
const projectDevDeps = { ...(projectPkg.devDependencies || {}) };
|
|
125
|
+
const templateDevDeps = templatePkg.devDependencies || {};
|
|
126
|
+
const updated = [];
|
|
127
|
+
const removed = [];
|
|
128
|
+
|
|
129
|
+
for (const [name, version] of Object.entries(templateDevDeps)) {
|
|
130
|
+
if (projectDevDeps[name] !== version) {
|
|
131
|
+
projectDevDeps[name] = version;
|
|
132
|
+
updated.push(`${name}@${version}`);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
for (const name of OBSOLETE_MANAGED_DEV_DEPENDENCIES) {
|
|
137
|
+
if (projectDevDeps[name]) {
|
|
138
|
+
delete projectDevDeps[name];
|
|
139
|
+
removed.push(name);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
projectPkg.devDependencies = Object.fromEntries(
|
|
144
|
+
Object.entries(projectDevDeps).sort(([a], [b]) => a.localeCompare(b))
|
|
145
|
+
);
|
|
146
|
+
|
|
147
|
+
const nodeEngine = templatePkg.engines?.node;
|
|
148
|
+
const engineUpdated = Boolean(nodeEngine && projectPkg.engines?.node !== nodeEngine);
|
|
149
|
+
if (engineUpdated) {
|
|
150
|
+
projectPkg.engines = { ...(projectPkg.engines || {}), node: nodeEngine };
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return { updated, removed, engineUpdated };
|
|
154
|
+
}
|
|
155
|
+
|
|
116
156
|
export async function upgrade(options = {}) {
|
|
117
157
|
const cwd = process.cwd();
|
|
118
158
|
const rcPath = path.join(cwd, '.coursecoderc.json');
|
|
@@ -175,7 +215,8 @@ export async function upgrade(options = {}) {
|
|
|
175
215
|
if (options.configs) {
|
|
176
216
|
dryRunMsg += `
|
|
177
217
|
- vite.config.js (replace, backup original)
|
|
178
|
-
- eslint.config.js (replace, backup original)
|
|
218
|
+
- eslint.config.js (replace, backup original)
|
|
219
|
+
- package.json (sync managed Vite/ESLint tooling and remove plugin-legacy)`;
|
|
179
220
|
}
|
|
180
221
|
|
|
181
222
|
dryRunMsg += `
|
|
@@ -266,10 +307,18 @@ export async function upgrade(options = {}) {
|
|
|
266
307
|
// the project's existing version ranges.
|
|
267
308
|
const pkgPath = path.join(cwd, 'package.json');
|
|
268
309
|
let addedRuntimeDeps = [];
|
|
310
|
+
let toolingChanges = null;
|
|
269
311
|
if (fs.existsSync(pkgPath)) {
|
|
270
312
|
const projectPkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
|
|
271
313
|
addedRuntimeDeps = addMissingRuntimeDependencies(projectPkg, templatePkg);
|
|
272
|
-
if (
|
|
314
|
+
if (options.configs) {
|
|
315
|
+
toolingChanges = syncManagedBuildTooling(projectPkg, templatePkg);
|
|
316
|
+
}
|
|
317
|
+
const packageChanged = addedRuntimeDeps.length > 0 ||
|
|
318
|
+
toolingChanges?.updated.length > 0 ||
|
|
319
|
+
toolingChanges?.removed.length > 0 ||
|
|
320
|
+
toolingChanges?.engineUpdated;
|
|
321
|
+
if (packageChanged) {
|
|
273
322
|
fs.writeFileSync(pkgPath, JSON.stringify(projectPkg, null, 2) + '\n');
|
|
274
323
|
}
|
|
275
324
|
}
|
|
@@ -290,6 +339,31 @@ export async function upgrade(options = {}) {
|
|
|
290
339
|
Run npm install to update node_modules and your lockfile.`;
|
|
291
340
|
}
|
|
292
341
|
|
|
342
|
+
if (toolingChanges && (
|
|
343
|
+
toolingChanges.updated.length > 0 ||
|
|
344
|
+
toolingChanges.removed.length > 0 ||
|
|
345
|
+
toolingChanges.engineUpdated
|
|
346
|
+
)) {
|
|
347
|
+
successMsg += `
|
|
348
|
+
|
|
349
|
+
Managed build tooling synchronized for the new config:`;
|
|
350
|
+
if (toolingChanges.updated.length > 0) {
|
|
351
|
+
successMsg += `
|
|
352
|
+
- Updated: ${toolingChanges.updated.join(', ')}`;
|
|
353
|
+
}
|
|
354
|
+
if (toolingChanges.removed.length > 0) {
|
|
355
|
+
successMsg += `
|
|
356
|
+
- Removed: ${toolingChanges.removed.join(', ')}`;
|
|
357
|
+
}
|
|
358
|
+
if (toolingChanges.engineUpdated) {
|
|
359
|
+
successMsg += `
|
|
360
|
+
- Updated Node engine requirement to ${templatePkg.engines.node}`;
|
|
361
|
+
}
|
|
362
|
+
successMsg += `
|
|
363
|
+
|
|
364
|
+
Run npm install to update node_modules and your lockfile.`;
|
|
365
|
+
}
|
|
366
|
+
|
|
293
367
|
if (configUpdates.length > 0) {
|
|
294
368
|
successMsg += `
|
|
295
369
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "coursecode",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.54",
|
|
4
4
|
"description": "Multi-format course authoring framework with CLI tools (SCORM 2004, SCORM 1.2, cmi5, LTI 1.3)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -57,20 +57,21 @@
|
|
|
57
57
|
"build:scorm12": "LMS_FORMAT=scorm1.2 npx vite build --config vite.framework-dev.config.js",
|
|
58
58
|
"build:cmi5": "LMS_FORMAT=cmi5 npx vite build --config vite.framework-dev.config.js",
|
|
59
59
|
"build:lti": "LMS_FORMAT=lti npx vite build --config vite.framework-dev.config.js",
|
|
60
|
-
"package": "npm run lint && PACKAGE=true npx vite build --config vite.framework-dev.config.js",
|
|
61
|
-
"package:scorm2004": "npm run lint && LMS_FORMAT=scorm2004 PACKAGE=true npx vite build --config vite.framework-dev.config.js",
|
|
62
|
-
"package:scorm12": "npm run lint && LMS_FORMAT=scorm1.2 PACKAGE=true npx vite build --config vite.framework-dev.config.js",
|
|
63
|
-
"package:cmi5": "npm run lint && LMS_FORMAT=cmi5 PACKAGE=true npx vite build --config vite.framework-dev.config.js",
|
|
64
|
-
"package:lti": "npm run lint && LMS_FORMAT=lti PACKAGE=true npx vite build --config vite.framework-dev.config.js",
|
|
60
|
+
"package": "npm run lint && npm run test:coverage && PACKAGE=true npx vite build --config vite.framework-dev.config.js",
|
|
61
|
+
"package:scorm2004": "npm run lint && npm run test:coverage && LMS_FORMAT=scorm2004 PACKAGE=true npx vite build --config vite.framework-dev.config.js",
|
|
62
|
+
"package:scorm12": "npm run lint && npm run test:coverage && LMS_FORMAT=scorm1.2 PACKAGE=true npx vite build --config vite.framework-dev.config.js",
|
|
63
|
+
"package:cmi5": "npm run lint && npm run test:coverage && LMS_FORMAT=cmi5 PACKAGE=true npx vite build --config vite.framework-dev.config.js",
|
|
64
|
+
"package:lti": "npm run lint && npm run test:coverage && LMS_FORMAT=lti PACKAGE=true npx vite build --config vite.framework-dev.config.js",
|
|
65
65
|
"test": "vitest run --config tests/vitest.config.js",
|
|
66
66
|
"test:e2e": "vitest run --config tests/vitest.e2e.config.js",
|
|
67
|
-
"test:
|
|
67
|
+
"test:drivers": "vitest run --config tests/vitest.e2e.scorm2004.config.js && vitest run --config tests/vitest.e2e.scorm12.config.js && vitest run --config tests/vitest.e2e.cmi5.config.js && vitest run --config tests/vitest.e2e.lti.config.js",
|
|
68
|
+
"test:all": "npm test && npm run test:e2e && npm run test:drivers",
|
|
68
69
|
"test:cloud": "vitest run --config tests/vitest.cloud.config.js",
|
|
69
70
|
"test:watch": "vitest --config tests/vitest.config.js",
|
|
70
71
|
"test:coverage": "vitest run --config tests/vitest.config.js --coverage",
|
|
71
|
-
"prerelease:check": "npm run lint && npm run lint:responsive && npm run lint:responsive:structure && npm run build",
|
|
72
|
-
"prerelease:check:full": "npm run prerelease:check && npm run smoke:responsive -- --profile=expanded",
|
|
73
|
-
"prepublishOnly": "
|
|
72
|
+
"prerelease:check": "npm run lint && npm run lint:responsive && npm run lint:responsive:structure && npm run test:coverage && npm run build",
|
|
73
|
+
"prerelease:check:full": "npm run prerelease:check && npm run test:e2e && npm run test:drivers && npm run smoke:responsive -- --profile=expanded",
|
|
74
|
+
"prepublishOnly": "npm run prerelease:check"
|
|
74
75
|
},
|
|
75
76
|
"repository": {
|
|
76
77
|
"type": "git",
|
|
@@ -98,34 +99,40 @@
|
|
|
98
99
|
},
|
|
99
100
|
"homepage": "https://github.com/course-code-framework/coursecode/blob/main/framework/docs/USER_GUIDE.md",
|
|
100
101
|
"engines": {
|
|
101
|
-
"node": ">=
|
|
102
|
+
"node": ">=20.19.0"
|
|
102
103
|
},
|
|
103
104
|
"dependencies": {
|
|
104
|
-
"@modelcontextprotocol/sdk": "^1.
|
|
105
|
-
"acorn": "^8.
|
|
105
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
106
|
+
"acorn": "^8.17.0",
|
|
106
107
|
"archiver": "^7.0.1",
|
|
107
108
|
"commander": "^14.0.3",
|
|
108
109
|
"lz-string": "^1.5.0",
|
|
109
|
-
"mammoth": "^1.
|
|
110
|
+
"mammoth": "^1.12.0",
|
|
110
111
|
"marked": "^17.0.6",
|
|
111
112
|
"marked-gfm-heading-id": "^4.1.4",
|
|
112
113
|
"node-pptx-parser": "^1.0.1",
|
|
113
|
-
"pdf2json": "^4.0.
|
|
114
|
+
"pdf2json": "^4.0.3",
|
|
114
115
|
"pdf2md": "^1.0.2",
|
|
115
|
-
"puppeteer-core": "^24.
|
|
116
|
+
"puppeteer-core": "^24.43.1",
|
|
116
117
|
"win-ca": "^3.5.1",
|
|
117
|
-
"ws": "^8.
|
|
118
|
+
"ws": "^8.21.0"
|
|
118
119
|
},
|
|
119
120
|
"devDependencies": {
|
|
120
|
-
"@
|
|
121
|
-
"@vitest/coverage-v8": "^4.1.5",
|
|
121
|
+
"@vitest/coverage-v8": "^4.1.10",
|
|
122
122
|
"@xapi/cmi5": "^1.4.0",
|
|
123
|
-
"eslint": "^10.
|
|
124
|
-
"globals": "^17.
|
|
125
|
-
"jose": "^6.2.2",
|
|
123
|
+
"eslint": "^10.6.0",
|
|
124
|
+
"globals": "^17.7.0",
|
|
126
125
|
"pptxgenjs": "^4.0.1",
|
|
127
|
-
"vite": "~
|
|
128
|
-
"vite-plugin-static-copy": "^
|
|
129
|
-
"vitest": "^4.1.
|
|
126
|
+
"vite": "~8.1.4",
|
|
127
|
+
"vite-plugin-static-copy": "^4.1.1",
|
|
128
|
+
"vitest": "^4.1.10"
|
|
129
|
+
},
|
|
130
|
+
"overrides": {
|
|
131
|
+
"uuid": "^11.1.1"
|
|
132
|
+
},
|
|
133
|
+
"pnpm": {
|
|
134
|
+
"overrides": {
|
|
135
|
+
"uuid": "^11.1.1"
|
|
136
|
+
}
|
|
130
137
|
}
|
|
131
138
|
}
|
|
@@ -92,13 +92,15 @@
|
|
|
92
92
|
* EXTERNAL HOSTING (CDN deployment with proxy packages):
|
|
93
93
|
* format: 'scorm1.2-proxy' | 'scorm2004-proxy' | 'cmi5-remote'
|
|
94
94
|
* externalUrl: 'https://cdn.example.com/courses/my-course' // Where course is hosted
|
|
95
|
-
* accessControl: {
|
|
96
|
-
*
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
*
|
|
95
|
+
* accessControl: { enforcement: 'server' }
|
|
96
|
+
* Client tokens are stored outside course source in the gitignored file
|
|
97
|
+
* .coursecode/access-control.json (created by coursecode token --add <clientId>).
|
|
98
|
+
* The CDN/backend MUST validate credentials before serving index.html/assets.
|
|
99
|
+
*
|
|
100
|
+
* LMS PACKAGE MASTERY (optional):
|
|
101
|
+
* lms: { masteryScore: 80 } // 0-100; omitted by default
|
|
102
|
+
* Only set this when the whole package has one LMS-level threshold. Individual
|
|
103
|
+
* assessment passing scores remain in their assessment settings.
|
|
102
104
|
* Build output: One package per client (e.g., acme-corp_proxy.zip)
|
|
103
105
|
*
|
|
104
106
|
* CLOUD DEPLOYMENT:
|
package/template/package.json
CHANGED
|
@@ -4,6 +4,9 @@
|
|
|
4
4
|
"private": true,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "CourseCode project",
|
|
7
|
+
"engines": {
|
|
8
|
+
"node": ">=20.19.0"
|
|
9
|
+
},
|
|
7
10
|
"scripts": {
|
|
8
11
|
"dev": "vite build --mode development --watch",
|
|
9
12
|
"build": "npm run lint && vite build",
|
|
@@ -13,16 +16,22 @@
|
|
|
13
16
|
},
|
|
14
17
|
"dependencies": {
|
|
15
18
|
"@xapi/cmi5": "^1.4.0",
|
|
16
|
-
"jose": "^6.2.2",
|
|
17
19
|
"lz-string": "^1.5.0",
|
|
18
20
|
"marked": "^17.0.6",
|
|
19
21
|
"marked-gfm-heading-id": "^4.1.4"
|
|
20
22
|
},
|
|
21
23
|
"devDependencies": {
|
|
22
|
-
"@vitejs/plugin-legacy": "~7.2.1",
|
|
23
24
|
"archiver": "^7.0.1",
|
|
24
25
|
"eslint": "^9.39.4",
|
|
25
|
-
"vite": "~
|
|
26
|
-
"vite-plugin-static-copy": "^
|
|
26
|
+
"vite": "~8.1.4",
|
|
27
|
+
"vite-plugin-static-copy": "^4.1.1"
|
|
28
|
+
},
|
|
29
|
+
"overrides": {
|
|
30
|
+
"uuid": "^11.1.1"
|
|
31
|
+
},
|
|
32
|
+
"pnpm": {
|
|
33
|
+
"overrides": {
|
|
34
|
+
"uuid": "^11.1.1"
|
|
35
|
+
}
|
|
27
36
|
}
|
|
28
37
|
}
|
package/template/vite.config.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { defineConfig } from 'vite';
|
|
2
|
-
import legacy from '@vitejs/plugin-legacy';
|
|
3
2
|
import { viteStaticCopy } from 'vite-plugin-static-copy';
|
|
4
3
|
import path from 'path';
|
|
5
4
|
import { fileURLToPath, pathToFileURL } from 'url';
|
|
@@ -10,10 +9,12 @@ let contentDiscoveryPlugin;
|
|
|
10
9
|
let createStandardPackage;
|
|
11
10
|
let createExternalPackagesForClients;
|
|
12
11
|
let validateExternalHostingConfig;
|
|
12
|
+
let loadExternalAccessConfig;
|
|
13
13
|
|
|
14
14
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
15
15
|
const ROOT_DIR = path.resolve(__dirname);
|
|
16
16
|
const DIST_DIR = path.join(ROOT_DIR, 'dist');
|
|
17
|
+
const SUPPORTED_BROWSER_TARGETS = ['chrome111', 'edge111', 'firefox114', 'safari16.4'];
|
|
17
18
|
|
|
18
19
|
async function loadBuildUtils() {
|
|
19
20
|
if (
|
|
@@ -21,7 +22,8 @@ async function loadBuildUtils() {
|
|
|
21
22
|
contentDiscoveryPlugin &&
|
|
22
23
|
createStandardPackage &&
|
|
23
24
|
createExternalPackagesForClients &&
|
|
24
|
-
validateExternalHostingConfig
|
|
25
|
+
validateExternalHostingConfig &&
|
|
26
|
+
loadExternalAccessConfig
|
|
25
27
|
) {
|
|
26
28
|
return;
|
|
27
29
|
}
|
|
@@ -31,7 +33,8 @@ async function loadBuildUtils() {
|
|
|
31
33
|
({
|
|
32
34
|
createStandardPackage,
|
|
33
35
|
createExternalPackagesForClients,
|
|
34
|
-
validateExternalHostingConfig
|
|
36
|
+
validateExternalHostingConfig,
|
|
37
|
+
loadExternalAccessConfig
|
|
35
38
|
} = await import('coursecode/build-packaging'));
|
|
36
39
|
({ default: contentDiscoveryPlugin } = await import('coursecode/vite-plugin-content-discovery'));
|
|
37
40
|
} catch {
|
|
@@ -43,7 +46,8 @@ async function loadBuildUtils() {
|
|
|
43
46
|
({
|
|
44
47
|
createStandardPackage,
|
|
45
48
|
createExternalPackagesForClients,
|
|
46
|
-
validateExternalHostingConfig
|
|
49
|
+
validateExternalHostingConfig,
|
|
50
|
+
loadExternalAccessConfig
|
|
47
51
|
} = await import(toUrl('build-packaging.js')));
|
|
48
52
|
({ default: contentDiscoveryPlugin } = await import(toUrl('vite-plugin-content-discovery.js')));
|
|
49
53
|
}
|
|
@@ -89,6 +93,34 @@ function validatePackage(format = 'scorm2004') {
|
|
|
89
93
|
warnings.push('No assets directory found');
|
|
90
94
|
}
|
|
91
95
|
|
|
96
|
+
// Authoring inputs must never be published with the learner runtime.
|
|
97
|
+
const forbiddenCoursePaths = [
|
|
98
|
+
'course/course-config.js',
|
|
99
|
+
'course/references',
|
|
100
|
+
'course/slides',
|
|
101
|
+
'course/assessments',
|
|
102
|
+
'course/theme.css'
|
|
103
|
+
];
|
|
104
|
+
for (const relativePath of forbiddenCoursePaths) {
|
|
105
|
+
if (fs.existsSync(path.join(DIST_DIR, relativePath))) {
|
|
106
|
+
errors.push(`Authoring source leaked into package: ${relativePath}`);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const invalidCopyLayouts = [
|
|
111
|
+
'.vite',
|
|
112
|
+
'schemas',
|
|
113
|
+
'common/schemas',
|
|
114
|
+
'course/course',
|
|
115
|
+
'course/template',
|
|
116
|
+
'js/vendor/framework'
|
|
117
|
+
];
|
|
118
|
+
for (const relativePath of invalidCopyLayouts) {
|
|
119
|
+
if (fs.existsSync(path.join(DIST_DIR, relativePath))) {
|
|
120
|
+
errors.push(`Static-copy output has an unexpected nested source path: ${relativePath}`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
92
124
|
// Validate manifest/tool config content by format
|
|
93
125
|
if (format === 'cmi5') {
|
|
94
126
|
// cmi5: Validate cmi5.xml structure
|
|
@@ -142,6 +174,12 @@ async function loadCourseConfig() {
|
|
|
142
174
|
const module = await import(configUrl);
|
|
143
175
|
const config = module.courseConfig || module.default || {};
|
|
144
176
|
|
|
177
|
+
const masteryScore = config.lms?.masteryScore;
|
|
178
|
+
if (masteryScore !== undefined && (!Number.isFinite(masteryScore) || masteryScore < 0 || masteryScore > 100)) {
|
|
179
|
+
throw new Error('lms.masteryScore must be a number between 0 and 100');
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const lmsFormat = process.env.LMS_FORMAT || config.format || 'cmi5';
|
|
145
183
|
return {
|
|
146
184
|
title: config.metadata?.title || 'SCORM Course',
|
|
147
185
|
description: config.metadata?.description || 'SCORM 2004 4th Edition Course',
|
|
@@ -151,9 +189,10 @@ async function loadCourseConfig() {
|
|
|
151
189
|
windowWidth: config.environment?.window?.width || 1024,
|
|
152
190
|
windowHeight: config.environment?.window?.height || 768,
|
|
153
191
|
// LMS format: CLI override (env var) takes precedence over config file
|
|
154
|
-
lmsFormat
|
|
155
|
-
externalUrl: config.externalUrl || null,
|
|
156
|
-
|
|
192
|
+
lmsFormat,
|
|
193
|
+
externalUrl: process.env.LTI_EXTERNAL_URL || config.externalUrl || null,
|
|
194
|
+
masteryScore: masteryScore ?? null,
|
|
195
|
+
accessControl: loadExternalAccessConfig(ROOT_DIR, config),
|
|
157
196
|
galleryConfig: config.navigation?.documentGallery || null
|
|
158
197
|
};
|
|
159
198
|
}
|
|
@@ -184,14 +223,31 @@ function scanDistFiles() {
|
|
|
184
223
|
return files;
|
|
185
224
|
}
|
|
186
225
|
|
|
226
|
+
function removeHiddenBuildArtifacts(dir) {
|
|
227
|
+
if (!fs.existsSync(dir)) return;
|
|
228
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
229
|
+
const fullPath = path.join(dir, entry.name);
|
|
230
|
+
if (entry.isDirectory()) {
|
|
231
|
+
removeHiddenBuildArtifacts(fullPath);
|
|
232
|
+
} else if (entry.name === '.DS_Store' || entry.name === '.gitkeep' || entry.name.startsWith('._')) {
|
|
233
|
+
fs.rmSync(fullPath, { force: true });
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
187
238
|
/**
|
|
188
239
|
* SCORM post-build plugin
|
|
189
240
|
*/
|
|
190
|
-
function scormPostBuild(isDev) {
|
|
241
|
+
function scormPostBuild(isDev, { isWatchBuild }) {
|
|
242
|
+
let initialBuild = true;
|
|
191
243
|
return {
|
|
192
244
|
name: 'scorm-post-build',
|
|
193
245
|
enforce: 'post',
|
|
194
246
|
buildStart() {
|
|
247
|
+
if (initialBuild && isWatchBuild && fs.existsSync(DIST_DIR)) {
|
|
248
|
+
fs.rmSync(DIST_DIR, { recursive: true, force: true });
|
|
249
|
+
}
|
|
250
|
+
initialBuild = false;
|
|
195
251
|
if (isDev) console.log('🔨 Building...');
|
|
196
252
|
},
|
|
197
253
|
closeBundle: async () => {
|
|
@@ -199,6 +255,7 @@ function scormPostBuild(isDev) {
|
|
|
199
255
|
// Move index.html to root
|
|
200
256
|
const indexSource = path.join(DIST_DIR, 'framework', 'index.html');
|
|
201
257
|
const indexDest = path.join(DIST_DIR, 'index.html');
|
|
258
|
+
removeHiddenBuildArtifacts(DIST_DIR);
|
|
202
259
|
|
|
203
260
|
// Load course config once for meta tag injection and manifest generation
|
|
204
261
|
const config = await loadCourseConfig();
|
|
@@ -267,6 +324,7 @@ function scormPostBuild(isDev) {
|
|
|
267
324
|
export default defineConfig(async ({ mode }) => {
|
|
268
325
|
await loadBuildUtils();
|
|
269
326
|
const isDev = mode === 'development';
|
|
327
|
+
const isWatchBuild = process.argv.includes('--watch');
|
|
270
328
|
const courseConfig = await loadCourseConfig();
|
|
271
329
|
|
|
272
330
|
return {
|
|
@@ -292,18 +350,20 @@ export default defineConfig(async ({ mode }) => {
|
|
|
292
350
|
|
|
293
351
|
build: {
|
|
294
352
|
outDir: 'dist',
|
|
295
|
-
emptyOutDir: !
|
|
296
|
-
|
|
353
|
+
emptyOutDir: !isWatchBuild,
|
|
354
|
+
// Stable browser contract for LMS launches. SCORM defines the LMS API,
|
|
355
|
+
// not an obsolete browser requirement; IE11/SystemJS is unsupported.
|
|
356
|
+
target: SUPPORTED_BROWSER_TARGETS,
|
|
297
357
|
// LMS environments (SCORM/cmi5) can be restrictive with dynamic imports,
|
|
298
358
|
// so we intentionally keep main.js as a single chunk
|
|
299
359
|
chunkSizeWarningLimit: 600,
|
|
300
|
-
|
|
360
|
+
rolldownOptions: {
|
|
301
361
|
input: {
|
|
302
362
|
main: path.resolve(__dirname, 'framework/index.html')
|
|
303
363
|
},
|
|
304
364
|
// All drivers are bundled as lazy chunks in the universal build.
|
|
305
|
-
//
|
|
306
|
-
//
|
|
365
|
+
// LTI OIDC/JWT validation remains server-side, so no browser crypto
|
|
366
|
+
// verification dependency is required here.
|
|
307
367
|
output: {
|
|
308
368
|
entryFileNames: 'assets/[name].js',
|
|
309
369
|
chunkFileNames: 'assets/[name].js',
|
|
@@ -316,26 +376,19 @@ export default defineConfig(async ({ mode }) => {
|
|
|
316
376
|
// Content discovery - generates manifest at build time
|
|
317
377
|
contentDiscoveryPlugin({ galleryConfig: courseConfig.galleryConfig }),
|
|
318
378
|
|
|
319
|
-
...(!isDev ? [
|
|
320
|
-
legacy({
|
|
321
|
-
targets: ['ie >= 11'],
|
|
322
|
-
renderLegacyChunks: true,
|
|
323
|
-
modernPolyfills: true,
|
|
324
|
-
ignoreBrowserslistConfig: true
|
|
325
|
-
})
|
|
326
|
-
] : []),
|
|
327
|
-
|
|
328
379
|
viteStaticCopy({
|
|
329
380
|
targets: [
|
|
330
|
-
{ src: 'schemas/*.{xml,xsd,dtd}', dest: '.' },
|
|
331
|
-
{ src: 'schemas/common/*', dest: 'common' },
|
|
332
|
-
|
|
333
|
-
|
|
381
|
+
{ src: 'schemas/*.{xml,xsd,dtd}', dest: '.', rename: { stripBase: 1 } },
|
|
382
|
+
{ src: 'schemas/common/*', dest: 'common', rename: { stripBase: 2 } },
|
|
383
|
+
// Publish runtime assets only. Never ship authoring references,
|
|
384
|
+
// configuration source, assessment source, or hidden project files.
|
|
385
|
+
{ src: 'course/assets', dest: 'course', rename: { stripBase: 1 } },
|
|
386
|
+
{ src: 'framework/js/vendor/**/*', dest: 'js/vendor', rename: { stripBase: 3 } }
|
|
334
387
|
],
|
|
335
388
|
watch: isDev ? { rerun: true } : undefined
|
|
336
389
|
}),
|
|
337
390
|
|
|
338
|
-
scormPostBuild(isDev)
|
|
391
|
+
scormPostBuild(isDev, { isWatchBuild })
|
|
339
392
|
]
|
|
340
393
|
};
|
|
341
394
|
});
|