@lxpack/scorm 0.2.0 → 0.2.2
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 +1 -1
- package/dist/index.d.ts +30 -10
- package/dist/index.js +224 -151
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
SCORM 1.2, SCORM 2004 (multi-SCO), and standalone HTML export for LXPack courses.
|
|
9
9
|
|
|
10
|
-
Part of [LXPack](https://github.com/eddiethedean/lxpack) — an AI-native learning experience compiler and runtime (**v0.2.
|
|
10
|
+
Part of [LXPack](https://github.com/eddiethedean/lxpack) — an AI-native learning experience compiler and runtime (**v0.2.2**).
|
|
11
11
|
|
|
12
12
|
| Related | Package |
|
|
13
13
|
|---------|---------|
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { CourseManifest, RuntimeAssessmentBundle } from '@lxpack/validators';
|
|
2
|
+
export { CourseActivity, enumerateActivities as listCourseActivities } from '@lxpack/validators';
|
|
2
3
|
|
|
3
4
|
declare function manifestIdentifier(manifest: CourseManifest): string;
|
|
4
5
|
declare function generateImsManifest(manifest: CourseManifest, files: string[], launchUrl?: string): string;
|
|
@@ -17,6 +18,15 @@ declare function buildScoIndexHtml(options: BuildHtmlOptions & {
|
|
|
17
18
|
}): string;
|
|
18
19
|
declare function buildIndexHtml(options: BuildHtmlOptions): string;
|
|
19
20
|
|
|
21
|
+
interface PageTemplateOptions {
|
|
22
|
+
title: string;
|
|
23
|
+
runtimeCss: string;
|
|
24
|
+
configJson: string;
|
|
25
|
+
runtimeScript: string;
|
|
26
|
+
componentsScript?: string;
|
|
27
|
+
}
|
|
28
|
+
declare function buildLearnerPageHtml(options: PageTemplateOptions): string;
|
|
29
|
+
|
|
20
30
|
type ExportTarget = "scorm12" | "scorm2004" | "standalone";
|
|
21
31
|
interface PackageOptions {
|
|
22
32
|
courseDir: string;
|
|
@@ -36,10 +46,22 @@ declare function collectFiles(dir: string, baseDir: string): Promise<Array<{
|
|
|
36
46
|
declare function buildManifestFileList(courseFiles: Array<{
|
|
37
47
|
path: string;
|
|
38
48
|
}>): string[];
|
|
49
|
+
interface PackageSink {
|
|
50
|
+
writeFile(relPath: string, content: string | Buffer): Promise<void>;
|
|
51
|
+
}
|
|
52
|
+
declare function assemblePackage(options: Omit<PackageOptions, "outputPath">, sink: PackageSink): Promise<{
|
|
53
|
+
fileCount: number;
|
|
54
|
+
}>;
|
|
39
55
|
declare function packageScorm2004(options: PackageOptions): Promise<{
|
|
40
56
|
outputPath: string;
|
|
41
57
|
fileCount: number;
|
|
42
58
|
}>;
|
|
59
|
+
declare function packageScorm2004Dir(options: Omit<PackageOptions, "outputPath"> & {
|
|
60
|
+
outputDir: string;
|
|
61
|
+
}): Promise<{
|
|
62
|
+
outputDir: string;
|
|
63
|
+
fileCount: number;
|
|
64
|
+
}>;
|
|
43
65
|
declare function packageCourse(options: PackageOptions): Promise<{
|
|
44
66
|
outputPath: string;
|
|
45
67
|
fileCount: number;
|
|
@@ -51,21 +73,19 @@ declare function packageStandaloneDir(options: Omit<PackageOptions, "outputPath"
|
|
|
51
73
|
fileCount: number;
|
|
52
74
|
}>;
|
|
53
75
|
|
|
76
|
+
/** Omit embedded quiz data from lesson SCOs; limit assessment SCOs to one quiz. */
|
|
77
|
+
declare function sliceAssessmentBundleForActivity(bundle: RuntimeAssessmentBundle, activityId: string, activityKind: "lesson" | "assessment"): RuntimeAssessmentBundle;
|
|
78
|
+
|
|
54
79
|
/** JSON safe to embed in HTML script blocks (prevents `</script>` breakout). */
|
|
55
80
|
declare function safeJsonForHtml(value: unknown): string;
|
|
56
81
|
|
|
57
82
|
/** Stable slug for ZIP filenames and SCORM identifiers. */
|
|
58
83
|
declare function courseSlug(manifest: CourseManifest): string;
|
|
59
84
|
|
|
60
|
-
interface CourseActivity {
|
|
61
|
-
id: string;
|
|
62
|
-
title: string;
|
|
63
|
-
kind: "lesson" | "assessment";
|
|
64
|
-
}
|
|
65
|
-
declare function listCourseActivities(manifest: CourseManifest): CourseActivity[];
|
|
66
|
-
|
|
67
85
|
declare function scoLaunchPath(activityId: string): string;
|
|
68
|
-
declare function buildScorm2004ManifestFiles(manifest: CourseManifest, courseFiles: string[]): string[];
|
|
69
|
-
declare function generateScorm2004Manifest(manifest: CourseManifest, courseFiles: string[]
|
|
86
|
+
declare function buildScorm2004ManifestFiles(manifest: CourseManifest, courseFiles: string[], hasComponentsBundle?: boolean): string[];
|
|
87
|
+
declare function generateScorm2004Manifest(manifest: CourseManifest, courseFiles: string[], options?: {
|
|
88
|
+
hasComponentsBundle?: boolean;
|
|
89
|
+
}): string;
|
|
70
90
|
|
|
71
|
-
export { type BuildHtmlOptions, type
|
|
91
|
+
export { type BuildHtmlOptions, type ExportTarget, type PackageOptions, type PackageSink, type PageTemplateOptions, assemblePackage, buildIndexHtml, buildLearnerPageHtml, buildManifestFileList, buildRuntimeConfig, buildScoIndexHtml, buildScorm2004ManifestFiles, collectFiles, courseSlug, generateImsManifest, generateScorm2004Manifest, manifestIdentifier, packageCourse, packageScorm2004, packageScorm2004Dir, packageStandaloneDir, safeJsonForHtml, scoLaunchPath, shouldSkipCourseFile, sliceAssessmentBundleForActivity };
|
package/dist/index.js
CHANGED
|
@@ -57,116 +57,124 @@ function safeJsonForHtml(value) {
|
|
|
57
57
|
return JSON.stringify(value).replace(/</g, "\\u003c");
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
-
// src/
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
manifest,
|
|
65
|
-
baseUrl: ".",
|
|
66
|
-
mode,
|
|
67
|
-
...activityId ? { activityId } : {},
|
|
68
|
-
...assessmentBundle ? {
|
|
69
|
-
assessments: assessmentBundle.assessments,
|
|
70
|
-
answerKeys: assessmentBundle.answerKeys,
|
|
71
|
-
assessmentConfigs: assessmentBundle.configs,
|
|
72
|
-
assessmentFeedback: assessmentBundle.feedback
|
|
73
|
-
} : {}
|
|
74
|
-
};
|
|
75
|
-
}
|
|
76
|
-
function buildScoIndexHtml(options) {
|
|
77
|
-
const { manifest, runtimeCss, activityId, componentsScript } = options;
|
|
78
|
-
const config = safeJsonForHtml(buildRuntimeConfig({ ...options, activityId }));
|
|
79
|
-
const componentsTag = componentsScript ? `<script type="module" src="${escapeHtml(componentsScript)}"></script>` : "";
|
|
60
|
+
// src/page-template.ts
|
|
61
|
+
import { escapeHtml } from "@lxpack/validators";
|
|
62
|
+
function buildLearnerPageHtml(options) {
|
|
63
|
+
const componentsTag = options.componentsScript ? `<script type="module" src="${escapeHtml(options.componentsScript)}"></script>` : "";
|
|
80
64
|
return `<!DOCTYPE html>
|
|
81
65
|
<html lang="en">
|
|
82
66
|
<head>
|
|
83
67
|
<meta charset="UTF-8">
|
|
84
68
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
85
|
-
<title>${escapeHtml(
|
|
86
|
-
<style>${runtimeCss}</style>
|
|
69
|
+
<title>${escapeHtml(options.title)}</title>
|
|
70
|
+
<style>${options.runtimeCss}</style>
|
|
87
71
|
</head>
|
|
88
72
|
<body>
|
|
89
73
|
<div id="lxpack-app"></div>
|
|
90
|
-
<script type="application/json" id="lxpack-config">${
|
|
74
|
+
<script type="application/json" id="lxpack-config">${options.configJson}</script>
|
|
91
75
|
<script>
|
|
92
76
|
window.__LXPACK_CONFIG__ = JSON.parse(document.getElementById('lxpack-config').textContent);
|
|
93
77
|
</script>
|
|
94
78
|
${componentsTag}
|
|
95
|
-
<script type="module" src="
|
|
79
|
+
<script type="module" src="${escapeHtml(options.runtimeScript)}"></script>
|
|
96
80
|
</body>
|
|
97
81
|
</html>`;
|
|
98
82
|
}
|
|
83
|
+
|
|
84
|
+
// src/build-html.ts
|
|
85
|
+
function buildRuntimeConfig(options) {
|
|
86
|
+
const { manifest, mode, assessmentBundle, activityId } = options;
|
|
87
|
+
return {
|
|
88
|
+
manifest,
|
|
89
|
+
baseUrl: activityId ? "../.." : ".",
|
|
90
|
+
mode,
|
|
91
|
+
...activityId ? { activityId } : {},
|
|
92
|
+
...assessmentBundle ? {
|
|
93
|
+
...Object.keys(assessmentBundle.assessments).length ? { assessments: assessmentBundle.assessments } : {},
|
|
94
|
+
...Object.keys(assessmentBundle.answerKeys).length ? { answerKeys: assessmentBundle.answerKeys } : {},
|
|
95
|
+
...Object.keys(assessmentBundle.configs ?? {}).length ? { assessmentConfigs: assessmentBundle.configs } : {},
|
|
96
|
+
...Object.keys(assessmentBundle.feedback ?? {}).length ? { assessmentFeedback: assessmentBundle.feedback } : {}
|
|
97
|
+
} : {}
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
function buildScoIndexHtml(options) {
|
|
101
|
+
const { manifest, runtimeCss, activityId, componentsScript } = options;
|
|
102
|
+
const config = safeJsonForHtml(buildRuntimeConfig({ ...options, activityId }));
|
|
103
|
+
return buildLearnerPageHtml({
|
|
104
|
+
title: `${manifest.title} \u2014 ${activityId}`,
|
|
105
|
+
runtimeCss,
|
|
106
|
+
configJson: config,
|
|
107
|
+
runtimeScript: "../../lxpack-runtime.js",
|
|
108
|
+
componentsScript
|
|
109
|
+
});
|
|
110
|
+
}
|
|
99
111
|
function buildIndexHtml(options) {
|
|
100
112
|
const { manifest, runtimeCss, componentsScript } = options;
|
|
101
113
|
const config = safeJsonForHtml(buildRuntimeConfig(options));
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
<style>${runtimeCss}</style>
|
|
110
|
-
</head>
|
|
111
|
-
<body>
|
|
112
|
-
<div id="lxpack-app"></div>
|
|
113
|
-
<script type="application/json" id="lxpack-config">${config}</script>
|
|
114
|
-
<script>
|
|
115
|
-
window.__LXPACK_CONFIG__ = JSON.parse(document.getElementById('lxpack-config').textContent);
|
|
116
|
-
</script>
|
|
117
|
-
${componentsTag}
|
|
118
|
-
<script type="module" src="./lxpack-runtime.js"></script>
|
|
119
|
-
</body>
|
|
120
|
-
</html>`;
|
|
121
|
-
}
|
|
122
|
-
function escapeHtml(text) {
|
|
123
|
-
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
114
|
+
return buildLearnerPageHtml({
|
|
115
|
+
title: manifest.title,
|
|
116
|
+
runtimeCss,
|
|
117
|
+
configJson: config,
|
|
118
|
+
runtimeScript: "./lxpack-runtime.js",
|
|
119
|
+
componentsScript
|
|
120
|
+
});
|
|
124
121
|
}
|
|
125
122
|
|
|
126
123
|
// src/package.ts
|
|
127
|
-
import { readFile, readdir, writeFile, mkdir
|
|
124
|
+
import { readFile, readdir, writeFile, mkdir } from "fs/promises";
|
|
128
125
|
import { existsSync } from "fs";
|
|
129
126
|
import { dirname, join, relative } from "path";
|
|
130
127
|
import JSZip from "jszip";
|
|
131
128
|
|
|
132
|
-
// src/
|
|
133
|
-
function
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
}
|
|
129
|
+
// src/assessment-slice.ts
|
|
130
|
+
function sliceAssessmentBundleForActivity(bundle, activityId, activityKind) {
|
|
131
|
+
if (activityKind === "assessment") {
|
|
132
|
+
const assessment = bundle.assessments[activityId];
|
|
133
|
+
if (!assessment) {
|
|
134
|
+
return { assessments: {}, answerKeys: {}, configs: {}, feedback: {} };
|
|
135
|
+
}
|
|
136
|
+
return {
|
|
137
|
+
assessments: { [activityId]: assessment },
|
|
138
|
+
answerKeys: bundle.answerKeys[activityId] ? { [activityId]: bundle.answerKeys[activityId] } : {},
|
|
139
|
+
configs: bundle.configs[activityId] ? { [activityId]: bundle.configs[activityId] } : {},
|
|
140
|
+
feedback: bundle.feedback[activityId] ? { [activityId]: bundle.feedback[activityId] } : {}
|
|
141
|
+
};
|
|
145
142
|
}
|
|
146
|
-
return
|
|
143
|
+
return {
|
|
144
|
+
assessments: {},
|
|
145
|
+
answerKeys: {},
|
|
146
|
+
configs: {},
|
|
147
|
+
feedback: {}
|
|
148
|
+
};
|
|
147
149
|
}
|
|
148
150
|
|
|
151
|
+
// src/activities.ts
|
|
152
|
+
import {
|
|
153
|
+
enumerateActivities
|
|
154
|
+
} from "@lxpack/validators";
|
|
155
|
+
|
|
149
156
|
// src/scorm2004-manifest.ts
|
|
150
157
|
function escapeXml2(text) {
|
|
151
|
-
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
158
|
+
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
152
159
|
}
|
|
153
160
|
function scoLaunchPath(activityId) {
|
|
154
161
|
return `sco/${activityId}/index.html`;
|
|
155
162
|
}
|
|
156
|
-
function buildScorm2004ManifestFiles(manifest, courseFiles) {
|
|
157
|
-
const activities =
|
|
163
|
+
function buildScorm2004ManifestFiles(manifest, courseFiles, hasComponentsBundle = false) {
|
|
164
|
+
const activities = enumerateActivities(manifest);
|
|
158
165
|
const scoFiles = activities.map((a) => scoLaunchPath(a.id));
|
|
159
166
|
return [
|
|
160
167
|
"lxpack-runtime.js",
|
|
161
|
-
"lxpack-components.js",
|
|
168
|
+
...hasComponentsBundle ? ["lxpack-components.js"] : [],
|
|
162
169
|
...scoFiles,
|
|
163
170
|
...courseFiles
|
|
164
171
|
];
|
|
165
172
|
}
|
|
166
|
-
function generateScorm2004Manifest(manifest, courseFiles) {
|
|
173
|
+
function generateScorm2004Manifest(manifest, courseFiles, options) {
|
|
174
|
+
const hasComponentsBundle = options?.hasComponentsBundle ?? false;
|
|
167
175
|
const identifier = manifestIdentifier(manifest);
|
|
168
176
|
const orgId = `${identifier}-org`;
|
|
169
|
-
const activities =
|
|
177
|
+
const activities = enumerateActivities(manifest);
|
|
170
178
|
const itemEntries = activities.map((activity) => {
|
|
171
179
|
const itemId = `item_${activity.id}`;
|
|
172
180
|
const resourceId = `res_${activity.id}`;
|
|
@@ -184,10 +192,11 @@ function generateScorm2004Manifest(manifest, courseFiles) {
|
|
|
184
192
|
const resources = activities.map((activity) => {
|
|
185
193
|
const resourceId = `res_${activity.id}`;
|
|
186
194
|
const href = scoLaunchPath(activity.id);
|
|
195
|
+
const componentFile = hasComponentsBundle ? `
|
|
196
|
+
<file href="lxpack-components.js"/>` : "";
|
|
187
197
|
return ` <resource identifier="${escapeXml2(resourceId)}" type="webcontent" adlcp:scormType="sco" href="${escapeXml2(href)}">
|
|
188
198
|
<file href="${escapeXml2(href)}"/>
|
|
189
|
-
<file href="lxpack-runtime.js"
|
|
190
|
-
<file href="lxpack-components.js"/>
|
|
199
|
+
<file href="lxpack-runtime.js"/>${componentFile}
|
|
191
200
|
</resource>`;
|
|
192
201
|
}).join("\n");
|
|
193
202
|
const orgSequencing = `
|
|
@@ -209,7 +218,8 @@ function generateScorm2004Manifest(manifest, courseFiles) {
|
|
|
209
218
|
<schemaversion>2004 4th Edition</schemaversion>
|
|
210
219
|
</metadata>
|
|
211
220
|
<organizations default="${escapeXml2(orgId)}">
|
|
212
|
-
<organization identifier="${escapeXml2(orgId)}"
|
|
221
|
+
<organization identifier="${escapeXml2(orgId)}">
|
|
222
|
+
<title>${escapeXml2(manifest.title)}</title>${orgSequencing}
|
|
213
223
|
${itemEntries}
|
|
214
224
|
</organization>
|
|
215
225
|
</organizations>
|
|
@@ -217,8 +227,7 @@ ${itemEntries}
|
|
|
217
227
|
${resources}
|
|
218
228
|
<resource identifier="shared_assets" type="webcontent" adlcp:scormType="asset">
|
|
219
229
|
${uniqueCourseFiles}
|
|
220
|
-
<file href="lxpack-runtime.js"/>
|
|
221
|
-
<file href="lxpack-components.js"/>
|
|
230
|
+
<file href="lxpack-runtime.js"/>${hasComponentsBundle ? '\n <file href="lxpack-components.js"/>' : ""}
|
|
222
231
|
</resource>
|
|
223
232
|
</resources>
|
|
224
233
|
</manifest>`;
|
|
@@ -265,43 +274,119 @@ async function collectFiles(dir, baseDir) {
|
|
|
265
274
|
function buildManifestFileList(courseFiles) {
|
|
266
275
|
return ["index.html", "lxpack-runtime.js", ...courseFiles.map((f) => f.path)];
|
|
267
276
|
}
|
|
268
|
-
async function
|
|
277
|
+
async function writeSingleScoArtifacts(options) {
|
|
269
278
|
const {
|
|
270
279
|
courseDir,
|
|
271
280
|
manifest,
|
|
272
|
-
|
|
281
|
+
target,
|
|
273
282
|
runtimeClientJs,
|
|
274
283
|
runtimeCss,
|
|
284
|
+
assessmentBundle,
|
|
275
285
|
componentsBundleJs,
|
|
276
|
-
|
|
286
|
+
writeFile: writeArtifact
|
|
287
|
+
} = options;
|
|
288
|
+
const mode = target === "scorm12" ? "scorm12" : "standalone";
|
|
289
|
+
const courseFiles = await collectFiles(courseDir, courseDir);
|
|
290
|
+
let fileCount = 0;
|
|
291
|
+
for (const file of courseFiles) {
|
|
292
|
+
const content = await readFile(file.fullPath);
|
|
293
|
+
await writeArtifact(file.path, content);
|
|
294
|
+
fileCount++;
|
|
295
|
+
}
|
|
296
|
+
const indexHtml = buildIndexHtml({
|
|
297
|
+
manifest,
|
|
298
|
+
runtimeCss,
|
|
299
|
+
mode,
|
|
300
|
+
assessmentBundle,
|
|
301
|
+
componentsScript: componentsBundleJs ? "./lxpack-components.js" : void 0
|
|
302
|
+
});
|
|
303
|
+
await writeArtifact("index.html", indexHtml);
|
|
304
|
+
await writeArtifact("lxpack-runtime.js", runtimeClientJs);
|
|
305
|
+
fileCount += 2;
|
|
306
|
+
if (componentsBundleJs) {
|
|
307
|
+
await writeArtifact("lxpack-components.js", componentsBundleJs);
|
|
308
|
+
fileCount++;
|
|
309
|
+
}
|
|
310
|
+
if (target === "scorm12") {
|
|
311
|
+
const manifestFiles = buildManifestFileList(courseFiles);
|
|
312
|
+
await writeArtifact(
|
|
313
|
+
"imsmanifest.xml",
|
|
314
|
+
generateImsManifest(manifest, manifestFiles)
|
|
315
|
+
);
|
|
316
|
+
fileCount++;
|
|
317
|
+
}
|
|
318
|
+
return { fileCount };
|
|
319
|
+
}
|
|
320
|
+
async function assemblePackage(options, sink) {
|
|
321
|
+
if (options.target === "scorm2004") {
|
|
322
|
+
return writeScorm2004Artifacts({ ...options, writeFile: sink.writeFile });
|
|
323
|
+
}
|
|
324
|
+
return writeSingleScoArtifacts({ ...options, writeFile: sink.writeFile });
|
|
325
|
+
}
|
|
326
|
+
async function writeScorm2004Artifacts(options) {
|
|
327
|
+
const {
|
|
328
|
+
courseDir,
|
|
329
|
+
manifest,
|
|
330
|
+
runtimeClientJs,
|
|
331
|
+
runtimeCss,
|
|
332
|
+
componentsBundleJs,
|
|
333
|
+
assessmentBundle,
|
|
334
|
+
writeFile: writeArtifact
|
|
277
335
|
} = options;
|
|
278
|
-
const zip = new JSZip();
|
|
279
336
|
const courseFiles = await collectFiles(courseDir, courseDir);
|
|
337
|
+
let fileCount = 0;
|
|
280
338
|
for (const file of courseFiles) {
|
|
281
339
|
const content = await readFile(file.fullPath);
|
|
282
|
-
|
|
340
|
+
await writeArtifact(file.path, content);
|
|
341
|
+
fileCount++;
|
|
283
342
|
}
|
|
284
|
-
|
|
343
|
+
await writeArtifact("lxpack-runtime.js", runtimeClientJs);
|
|
344
|
+
fileCount++;
|
|
285
345
|
if (componentsBundleJs) {
|
|
286
|
-
|
|
346
|
+
await writeArtifact("lxpack-components.js", componentsBundleJs);
|
|
347
|
+
fileCount++;
|
|
287
348
|
}
|
|
288
|
-
const activities =
|
|
349
|
+
const activities = enumerateActivities(manifest);
|
|
289
350
|
for (const activity of activities) {
|
|
351
|
+
const scoBundle = assessmentBundle != null ? sliceAssessmentBundleForActivity(
|
|
352
|
+
assessmentBundle,
|
|
353
|
+
activity.id,
|
|
354
|
+
activity.kind
|
|
355
|
+
) : void 0;
|
|
290
356
|
const html = buildScoIndexHtml({
|
|
291
357
|
manifest,
|
|
292
358
|
runtimeCss,
|
|
293
359
|
mode: "scorm2004",
|
|
294
360
|
activityId: activity.id,
|
|
295
|
-
assessmentBundle,
|
|
361
|
+
assessmentBundle: scoBundle,
|
|
296
362
|
componentsScript: componentsBundleJs ? "../../lxpack-components.js" : void 0
|
|
297
363
|
});
|
|
298
|
-
|
|
364
|
+
await writeArtifact(`sco/${activity.id}/index.html`, html);
|
|
365
|
+
fileCount++;
|
|
299
366
|
}
|
|
300
367
|
const manifestFiles = buildScorm2004ManifestFiles(
|
|
301
368
|
manifest,
|
|
302
|
-
courseFiles.map((f) => f.path)
|
|
369
|
+
courseFiles.map((f) => f.path),
|
|
370
|
+
Boolean(componentsBundleJs)
|
|
371
|
+
);
|
|
372
|
+
await writeArtifact(
|
|
373
|
+
"imsmanifest.xml",
|
|
374
|
+
generateScorm2004Manifest(manifest, manifestFiles, {
|
|
375
|
+
hasComponentsBundle: Boolean(componentsBundleJs)
|
|
376
|
+
})
|
|
303
377
|
);
|
|
304
|
-
|
|
378
|
+
fileCount++;
|
|
379
|
+
return { fileCount };
|
|
380
|
+
}
|
|
381
|
+
async function packageScorm2004(options) {
|
|
382
|
+
const { outputPath, ...rest } = options;
|
|
383
|
+
const zip = new JSZip();
|
|
384
|
+
const { fileCount } = await writeScorm2004Artifacts({
|
|
385
|
+
...rest,
|
|
386
|
+
writeFile: async (relPath, content) => {
|
|
387
|
+
zip.file(relPath, content);
|
|
388
|
+
}
|
|
389
|
+
});
|
|
305
390
|
const buffer = await zip.generateAsync({
|
|
306
391
|
type: "nodebuffer",
|
|
307
392
|
compression: "DEFLATE"
|
|
@@ -309,49 +394,36 @@ async function packageScorm2004(options) {
|
|
|
309
394
|
await writeFile(outputPath, buffer);
|
|
310
395
|
return {
|
|
311
396
|
outputPath,
|
|
312
|
-
fileCount: Object.keys(zip.files).length
|
|
397
|
+
fileCount: Object.keys(zip.files).length || fileCount
|
|
313
398
|
};
|
|
314
399
|
}
|
|
400
|
+
async function packageScorm2004Dir(options) {
|
|
401
|
+
const { outputDir, ...rest } = options;
|
|
402
|
+
await mkdir(outputDir, { recursive: true });
|
|
403
|
+
const { fileCount } = await writeScorm2004Artifacts({
|
|
404
|
+
...rest,
|
|
405
|
+
writeFile: async (relPath, content) => {
|
|
406
|
+
const dest = join(outputDir, relPath);
|
|
407
|
+
const destDir = dirname(dest);
|
|
408
|
+
if (!existsSync(destDir)) {
|
|
409
|
+
await mkdir(destDir, { recursive: true });
|
|
410
|
+
}
|
|
411
|
+
await writeFile(dest, content);
|
|
412
|
+
}
|
|
413
|
+
});
|
|
414
|
+
return { outputDir, fileCount };
|
|
415
|
+
}
|
|
315
416
|
async function packageCourse(options) {
|
|
316
417
|
if (options.target === "scorm2004") {
|
|
317
418
|
return packageScorm2004(options);
|
|
318
419
|
}
|
|
319
|
-
const {
|
|
320
|
-
courseDir,
|
|
321
|
-
manifest,
|
|
322
|
-
outputPath,
|
|
323
|
-
target,
|
|
324
|
-
runtimeClientJs,
|
|
325
|
-
runtimeCss,
|
|
326
|
-
assessmentBundle,
|
|
327
|
-
componentsBundleJs
|
|
328
|
-
} = options;
|
|
420
|
+
const { outputPath, ...rest } = options;
|
|
329
421
|
const zip = new JSZip();
|
|
330
|
-
const
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
zip.file(file.path, content);
|
|
335
|
-
}
|
|
336
|
-
const indexHtml = buildIndexHtml({
|
|
337
|
-
manifest,
|
|
338
|
-
runtimeCss,
|
|
339
|
-
mode,
|
|
340
|
-
assessmentBundle,
|
|
341
|
-
componentsScript: componentsBundleJs ? "./lxpack-components.js" : void 0
|
|
422
|
+
const { fileCount } = await assemblePackage(rest, {
|
|
423
|
+
writeFile: async (relPath, content) => {
|
|
424
|
+
zip.file(relPath, content);
|
|
425
|
+
}
|
|
342
426
|
});
|
|
343
|
-
zip.file("index.html", indexHtml);
|
|
344
|
-
zip.file("lxpack-runtime.js", runtimeClientJs);
|
|
345
|
-
if (componentsBundleJs) {
|
|
346
|
-
zip.file("lxpack-components.js", componentsBundleJs);
|
|
347
|
-
}
|
|
348
|
-
if (target === "scorm12") {
|
|
349
|
-
const manifestFiles = buildManifestFileList(courseFiles);
|
|
350
|
-
zip.file(
|
|
351
|
-
"imsmanifest.xml",
|
|
352
|
-
generateImsManifest(manifest, manifestFiles)
|
|
353
|
-
);
|
|
354
|
-
}
|
|
355
427
|
const buffer = await zip.generateAsync({
|
|
356
428
|
type: "nodebuffer",
|
|
357
429
|
compression: "DEFLATE"
|
|
@@ -359,7 +431,7 @@ async function packageCourse(options) {
|
|
|
359
431
|
await writeFile(outputPath, buffer);
|
|
360
432
|
return {
|
|
361
433
|
outputPath,
|
|
362
|
-
fileCount: Object.keys(zip.files).length
|
|
434
|
+
fileCount: Object.keys(zip.files).length || fileCount
|
|
363
435
|
};
|
|
364
436
|
}
|
|
365
437
|
async function packageStandaloneDir(options) {
|
|
@@ -370,42 +442,41 @@ async function packageStandaloneDir(options) {
|
|
|
370
442
|
target,
|
|
371
443
|
runtimeClientJs,
|
|
372
444
|
runtimeCss,
|
|
373
|
-
assessmentBundle
|
|
445
|
+
assessmentBundle,
|
|
446
|
+
componentsBundleJs
|
|
374
447
|
} = options;
|
|
375
448
|
await mkdir(outputDir, { recursive: true });
|
|
376
|
-
const
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
449
|
+
const { fileCount } = await assemblePackage(
|
|
450
|
+
{
|
|
451
|
+
courseDir,
|
|
452
|
+
manifest,
|
|
453
|
+
target,
|
|
454
|
+
runtimeClientJs,
|
|
455
|
+
runtimeCss,
|
|
456
|
+
assessmentBundle,
|
|
457
|
+
componentsBundleJs
|
|
458
|
+
},
|
|
459
|
+
{
|
|
460
|
+
writeFile: async (relPath, content) => {
|
|
461
|
+
const dest = join(outputDir, relPath);
|
|
462
|
+
const destDir = dirname(dest);
|
|
463
|
+
if (!existsSync(destDir)) {
|
|
464
|
+
await mkdir(destDir, { recursive: true });
|
|
465
|
+
}
|
|
466
|
+
if (typeof content === "string") {
|
|
467
|
+
await writeFile(dest, content);
|
|
468
|
+
} else {
|
|
469
|
+
await writeFile(dest, content);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
384
472
|
}
|
|
385
|
-
|
|
386
|
-
fileCount++;
|
|
387
|
-
}
|
|
388
|
-
const indexHtml = buildIndexHtml({
|
|
389
|
-
manifest,
|
|
390
|
-
runtimeCss,
|
|
391
|
-
mode,
|
|
392
|
-
assessmentBundle
|
|
393
|
-
});
|
|
394
|
-
await writeFile(join(outputDir, "index.html"), indexHtml);
|
|
395
|
-
await writeFile(join(outputDir, "lxpack-runtime.js"), runtimeClientJs);
|
|
396
|
-
fileCount += 2;
|
|
397
|
-
if (target === "scorm12") {
|
|
398
|
-
const manifestFiles = buildManifestFileList(courseFiles);
|
|
399
|
-
await writeFile(
|
|
400
|
-
join(outputDir, "imsmanifest.xml"),
|
|
401
|
-
generateImsManifest(manifest, manifestFiles)
|
|
402
|
-
);
|
|
403
|
-
fileCount++;
|
|
404
|
-
}
|
|
473
|
+
);
|
|
405
474
|
return { outputDir, fileCount };
|
|
406
475
|
}
|
|
407
476
|
export {
|
|
477
|
+
assemblePackage,
|
|
408
478
|
buildIndexHtml,
|
|
479
|
+
buildLearnerPageHtml,
|
|
409
480
|
buildManifestFileList,
|
|
410
481
|
buildRuntimeConfig,
|
|
411
482
|
buildScoIndexHtml,
|
|
@@ -414,12 +485,14 @@ export {
|
|
|
414
485
|
courseSlug,
|
|
415
486
|
generateImsManifest,
|
|
416
487
|
generateScorm2004Manifest,
|
|
417
|
-
listCourseActivities,
|
|
488
|
+
enumerateActivities as listCourseActivities,
|
|
418
489
|
manifestIdentifier,
|
|
419
490
|
packageCourse,
|
|
420
491
|
packageScorm2004,
|
|
492
|
+
packageScorm2004Dir,
|
|
421
493
|
packageStandaloneDir,
|
|
422
494
|
safeJsonForHtml,
|
|
423
495
|
scoLaunchPath,
|
|
424
|
-
shouldSkipCourseFile
|
|
496
|
+
shouldSkipCourseFile,
|
|
497
|
+
sliceAssessmentBundleForActivity
|
|
425
498
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lxpack/scorm",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"description": "SCORM and standalone HTML export for LXPack",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
],
|
|
40
40
|
"dependencies": {
|
|
41
41
|
"jszip": "^3.10.1",
|
|
42
|
-
"@lxpack/validators": "0.2.
|
|
42
|
+
"@lxpack/validators": "0.2.2"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
45
|
"@types/node": "^22.13.10",
|