coursecode 0.1.53 → 0.1.55
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 +6 -3
- package/bin/cli.js +14 -1
- package/framework/docs/COURSE_AUTHORING_GUIDE.md +13 -7
- package/framework/docs/FRAMEWORK_GUIDE.md +23 -22
- package/framework/docs/USER_GUIDE.md +9 -3
- package/framework/index.html +1 -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 +3 -15
- 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/create.js +71 -18
- 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/scaffold.js +33 -4
- package/lib/stub-player.js +19 -2
- package/lib/token.js +26 -29
- package/lib/upgrade.js +76 -2
- package/package.json +33 -26
- package/template/course/course-config.js +9 -7
- package/template/gitattributes +44 -0
- package/template/gitignore +38 -0
- package/template/package.json +13 -4
- package/template/vite.config.js +90 -27
|
@@ -44,11 +44,15 @@
|
|
|
44
44
|
</div>
|
|
45
45
|
|
|
46
46
|
<iframe id="course" style="display:none"></iframe>
|
|
47
|
-
|
|
47
|
+
|
|
48
|
+
<script src="pipwerks.js"></script>
|
|
49
|
+
<script>
|
|
50
|
+
// Injected as a JSON string by the build system before the bridge loads.
|
|
51
|
+
window.COURSECODE_PROXY_CONFIG = { courseUrl: {{EXTERNAL_URL_JSON}} };
|
|
52
|
+
</script>
|
|
48
53
|
<script src="scorm-bridge.js"></script>
|
|
49
54
|
<script>
|
|
50
|
-
|
|
51
|
-
const COURSE_URL = '{{EXTERNAL_URL}}';
|
|
55
|
+
const COURSE_URL = window.COURSECODE_PROXY_CONFIG.courseUrl;
|
|
52
56
|
|
|
53
57
|
const iframe = document.getElementById('course');
|
|
54
58
|
const loading = document.getElementById('loading');
|
|
@@ -22,16 +22,17 @@
|
|
|
22
22
|
const iframe = document.getElementById('course');
|
|
23
23
|
let initialized = false;
|
|
24
24
|
|
|
25
|
-
// Derive expected course origin from build-time
|
|
26
|
-
//
|
|
27
|
-
let courseOrigin
|
|
25
|
+
// Derive the exact expected course origin from build-time configuration.
|
|
26
|
+
// Fail closed: wildcard origins would allow unrelated frames to operate the LMS API.
|
|
27
|
+
let courseOrigin;
|
|
28
28
|
try {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
29
|
+
const courseUrl = window.COURSECODE_PROXY_CONFIG?.courseUrl;
|
|
30
|
+
if (!courseUrl) throw new Error('Missing proxy course URL');
|
|
31
|
+
courseOrigin = new URL(courseUrl).origin;
|
|
32
|
+
console.log('SCORM Bridge: Expected course origin:', courseOrigin);
|
|
33
33
|
} catch (e) {
|
|
34
|
-
console.
|
|
34
|
+
console.error('SCORM Bridge: Invalid course URL; bridge disabled:', e.message);
|
|
35
|
+
return;
|
|
35
36
|
}
|
|
36
37
|
|
|
37
38
|
// Disable pipwerks auto-handling - we manage status ourselves
|
|
@@ -49,8 +50,13 @@
|
|
|
49
50
|
return;
|
|
50
51
|
}
|
|
51
52
|
|
|
53
|
+
if (!iframe || event.source !== iframe.contentWindow) {
|
|
54
|
+
console.warn('SCORM Bridge: Rejected message not sent by the course iframe');
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
|
|
52
58
|
// Validate origin when we have a known course origin
|
|
53
|
-
if (
|
|
59
|
+
if (event.origin !== courseOrigin) {
|
|
54
60
|
console.warn('SCORM Bridge: Rejected message from unexpected origin:', event.origin);
|
|
55
61
|
return;
|
|
56
62
|
}
|
package/lib/scaffold.js
CHANGED
|
@@ -34,13 +34,24 @@ const MINIMAL_CONFIG = `export const courseConfig = {
|
|
|
34
34
|
};
|
|
35
35
|
`;
|
|
36
36
|
|
|
37
|
+
const DEMO_ASSETS = [
|
|
38
|
+
'docs/example_md_1.md',
|
|
39
|
+
'docs/example_md_2.md',
|
|
40
|
+
'docs/example_pdf_1_thumbnail.png',
|
|
41
|
+
'docs/example_pdf_2.pdf',
|
|
42
|
+
'images/course-architecture.svg',
|
|
43
|
+
'images/logo.svg',
|
|
44
|
+
'widgets/counter-demo.html',
|
|
45
|
+
'widgets/gravity-painter.html'
|
|
46
|
+
];
|
|
47
|
+
|
|
37
48
|
/**
|
|
38
49
|
* Minimal slide template
|
|
39
50
|
*/
|
|
40
51
|
function slideTemplate(id) {
|
|
41
52
|
const title = id.replace(/[-_]/g, ' ').replace(/\b\w/g, c => c.toUpperCase());
|
|
42
53
|
return `export const slide = {
|
|
43
|
-
render(
|
|
54
|
+
render(_root, _context) {
|
|
44
55
|
const container = document.createElement('div');
|
|
45
56
|
container.innerHTML = \`
|
|
46
57
|
<h1>${title}</h1>
|
|
@@ -83,9 +94,11 @@ const questions = [
|
|
|
83
94
|
];
|
|
84
95
|
|
|
85
96
|
export const slide = {
|
|
86
|
-
render(
|
|
87
|
-
const
|
|
88
|
-
|
|
97
|
+
render(_root, context = {}) {
|
|
98
|
+
const container = document.createElement('div');
|
|
99
|
+
const assessment = AssessmentManager.createAssessment({ ...config, questions });
|
|
100
|
+
assessment.render(container, context);
|
|
101
|
+
return container;
|
|
89
102
|
}
|
|
90
103
|
};
|
|
91
104
|
`;
|
|
@@ -128,6 +141,22 @@ export function clean(options = {}) {
|
|
|
128
141
|
}
|
|
129
142
|
}
|
|
130
143
|
|
|
144
|
+
// Remove the known demo documents, images, and widgets without touching
|
|
145
|
+
// any author-created assets that may already exist in the project.
|
|
146
|
+
const assetsDir = path.join(coursePath, 'assets');
|
|
147
|
+
for (const relativePath of DEMO_ASSETS) {
|
|
148
|
+
const assetPath = path.join(assetsDir, relativePath);
|
|
149
|
+
if (fs.existsSync(assetPath)) {
|
|
150
|
+
fs.rmSync(assetPath, { force: true });
|
|
151
|
+
removed++;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// A freshly blank scaffold must not inherit the example narration cache.
|
|
156
|
+
if (options.blank) {
|
|
157
|
+
fs.rmSync(path.join(basePath, '.narration-cache.json'), { force: true });
|
|
158
|
+
}
|
|
159
|
+
|
|
131
160
|
// Rewrite course-config.js to minimal starter
|
|
132
161
|
const configPath = path.join(coursePath, 'course-config.js');
|
|
133
162
|
if (fs.existsSync(configPath)) {
|
package/lib/stub-player.js
CHANGED
|
@@ -58,7 +58,7 @@ export { escapeHtml };
|
|
|
58
58
|
* @returns {string} - Complete HTML for the player page
|
|
59
59
|
*/
|
|
60
60
|
export function generateStubPlayer(config) {
|
|
61
|
-
const { title, launchUrl, storageKey, passwordHash, isLive, liveReload, courseContent, startSlide, isDesktop, showHeader = true, skipGating = null, moduleBasePath = '/__stub-player' } = config;
|
|
61
|
+
const { title, launchUrl, storageKey, passwordHash, isLive, liveReload, courseContent, startSlide, isDesktop, showHeader = true, skipGating = null, moduleBasePath = '/__stub-player', previewToken = null } = config;
|
|
62
62
|
const hasPassword = !!passwordHash;
|
|
63
63
|
const hasContent = !!courseContent;
|
|
64
64
|
const headerVisible = showHeader !== false;
|
|
@@ -115,8 +115,25 @@ ${stubPlayerStyles}
|
|
|
115
115
|
showHeader: ${headerVisible},
|
|
116
116
|
skipGating: ${typeof skipGating === 'boolean' ? skipGating : 'null'},
|
|
117
117
|
isDesktop: ${isDesktop || false},
|
|
118
|
-
isCI: ${!!process.env.CI}
|
|
118
|
+
isCI: ${!!process.env.CI},
|
|
119
|
+
previewToken: ${previewToken ? JSON.stringify(previewToken) : 'null'}
|
|
119
120
|
};
|
|
121
|
+
|
|
122
|
+
// Protect source-mutating preview endpoints from cross-site requests. The
|
|
123
|
+
// token is random for each preview process and is never used by exports.
|
|
124
|
+
if (window.STUB_CONFIG.previewToken) {
|
|
125
|
+
const nativeFetch = window.fetch.bind(window);
|
|
126
|
+
window.fetch = function(input, init = {}) {
|
|
127
|
+
const requestUrl = typeof input === 'string' ? input : input?.url;
|
|
128
|
+
const requestMethod = String(init.method || input?.method || 'GET').toUpperCase();
|
|
129
|
+
if (requestUrl?.startsWith('/__') && !['GET', 'HEAD', 'OPTIONS'].includes(requestMethod)) {
|
|
130
|
+
const headers = new Headers(init.headers || input?.headers || {});
|
|
131
|
+
headers.set('X-CourseCode-Preview-Token', window.STUB_CONFIG.previewToken);
|
|
132
|
+
init = { ...init, headers };
|
|
133
|
+
}
|
|
134
|
+
return nativeFetch(input, init);
|
|
135
|
+
};
|
|
136
|
+
}
|
|
120
137
|
</script>
|
|
121
138
|
<script type="module" src="${moduleBasePath}/${isLive ? 'app' : 'app-viewer'}.js"></script>
|
|
122
139
|
|
package/lib/token.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Usage:
|
|
5
5
|
* coursecode token # Generate random token
|
|
6
|
-
* coursecode token --add client-name # Add client to
|
|
6
|
+
* coursecode token --add client-name # Add client to gitignored access file
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import crypto from 'crypto';
|
|
@@ -25,51 +25,48 @@ export function generateToken(length = 24) {
|
|
|
25
25
|
export async function token(options = {}) {
|
|
26
26
|
const newToken = generateToken();
|
|
27
27
|
|
|
28
|
-
// If --add specified, add to
|
|
28
|
+
// If --add specified, add to the non-published access file. Credentials
|
|
29
|
+
// must never live in course-config.js because that module is bundled into
|
|
30
|
+
// learner-facing JavaScript.
|
|
29
31
|
if (options.add) {
|
|
30
32
|
const clientId = options.add;
|
|
31
|
-
|
|
32
|
-
|
|
33
|
+
if (!/^[a-zA-Z0-9][a-zA-Z0-9._-]{0,127}$/.test(clientId)) {
|
|
34
|
+
throw new Error('Client ID must use letters, numbers, dots, underscores, or hyphens');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const configPath = path.join(process.cwd(), 'course', 'course-config.js');
|
|
33
38
|
|
|
34
39
|
if (!fs.existsSync(configPath)) {
|
|
35
40
|
console.error('\n❌ No course-config.js found. Run from a CourseCode project directory.\n');
|
|
36
41
|
process.exit(1);
|
|
37
42
|
}
|
|
38
43
|
|
|
39
|
-
|
|
44
|
+
const accessDir = path.join(process.cwd(), '.coursecode');
|
|
45
|
+
const accessPath = path.join(accessDir, 'access-control.json');
|
|
46
|
+
fs.mkdirSync(accessDir, { recursive: true });
|
|
40
47
|
|
|
41
|
-
|
|
42
|
-
if (
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
const newClient = `\n '${clientId}': { token: '${newToken}' },`;
|
|
48
|
-
content = content.slice(0, insertPos) + newClient + content.slice(insertPos);
|
|
48
|
+
let accessConfig = { clients: {} };
|
|
49
|
+
if (fs.existsSync(accessPath)) {
|
|
50
|
+
try {
|
|
51
|
+
accessConfig = JSON.parse(fs.readFileSync(accessPath, 'utf-8'));
|
|
52
|
+
} catch (error) {
|
|
53
|
+
throw new Error(`Cannot parse ${accessPath}: ${error.message}`);
|
|
49
54
|
}
|
|
50
|
-
} else {
|
|
51
|
-
// Add accessControl section before closing brace
|
|
52
|
-
const accessControlBlock = `
|
|
53
|
-
accessControl: {
|
|
54
|
-
enabled: true,
|
|
55
|
-
clients: {
|
|
56
|
-
'${clientId}': { token: '${newToken}' }
|
|
57
55
|
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
const lastBrace = content.lastIndexOf('};');
|
|
61
|
-
if (lastBrace !== -1) {
|
|
62
|
-
content = content.slice(0, lastBrace) + accessControlBlock + '\n' + content.slice(lastBrace);
|
|
63
|
-
}
|
|
56
|
+
if (!accessConfig.clients || typeof accessConfig.clients !== 'object') {
|
|
57
|
+
accessConfig.clients = {};
|
|
64
58
|
}
|
|
65
|
-
|
|
66
|
-
fs.writeFileSync(
|
|
59
|
+
accessConfig.clients[clientId] = { token: newToken };
|
|
60
|
+
fs.writeFileSync(accessPath, JSON.stringify(accessConfig, null, 2) + '\n', { mode: 0o600 });
|
|
61
|
+
try { fs.chmodSync(accessPath, 0o600); } catch { /* Windows/filesystem may not support chmod */ }
|
|
67
62
|
|
|
68
63
|
console.log(`
|
|
69
|
-
✅ Added client '${clientId}' to
|
|
64
|
+
✅ Added client '${clientId}' to .coursecode/access-control.json
|
|
70
65
|
|
|
71
66
|
Token: ${newToken}
|
|
72
67
|
|
|
68
|
+
Keep accessControl.enforcement = 'server' in course-config.js.
|
|
69
|
+
Your CDN/backend must validate this token before serving course files.
|
|
73
70
|
Build will generate: ${clientId}_proxy.zip
|
|
74
71
|
`);
|
|
75
72
|
} else {
|
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.55",
|
|
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": {
|
|
@@ -14,7 +14,6 @@
|
|
|
14
14
|
"!framework/dist/**",
|
|
15
15
|
"schemas",
|
|
16
16
|
"template",
|
|
17
|
-
"!template/.gitattributes",
|
|
18
17
|
"!template/**/.gitkeep",
|
|
19
18
|
"README.md",
|
|
20
19
|
"THIRD_PARTY_NOTICES.md"
|
|
@@ -51,26 +50,28 @@
|
|
|
51
50
|
"lint:responsive:structure": "node scripts/check-responsive-structure-scoping.mjs",
|
|
52
51
|
"lint:fix": "eslint . --fix",
|
|
53
52
|
"smoke:responsive": "node scripts/responsive-visual-smoke.mjs",
|
|
53
|
+
"smoke:packed-create": "node scripts/smoke-packed-create.mjs",
|
|
54
54
|
"build": "npx vite build --config vite.framework-dev.config.js",
|
|
55
55
|
"build:dev": "npx vite build --config vite.framework-dev.config.js",
|
|
56
56
|
"build:scorm2004": "LMS_FORMAT=scorm2004 npx vite build --config vite.framework-dev.config.js",
|
|
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 && npm run smoke:packed-create",
|
|
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:
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Auto detect text files and perform LF normalization
|
|
2
|
+
* text=auto
|
|
3
|
+
|
|
4
|
+
# Force LF line endings for text files
|
|
5
|
+
*.js text eol=lf
|
|
6
|
+
*.mjs text eol=lf
|
|
7
|
+
*.cjs text eol=lf
|
|
8
|
+
*.json text eol=lf
|
|
9
|
+
*.css text eol=lf
|
|
10
|
+
*.html text eol=lf
|
|
11
|
+
*.htm text eol=lf
|
|
12
|
+
*.xml text eol=lf
|
|
13
|
+
*.xsd text eol=lf
|
|
14
|
+
*.dtd text eol=lf
|
|
15
|
+
*.md text eol=lf
|
|
16
|
+
*.txt text eol=lf
|
|
17
|
+
*.yml text eol=lf
|
|
18
|
+
*.yaml text eol=lf
|
|
19
|
+
*.env text eol=lf
|
|
20
|
+
|
|
21
|
+
# Binary files
|
|
22
|
+
*.png binary
|
|
23
|
+
*.jpg binary
|
|
24
|
+
*.jpeg binary
|
|
25
|
+
*.gif binary
|
|
26
|
+
*.ico binary
|
|
27
|
+
*.webp binary
|
|
28
|
+
*.svg binary
|
|
29
|
+
*.mp3 binary
|
|
30
|
+
*.mp4 binary
|
|
31
|
+
*.wav binary
|
|
32
|
+
*.ogg binary
|
|
33
|
+
*.webm binary
|
|
34
|
+
*.woff binary
|
|
35
|
+
*.woff2 binary
|
|
36
|
+
*.ttf binary
|
|
37
|
+
*.eot binary
|
|
38
|
+
*.otf binary
|
|
39
|
+
*.pdf binary
|
|
40
|
+
*.zip binary
|
|
41
|
+
|
|
42
|
+
# Linguist overrides for GitHub stats
|
|
43
|
+
*.css linguist-detectable=false
|
|
44
|
+
*.xsd linguist-detectable=false
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Dependencies
|
|
2
|
+
node_modules/
|
|
3
|
+
|
|
4
|
+
# Build output
|
|
5
|
+
dist/
|
|
6
|
+
*.zip
|
|
7
|
+
|
|
8
|
+
# Preview export (comment this line to include in repo for GitHub Pages deployment)
|
|
9
|
+
course-preview/
|
|
10
|
+
|
|
11
|
+
# Environment
|
|
12
|
+
.env
|
|
13
|
+
.env.local
|
|
14
|
+
|
|
15
|
+
# Google Cloud service account credentials (NEVER commit these!)
|
|
16
|
+
**/service-account*.json
|
|
17
|
+
**/*-credentials.json
|
|
18
|
+
**/*-keyfile.json
|
|
19
|
+
|
|
20
|
+
# System files
|
|
21
|
+
.DS_Store
|
|
22
|
+
Thumbs.db
|
|
23
|
+
|
|
24
|
+
# IDE
|
|
25
|
+
.idea/
|
|
26
|
+
.vscode/
|
|
27
|
+
*.swp
|
|
28
|
+
*.swo
|
|
29
|
+
|
|
30
|
+
# CourseCode Cloud (project binding)
|
|
31
|
+
.coursecode/
|
|
32
|
+
|
|
33
|
+
# Narration cache
|
|
34
|
+
.narration-cache.json
|
|
35
|
+
|
|
36
|
+
# Logs
|
|
37
|
+
*.log
|
|
38
|
+
npm-debug.log*
|
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
|
}
|