coursecode 0.1.56 → 0.1.58

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 (86) hide show
  1. package/README.md +8 -1
  2. package/bin/cli.js +16 -0
  3. package/framework/docs/FRAMEWORK_GUIDE.md +25 -7
  4. package/framework/docs/USER_GUIDE.md +14 -0
  5. package/framework/js/app/AppActions.js +17 -23
  6. package/framework/js/app/AppUI.js +7 -4
  7. package/framework/js/assessment/AssessmentActions.js +16 -15
  8. package/framework/js/assessment/AssessmentFactory.js +44 -15
  9. package/framework/js/automation/api-engagement.js +6 -3
  10. package/framework/js/components/ui-components/audio-player.js +30 -8
  11. package/framework/js/components/ui-components/collapse.js +4 -2
  12. package/framework/js/components/ui-components/dropdown.js +7 -3
  13. package/framework/js/components/ui-components/embed-frame.js +5 -1
  14. package/framework/js/components/ui-components/flip-card.js +12 -6
  15. package/framework/js/components/ui-components/interactive-image.js +28 -52
  16. package/framework/js/components/ui-components/lightbox.js +19 -5
  17. package/framework/js/components/ui-components/modal.js +20 -7
  18. package/framework/js/components/ui-components/notifications.js +8 -3
  19. package/framework/js/components/ui-components/video-player.js +46 -20
  20. package/framework/js/core/event-bus.js +31 -28
  21. package/framework/js/drivers/cmi5-driver.js +186 -83
  22. package/framework/js/drivers/driver-factory.js +7 -1
  23. package/framework/js/drivers/driver-interface.js +1 -1
  24. package/framework/js/drivers/http-driver-base.js +6 -0
  25. package/framework/js/drivers/lti-driver.js +112 -47
  26. package/framework/js/drivers/proxy-driver.js +347 -49
  27. package/framework/js/drivers/scorm-12-driver.js +213 -130
  28. package/framework/js/drivers/scorm-2004-driver.js +32 -19
  29. package/framework/js/drivers/scorm-driver-base.js +12 -0
  30. package/framework/js/drivers/standalone-driver.js +139 -0
  31. package/framework/js/engagement/engagement-trackers.js +38 -13
  32. package/framework/js/engagement/requirement-strategies.js +5 -1
  33. package/framework/js/main.js +7 -0
  34. package/framework/js/managers/assessment-manager.js +16 -1
  35. package/framework/js/managers/audio-manager.js +50 -16
  36. package/framework/js/managers/flag-manager.js +5 -1
  37. package/framework/js/managers/objective-manager.js +15 -7
  38. package/framework/js/managers/score-manager.js +27 -6
  39. package/framework/js/managers/video-manager.js +149 -70
  40. package/framework/js/navigation/NavigationActions.js +18 -4
  41. package/framework/js/navigation/document-gallery.js +7 -2
  42. package/framework/js/navigation/navigation-validators.js +3 -2
  43. package/framework/js/state/lms-connection.js +41 -16
  44. package/framework/js/state/state-commits.js +10 -5
  45. package/framework/js/state/state-manager.js +95 -9
  46. package/framework/js/state/state-validation.js +44 -11
  47. package/framework/js/utilities/course-helpers.js +3 -2
  48. package/framework/js/utilities/media-utils.js +28 -0
  49. package/framework/js/utilities/portable-assets.js +131 -0
  50. package/framework/js/utilities/ui-initializer.js +79 -5
  51. package/framework/js/utilities/utilities.js +32 -20
  52. package/framework/js/utilities/view-manager.js +16 -1
  53. package/framework/js/validation/scorm-validators.js +69 -0
  54. package/framework/version.json +2 -2
  55. package/lib/authoring-api.js +37 -18
  56. package/lib/build-packaging.js +49 -5
  57. package/lib/build.js +1 -1
  58. package/lib/cloud.js +40 -11
  59. package/lib/convert.js +30 -2
  60. package/lib/create.js +28 -3
  61. package/lib/dev.js +1 -1
  62. package/lib/headless-browser.js +66 -14
  63. package/lib/manifest/cmi5-manifest.js +26 -4
  64. package/lib/manifest/lti-tool-config.js +7 -2
  65. package/lib/manifest/manifest-factory.d.ts +1 -1
  66. package/lib/manifest/manifest-factory.js +3 -2
  67. package/lib/manifest/scorm-12-manifest.js +2 -2
  68. package/lib/manifest/scorm-2004-manifest.js +3 -28
  69. package/lib/manifest/scorm-proxy-manifest.js +37 -32
  70. package/lib/manifest/xml-utils.js +10 -0
  71. package/lib/narration.js +2 -2
  72. package/lib/portable-html.js +196 -0
  73. package/lib/preview-export.js +1 -1
  74. package/lib/preview-server.js +27 -6
  75. package/lib/project-utils.js +9 -1
  76. package/lib/proxy-templates/proxy.html +4 -1
  77. package/lib/proxy-templates/scorm-bridge.js +82 -10
  78. package/lib/scaffold.js +9 -4
  79. package/lib/stub-player/lms-api.js +63 -8
  80. package/package.json +6 -2
  81. package/schemas/adlcp_rootv1p2.xsd +110 -0
  82. package/schemas/coursecode_scorm12_package.xsd +5 -0
  83. package/schemas/coursecode_scorm2004_package.xsd +8 -0
  84. package/schemas/imscp_rootv1p1p2.xsd +304 -0
  85. package/template/package.json +1 -1
  86. package/template/vite.config.js +50 -22
@@ -0,0 +1,304 @@
1
+ <?xml version="1.0"?>
2
+ <!-- edited with XMLSPY v5 rel. 4 U (http://www.xmlspy.com) by Pascal FERTON (AISAKOS) -->
3
+ <!-- edited with XML Spy v4.4 U (http://www.xmlspy.com) by sa (li) -->
4
+ <!-- filename=ims_cp_rootv1p1p2.xsd -->
5
+ <!-- Copyright (2) 2001 IMS Global Learning Consortium, Inc. -->
6
+ <!-- edited by Thomas Wason -->
7
+ <!-- Conforms to w3c http://www.w3.org/TR/xmlschema-1/ 2000-10-24-->
8
+ <xsd:schema targetNamespace="http://www.imsproject.org/xsd/imscp_rootv1p1p2" xmlns="http://www.imsproject.org/xsd/imscp_rootv1p1p2" xmlns:xsd="http://www.w3.org/2001/XMLSchema" elementFormDefault="unqualified" version="IMS CP 1.1.2">
9
+ <!-- ******************** -->
10
+ <!-- ** Change History ** -->
11
+ <!-- ******************** -->
12
+ <xsd:annotation>
13
+ <xsd:documentation xml:lang="en">DRAFT XSD for IMS Content Packaging version 1.1 DRAFT</xsd:documentation>
14
+ <xsd:documentation> Copyright (c) 2001 IMS GLC, Inc. </xsd:documentation>
15
+ <xsd:documentation>2000-04-21, Adjustments by T.D. Wason from CP 1.0.</xsd:documentation>
16
+ <xsd:documentation>2001-02-22, T.D.Wason: Modify for 2000-10-24 XML-Schema version. Modified to support extension.</xsd:documentation>
17
+ <xsd:documentation>2001-03-12, T.D.Wason: Change filename, target and meta-data namespaces and meta-data fielname. Add meta-data to itemType, fileType and organizationType.</xsd:documentation>
18
+ <xsd:documentation>Do not define namespaces for xml in XML instances generated from this xsd.</xsd:documentation>
19
+ <xsd:documentation>Imports IMS meta-data xsd, lower case element names. </xsd:documentation>
20
+ <xsd:documentation>This XSD provides a reference to the IMS meta-data root element as imsmd:record</xsd:documentation>
21
+ <xsd:documentation>If the IMS meta-data is to be used in the XML instance then the instance must define an IMS meta-data prefix with a namespace. The meta-data targetNamespace should be used. </xsd:documentation>
22
+ <xsd:documentation>2001-03-20, Thor Anderson: Remove manifestref, change resourceref back to identifierref, change manifest back to contained by manifest. --Tom Wason: manifest may contain _none_ or more manifests.</xsd:documentation>
23
+ <xsd:documentation>2001-04-13 Tom Wason: corrected attirbute name structure. Was misnamed type. </xsd:documentation>
24
+ <xsd:documentation>2001-05-14 Schawn Thropp: Made all complexType extensible with the group.any</xsd:documentation>
25
+ <xsd:documentation>Added the anyAttribute to all complexTypes. Changed the href attribute on the fileType and resourceType to xsd:string</xsd:documentation>
26
+ <xsd:documentation>Changed the maxLength of the href, identifierref, parameters, structure attributes to match the Information model.</xsd:documentation>
27
+ <xsd:documentation>2001-07-25 Schawn Thropp: Changed the namespace for the Schema of Schemas to the 5/2/2001 W3C XML Schema</xsd:documentation>
28
+ <xsd:documentation>Recommendation. attributeGroup attr.imsmd deleted, was not used anywhere. Any attribute declarations that have</xsd:documentation>
29
+ <xsd:documentation>use = "default" changed to use="optional" - attr.structure.req.</xsd:documentation>
30
+ <xsd:documentation>Any attribute declarations that have value="somevalue" changed to default="somevalue",</xsd:documentation>
31
+ <xsd:documentation>attr.structure.req (hierarchical). Removed references to IMS MD Version 1.1.</xsd:documentation>
32
+ <xsd:documentation>Modified attribute group "attr.resourcetype.req" to change use from optional</xsd:documentation>
33
+ <xsd:documentation>to required to match the information model. As a result the default value also needed to be removed</xsd:documentation>
34
+ <xsd:documentation>Name change for XSD. Changed to match version of CP Spec </xsd:documentation>
35
+ </xsd:annotation>
36
+ <xsd:annotation>
37
+ <xsd:documentation>Inclusions and Imports</xsd:documentation>
38
+ </xsd:annotation>
39
+ <xsd:import namespace="http://www.w3.org/XML/1998/namespace" schemaLocation="xml.xsd"/>
40
+ <xsd:annotation>
41
+ <xsd:documentation>Attribute Declarations</xsd:documentation>
42
+ </xsd:annotation>
43
+ <!-- **************************** -->
44
+ <!-- ** Attribute Declarations ** -->
45
+ <!-- **************************** -->
46
+ <xsd:attributeGroup name="attr.base">
47
+ <xsd:attribute ref="xml:base" use="optional"/>
48
+ </xsd:attributeGroup>
49
+ <xsd:attributeGroup name="attr.default">
50
+ <xsd:attribute name="default" type="xsd:IDREF" use="optional"/>
51
+ </xsd:attributeGroup>
52
+ <xsd:attributeGroup name="attr.href">
53
+ <xsd:attribute name="href" use="optional">
54
+ <xsd:simpleType>
55
+ <xsd:restriction base="xsd:anyURI">
56
+ <xsd:maxLength value="2000"/>
57
+ </xsd:restriction>
58
+ </xsd:simpleType>
59
+ </xsd:attribute>
60
+ </xsd:attributeGroup>
61
+ <xsd:attributeGroup name="attr.href.req">
62
+ <xsd:attribute name="href" use="required">
63
+ <xsd:simpleType>
64
+ <xsd:restriction base="xsd:anyURI">
65
+ <xsd:maxLength value="2000"/>
66
+ </xsd:restriction>
67
+ </xsd:simpleType>
68
+ </xsd:attribute>
69
+ </xsd:attributeGroup>
70
+ <xsd:attributeGroup name="attr.identifier.req">
71
+ <xsd:attribute name="identifier" type="xsd:ID" use="required"/>
72
+ </xsd:attributeGroup>
73
+ <xsd:attributeGroup name="attr.identifier">
74
+ <xsd:attribute name="identifier" type="xsd:ID" use="optional"/>
75
+ </xsd:attributeGroup>
76
+ <xsd:attributeGroup name="attr.isvisible">
77
+ <xsd:attribute name="isvisible" type="xsd:boolean" use="optional"/>
78
+ </xsd:attributeGroup>
79
+ <xsd:attributeGroup name="attr.parameters">
80
+ <xsd:attribute name="parameters" use="optional">
81
+ <xsd:simpleType>
82
+ <xsd:restriction base="xsd:string">
83
+ <xsd:maxLength value="1000"/>
84
+ </xsd:restriction>
85
+ </xsd:simpleType>
86
+ </xsd:attribute>
87
+ </xsd:attributeGroup>
88
+ <xsd:attributeGroup name="attr.identifierref">
89
+ <xsd:attribute name="identifierref" use="optional">
90
+ <xsd:simpleType>
91
+ <xsd:restriction base="xsd:string">
92
+ <xsd:maxLength value="2000"/>
93
+ </xsd:restriction>
94
+ </xsd:simpleType>
95
+ </xsd:attribute>
96
+ </xsd:attributeGroup>
97
+ <xsd:attributeGroup name="attr.identifierref.req">
98
+ <xsd:attribute name="identifierref" use="required">
99
+ <xsd:simpleType>
100
+ <xsd:restriction base="xsd:string">
101
+ <xsd:maxLength value="2000"/>
102
+ </xsd:restriction>
103
+ </xsd:simpleType>
104
+ </xsd:attribute>
105
+ </xsd:attributeGroup>
106
+ <xsd:attributeGroup name="attr.resourcetype.req">
107
+ <xsd:attribute name="type" use="required">
108
+ <xsd:simpleType>
109
+ <xsd:restriction base="xsd:string">
110
+ <xsd:maxLength value="1000"/>
111
+ </xsd:restriction>
112
+ </xsd:simpleType>
113
+ </xsd:attribute>
114
+ </xsd:attributeGroup>
115
+ <xsd:attributeGroup name="attr.structure.req">
116
+ <xsd:attribute name="structure" use="optional" default="hierarchical">
117
+ <xsd:simpleType>
118
+ <xsd:restriction base="xsd:string">
119
+ <xsd:maxLength value="200"/>
120
+ </xsd:restriction>
121
+ </xsd:simpleType>
122
+ </xsd:attribute>
123
+ </xsd:attributeGroup>
124
+ <xsd:attributeGroup name="attr.version">
125
+ <xsd:attribute name="version" use="optional">
126
+ <xsd:simpleType>
127
+ <xsd:restriction base="xsd:string">
128
+ <xsd:maxLength value="20"/>
129
+ </xsd:restriction>
130
+ </xsd:simpleType>
131
+ </xsd:attribute>
132
+ </xsd:attributeGroup>
133
+ <xsd:annotation>
134
+ <xsd:documentation>element groups</xsd:documentation>
135
+ </xsd:annotation>
136
+ <xsd:group name="grp.any">
137
+ <xsd:annotation>
138
+ <xsd:documentation>Any namespaced element from any namespace may be included within an &quot;any&quot; element. The namespace for the imported element must be defined in the instance, and the schema must be imported. </xsd:documentation>
139
+ </xsd:annotation>
140
+ <xsd:sequence>
141
+ <xsd:any namespace="##other" processContents="strict" minOccurs="0" maxOccurs="unbounded"/>
142
+ </xsd:sequence>
143
+ </xsd:group>
144
+ <!-- ************************** -->
145
+ <!-- ** Element Declarations ** -->
146
+ <!-- ************************** -->
147
+ <xsd:element name="dependency" type="dependencyType"/>
148
+ <xsd:element name="file" type="fileType"/>
149
+ <xsd:element name="item" type="itemType"/>
150
+ <xsd:element name="manifest" type="manifestType"/>
151
+ <xsd:element name="metadata" type="metadataType"/>
152
+ <xsd:element name="organization" type="organizationType"/>
153
+ <xsd:element name="organizations" type="organizationsType"/>
154
+ <xsd:element name="resource" type="resourceType"/>
155
+ <xsd:element name="resources" type="resourcesType"/>
156
+ <xsd:element name="schema" type="schemaType"/>
157
+ <xsd:element name="schemaversion" type="schemaversionType"/>
158
+ <xsd:element name="title" type="titleType"/>
159
+ <!-- ******************* -->
160
+ <!-- ** Complex Types ** -->
161
+ <!-- ******************* -->
162
+ <!-- **************** -->
163
+ <!-- ** dependency ** -->
164
+ <!-- **************** -->
165
+ <xsd:complexType name="dependencyType">
166
+ <xsd:sequence>
167
+ <xsd:group ref="grp.any"/>
168
+ </xsd:sequence>
169
+ <xsd:attributeGroup ref="attr.identifierref.req"/>
170
+ <xsd:anyAttribute namespace="##other" processContents="strict"/>
171
+ </xsd:complexType>
172
+ <!-- ********** -->
173
+ <!-- ** file ** -->
174
+ <!-- ********** -->
175
+ <xsd:complexType name="fileType">
176
+ <xsd:sequence>
177
+ <xsd:element ref="metadata" minOccurs="0"/>
178
+ <xsd:group ref="grp.any"/>
179
+ </xsd:sequence>
180
+ <xsd:attributeGroup ref="attr.href.req"/>
181
+ <xsd:anyAttribute namespace="##other" processContents="strict"/>
182
+ </xsd:complexType>
183
+ <!-- ********** -->
184
+ <!-- ** item ** -->
185
+ <!-- ********** -->
186
+ <xsd:complexType name="itemType">
187
+ <xsd:sequence>
188
+ <xsd:element ref="title" minOccurs="0"/>
189
+ <xsd:element ref="item" minOccurs="0" maxOccurs="unbounded"/>
190
+ <xsd:element ref="metadata" minOccurs="0"/>
191
+ <xsd:group ref="grp.any"/>
192
+ </xsd:sequence>
193
+ <xsd:attributeGroup ref="attr.identifier.req"/>
194
+ <xsd:attributeGroup ref="attr.identifierref"/>
195
+ <xsd:attributeGroup ref="attr.isvisible"/>
196
+ <xsd:attributeGroup ref="attr.parameters"/>
197
+ <xsd:anyAttribute namespace="##other" processContents="strict"/>
198
+ </xsd:complexType>
199
+ <!-- ************** -->
200
+ <!-- ** manifest ** -->
201
+ <!-- ************** -->
202
+ <xsd:complexType name="manifestType">
203
+ <xsd:sequence>
204
+ <xsd:element ref="metadata" minOccurs="0"/>
205
+ <xsd:element ref="organizations"/>
206
+ <xsd:element ref="resources"/>
207
+ <xsd:element ref="manifest" minOccurs="0" maxOccurs="unbounded"/>
208
+ <xsd:group ref="grp.any"/>
209
+ </xsd:sequence>
210
+ <xsd:attributeGroup ref="attr.identifier.req"/>
211
+ <xsd:attributeGroup ref="attr.version"/>
212
+ <xsd:attribute ref="xml:base"/>
213
+ <xsd:anyAttribute namespace="##other" processContents="strict"/>
214
+ </xsd:complexType>
215
+ <!-- ************** -->
216
+ <!-- ** metadata ** -->
217
+ <!-- ************** -->
218
+ <xsd:complexType name="metadataType">
219
+ <xsd:sequence>
220
+ <xsd:element ref="schema" minOccurs="0"/>
221
+ <xsd:element ref="schemaversion" minOccurs="0"/>
222
+ <xsd:group ref="grp.any"/>
223
+ </xsd:sequence>
224
+ </xsd:complexType>
225
+ <!-- ******************* -->
226
+ <!-- ** organizations ** -->
227
+ <!-- ******************* -->
228
+ <xsd:complexType name="organizationsType">
229
+ <xsd:sequence>
230
+ <xsd:element ref="organization" minOccurs="0" maxOccurs="unbounded"/>
231
+ <xsd:group ref="grp.any"/>
232
+ </xsd:sequence>
233
+ <xsd:attributeGroup ref="attr.default"/>
234
+ <xsd:anyAttribute namespace="##other" processContents="strict"/>
235
+ </xsd:complexType>
236
+ <!-- ****************** -->
237
+ <!-- ** organization ** -->
238
+ <!-- ****************** -->
239
+ <xsd:complexType name="organizationType">
240
+ <xsd:sequence>
241
+ <xsd:element ref="title" minOccurs="0"/>
242
+ <xsd:element ref="item" minOccurs="0" maxOccurs="unbounded"/>
243
+ <xsd:element ref="metadata" minOccurs="0"/>
244
+ <xsd:group ref="grp.any"/>
245
+ </xsd:sequence>
246
+ <xsd:attributeGroup ref="attr.identifier.req"/>
247
+ <xsd:attributeGroup ref="attr.structure.req"/>
248
+ <xsd:anyAttribute namespace="##other" processContents="strict"/>
249
+ </xsd:complexType>
250
+ <!-- *************** -->
251
+ <!-- ** resources ** -->
252
+ <!-- *************** -->
253
+ <xsd:complexType name="resourcesType">
254
+ <xsd:sequence>
255
+ <xsd:element ref="resource" minOccurs="0" maxOccurs="unbounded"/>
256
+ <xsd:group ref="grp.any"/>
257
+ </xsd:sequence>
258
+ <xsd:attributeGroup ref="attr.base"/>
259
+ <xsd:anyAttribute namespace="##other" processContents="strict"/>
260
+ </xsd:complexType>
261
+ <!-- ************** -->
262
+ <!-- ** resource ** -->
263
+ <!-- ************** -->
264
+ <xsd:complexType name="resourceType">
265
+ <xsd:sequence>
266
+ <xsd:element ref="metadata" minOccurs="0"/>
267
+ <xsd:element ref="file" minOccurs="0" maxOccurs="unbounded"/>
268
+ <xsd:element ref="dependency" minOccurs="0" maxOccurs="unbounded"/>
269
+ <xsd:group ref="grp.any"/>
270
+ </xsd:sequence>
271
+ <xsd:attributeGroup ref="attr.identifier.req"/>
272
+ <xsd:attributeGroup ref="attr.resourcetype.req"/>
273
+ <xsd:attributeGroup ref="attr.base"/>
274
+ <xsd:attributeGroup ref="attr.href"/>
275
+ <xsd:anyAttribute namespace="##other" processContents="strict"/>
276
+ </xsd:complexType>
277
+ <!-- ****************** -->
278
+ <!-- ** Simple Types ** -->
279
+ <!-- ****************** -->
280
+ <!-- ************ -->
281
+ <!-- ** schema ** -->
282
+ <!-- ************ -->
283
+ <xsd:simpleType name="schemaType">
284
+ <xsd:restriction base="xsd:string">
285
+ <xsd:maxLength value="100"/>
286
+ </xsd:restriction>
287
+ </xsd:simpleType>
288
+ <!-- ******************* -->
289
+ <!-- ** schemaversion ** -->
290
+ <!-- ******************* -->
291
+ <xsd:simpleType name="schemaversionType">
292
+ <xsd:restriction base="xsd:string">
293
+ <xsd:maxLength value="20"/>
294
+ </xsd:restriction>
295
+ </xsd:simpleType>
296
+ <!-- *********** -->
297
+ <!-- ** title ** -->
298
+ <!-- *********** -->
299
+ <xsd:simpleType name="titleType">
300
+ <xsd:restriction base="xsd:string">
301
+ <xsd:maxLength value="200"/>
302
+ </xsd:restriction>
303
+ </xsd:simpleType>
304
+ </xsd:schema>
@@ -21,7 +21,7 @@
21
21
  "marked-gfm-heading-id": "^4.1.4"
22
22
  },
23
23
  "devDependencies": {
24
- "archiver": "^7.0.1",
24
+ "archiver": "^8.0.0",
25
25
  "eslint": "^9.39.4",
26
26
  "vite": "~8.1.4",
27
27
  "vite-plugin-static-copy": "^4.1.1"
@@ -10,10 +10,13 @@ let createStandardPackage;
10
10
  let createExternalPackagesForClients;
11
11
  let validateExternalHostingConfig;
12
12
  let loadExternalAccessConfig;
13
+ let createPortableHtmlPlugin;
13
14
 
14
15
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
15
16
  const ROOT_DIR = path.resolve(__dirname);
16
- const DIST_DIR = path.join(ROOT_DIR, 'dist');
17
+ const BUILD_OUTPUT = process.env.COURSECODE_OUT_DIR || 'dist';
18
+ const DIST_DIR = path.resolve(ROOT_DIR, BUILD_OUTPUT);
19
+ const PORTABLE_HTML = process.env.COURSECODE_PORTABLE_HTML === 'true';
17
20
  const SUPPORTED_BROWSER_TARGETS = ['chrome111', 'edge111', 'firefox114', 'safari16.4'];
18
21
 
19
22
  function directoryContainsFiles(directory) {
@@ -23,6 +26,14 @@ function directoryContainsFiles(directory) {
23
26
  ));
24
27
  }
25
28
 
29
+ function escapeHtmlAttribute(value) {
30
+ return String(value)
31
+ .replace(/&/g, '&amp;')
32
+ .replace(/"/g, '&quot;')
33
+ .replace(/</g, '&lt;')
34
+ .replace(/>/g, '&gt;');
35
+ }
36
+
26
37
  async function loadBuildUtils() {
27
38
  if (
28
39
  generateManifest &&
@@ -30,7 +41,8 @@ async function loadBuildUtils() {
30
41
  createStandardPackage &&
31
42
  createExternalPackagesForClients &&
32
43
  validateExternalHostingConfig &&
33
- loadExternalAccessConfig
44
+ loadExternalAccessConfig &&
45
+ createPortableHtmlPlugin
34
46
  ) {
35
47
  return;
36
48
  }
@@ -44,6 +56,7 @@ async function loadBuildUtils() {
44
56
  loadExternalAccessConfig
45
57
  } = await import('coursecode/build-packaging'));
46
58
  ({ default: contentDiscoveryPlugin } = await import('coursecode/vite-plugin-content-discovery'));
59
+ ({ createPortableHtmlPlugin } = await import('coursecode/portable-html'));
47
60
  } catch {
48
61
  // Fallback for npm link / local dev — preview-server provides COURSECODE_LIB_DIR
49
62
  const libDir = process.env.COURSECODE_LIB_DIR;
@@ -57,6 +70,7 @@ async function loadBuildUtils() {
57
70
  loadExternalAccessConfig
58
71
  } = await import(toUrl('build-packaging.js')));
59
72
  ({ default: contentDiscoveryPlugin } = await import(toUrl('vite-plugin-content-discovery.js')));
73
+ ({ createPortableHtmlPlugin } = await import(toUrl('portable-html.js')));
60
74
  }
61
75
  }
62
76
 
@@ -75,8 +89,13 @@ function validatePackage(format = 'scorm2004') {
75
89
  } else if (format === 'lti') {
76
90
  requiredFiles = ['lti-tool-config.json', 'index.html'];
77
91
  } else if (format === 'scorm1.2') {
78
- requiredFiles = ['imsmanifest.xml', 'index.html'];
79
- // SCORM 1.2 schemas are optional for most LMSs
92
+ requiredFiles = [
93
+ 'imsmanifest.xml',
94
+ 'index.html',
95
+ 'imscp_rootv1p1p2.xsd',
96
+ 'adlcp_rootv1p2.xsd',
97
+ 'ims_xml.xsd'
98
+ ];
80
99
  } else {
81
100
  // SCORM 2004 default
82
101
  requiredFiles = [
@@ -199,6 +218,7 @@ async function loadCourseConfig() {
199
218
  lmsFormat,
200
219
  externalUrl: process.env.LTI_EXTERNAL_URL || config.externalUrl || null,
201
220
  masteryScore: masteryScore ?? null,
221
+ moveOn: config.lms?.moveOn || null,
202
222
  accessControl: loadExternalAccessConfig(ROOT_DIR, config),
203
223
  galleryConfig: config.navigation?.documentGallery || null
204
224
  };
@@ -276,7 +296,7 @@ function scormPostBuild(isDev, { isWatchBuild }) {
276
296
  // Stamp LMS format meta tag for runtime driver selection
277
297
  indexContent = indexContent.replace(
278
298
  '<meta charset="UTF-8" />',
279
- `<meta charset="UTF-8" />\n <meta name="lms-format" content="${config.lmsFormat}" />`
299
+ `<meta charset="UTF-8" />\n <meta name="lms-format" content="${config.lmsFormat}" />\n <meta name="coursecode-id" content="${escapeHtmlAttribute(`${config.title}:${config.version}`)}" />`
280
300
  );
281
301
 
282
302
  fs.writeFileSync(indexDest, indexContent, 'utf-8');
@@ -287,6 +307,11 @@ function scormPostBuild(isDev, { isWatchBuild }) {
287
307
  }
288
308
  }
289
309
 
310
+ if (PORTABLE_HTML) {
311
+ if (!isDev) console.log('✓ Portable runtime assembled (LMS package files omitted)');
312
+ return;
313
+ }
314
+
290
315
  // Generate manifest using factory
291
316
  const files = isDev ? ['index.html'] : scanDistFiles();
292
317
  const manifestOptions = { externalUrl: config.externalUrl };
@@ -333,6 +358,17 @@ export default defineConfig(async ({ mode }) => {
333
358
  const isDev = mode === 'development';
334
359
  const isWatchBuild = process.argv.includes('--watch');
335
360
  const courseConfig = await loadCourseConfig();
361
+ const staticCopyTargets = [
362
+ ...(!PORTABLE_HTML ? [
363
+ { src: 'schemas/*.{xml,xsd,dtd}', dest: '.', rename: { stripBase: 1 } },
364
+ { src: 'schemas/common/*', dest: 'common', rename: { stripBase: 2 } },
365
+ { src: 'framework/js/vendor/**/*', dest: 'js/vendor', rename: { stripBase: 3 } }
366
+ ] : []),
367
+ // Publish runtime assets only. Authoring source stays out of every output.
368
+ ...(directoryContainsFiles(path.join(ROOT_DIR, 'course', 'assets'))
369
+ ? [{ src: 'course/assets', dest: 'course', rename: { stripBase: 1 } }]
370
+ : [])
371
+ ];
336
372
 
337
373
  return {
338
374
  root: '.',
@@ -356,7 +392,7 @@ export default defineConfig(async ({ mode }) => {
356
392
  },
357
393
 
358
394
  build: {
359
- outDir: 'dist',
395
+ outDir: BUILD_OUTPUT,
360
396
  emptyOutDir: !isWatchBuild,
361
397
  // Stable browser contract for LMS launches. SCORM defines the LMS API,
362
398
  // not an obsolete browser requirement; IE11/SystemJS is unsupported.
@@ -372,9 +408,9 @@ export default defineConfig(async ({ mode }) => {
372
408
  // LTI OIDC/JWT validation remains server-side, so no browser crypto
373
409
  // verification dependency is required here.
374
410
  output: {
375
- entryFileNames: 'assets/[name].js',
376
- chunkFileNames: 'assets/[name].js',
377
- assetFileNames: 'assets/[name].[ext]'
411
+ entryFileNames: 'assets/[name]-[hash].js',
412
+ chunkFileNames: 'assets/[name]-[hash].js',
413
+ assetFileNames: 'assets/[name]-[hash].[ext]'
378
414
  }
379
415
  }
380
416
  },
@@ -383,20 +419,12 @@ export default defineConfig(async ({ mode }) => {
383
419
  // Content discovery - generates manifest at build time
384
420
  contentDiscoveryPlugin({ galleryConfig: courseConfig.galleryConfig }),
385
421
 
386
- viteStaticCopy({
387
- targets: [
388
- { src: 'schemas/*.{xml,xsd,dtd}', dest: '.', rename: { stripBase: 1 } },
389
- { src: 'schemas/common/*', dest: 'common', rename: { stripBase: 2 } },
390
- // Publish runtime assets only. Never ship authoring references,
391
- // configuration source, assessment source, or hidden project files.
392
- // A blank course is valid and may not contain any assets yet.
393
- ...(directoryContainsFiles(path.join(ROOT_DIR, 'course', 'assets'))
394
- ? [{ src: 'course/assets', dest: 'course', rename: { stripBase: 1 } }]
395
- : []),
396
- { src: 'framework/js/vendor/**/*', dest: 'js/vendor', rename: { stripBase: 3 } }
397
- ],
422
+ ...(PORTABLE_HTML ? [createPortableHtmlPlugin()] : []),
423
+
424
+ ...(staticCopyTargets.length > 0 ? [viteStaticCopy({
425
+ targets: staticCopyTargets,
398
426
  watch: isDev ? { rerun: true } : undefined
399
- }),
427
+ })] : []),
400
428
 
401
429
  scormPostBuild(isDev, { isWatchBuild })
402
430
  ]