coursecode 0.1.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.
Files changed (362) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +322 -0
  3. package/THIRD_PARTY_NOTICES.md +22 -0
  4. package/bin/cli.js +331 -0
  5. package/framework/assets/logo-coursecode-black.svg +14 -0
  6. package/framework/assets/logo-coursecode-white.svg +14 -0
  7. package/framework/assets/logo-coursecode.svg +14 -0
  8. package/framework/css/01-base.css +160 -0
  9. package/framework/css/02-layout.css +499 -0
  10. package/framework/css/accessibility.css +834 -0
  11. package/framework/css/components/accordions.css +710 -0
  12. package/framework/css/components/assessments.css +520 -0
  13. package/framework/css/components/audio-player.css +570 -0
  14. package/framework/css/components/badges.css +80 -0
  15. package/framework/css/components/breadcrumbs.css +87 -0
  16. package/framework/css/components/buttons.css +707 -0
  17. package/framework/css/components/callouts.css +1280 -0
  18. package/framework/css/components/cards.css +475 -0
  19. package/framework/css/components/carousel.css +193 -0
  20. package/framework/css/components/checkbox-group.css +123 -0
  21. package/framework/css/components/checklist.css +203 -0
  22. package/framework/css/components/collapse.css +96 -0
  23. package/framework/css/components/comparison.css +33 -0
  24. package/framework/css/components/content-image.css +36 -0
  25. package/framework/css/components/document-gallery.css +425 -0
  26. package/framework/css/components/dropdown.css +115 -0
  27. package/framework/css/components/embed-frame.css +142 -0
  28. package/framework/css/components/engagement.css +412 -0
  29. package/framework/css/components/features.css +35 -0
  30. package/framework/css/components/flip-cards.css +253 -0
  31. package/framework/css/components/footer.css +353 -0
  32. package/framework/css/components/forms.css +294 -0
  33. package/framework/css/components/hero.css +216 -0
  34. package/framework/css/components/images.css +528 -0
  35. package/framework/css/components/interactive-timeline.css +274 -0
  36. package/framework/css/components/intro-cards.css +30 -0
  37. package/framework/css/components/lightbox.css +666 -0
  38. package/framework/css/components/loading.css +65 -0
  39. package/framework/css/components/modals.css +235 -0
  40. package/framework/css/components/notifications.css +107 -0
  41. package/framework/css/components/quote.css +150 -0
  42. package/framework/css/components/sidebar.css +684 -0
  43. package/framework/css/components/slide-header.css +52 -0
  44. package/framework/css/components/spinner.css +62 -0
  45. package/framework/css/components/stats.css +44 -0
  46. package/framework/css/components/steps.css +232 -0
  47. package/framework/css/components/tables.css +90 -0
  48. package/framework/css/components/tabs.css +347 -0
  49. package/framework/css/components/timeline.css +154 -0
  50. package/framework/css/components/toggle.css +95 -0
  51. package/framework/css/components/tooltip.css +226 -0
  52. package/framework/css/components/video-player.css +438 -0
  53. package/framework/css/design-tokens.css +707 -0
  54. package/framework/css/framework.css +86 -0
  55. package/framework/css/interactions/accessibility.css +75 -0
  56. package/framework/css/interactions/base.css +92 -0
  57. package/framework/css/interactions/drag-drop.css +295 -0
  58. package/framework/css/interactions/fill-in-the-blank.css +236 -0
  59. package/framework/css/interactions/hotspots.css +69 -0
  60. package/framework/css/interactions/index.css +45 -0
  61. package/framework/css/interactions/interactive-image.css +359 -0
  62. package/framework/css/interactions/likert.css +126 -0
  63. package/framework/css/interactions/matching.css +354 -0
  64. package/framework/css/interactions/numeric-input.css +78 -0
  65. package/framework/css/interactions/sequencing.css +378 -0
  66. package/framework/css/interactions/true-false.css +177 -0
  67. package/framework/css/layouts/article.css +258 -0
  68. package/framework/css/layouts/base.css +30 -0
  69. package/framework/css/layouts/canvas.css +38 -0
  70. package/framework/css/layouts/focused.css +236 -0
  71. package/framework/css/layouts/index.css +29 -0
  72. package/framework/css/layouts/presentation.css +191 -0
  73. package/framework/css/layouts/traditional.css +52 -0
  74. package/framework/css/responsive.css +439 -0
  75. package/framework/css/utilities/accessibility-utils.css +59 -0
  76. package/framework/css/utilities/animations.css +419 -0
  77. package/framework/css/utilities/borders.css +72 -0
  78. package/framework/css/utilities/colors.css +76 -0
  79. package/framework/css/utilities/container.css +46 -0
  80. package/framework/css/utilities/decorative.css +442 -0
  81. package/framework/css/utilities/display.css +257 -0
  82. package/framework/css/utilities/flexbox.css +80 -0
  83. package/framework/css/utilities/grid.css +69 -0
  84. package/framework/css/utilities/icons.css +534 -0
  85. package/framework/css/utilities/lists.css +190 -0
  86. package/framework/css/utilities/spacing.css +167 -0
  87. package/framework/css/utilities/tables.css +81 -0
  88. package/framework/css/utilities/typography.css +159 -0
  89. package/framework/css/utilities/visibility.css +117 -0
  90. package/framework/docs/COURSE_AUTHORING_GUIDE.md +1773 -0
  91. package/framework/docs/COURSE_OUTLINE_GUIDE.md +725 -0
  92. package/framework/docs/COURSE_OUTLINE_TEMPLATE.md +161 -0
  93. package/framework/docs/DATA_MODEL.md +409 -0
  94. package/framework/docs/FRAMEWORK_GUIDE.md +1088 -0
  95. package/framework/docs/USER_GUIDE.md +583 -0
  96. package/framework/docs/examples/cloudflare-channel-relay.js +169 -0
  97. package/framework/docs/examples/cloudflare-data-worker.js +102 -0
  98. package/framework/docs/examples/cloudflare-error-worker.js +228 -0
  99. package/framework/index.html +175 -0
  100. package/framework/js/app/AppActions.js +410 -0
  101. package/framework/js/app/AppState.js +225 -0
  102. package/framework/js/app/AppUI.js +616 -0
  103. package/framework/js/assessment/AssessmentActions.js +615 -0
  104. package/framework/js/assessment/AssessmentFactory.js +471 -0
  105. package/framework/js/assessment/AssessmentState.js +322 -0
  106. package/framework/js/assessment/AssessmentUI.js +451 -0
  107. package/framework/js/automation/api-engagement.js +196 -0
  108. package/framework/js/automation/api-interactions.js +167 -0
  109. package/framework/js/automation/api.js +242 -0
  110. package/framework/js/automation/index.js +41 -0
  111. package/framework/js/components/interactions/drag-drop.js +884 -0
  112. package/framework/js/components/interactions/fill-in.js +535 -0
  113. package/framework/js/components/interactions/hotspot.js +702 -0
  114. package/framework/js/components/interactions/interaction-base.js +511 -0
  115. package/framework/js/components/interactions/likert.js +301 -0
  116. package/framework/js/components/interactions/matching.js +699 -0
  117. package/framework/js/components/interactions/multiple-choice.js +377 -0
  118. package/framework/js/components/interactions/numeric.js +271 -0
  119. package/framework/js/components/interactions/sequencing.js +423 -0
  120. package/framework/js/components/interactions/true-false.js +241 -0
  121. package/framework/js/components/ui-components/accordion.js +442 -0
  122. package/framework/js/components/ui-components/alert.js +88 -0
  123. package/framework/js/components/ui-components/audio-player.js +1193 -0
  124. package/framework/js/components/ui-components/callout.js +121 -0
  125. package/framework/js/components/ui-components/carousel.js +145 -0
  126. package/framework/js/components/ui-components/checkbox-group.js +87 -0
  127. package/framework/js/components/ui-components/checklist.js +40 -0
  128. package/framework/js/components/ui-components/collapse.js +114 -0
  129. package/framework/js/components/ui-components/comparison.js +30 -0
  130. package/framework/js/components/ui-components/conditional-display.js +150 -0
  131. package/framework/js/components/ui-components/content-image.js +41 -0
  132. package/framework/js/components/ui-components/dropdown.js +262 -0
  133. package/framework/js/components/ui-components/embed-frame.js +274 -0
  134. package/framework/js/components/ui-components/features.js +33 -0
  135. package/framework/js/components/ui-components/flip-card.js +230 -0
  136. package/framework/js/components/ui-components/form-validator.js +76 -0
  137. package/framework/js/components/ui-components/hero.js +49 -0
  138. package/framework/js/components/ui-components/index.js +12 -0
  139. package/framework/js/components/ui-components/interactive-image.js +235 -0
  140. package/framework/js/components/ui-components/interactive-timeline.js +285 -0
  141. package/framework/js/components/ui-components/intro-cards.js +35 -0
  142. package/framework/js/components/ui-components/lightbox.js +652 -0
  143. package/framework/js/components/ui-components/modal.js +386 -0
  144. package/framework/js/components/ui-components/notifications.js +145 -0
  145. package/framework/js/components/ui-components/progress.js +88 -0
  146. package/framework/js/components/ui-components/quote.js +41 -0
  147. package/framework/js/components/ui-components/stats.js +33 -0
  148. package/framework/js/components/ui-components/steps.js +41 -0
  149. package/framework/js/components/ui-components/tabs.js +255 -0
  150. package/framework/js/components/ui-components/timeline.js +42 -0
  151. package/framework/js/components/ui-components/toggle-group.js +73 -0
  152. package/framework/js/components/ui-components/tooltip.js +458 -0
  153. package/framework/js/components/ui-components/value-display.js +133 -0
  154. package/framework/js/components/ui-components/video-player.js +686 -0
  155. package/framework/js/core/component-catalog.js +121 -0
  156. package/framework/js/core/event-bus.js +178 -0
  157. package/framework/js/core/interaction-catalog.js +149 -0
  158. package/framework/js/dev/runtime-linter.js +1725 -0
  159. package/framework/js/drivers/cmi5-driver.js +768 -0
  160. package/framework/js/drivers/driver-factory.js +77 -0
  161. package/framework/js/drivers/driver-interface.js +110 -0
  162. package/framework/js/drivers/http-driver-base.js +241 -0
  163. package/framework/js/drivers/lti-driver.js +508 -0
  164. package/framework/js/drivers/proxy-driver.js +444 -0
  165. package/framework/js/drivers/scorm-12-driver.js +560 -0
  166. package/framework/js/drivers/scorm-2004-driver.js +775 -0
  167. package/framework/js/drivers/scorm-driver-base.js +112 -0
  168. package/framework/js/engagement/engagement-manager.js +404 -0
  169. package/framework/js/engagement/engagement-progress.js +191 -0
  170. package/framework/js/engagement/engagement-trackers.js +215 -0
  171. package/framework/js/engagement/requirement-strategies.js +268 -0
  172. package/framework/js/main.js +727 -0
  173. package/framework/js/managers/accessibility-manager.js +499 -0
  174. package/framework/js/managers/assessment-manager.js +230 -0
  175. package/framework/js/managers/audio-manager.js +944 -0
  176. package/framework/js/managers/comment-manager.js +88 -0
  177. package/framework/js/managers/flag-manager.js +86 -0
  178. package/framework/js/managers/interaction-manager.js +254 -0
  179. package/framework/js/managers/interaction-registry.js +96 -0
  180. package/framework/js/managers/objective-manager.js +423 -0
  181. package/framework/js/managers/score-manager.js +441 -0
  182. package/framework/js/managers/video-manager.js +536 -0
  183. package/framework/js/navigation/Breadcrumbs.js +234 -0
  184. package/framework/js/navigation/NavigationActions.js +1132 -0
  185. package/framework/js/navigation/NavigationState.js +276 -0
  186. package/framework/js/navigation/NavigationUI.js +574 -0
  187. package/framework/js/navigation/document-gallery.js +357 -0
  188. package/framework/js/navigation/navigation-helpers.js +175 -0
  189. package/framework/js/navigation/navigation-validators.js +174 -0
  190. package/framework/js/state/index.js +8 -0
  191. package/framework/js/state/lms-connection.js +482 -0
  192. package/framework/js/state/lms-error-utils.js +58 -0
  193. package/framework/js/state/state-commits.js +200 -0
  194. package/framework/js/state/state-domains.js +86 -0
  195. package/framework/js/state/state-manager.js +502 -0
  196. package/framework/js/state/state-validation.js +311 -0
  197. package/framework/js/state/transaction-log.js +41 -0
  198. package/framework/js/state/xapi-statement-service.js +325 -0
  199. package/framework/js/utilities/access-control.js +99 -0
  200. package/framework/js/utilities/breakpoint-manager.js +315 -0
  201. package/framework/js/utilities/canvas-slide.js +35 -0
  202. package/framework/js/utilities/conditional-display.js +388 -0
  203. package/framework/js/utilities/course-channel.js +214 -0
  204. package/framework/js/utilities/course-helpers.js +420 -0
  205. package/framework/js/utilities/data-reporter.js +273 -0
  206. package/framework/js/utilities/error-reporter.js +313 -0
  207. package/framework/js/utilities/hotspot-helper.js +341 -0
  208. package/framework/js/utilities/icons.js +348 -0
  209. package/framework/js/utilities/logger.js +92 -0
  210. package/framework/js/utilities/markdown-renderer.js +45 -0
  211. package/framework/js/utilities/scroll-tracker.js +68 -0
  212. package/framework/js/utilities/ui-initializer.js +146 -0
  213. package/framework/js/utilities/utilities.js +293 -0
  214. package/framework/js/utilities/view-manager.js +227 -0
  215. package/framework/js/validation/html-validators.js +422 -0
  216. package/framework/js/validation/scorm-validators.js +438 -0
  217. package/framework/js/vendor/pipwerks.js +931 -0
  218. package/framework/scripts/generate-narration.js +629 -0
  219. package/framework/scripts/tts-providers/azure-provider.js +178 -0
  220. package/framework/scripts/tts-providers/base-provider.js +81 -0
  221. package/framework/scripts/tts-providers/deepgram-provider.js +135 -0
  222. package/framework/scripts/tts-providers/elevenlabs-provider.js +148 -0
  223. package/framework/scripts/tts-providers/google-provider.js +272 -0
  224. package/framework/scripts/tts-providers/index.js +158 -0
  225. package/framework/scripts/tts-providers/openai-provider.js +143 -0
  226. package/framework/version.json +63 -0
  227. package/lib/authoring-api.js +919 -0
  228. package/lib/build-linter.js +450 -0
  229. package/lib/build-packaging.js +186 -0
  230. package/lib/build.js +88 -0
  231. package/lib/cloud.js +691 -0
  232. package/lib/convert.js +341 -0
  233. package/lib/course-parser.js +936 -0
  234. package/lib/course-writer.js +258 -0
  235. package/lib/create.js +248 -0
  236. package/lib/css-index.js +237 -0
  237. package/lib/dev.js +51 -0
  238. package/lib/export-content.js +1246 -0
  239. package/lib/headless-browser.js +413 -0
  240. package/lib/import.js +377 -0
  241. package/lib/index.js +80 -0
  242. package/lib/info.js +79 -0
  243. package/lib/interaction-formatters.js +568 -0
  244. package/lib/manifest/cmi5-manifest.js +63 -0
  245. package/lib/manifest/lti-tool-config.js +53 -0
  246. package/lib/manifest/manifest-factory.js +99 -0
  247. package/lib/manifest/scorm-12-manifest.js +61 -0
  248. package/lib/manifest/scorm-2004-manifest.js +94 -0
  249. package/lib/manifest/scorm-proxy-manifest.js +104 -0
  250. package/lib/manifest-parser.js +96 -0
  251. package/lib/mcp-prompts.js +753 -0
  252. package/lib/mcp-server.js +316 -0
  253. package/lib/narration.js +53 -0
  254. package/lib/pdf-structure.js +142 -0
  255. package/lib/preview-export.js +231 -0
  256. package/lib/preview-routes-api.js +662 -0
  257. package/lib/preview-routes-editing.js +159 -0
  258. package/lib/preview-routes-lms.js +230 -0
  259. package/lib/preview-server.js +564 -0
  260. package/lib/project-utils.js +269 -0
  261. package/lib/proxy-templates/proxy.html +68 -0
  262. package/lib/proxy-templates/scorm-bridge.js +112 -0
  263. package/lib/scaffold.js +193 -0
  264. package/lib/schema-extractor.js +361 -0
  265. package/lib/slide-source-editor.js +586 -0
  266. package/lib/stub-player/app-viewer.js +195 -0
  267. package/lib/stub-player/app.js +370 -0
  268. package/lib/stub-player/catalog-panel.js +312 -0
  269. package/lib/stub-player/config-panel.js +1303 -0
  270. package/lib/stub-player/content-generator.js +586 -0
  271. package/lib/stub-player/content-viewer.js +173 -0
  272. package/lib/stub-player/debug-panel.js +420 -0
  273. package/lib/stub-player/edit-mode.js +922 -0
  274. package/lib/stub-player/edit-utils.js +400 -0
  275. package/lib/stub-player/header-bar.js +354 -0
  276. package/lib/stub-player/interaction-editor.js +210 -0
  277. package/lib/stub-player/interactions-panel.js +565 -0
  278. package/lib/stub-player/lms-api.js +1094 -0
  279. package/lib/stub-player/login-screen.js +74 -0
  280. package/lib/stub-player/outline-mode.js +689 -0
  281. package/lib/stub-player/styles/_assessments-panel.css +245 -0
  282. package/lib/stub-player/styles/_base.css +89 -0
  283. package/lib/stub-player/styles/_catalog-icons.css +96 -0
  284. package/lib/stub-player/styles/_catalog-panel.css +291 -0
  285. package/lib/stub-player/styles/_config-panel.css +636 -0
  286. package/lib/stub-player/styles/_content-viewer.css +834 -0
  287. package/lib/stub-player/styles/_debug-panel.css +576 -0
  288. package/lib/stub-player/styles/_edit-mode.css +128 -0
  289. package/lib/stub-player/styles/_header-bar.css +343 -0
  290. package/lib/stub-player/styles/_interaction-editor.css +140 -0
  291. package/lib/stub-player/styles/_interactions-panel.css +1038 -0
  292. package/lib/stub-player/styles/_login-screen.css +102 -0
  293. package/lib/stub-player/styles/_outline-mode.css +752 -0
  294. package/lib/stub-player/styles.css +15 -0
  295. package/lib/stub-player.js +160 -0
  296. package/lib/test-data-reporting.js +176 -0
  297. package/lib/test-error-reporting.js +146 -0
  298. package/lib/token.js +86 -0
  299. package/lib/upgrade.js +257 -0
  300. package/lib/validation-rules.js +517 -0
  301. package/lib/vite-plugin-content-discovery.js +296 -0
  302. package/package.json +108 -0
  303. package/schemas/XMLSchema.dtd +402 -0
  304. package/schemas/adlcp_v1p3.xsd +111 -0
  305. package/schemas/adlnav_v1p3.xsd +61 -0
  306. package/schemas/adlseq_v1p3.xsd +93 -0
  307. package/schemas/common/anyElement.xsd +27 -0
  308. package/schemas/common/dataTypes.xsd +138 -0
  309. package/schemas/common/elementNames.xsd +767 -0
  310. package/schemas/common/elementTypes.xsd +786 -0
  311. package/schemas/common/rootElement.xsd +31 -0
  312. package/schemas/common/vocabTypes.xsd +345 -0
  313. package/schemas/common/vocabValues.xsd +257 -0
  314. package/schemas/datatypes.dtd +203 -0
  315. package/schemas/ims_xml.xsd +35 -0
  316. package/schemas/imscp_v1p1.xsd +368 -0
  317. package/schemas/imsss_v1p0.xsd +67 -0
  318. package/schemas/imsss_v1p0auxresource.xsd +19 -0
  319. package/schemas/imsss_v1p0control.xsd +20 -0
  320. package/schemas/imsss_v1p0delivery.xsd +17 -0
  321. package/schemas/imsss_v1p0limit.xsd +47 -0
  322. package/schemas/imsss_v1p0objective.xsd +67 -0
  323. package/schemas/imsss_v1p0random.xsd +16 -0
  324. package/schemas/imsss_v1p0rollup.xsd +46 -0
  325. package/schemas/imsss_v1p0seqrule.xsd +108 -0
  326. package/schemas/imsss_v1p0util.xsd +94 -0
  327. package/schemas/license.txt +17 -0
  328. package/schemas/lom.xsd +102 -0
  329. package/schemas/lomCustom.xsd +62 -0
  330. package/schemas/lomLoose.xsd +62 -0
  331. package/schemas/lomStrict.xsd +62 -0
  332. package/schemas/xml.xsd +81 -0
  333. package/template/.env.example +92 -0
  334. package/template/course/assets/audio/example-intro.mp3 +0 -0
  335. package/template/course/assets/audio/example-ui-demo--compact-player.mp3 +0 -0
  336. package/template/course/assets/audio/example-ui-demo--demo-modal.mp3 +0 -0
  337. package/template/course/assets/audio/example-ui-demo--full-player.mp3 +0 -0
  338. package/template/course/assets/docs/example_md_1.md +39 -0
  339. package/template/course/assets/docs/example_md_2.md +41 -0
  340. package/template/course/assets/docs/example_pdf_1_thumbnail.png +0 -0
  341. package/template/course/assets/docs/example_pdf_2.pdf +0 -0
  342. package/template/course/assets/images/course-architecture.svg +36 -0
  343. package/template/course/assets/images/logo.svg +14 -0
  344. package/template/course/assets/widgets/counter-demo.html +190 -0
  345. package/template/course/assets/widgets/gravity-painter.html +384 -0
  346. package/template/course/course-config.js +539 -0
  347. package/template/course/icons.js +19 -0
  348. package/template/course/interactions/PLUGIN_GUIDE.md +97 -0
  349. package/template/course/slides/example-course-structure.js +138 -0
  350. package/template/course/slides/example-final-exam.js +144 -0
  351. package/template/course/slides/example-finishing.js +127 -0
  352. package/template/course/slides/example-interactions-showcase.js +615 -0
  353. package/template/course/slides/example-preview-tour.js +129 -0
  354. package/template/course/slides/example-remedial.js +143 -0
  355. package/template/course/slides/example-summary.js +103 -0
  356. package/template/course/slides/example-ui-showcase.js +1805 -0
  357. package/template/course/slides/example-welcome.js +123 -0
  358. package/template/course/slides/example-workflow.js +140 -0
  359. package/template/course/theme.css +165 -0
  360. package/template/eslint.config.js +47 -0
  361. package/template/package.json +28 -0
  362. package/template/vite.config.js +339 -0
@@ -0,0 +1,15 @@
1
+ /* Stub Player Styles — split into per-module partials */
2
+ @import url('./styles/_base.css');
3
+ @import url('./styles/_header-bar.css');
4
+ @import url('./styles/_debug-panel.css');
5
+ @import url('./styles/_edit-mode.css');
6
+
7
+ @import url('./styles/_content-viewer.css');
8
+ @import url('./styles/_config-panel.css');
9
+ @import url('./styles/_interactions-panel.css');
10
+ @import url('./styles/_assessments-panel.css');
11
+ @import url('./styles/_interaction-editor.css');
12
+ @import url('./styles/_login-screen.css');
13
+ @import url('./styles/_catalog-panel.css');
14
+ @import url('./styles/_catalog-icons.css');
15
+ @import url('./styles/_outline-mode.css');
@@ -0,0 +1,160 @@
1
+ /**
2
+ * stub-player.js - Shared stub LMS player generator
3
+ *
4
+ * Used by both preview-server.js (live mode) and preview-export.js (static export)
5
+ */
6
+ import { readFileSync, readdirSync } from 'fs';
7
+ import { dirname, join } from 'path';
8
+ import { fileURLToPath } from 'url';
9
+
10
+ const __filename = fileURLToPath(import.meta.url);
11
+ const __dirname = dirname(__filename);
12
+
13
+ // CSS is loaded lazily inside generateStubPlayer() to filter by mode
14
+ const stylesDir = join(__dirname, 'stub-player/styles');
15
+ const VIEWER_STYLES = ['_base.css', '_header-bar.css', '_content-viewer.css', '_login-screen.css'];
16
+
17
+ function loadStyles(isLive) {
18
+ return readdirSync(stylesDir)
19
+ .filter(f => f.endsWith('.css'))
20
+ .filter(f => isLive || VIEWER_STYLES.includes(f))
21
+ .sort()
22
+ .map(f => readFileSync(join(stylesDir, f), 'utf-8'))
23
+ .join('\n');
24
+ }
25
+
26
+ // HTML template modules
27
+ import { generateHeaderBar } from './stub-player/header-bar.js';
28
+ import { generateDebugPanel } from './stub-player/debug-panel.js';
29
+ import { generateConfigPanel } from './stub-player/config-panel.js';
30
+ import { generateInteractionsPanel } from './stub-player/interactions-panel.js';
31
+ import { generateContentViewer } from './stub-player/content-viewer.js';
32
+ import { generateCatalogPanel } from './stub-player/catalog-panel.js';
33
+ import { generateOutlineMode } from './stub-player/outline-mode.js';
34
+
35
+ import { generateLoginScreen } from './stub-player/login-screen.js';
36
+
37
+ import { generateInteractionEditor } from './stub-player/interaction-editor.js';
38
+
39
+ import { escapeHtml } from './project-utils.js';
40
+
41
+ // Re-export for consumers that import from stub-player
42
+ export { escapeHtml };
43
+
44
+
45
+ /**
46
+ * Generate the stub LMS player HTML
47
+ * @param {object} config - Configuration for the player
48
+ * @param {string} config.title - Course title
49
+ * @param {string} config.launchUrl - URL to load in iframe (file path or http URL)
50
+ * @param {string} config.storageKey - localStorage key for persistence
51
+ * @param {string} [config.passwordHash] - Optional SHA-256 hash of password for access
52
+ * @param {boolean} [config.isLive] - True for live mode (shows "Live" badge)
53
+ * @param {boolean} [config.liveReload] - True to enable live reload via SSE
54
+ * @param {string} [config.courseContent] - Markdown/HTML content for the content viewer
55
+ * @param {string|number} [config.startSlide] - Slide ID or index to navigate to on load
56
+ * @returns {string} - Complete HTML for the player page
57
+ */
58
+ export function generateStubPlayer(config) {
59
+ const { title, launchUrl, storageKey, passwordHash, isLive, liveReload, courseContent, startSlide, isDesktop, moduleBasePath = '/__stub-player' } = config;
60
+ const hasPassword = !!passwordHash;
61
+ const hasContent = !!courseContent;
62
+
63
+ const stubPlayerStyles = loadStyles(isLive);
64
+
65
+ return `<!DOCTYPE html>
66
+ <html lang="en">
67
+ <head>
68
+ <meta charset="UTF-8">
69
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
70
+ <title>${escapeHtml(title)} - Preview</title>
71
+ <link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100' fill='none' stroke='%23fff' stroke-width='7' stroke-linecap='round' stroke-linejoin='round'><polyline points='25,22 5,50 25,78'/><polyline points='75,22 95,50 75,78'/><path d='M50,28 C40,28 33,36 33,45 C33,52 38,56 42,60 L42,65 L58,65 L58,60 C62,56 67,52 67,45 C67,36 60,28 50,28' stroke-width='6'/><line x1='44' y1='70' x2='56' y2='70' stroke-width='6'/><line x1='46' y1='75' x2='54' y2='75' stroke-width='6'/></svg>">
72
+ <style>
73
+ ${stubPlayerStyles}
74
+ </style>
75
+ </head>
76
+ <body>
77
+ ${hasPassword ? generateLoginScreen({ title: escapeHtml(title) }) : ''}
78
+
79
+ <iframe id="stub-player-course-frame" name="stub-player-course-frame"></iframe>
80
+
81
+ ${isLive ? generateOutlineMode() : ''}
82
+
83
+ ${generateHeaderBar({ isLive, hasContent })}
84
+
85
+ ${isLive ? generateDebugPanel() : ''}
86
+
87
+
88
+
89
+
90
+
91
+ ${hasContent ? generateContentViewer({ isLive }) : ''}
92
+
93
+ ${isLive ? generateConfigPanel() : ''}
94
+
95
+ ${isLive ? generateInteractionsPanel() : ''}
96
+
97
+ ${isLive ? generateCatalogPanel() : ''}
98
+
99
+ ${isLive ? generateInteractionEditor() : ''}
100
+
101
+ <script>
102
+ // Inject Configuration
103
+ window.STUB_CONFIG = {
104
+ title: ${JSON.stringify(title)},
105
+ launchUrl: ${JSON.stringify(launchUrl)},
106
+ storageKey: ${JSON.stringify(storageKey)},
107
+ passwordHash: ${hasPassword ? JSON.stringify(passwordHash) : 'null'},
108
+ isLive: ${isLive || false},
109
+ liveReload: ${liveReload || false},
110
+ startSlide: ${startSlide !== undefined ? JSON.stringify(startSlide) : 'null'},
111
+ courseContent: ${hasContent ? JSON.stringify(courseContent) : 'null'},
112
+ isDesktop: ${isDesktop || false},
113
+ isCI: ${!!process.env.CI}
114
+ };
115
+ </script>
116
+ <script type="module" src="${moduleBasePath}/${isLive ? 'app' : 'app-viewer'}.js"></script>
117
+
118
+ ${liveReload ? `
119
+ <script>
120
+ // Live reload via Server-Sent Events
121
+ // Deferred to window.load to prevent "connection interrupted while page was loading" warnings
122
+ (function() {
123
+ let reconnectAttempts = 0;
124
+ const maxReconnectAttempts = 10;
125
+
126
+ function connect() {
127
+ const eventSource = new EventSource('/__reload');
128
+
129
+ eventSource.onmessage = function(event) {
130
+ if (event.data === 'reload') {
131
+ console.log('[Live Reload] Rebuilding complete, reloading course...');
132
+ // Clear error log before reload to prevent stale errors
133
+ if (window.stubPlayer?.clearErrors) window.stubPlayer.clearErrors();
134
+ const frame = document.getElementById('stub-player-course-frame');
135
+ if (frame) {
136
+ frame.contentWindow.location.reload();
137
+ }
138
+ } else if (event.data === 'connected') {
139
+ reconnectAttempts = 0;
140
+ }
141
+ };
142
+
143
+ eventSource.onerror = function() {
144
+ eventSource.close();
145
+ reconnectAttempts++;
146
+ if (reconnectAttempts <= maxReconnectAttempts) {
147
+ setTimeout(connect, 2000);
148
+ } else {
149
+ console.warn('[Live Reload] Max reconnect attempts reached. Refresh page manually.');
150
+ }
151
+ };
152
+ }
153
+
154
+ window.addEventListener('load', connect);
155
+ })();
156
+ </script>
157
+ ` : ''}
158
+ </body>
159
+ </html>`;
160
+ }
@@ -0,0 +1,176 @@
1
+ /**
2
+ * Test Data Reporting
3
+ *
4
+ * Sends test data records to the configured endpoint to verify the setup works.
5
+ */
6
+
7
+ import fs from 'fs';
8
+ import path from 'path';
9
+ import { pathToFileURL } from 'url';
10
+
11
+ /**
12
+ * Send a test data record to the configured endpoint
13
+ * @param {Object} options - Command options
14
+ * @param {string} options.type - Type of test: 'assessment', 'objective', 'interaction'
15
+ * @param {string} options.message - Custom message to include
16
+ */
17
+ export async function testDataReporting(options = {}) {
18
+ // Handle TLS certificate issues (corporate proxies)
19
+ if (options.insecure) {
20
+ process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
21
+ }
22
+
23
+ const cwd = process.cwd();
24
+ const configPath = path.join(cwd, 'course', 'course-config.js');
25
+
26
+ // Check if we're in a course project
27
+ if (!fs.existsSync(configPath)) {
28
+ console.error('❌ Error: course/course-config.js not found.');
29
+ console.error(' Run this command from the root of a coursecode project.');
30
+ process.exit(1);
31
+ }
32
+
33
+ // Load course config
34
+ let courseConfig;
35
+ try {
36
+ const configModule = await import(pathToFileURL(configPath).href);
37
+ courseConfig = configModule.courseConfig;
38
+ } catch (error) {
39
+ console.error('❌ Error loading course-config.js:', error.message);
40
+ process.exit(1);
41
+ }
42
+
43
+ // Check if data reporting is configured
44
+ const endpoint = courseConfig.environment?.dataReporting?.endpoint;
45
+ if (!endpoint) {
46
+ console.error('❌ Data reporting is not configured.');
47
+ console.error('');
48
+ console.error(' Add to course-config.js:');
49
+ console.error('');
50
+ console.error(' environment: {');
51
+ console.error(' dataReporting: {');
52
+ console.error(" endpoint: 'https://your-endpoint.workers.dev/data'");
53
+ console.error(' }');
54
+ console.error(' }');
55
+ process.exit(1);
56
+ }
57
+
58
+ // Validate URL has protocol (must match production behavior)
59
+ if (!endpoint.startsWith('http://') && !endpoint.startsWith('https://')) {
60
+ console.error('❌ Invalid endpoint URL: missing protocol.');
61
+ console.error('');
62
+ console.error(` Found: ${endpoint}`);
63
+ console.error(` Expected: https://${endpoint}`);
64
+ console.error('');
65
+ console.error(' The endpoint must include the full URL with https:// protocol.');
66
+ console.error(' Update your course-config.js dataReporting.endpoint.');
67
+ process.exit(1);
68
+ }
69
+
70
+ console.log('📊 Testing data reporting endpoint...');
71
+ console.log(` Endpoint: ${endpoint}`);
72
+ console.log('');
73
+
74
+ const recordType = options.type || 'assessment';
75
+ const testId = `cli-test-${Date.now()}`;
76
+
77
+ // Build test record based on type
78
+ let record;
79
+ switch (recordType) {
80
+ case 'objective':
81
+ record = {
82
+ type: 'objective',
83
+ data: {
84
+ objectiveId: testId,
85
+ completion_status: 'completed',
86
+ success_status: 'passed',
87
+ score: { scaled: 1.0 }
88
+ },
89
+ timestamp: new Date().toISOString()
90
+ };
91
+ break;
92
+ case 'interaction':
93
+ record = {
94
+ type: 'interaction',
95
+ data: {
96
+ interactionId: testId,
97
+ type: 'choice',
98
+ result: 'correct',
99
+ learner_response: 'a'
100
+ },
101
+ timestamp: new Date().toISOString()
102
+ };
103
+ break;
104
+ case 'assessment':
105
+ default:
106
+ record = {
107
+ type: 'assessment',
108
+ data: {
109
+ assessmentId: testId,
110
+ score: 100,
111
+ passed: true,
112
+ attemptNumber: 1,
113
+ totalQuestions: 5,
114
+ correctCount: 5,
115
+ timeSpent: 120
116
+ },
117
+ timestamp: new Date().toISOString()
118
+ };
119
+ break;
120
+ }
121
+
122
+ // Build payload matching data-reporter.js format
123
+ const payload = {
124
+ records: [record],
125
+ sentAt: new Date().toISOString(),
126
+ course: {
127
+ title: courseConfig.metadata?.title || 'Unknown Course',
128
+ version: courseConfig.metadata?.version || '0.0.0',
129
+ id: courseConfig.metadata?.id
130
+ },
131
+ _test: {
132
+ source: 'coursecode-cli',
133
+ message: options.message || 'Test data record from coursecode CLI'
134
+ }
135
+ };
136
+
137
+ console.log(` Record type: ${recordType}`);
138
+ console.log('');
139
+
140
+ try {
141
+ const response = await fetch(endpoint, {
142
+ method: 'POST',
143
+ headers: {
144
+ 'Content-Type': 'application/json'
145
+ },
146
+ body: JSON.stringify(payload)
147
+ });
148
+
149
+ if (response.ok) {
150
+ console.log(`✅ Success! Test ${recordType} record sent.`);
151
+ console.log('');
152
+ console.log(' Payload sent:');
153
+ console.log(` - Record type: ${recordType}`);
154
+ console.log(` - Course: ${courseConfig.metadata?.title || 'Unknown'}`);
155
+ console.log(` - Test ID: ${testId}`);
156
+ } else {
157
+ const errorText = await response.text();
158
+ console.error(`❌ Failed with status ${response.status}`);
159
+ console.error(` Response: ${errorText}`);
160
+ console.error('');
161
+ console.error(' Check your endpoint logs for details.');
162
+ process.exit(1);
163
+ }
164
+ } catch (error) {
165
+ console.error('❌ Network error:', error.message);
166
+ if (error.cause) {
167
+ console.error(' Cause:', error.cause.message || error.cause);
168
+ }
169
+ console.error('');
170
+ console.error(' Make sure:');
171
+ console.error(' - The endpoint URL is correct');
172
+ console.error(' - Your endpoint is deployed and accessible');
173
+ console.error(' - You have internet connectivity');
174
+ process.exit(1);
175
+ }
176
+ }
@@ -0,0 +1,146 @@
1
+ /**
2
+ * Test Error Reporting
3
+ *
4
+ * Sends a test error to the configured endpoint to verify the setup works.
5
+ */
6
+
7
+ import fs from 'fs';
8
+ import path from 'path';
9
+ import { pathToFileURL } from 'url';
10
+
11
+ /**
12
+ * Send a test error/report to the configured endpoint
13
+ * @param {Object} options - Command options
14
+ * @param {string} options.type - Type of test: 'error' or 'report'
15
+ * @param {string} options.message - Custom message to include
16
+ */
17
+ export async function testErrorReporting(options = {}) {
18
+ // Handle TLS certificate issues (corporate proxies)
19
+ if (options.insecure) {
20
+ process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
21
+ }
22
+
23
+ const cwd = process.cwd();
24
+ const configPath = path.join(cwd, 'course', 'course-config.js');
25
+
26
+ // Check if we're in a course project
27
+ if (!fs.existsSync(configPath)) {
28
+ console.error('❌ Error: course/course-config.js not found.');
29
+ console.error(' Run this command from the root of a coursecode project.');
30
+ process.exit(1);
31
+ }
32
+
33
+ // Load course config
34
+ let courseConfig;
35
+ try {
36
+ const configModule = await import(pathToFileURL(configPath).href);
37
+ courseConfig = configModule.courseConfig;
38
+ } catch (error) {
39
+ console.error('❌ Error loading course-config.js:', error.message);
40
+ process.exit(1);
41
+ }
42
+
43
+ // Check if error reporting is configured
44
+ const endpoint = courseConfig.environment?.errorReporting?.endpoint;
45
+ if (!endpoint) {
46
+ console.error('❌ Error reporting is not configured.');
47
+ console.error('');
48
+ console.error(' Add to course-config.js:');
49
+ console.error('');
50
+ console.error(' environment: {');
51
+ console.error(' errorReporting: {');
52
+ console.error(" endpoint: 'https://your-worker.workers.dev'");
53
+ console.error(' }');
54
+ console.error(' }');
55
+ process.exit(1);
56
+ }
57
+
58
+ // Validate URL has protocol (must match production behavior)
59
+ if (!endpoint.startsWith('http://') && !endpoint.startsWith('https://')) {
60
+ console.error('❌ Invalid endpoint URL: missing protocol.');
61
+ console.error('');
62
+ console.error(` Found: ${endpoint}`);
63
+ console.error(` Expected: https://${endpoint}`);
64
+ console.error('');
65
+ console.error(' The endpoint must include the full URL with https:// protocol.');
66
+ console.error(' Update your course-config.js errorReporting.endpoint.');
67
+ process.exit(1);
68
+ }
69
+
70
+ console.log('📧 Testing error reporting endpoint...');
71
+ console.log(` Endpoint: ${endpoint}`);
72
+ console.log('');
73
+
74
+ const isUserReport = options.type === 'report';
75
+
76
+ // Build test payload
77
+ const payload = isUserReport ? {
78
+ type: 'user_report',
79
+ description: options.message || 'This is a test user report from the coursecode CLI.',
80
+ timestamp: new Date().toISOString(),
81
+ url: 'cli://coursecode/test-error',
82
+ userAgent: `coursecode-cli/${process.env.npm_package_version || '1.0.0'}`,
83
+ currentSlide: 'test-slide',
84
+ course: {
85
+ title: courseConfig.metadata?.title || 'Unknown Course',
86
+ version: courseConfig.metadata?.version || '0.0.0',
87
+ id: courseConfig.metadata?.id
88
+ }
89
+ } : {
90
+ domain: 'cli-test',
91
+ operation: 'testErrorReporting',
92
+ message: options.message || 'This is a test error from the coursecode CLI. If you received this email, error reporting is working correctly!',
93
+ timestamp: new Date().toISOString(),
94
+ url: 'cli://coursecode/test-error',
95
+ userAgent: `coursecode-cli/${process.env.npm_package_version || '1.0.0'}`,
96
+ course: {
97
+ title: courseConfig.metadata?.title || 'Unknown Course',
98
+ version: courseConfig.metadata?.version || '0.0.0',
99
+ id: courseConfig.metadata?.id
100
+ },
101
+ context: {
102
+ testType: 'cli-verification',
103
+ nodeVersion: process.version
104
+ }
105
+ };
106
+
107
+ try {
108
+ const response = await fetch(endpoint, {
109
+ method: 'POST',
110
+ headers: {
111
+ 'Content-Type': 'application/json'
112
+ },
113
+ body: JSON.stringify(payload)
114
+ });
115
+
116
+ if (response.ok) {
117
+ console.log('✅ Success! Test ' + (isUserReport ? 'report' : 'error') + ' sent.');
118
+ console.log('');
119
+ console.log(' Check your email inbox for the notification.');
120
+ console.log(' Subject will be:');
121
+ if (isUserReport) {
122
+ console.log(` "[User Report] Issue reported in ${courseConfig.metadata?.title || 'Course'}"`);
123
+ } else {
124
+ console.log(' "[Course Error] cli-test: testErrorReporting"');
125
+ }
126
+ } else {
127
+ const errorText = await response.text();
128
+ console.error(`❌ Failed with status ${response.status}`);
129
+ console.error(` Response: ${errorText}`);
130
+ console.error('');
131
+ console.error(' Check your Cloudflare Worker logs for details.');
132
+ process.exit(1);
133
+ }
134
+ } catch (error) {
135
+ console.error('❌ Network error:', error.message);
136
+ if (error.cause) {
137
+ console.error(' Cause:', error.cause.message || error.cause);
138
+ }
139
+ console.error('');
140
+ console.error(' Make sure:');
141
+ console.error(' - The endpoint URL is correct');
142
+ console.error(' - The Cloudflare Worker is deployed');
143
+ console.error(' - You have internet connectivity');
144
+ process.exit(1);
145
+ }
146
+ }
package/lib/token.js ADDED
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Token generator CLI - generate secure access tokens for multi-tenant deployment
3
+ *
4
+ * Usage:
5
+ * coursecode token # Generate random token
6
+ * coursecode token --add client-name # Add client to course-config
7
+ */
8
+
9
+ import crypto from 'crypto';
10
+ import fs from 'fs';
11
+ import path from 'path';
12
+
13
+ /**
14
+ * Generate a cryptographically secure token
15
+ * @param {number} length - Token length in bytes (default: 24 = 32 chars base64url)
16
+ * @returns {string} URL-safe token
17
+ */
18
+ export function generateToken(length = 24) {
19
+ return crypto.randomBytes(length).toString('base64url');
20
+ }
21
+
22
+ /**
23
+ * Token command handler
24
+ */
25
+ export async function token(options = {}) {
26
+ const newToken = generateToken();
27
+
28
+ // If --add specified, add to course-config
29
+ if (options.add) {
30
+ const clientId = options.add;
31
+ const courseDir = path.join(process.cwd(), 'course');
32
+ const configPath = path.join(courseDir, 'course-config.js');
33
+
34
+ if (!fs.existsSync(configPath)) {
35
+ console.error('\n❌ No course-config.js found. Run from a CourseCode project directory.\n');
36
+ process.exit(1);
37
+ }
38
+
39
+ let content = fs.readFileSync(configPath, 'utf-8');
40
+
41
+ // Check if accessControl already exists
42
+ if (content.includes('accessControl:')) {
43
+ // Add to existing clients object
44
+ const clientsMatch = content.match(/accessControl:\s*\{[\s\S]*?clients:\s*\{/);
45
+ if (clientsMatch) {
46
+ const insertPos = clientsMatch.index + clientsMatch[0].length;
47
+ const newClient = `\n '${clientId}': { token: '${newToken}' },`;
48
+ content = content.slice(0, insertPos) + newClient + content.slice(insertPos);
49
+ }
50
+ } else {
51
+ // Add accessControl section before closing brace
52
+ const accessControlBlock = `
53
+ accessControl: {
54
+ enabled: true,
55
+ clients: {
56
+ '${clientId}': { token: '${newToken}' }
57
+ }
58
+ },`;
59
+ // Find the last closing brace of courseConfig
60
+ const lastBrace = content.lastIndexOf('};');
61
+ if (lastBrace !== -1) {
62
+ content = content.slice(0, lastBrace) + accessControlBlock + '\n' + content.slice(lastBrace);
63
+ }
64
+ }
65
+
66
+ fs.writeFileSync(configPath, content, 'utf-8');
67
+
68
+ console.log(`
69
+ ✅ Added client '${clientId}' to accessControl
70
+
71
+ Token: ${newToken}
72
+
73
+ Build will generate: ${clientId}_proxy.zip
74
+ `);
75
+ } else {
76
+ // Just generate and print token
77
+ console.log(`
78
+ 🔑 Generated access token:
79
+
80
+ ${newToken}
81
+
82
+ Use with --add to add a client:
83
+ coursecode token --add client-name
84
+ `);
85
+ }
86
+ }