@lxpack/scorm 0.2.0 → 0.2.1

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.
Files changed (3) hide show
  1. package/dist/index.d.ts +30 -10
  2. package/dist/index.js +223 -150
  3. package/package.json +2 -2
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[]): 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 CourseActivity, type ExportTarget, type PackageOptions, buildIndexHtml, buildManifestFileList, buildRuntimeConfig, buildScoIndexHtml, buildScorm2004ManifestFiles, collectFiles, courseSlug, generateImsManifest, generateScorm2004Manifest, listCourseActivities, manifestIdentifier, packageCourse, packageScorm2004, packageStandaloneDir, safeJsonForHtml, scoLaunchPath, shouldSkipCourseFile };
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,95 +57,102 @@ function safeJsonForHtml(value) {
57
57
  return JSON.stringify(value).replace(/</g, "\\u003c");
58
58
  }
59
59
 
60
- // src/build-html.ts
61
- function buildRuntimeConfig(options) {
62
- const { manifest, mode, assessmentBundle, activityId } = options;
63
- return {
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(manifest.title)} \u2014 ${escapeHtml(activityId)}</title>
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">${config}</script>
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="../../lxpack-runtime.js"></script>
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
- const componentsTag = componentsScript ? `<script type="module" src="${escapeHtml(componentsScript)}"></script>` : "";
103
- return `<!DOCTYPE html>
104
- <html lang="en">
105
- <head>
106
- <meta charset="UTF-8">
107
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
108
- <title>${escapeHtml(manifest.title)}</title>
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, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
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, cp } from "fs/promises";
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/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
- });
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 activities;
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
158
  return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
@@ -153,20 +160,21 @@ function escapeXml2(text) {
153
160
  function scoLaunchPath(activityId) {
154
161
  return `sco/${activityId}/index.html`;
155
162
  }
156
- function buildScorm2004ManifestFiles(manifest, courseFiles) {
157
- const activities = listCourseActivities(manifest);
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 = listCourseActivities(manifest);
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)}">${orgSequencing}
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 packageScorm2004(options) {
277
+ async function writeSingleScoArtifacts(options) {
269
278
  const {
270
279
  courseDir,
271
280
  manifest,
272
- outputPath,
281
+ target,
273
282
  runtimeClientJs,
274
283
  runtimeCss,
284
+ assessmentBundle,
275
285
  componentsBundleJs,
276
- assessmentBundle
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
- zip.file(file.path, content);
340
+ await writeArtifact(file.path, content);
341
+ fileCount++;
283
342
  }
284
- zip.file("lxpack-runtime.js", runtimeClientJs);
343
+ await writeArtifact("lxpack-runtime.js", runtimeClientJs);
344
+ fileCount++;
285
345
  if (componentsBundleJs) {
286
- zip.file("lxpack-components.js", componentsBundleJs);
346
+ await writeArtifact("lxpack-components.js", componentsBundleJs);
347
+ fileCount++;
287
348
  }
288
- const activities = listCourseActivities(manifest);
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
- zip.file(`sco/${activity.id}/index.html`, html);
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
- zip.file("imsmanifest.xml", generateScorm2004Manifest(manifest, manifestFiles));
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 mode = target === "scorm12" ? "scorm12" : "standalone";
331
- const courseFiles = await collectFiles(courseDir, courseDir);
332
- for (const file of courseFiles) {
333
- const content = await readFile(file.fullPath);
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 mode = target === "scorm12" ? "scorm12" : "standalone";
377
- const courseFiles = await collectFiles(courseDir, courseDir);
378
- let fileCount = 0;
379
- for (const file of courseFiles) {
380
- const dest = join(outputDir, file.path);
381
- const destDir = dirname(dest);
382
- if (!existsSync(destDir)) {
383
- await mkdir(destDir, { recursive: true });
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
- await cp(file.fullPath, dest);
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.0",
3
+ "version": "0.2.1",
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.0"
42
+ "@lxpack/validators": "0.2.1"
43
43
  },
44
44
  "devDependencies": {
45
45
  "@types/node": "^22.13.10",