@lxpack/scorm 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +48 -13
- package/dist/index.d.ts +25 -10
- package/dist/index.js +191 -5
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -5,15 +5,16 @@
|
|
|
5
5
|
[](https://github.com/eddiethedean/lxpack/blob/main/LICENSE)
|
|
6
6
|
[](https://nodejs.org/)
|
|
7
7
|
|
|
8
|
-
SCORM 1.2 and standalone HTML export for LXPack courses.
|
|
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.
|
|
10
|
+
Part of [LXPack](https://github.com/eddiethedean/lxpack) — an AI-native learning experience compiler and runtime (**v0.2.0**).
|
|
11
11
|
|
|
12
12
|
| Related | Package |
|
|
13
13
|
|---------|---------|
|
|
14
14
|
| CLI | [`@lxpack/cli`](../cli/README.md) |
|
|
15
15
|
| Manifest validation | [`@lxpack/validators`](../validators/README.md) |
|
|
16
16
|
| Embedded client | [`@lxpack/runtime`](../runtime/README.md) |
|
|
17
|
+
| Components bundle | [`@lxpack/components`](../components/README.md) |
|
|
17
18
|
|
|
18
19
|
## Install
|
|
19
20
|
|
|
@@ -25,6 +26,8 @@ Requires Node.js 20+.
|
|
|
25
26
|
|
|
26
27
|
## Usage
|
|
27
28
|
|
|
29
|
+
### SCORM 1.2 or standalone ZIP
|
|
30
|
+
|
|
28
31
|
```ts
|
|
29
32
|
import { readFile } from "node:fs/promises";
|
|
30
33
|
import type { CourseManifest } from "@lxpack/validators";
|
|
@@ -44,6 +47,7 @@ const assessmentBundle = await buildRuntimeAssessmentBundle(courseDir, manifest)
|
|
|
44
47
|
|
|
45
48
|
const runtimeClientJs = await readFile("path/to/client.js", "utf8");
|
|
46
49
|
const runtimeCss = await readFile("path/to/styles.css", "utf8");
|
|
50
|
+
const componentsJs = await readFile("path/to/bundle.js", "utf8"); // optional
|
|
47
51
|
|
|
48
52
|
await packageCourse({
|
|
49
53
|
courseDir,
|
|
@@ -52,10 +56,31 @@ await packageCourse({
|
|
|
52
56
|
outputPath: "/path/to/output/course-scorm12.zip",
|
|
53
57
|
runtimeClientJs,
|
|
54
58
|
runtimeCss,
|
|
59
|
+
componentsJs,
|
|
60
|
+
assessmentBundle,
|
|
61
|
+
});
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### SCORM 2004 multi-SCO
|
|
65
|
+
|
|
66
|
+
```ts
|
|
67
|
+
import { packageScorm2004, buildScoIndexHtml, listCourseActivities } from "@lxpack/scorm";
|
|
68
|
+
|
|
69
|
+
const activities = listCourseActivities(manifest);
|
|
70
|
+
|
|
71
|
+
await packageScorm2004({
|
|
72
|
+
courseDir,
|
|
73
|
+
manifest,
|
|
74
|
+
outputPath: "/path/to/output/course-scorm2004.zip",
|
|
75
|
+
runtimeClientJs,
|
|
76
|
+
runtimeCss,
|
|
77
|
+
componentsJs,
|
|
55
78
|
assessmentBundle,
|
|
56
79
|
});
|
|
57
80
|
```
|
|
58
81
|
|
|
82
|
+
Each activity gets `sco/<activityId>/index.html` (via `buildScoIndexHtml`). Shared assets include `lxpack-runtime.js` and `lxpack-components.js`. `generateScorm2004Manifest` emits `imsmanifest.xml` with IMS Simple Sequencing metadata.
|
|
83
|
+
|
|
59
84
|
### Standalone directory
|
|
60
85
|
|
|
61
86
|
```ts
|
|
@@ -66,6 +91,7 @@ await packageStandaloneDir({
|
|
|
66
91
|
outputDir: "/path/to/output/standalone",
|
|
67
92
|
runtimeClientJs,
|
|
68
93
|
runtimeCss,
|
|
94
|
+
componentsJs,
|
|
69
95
|
assessmentBundle,
|
|
70
96
|
});
|
|
71
97
|
```
|
|
@@ -77,16 +103,21 @@ Most users should use `lxpack build` from [`@lxpack/cli`](../cli/README.md) inst
|
|
|
77
103
|
| Export | Description |
|
|
78
104
|
|--------|-------------|
|
|
79
105
|
| `packageCourse(options)` | Build a SCORM 1.2 or standalone ZIP |
|
|
80
|
-
| `
|
|
106
|
+
| `packageScorm2004(options)` | Build a SCORM 2004 multi-SCO ZIP |
|
|
107
|
+
| `packageStandaloneDir(options)` | Write an unpacked directory |
|
|
108
|
+
| `listCourseActivities(manifest)` | Ordered activities for multi-SCO layout |
|
|
81
109
|
| `generateImsManifest(manifest, files)` | SCORM 1.2 `imsmanifest.xml` |
|
|
82
|
-
| `
|
|
110
|
+
| `generateScorm2004Manifest(...)` | SCORM 2004 manifest with sequencing |
|
|
111
|
+
| `buildIndexHtml(options)` | Single-SCO course shell HTML |
|
|
112
|
+
| `buildScoIndexHtml(options)` | Per-activity launch HTML for SCORM 2004 |
|
|
113
|
+
| `scoLaunchPath(activityId)` | Relative launch path (`sco/<id>/index.html`) |
|
|
83
114
|
| `buildRuntimeConfig(options)` | Config object passed to `safeJsonForHtml` |
|
|
84
115
|
| `collectFiles(courseDir, baseDir)` | Course assets for packaging (respects skip rules) |
|
|
85
116
|
| `shouldSkipCourseFile(rel)` | Whether a relative path is omitted from exports |
|
|
86
117
|
| `buildManifestFileList(courseFiles)` | File list for `imsmanifest.xml` |
|
|
87
118
|
| `safeJsonForHtml(value)` | JSON safe for `<script type="application/json">` blocks |
|
|
88
119
|
| `courseSlug(manifest)` | Stable slug for ZIP names and manifest identifiers |
|
|
89
|
-
| `ExportTarget` | `"scorm12"` \| `"standalone"` |
|
|
120
|
+
| `ExportTarget` | `"scorm12"` \| `"scorm2004"` \| `"standalone"` |
|
|
90
121
|
|
|
91
122
|
### `PackageOptions`
|
|
92
123
|
|
|
@@ -94,28 +125,30 @@ Most users should use `lxpack build` from [`@lxpack/cli`](../cli/README.md) inst
|
|
|
94
125
|
|-------|-------------|
|
|
95
126
|
| `courseDir` | Course root directory |
|
|
96
127
|
| `manifest` | Parsed `course.yaml` |
|
|
97
|
-
| `outputPath` | Destination ZIP path
|
|
98
|
-
| `target` | `scorm12` or `standalone` |
|
|
128
|
+
| `outputPath` | Destination ZIP path |
|
|
129
|
+
| `target` | `scorm12`, `scorm2004`, or `standalone` |
|
|
99
130
|
| `runtimeClientJs` | Contents of `@lxpack/runtime/client` bundle |
|
|
100
131
|
| `runtimeCss` | Runtime stylesheet |
|
|
101
|
-
| `
|
|
132
|
+
| `componentsJs` | Optional `@lxpack/components/bundle` for component lessons |
|
|
133
|
+
| `assessmentBundle` | From `buildRuntimeAssessmentBundle()` (assessments, keys, configs, feedback) |
|
|
102
134
|
|
|
103
135
|
## What gets packaged
|
|
104
136
|
|
|
105
137
|
**Included**
|
|
106
138
|
|
|
107
|
-
- Lessons, interactions, and
|
|
108
|
-
- `index.html` — shell with embedded manifest + assessment config
|
|
139
|
+
- Lessons, interactions, assets, and component overrides referenced by the manifest
|
|
109
140
|
- `lxpack-runtime.js` — browser client bundle
|
|
110
|
-
- `
|
|
141
|
+
- `lxpack-components.js` — when component lessons are used
|
|
142
|
+
- **SCORM 1.2 / standalone:** root `index.html` with embedded config
|
|
143
|
+
- **SCORM 2004:** `sco/<activityId>/index.html` per activity + `imsmanifest.xml`
|
|
111
144
|
|
|
112
145
|
**Excluded** (`shouldSkipCourseFile`)
|
|
113
146
|
|
|
114
147
|
- `course.yaml`, `lxpack.config.json`, `.lxpack/`
|
|
115
148
|
- `assessments/**` — author YAML; assessments are embedded in config instead
|
|
116
|
-
- Root `index.html` if present (packager generates
|
|
149
|
+
- Root `index.html` if present (packager generates entry pages)
|
|
117
150
|
|
|
118
|
-
Answer keys are only present inside the JSON config block (with `<` escaped via `safeJsonForHtml`), not as downloadable files.
|
|
151
|
+
Answer keys and feedback text are only present inside the JSON config block (with `<` escaped via `safeJsonForHtml`), not as downloadable files.
|
|
119
152
|
|
|
120
153
|
## Development
|
|
121
154
|
|
|
@@ -130,6 +163,8 @@ pnpm --filter @lxpack/scorm typecheck
|
|
|
130
163
|
## Links
|
|
131
164
|
|
|
132
165
|
- [LXPack repository](https://github.com/eddiethedean/lxpack)
|
|
166
|
+
- [Documentation index](https://github.com/eddiethedean/lxpack/blob/main/docs/README.md)
|
|
167
|
+
- [Roadmap & phases](https://github.com/eddiethedean/lxpack/blob/main/docs/ROADMAP.md)
|
|
133
168
|
- [Changelog](https://github.com/eddiethedean/lxpack/blob/main/CHANGELOG.md)
|
|
134
169
|
|
|
135
170
|
## License
|
package/dist/index.d.ts
CHANGED
|
@@ -6,19 +6,18 @@ declare function generateImsManifest(manifest: CourseManifest, files: string[],
|
|
|
6
6
|
interface BuildHtmlOptions {
|
|
7
7
|
manifest: CourseManifest;
|
|
8
8
|
runtimeCss: string;
|
|
9
|
-
mode: "standalone" | "scorm12";
|
|
9
|
+
mode: "standalone" | "scorm12" | "scorm2004";
|
|
10
|
+
activityId?: string;
|
|
10
11
|
assessmentBundle?: RuntimeAssessmentBundle;
|
|
12
|
+
componentsScript?: string;
|
|
11
13
|
}
|
|
12
|
-
declare function buildRuntimeConfig(options: BuildHtmlOptions):
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
assessments?: RuntimeAssessmentBundle["assessments"];
|
|
17
|
-
answerKeys?: RuntimeAssessmentBundle["answerKeys"];
|
|
18
|
-
};
|
|
14
|
+
declare function buildRuntimeConfig(options: BuildHtmlOptions): Record<string, unknown>;
|
|
15
|
+
declare function buildScoIndexHtml(options: BuildHtmlOptions & {
|
|
16
|
+
activityId: string;
|
|
17
|
+
}): string;
|
|
19
18
|
declare function buildIndexHtml(options: BuildHtmlOptions): string;
|
|
20
19
|
|
|
21
|
-
type ExportTarget = "scorm12" | "standalone";
|
|
20
|
+
type ExportTarget = "scorm12" | "scorm2004" | "standalone";
|
|
22
21
|
interface PackageOptions {
|
|
23
22
|
courseDir: string;
|
|
24
23
|
manifest: CourseManifest;
|
|
@@ -26,6 +25,7 @@ interface PackageOptions {
|
|
|
26
25
|
target: ExportTarget;
|
|
27
26
|
runtimeClientJs: string;
|
|
28
27
|
runtimeCss: string;
|
|
28
|
+
componentsBundleJs?: string;
|
|
29
29
|
assessmentBundle?: RuntimeAssessmentBundle;
|
|
30
30
|
}
|
|
31
31
|
declare function shouldSkipCourseFile(rel: string): boolean;
|
|
@@ -36,6 +36,10 @@ declare function collectFiles(dir: string, baseDir: string): Promise<Array<{
|
|
|
36
36
|
declare function buildManifestFileList(courseFiles: Array<{
|
|
37
37
|
path: string;
|
|
38
38
|
}>): string[];
|
|
39
|
+
declare function packageScorm2004(options: PackageOptions): Promise<{
|
|
40
|
+
outputPath: string;
|
|
41
|
+
fileCount: number;
|
|
42
|
+
}>;
|
|
39
43
|
declare function packageCourse(options: PackageOptions): Promise<{
|
|
40
44
|
outputPath: string;
|
|
41
45
|
fileCount: number;
|
|
@@ -53,4 +57,15 @@ declare function safeJsonForHtml(value: unknown): string;
|
|
|
53
57
|
/** Stable slug for ZIP filenames and SCORM identifiers. */
|
|
54
58
|
declare function courseSlug(manifest: CourseManifest): string;
|
|
55
59
|
|
|
56
|
-
|
|
60
|
+
interface CourseActivity {
|
|
61
|
+
id: string;
|
|
62
|
+
title: string;
|
|
63
|
+
kind: "lesson" | "assessment";
|
|
64
|
+
}
|
|
65
|
+
declare function listCourseActivities(manifest: CourseManifest): CourseActivity[];
|
|
66
|
+
|
|
67
|
+
declare function scoLaunchPath(activityId: string): string;
|
|
68
|
+
declare function buildScorm2004ManifestFiles(manifest: CourseManifest, courseFiles: string[]): string[];
|
|
69
|
+
declare function generateScorm2004Manifest(manifest: CourseManifest, courseFiles: string[]): string;
|
|
70
|
+
|
|
71
|
+
export { type BuildHtmlOptions, type CourseActivity, type ExportTarget, type PackageOptions, buildIndexHtml, buildManifestFileList, buildRuntimeConfig, buildScoIndexHtml, buildScorm2004ManifestFiles, collectFiles, courseSlug, generateImsManifest, generateScorm2004Manifest, listCourseActivities, manifestIdentifier, packageCourse, packageScorm2004, packageStandaloneDir, safeJsonForHtml, scoLaunchPath, shouldSkipCourseFile };
|
package/dist/index.js
CHANGED
|
@@ -59,20 +59,47 @@ function safeJsonForHtml(value) {
|
|
|
59
59
|
|
|
60
60
|
// src/build-html.ts
|
|
61
61
|
function buildRuntimeConfig(options) {
|
|
62
|
-
const { manifest, mode, assessmentBundle } = options;
|
|
62
|
+
const { manifest, mode, assessmentBundle, activityId } = options;
|
|
63
63
|
return {
|
|
64
64
|
manifest,
|
|
65
65
|
baseUrl: ".",
|
|
66
66
|
mode,
|
|
67
|
+
...activityId ? { activityId } : {},
|
|
67
68
|
...assessmentBundle ? {
|
|
68
69
|
assessments: assessmentBundle.assessments,
|
|
69
|
-
answerKeys: assessmentBundle.answerKeys
|
|
70
|
+
answerKeys: assessmentBundle.answerKeys,
|
|
71
|
+
assessmentConfigs: assessmentBundle.configs,
|
|
72
|
+
assessmentFeedback: assessmentBundle.feedback
|
|
70
73
|
} : {}
|
|
71
74
|
};
|
|
72
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>` : "";
|
|
80
|
+
return `<!DOCTYPE html>
|
|
81
|
+
<html lang="en">
|
|
82
|
+
<head>
|
|
83
|
+
<meta charset="UTF-8">
|
|
84
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
85
|
+
<title>${escapeHtml(manifest.title)} \u2014 ${escapeHtml(activityId)}</title>
|
|
86
|
+
<style>${runtimeCss}</style>
|
|
87
|
+
</head>
|
|
88
|
+
<body>
|
|
89
|
+
<div id="lxpack-app"></div>
|
|
90
|
+
<script type="application/json" id="lxpack-config">${config}</script>
|
|
91
|
+
<script>
|
|
92
|
+
window.__LXPACK_CONFIG__ = JSON.parse(document.getElementById('lxpack-config').textContent);
|
|
93
|
+
</script>
|
|
94
|
+
${componentsTag}
|
|
95
|
+
<script type="module" src="../../lxpack-runtime.js"></script>
|
|
96
|
+
</body>
|
|
97
|
+
</html>`;
|
|
98
|
+
}
|
|
73
99
|
function buildIndexHtml(options) {
|
|
74
|
-
const { manifest, runtimeCss } = options;
|
|
100
|
+
const { manifest, runtimeCss, componentsScript } = options;
|
|
75
101
|
const config = safeJsonForHtml(buildRuntimeConfig(options));
|
|
102
|
+
const componentsTag = componentsScript ? `<script type="module" src="${escapeHtml(componentsScript)}"></script>` : "";
|
|
76
103
|
return `<!DOCTYPE html>
|
|
77
104
|
<html lang="en">
|
|
78
105
|
<head>
|
|
@@ -87,6 +114,7 @@ function buildIndexHtml(options) {
|
|
|
87
114
|
<script>
|
|
88
115
|
window.__LXPACK_CONFIG__ = JSON.parse(document.getElementById('lxpack-config').textContent);
|
|
89
116
|
</script>
|
|
117
|
+
${componentsTag}
|
|
90
118
|
<script type="module" src="./lxpack-runtime.js"></script>
|
|
91
119
|
</body>
|
|
92
120
|
</html>`;
|
|
@@ -100,6 +128,103 @@ import { readFile, readdir, writeFile, mkdir, cp } from "fs/promises";
|
|
|
100
128
|
import { existsSync } from "fs";
|
|
101
129
|
import { dirname, join, relative } from "path";
|
|
102
130
|
import JSZip from "jszip";
|
|
131
|
+
|
|
132
|
+
// src/activities.ts
|
|
133
|
+
function listCourseActivities(manifest) {
|
|
134
|
+
const activities = manifest.lessons.map((lesson) => ({
|
|
135
|
+
id: lesson.id,
|
|
136
|
+
title: lesson.title ?? lesson.id,
|
|
137
|
+
kind: "lesson"
|
|
138
|
+
}));
|
|
139
|
+
for (const ref of manifest.assessments ?? []) {
|
|
140
|
+
activities.push({
|
|
141
|
+
id: ref.id,
|
|
142
|
+
title: ref.id.replace(/_/g, " "),
|
|
143
|
+
kind: "assessment"
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
return activities;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// src/scorm2004-manifest.ts
|
|
150
|
+
function escapeXml2(text) {
|
|
151
|
+
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
152
|
+
}
|
|
153
|
+
function scoLaunchPath(activityId) {
|
|
154
|
+
return `sco/${activityId}/index.html`;
|
|
155
|
+
}
|
|
156
|
+
function buildScorm2004ManifestFiles(manifest, courseFiles) {
|
|
157
|
+
const activities = listCourseActivities(manifest);
|
|
158
|
+
const scoFiles = activities.map((a) => scoLaunchPath(a.id));
|
|
159
|
+
return [
|
|
160
|
+
"lxpack-runtime.js",
|
|
161
|
+
"lxpack-components.js",
|
|
162
|
+
...scoFiles,
|
|
163
|
+
...courseFiles
|
|
164
|
+
];
|
|
165
|
+
}
|
|
166
|
+
function generateScorm2004Manifest(manifest, courseFiles) {
|
|
167
|
+
const identifier = manifestIdentifier(manifest);
|
|
168
|
+
const orgId = `${identifier}-org`;
|
|
169
|
+
const activities = listCourseActivities(manifest);
|
|
170
|
+
const itemEntries = activities.map((activity) => {
|
|
171
|
+
const itemId = `item_${activity.id}`;
|
|
172
|
+
const resourceId = `res_${activity.id}`;
|
|
173
|
+
const sequencing = activity.kind === "assessment" ? `
|
|
174
|
+
<imsss:sequencing>
|
|
175
|
+
<imsss:deliveryControls tracked="true" completionSetByContent="true" objectiveSetByContent="true"/>
|
|
176
|
+
</imsss:sequencing>` : `
|
|
177
|
+
<imsss:sequencing>
|
|
178
|
+
<imsss:deliveryControls tracked="true" completionSetByContent="true"/>
|
|
179
|
+
</imsss:sequencing>`;
|
|
180
|
+
return ` <item identifier="${escapeXml2(itemId)}" identifierref="${escapeXml2(resourceId)}">
|
|
181
|
+
<title>${escapeXml2(activity.title)}</title>${sequencing}
|
|
182
|
+
</item>`;
|
|
183
|
+
}).join("\n");
|
|
184
|
+
const resources = activities.map((activity) => {
|
|
185
|
+
const resourceId = `res_${activity.id}`;
|
|
186
|
+
const href = scoLaunchPath(activity.id);
|
|
187
|
+
return ` <resource identifier="${escapeXml2(resourceId)}" type="webcontent" adlcp:scormType="sco" href="${escapeXml2(href)}">
|
|
188
|
+
<file href="${escapeXml2(href)}"/>
|
|
189
|
+
<file href="lxpack-runtime.js"/>
|
|
190
|
+
<file href="lxpack-components.js"/>
|
|
191
|
+
</resource>`;
|
|
192
|
+
}).join("\n");
|
|
193
|
+
const orgSequencing = `
|
|
194
|
+
<imsss:sequencing>
|
|
195
|
+
<imsss:controlMode choice="true" flow="true"/>
|
|
196
|
+
</imsss:sequencing>`;
|
|
197
|
+
const uniqueCourseFiles = [...new Set(courseFiles)].filter((f) => !f.startsWith("sco/")).map(
|
|
198
|
+
(href) => ` <file href="${escapeXml2(href)}"/>`
|
|
199
|
+
).join("\n");
|
|
200
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
201
|
+
<manifest identifier="${escapeXml2(identifier)}" version="${escapeXml2(manifest.version)}"
|
|
202
|
+
xmlns="http://www.imsglobal.org/xsd/imscp_v1p1"
|
|
203
|
+
xmlns:adlcp="http://www.adlnet.org/xsd/adlcp_v1p3"
|
|
204
|
+
xmlns:adlseq="http://www.adlnet.org/xsd/adlseq_v1p3"
|
|
205
|
+
xmlns:imsss="http://www.imsglobal.org/xsd/imsss"
|
|
206
|
+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
|
207
|
+
<metadata>
|
|
208
|
+
<schema>ADL SCORM</schema>
|
|
209
|
+
<schemaversion>2004 4th Edition</schemaversion>
|
|
210
|
+
</metadata>
|
|
211
|
+
<organizations default="${escapeXml2(orgId)}">
|
|
212
|
+
<organization identifier="${escapeXml2(orgId)}">${orgSequencing}
|
|
213
|
+
${itemEntries}
|
|
214
|
+
</organization>
|
|
215
|
+
</organizations>
|
|
216
|
+
<resources>
|
|
217
|
+
${resources}
|
|
218
|
+
<resource identifier="shared_assets" type="webcontent" adlcp:scormType="asset">
|
|
219
|
+
${uniqueCourseFiles}
|
|
220
|
+
<file href="lxpack-runtime.js"/>
|
|
221
|
+
<file href="lxpack-components.js"/>
|
|
222
|
+
</resource>
|
|
223
|
+
</resources>
|
|
224
|
+
</manifest>`;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// src/package.ts
|
|
103
228
|
var SKIP_FILES = /* @__PURE__ */ new Set([
|
|
104
229
|
"course.yaml",
|
|
105
230
|
"lxpack.config.ts",
|
|
@@ -140,7 +265,57 @@ async function collectFiles(dir, baseDir) {
|
|
|
140
265
|
function buildManifestFileList(courseFiles) {
|
|
141
266
|
return ["index.html", "lxpack-runtime.js", ...courseFiles.map((f) => f.path)];
|
|
142
267
|
}
|
|
268
|
+
async function packageScorm2004(options) {
|
|
269
|
+
const {
|
|
270
|
+
courseDir,
|
|
271
|
+
manifest,
|
|
272
|
+
outputPath,
|
|
273
|
+
runtimeClientJs,
|
|
274
|
+
runtimeCss,
|
|
275
|
+
componentsBundleJs,
|
|
276
|
+
assessmentBundle
|
|
277
|
+
} = options;
|
|
278
|
+
const zip = new JSZip();
|
|
279
|
+
const courseFiles = await collectFiles(courseDir, courseDir);
|
|
280
|
+
for (const file of courseFiles) {
|
|
281
|
+
const content = await readFile(file.fullPath);
|
|
282
|
+
zip.file(file.path, content);
|
|
283
|
+
}
|
|
284
|
+
zip.file("lxpack-runtime.js", runtimeClientJs);
|
|
285
|
+
if (componentsBundleJs) {
|
|
286
|
+
zip.file("lxpack-components.js", componentsBundleJs);
|
|
287
|
+
}
|
|
288
|
+
const activities = listCourseActivities(manifest);
|
|
289
|
+
for (const activity of activities) {
|
|
290
|
+
const html = buildScoIndexHtml({
|
|
291
|
+
manifest,
|
|
292
|
+
runtimeCss,
|
|
293
|
+
mode: "scorm2004",
|
|
294
|
+
activityId: activity.id,
|
|
295
|
+
assessmentBundle,
|
|
296
|
+
componentsScript: componentsBundleJs ? "../../lxpack-components.js" : void 0
|
|
297
|
+
});
|
|
298
|
+
zip.file(`sco/${activity.id}/index.html`, html);
|
|
299
|
+
}
|
|
300
|
+
const manifestFiles = buildScorm2004ManifestFiles(
|
|
301
|
+
manifest,
|
|
302
|
+
courseFiles.map((f) => f.path)
|
|
303
|
+
);
|
|
304
|
+
zip.file("imsmanifest.xml", generateScorm2004Manifest(manifest, manifestFiles));
|
|
305
|
+
const buffer = await zip.generateAsync({
|
|
306
|
+
type: "nodebuffer",
|
|
307
|
+
compression: "DEFLATE"
|
|
308
|
+
});
|
|
309
|
+
await writeFile(outputPath, buffer);
|
|
310
|
+
return {
|
|
311
|
+
outputPath,
|
|
312
|
+
fileCount: Object.keys(zip.files).length
|
|
313
|
+
};
|
|
314
|
+
}
|
|
143
315
|
async function packageCourse(options) {
|
|
316
|
+
if (options.target === "scorm2004") {
|
|
317
|
+
return packageScorm2004(options);
|
|
318
|
+
}
|
|
144
319
|
const {
|
|
145
320
|
courseDir,
|
|
146
321
|
manifest,
|
|
@@ -148,7 +323,8 @@ async function packageCourse(options) {
|
|
|
148
323
|
target,
|
|
149
324
|
runtimeClientJs,
|
|
150
325
|
runtimeCss,
|
|
151
|
-
assessmentBundle
|
|
326
|
+
assessmentBundle,
|
|
327
|
+
componentsBundleJs
|
|
152
328
|
} = options;
|
|
153
329
|
const zip = new JSZip();
|
|
154
330
|
const mode = target === "scorm12" ? "scorm12" : "standalone";
|
|
@@ -161,10 +337,14 @@ async function packageCourse(options) {
|
|
|
161
337
|
manifest,
|
|
162
338
|
runtimeCss,
|
|
163
339
|
mode,
|
|
164
|
-
assessmentBundle
|
|
340
|
+
assessmentBundle,
|
|
341
|
+
componentsScript: componentsBundleJs ? "./lxpack-components.js" : void 0
|
|
165
342
|
});
|
|
166
343
|
zip.file("index.html", indexHtml);
|
|
167
344
|
zip.file("lxpack-runtime.js", runtimeClientJs);
|
|
345
|
+
if (componentsBundleJs) {
|
|
346
|
+
zip.file("lxpack-components.js", componentsBundleJs);
|
|
347
|
+
}
|
|
168
348
|
if (target === "scorm12") {
|
|
169
349
|
const manifestFiles = buildManifestFileList(courseFiles);
|
|
170
350
|
zip.file(
|
|
@@ -228,12 +408,18 @@ export {
|
|
|
228
408
|
buildIndexHtml,
|
|
229
409
|
buildManifestFileList,
|
|
230
410
|
buildRuntimeConfig,
|
|
411
|
+
buildScoIndexHtml,
|
|
412
|
+
buildScorm2004ManifestFiles,
|
|
231
413
|
collectFiles,
|
|
232
414
|
courseSlug,
|
|
233
415
|
generateImsManifest,
|
|
416
|
+
generateScorm2004Manifest,
|
|
417
|
+
listCourseActivities,
|
|
234
418
|
manifestIdentifier,
|
|
235
419
|
packageCourse,
|
|
420
|
+
packageScorm2004,
|
|
236
421
|
packageStandaloneDir,
|
|
237
422
|
safeJsonForHtml,
|
|
423
|
+
scoLaunchPath,
|
|
238
424
|
shouldSkipCourseFile
|
|
239
425
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lxpack/scorm",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
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.
|
|
42
|
+
"@lxpack/validators": "0.2.0"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
45
|
"@types/node": "^22.13.10",
|