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,361 @@
1
+ /**
2
+ * Schema Extractor - Build-time AST extraction using acorn
3
+ *
4
+ * Extracts interaction schemas WITHOUT importing files (avoiding dependency chain).
5
+ * Used by course-parser.js and other build tools.
6
+ */
7
+
8
+ import * as acorn from 'acorn';
9
+ import fs from 'fs';
10
+ import path from 'path';
11
+ import { fileURLToPath } from 'url';
12
+
13
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
14
+ const INTERACTIONS_DIR = path.join(__dirname, '../framework/js/components/interactions');
15
+ const UI_COMPONENTS_DIR = path.join(__dirname, '../framework/js/components/ui-components');
16
+
17
+ // Base schema properties shared by all interactions
18
+ const baseSchema = {
19
+ id: { type: 'string', required: true, description: 'Unique identifier' },
20
+ prompt: { type: 'string', required: true, description: 'Question or instruction text' },
21
+ controlled: { type: 'boolean', default: false, description: 'Managed externally' },
22
+ feedback: { type: 'object', description: 'Custom feedback messages' }
23
+ };
24
+
25
+ // Cache after first load
26
+ let schemasCache = null;
27
+ let metadataCache = null;
28
+ let componentSchemasCache = null;
29
+ let componentMetadataCache = null;
30
+
31
+ /**
32
+ * Get all interaction schemas (auto-discovered)
33
+ */
34
+ export function getAllSchemas() {
35
+ if (schemasCache) return schemasCache;
36
+ loadAll();
37
+ return schemasCache;
38
+ }
39
+
40
+ /**
41
+ * Get all interaction metadata (auto-discovered)
42
+ */
43
+ export function getAllMetadata() {
44
+ if (metadataCache) return metadataCache;
45
+ loadAll();
46
+ return metadataCache;
47
+ }
48
+
49
+ /**
50
+ * Load schemas and metadata from all interaction files
51
+ */
52
+ function loadAll() {
53
+ schemasCache = {};
54
+ metadataCache = {};
55
+
56
+ // Scan built-in interactions
57
+ scanDirectory(INTERACTIONS_DIR);
58
+
59
+ // Scan course-specific custom interactions
60
+ const customDir = path.join(__dirname, '../course/interactions');
61
+ scanDirectory(customDir);
62
+
63
+ // Alias
64
+ if (schemasCache['multiple-choice']) {
65
+ schemasCache['multiple-choice-single'] = schemasCache['multiple-choice'];
66
+ }
67
+ }
68
+
69
+ /**
70
+ * Scan a directory for interaction files
71
+ */
72
+ function scanDirectory(dir) {
73
+ if (!fs.existsSync(dir)) return;
74
+
75
+ for (const file of fs.readdirSync(dir)) {
76
+ if (!file.endsWith('.js') || file === 'interaction-base.js') continue;
77
+
78
+ const filePath = path.join(dir, file);
79
+ const exports = extractExports(filePath);
80
+
81
+ // Find standardized 'schema' and 'metadata' exports
82
+ const { schema, metadata } = exports;
83
+
84
+ if (schema?.type) {
85
+ schemasCache[schema.type] = schema;
86
+ }
87
+ if (metadata?.creator) {
88
+ metadataCache[metadata.creator] = metadata;
89
+ }
90
+ }
91
+ }
92
+
93
+ /**
94
+ * Get registered interaction types
95
+ */
96
+ export function getRegisteredTypes() {
97
+ return Object.keys(getAllSchemas());
98
+ }
99
+
100
+ /**
101
+ * Get schema for a type
102
+ */
103
+ export function getSchema(type) {
104
+ return getAllSchemas()[type] || null;
105
+ }
106
+
107
+ /**
108
+ * Get full schema with base properties merged
109
+ */
110
+ export function getFullSchema(type) {
111
+ const schema = getSchema(type);
112
+ if (!schema) return null;
113
+ return { ...schema, properties: { ...baseSchema, ...schema.properties } };
114
+ }
115
+
116
+ /**
117
+ * Get factory name for a type (from metadata)
118
+ */
119
+ export function getFactoryName(type) {
120
+ const schema = getSchema(type);
121
+ if (!schema) return null;
122
+
123
+ // Derive factory name from type: 'multiple-choice' -> 'createMultipleChoiceQuestion'
124
+ const camelCase = type.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
125
+ return `create${camelCase.charAt(0).toUpperCase() + camelCase.slice(1)}Question`;
126
+ }
127
+
128
+ // =============================================================================
129
+ // COMPONENT SCHEMAS (UI Components)
130
+ // =============================================================================
131
+
132
+ /**
133
+ * Get all component schemas (auto-discovered)
134
+ */
135
+ export function getAllComponentSchemas() {
136
+ if (componentSchemasCache) return componentSchemasCache;
137
+ loadAllComponents();
138
+ return componentSchemasCache;
139
+ }
140
+
141
+ /**
142
+ * Load schemas from all component files
143
+ */
144
+ function loadAllComponents() {
145
+ componentSchemasCache = {};
146
+ componentMetadataCache = {};
147
+
148
+ // Scan built-in UI components
149
+ scanComponentDirectory(UI_COMPONENTS_DIR);
150
+
151
+ // Scan course-specific custom components
152
+ const customDir = path.join(__dirname, '../course/components');
153
+ scanComponentDirectory(customDir);
154
+ }
155
+
156
+ /**
157
+ * Scan a directory for component files
158
+ */
159
+ function scanComponentDirectory(dir) {
160
+ if (!fs.existsSync(dir)) return;
161
+
162
+ for (const file of fs.readdirSync(dir)) {
163
+ if (!file.endsWith('.js') || file === 'index.js' || file.includes('-base')) continue;
164
+
165
+ const filePath = path.join(dir, file);
166
+ const exports = extractExports(filePath);
167
+
168
+ const { schema, metadata } = exports;
169
+
170
+ if (schema?.type) {
171
+ componentSchemasCache[schema.type] = schema;
172
+ if (metadata) componentMetadataCache[schema.type] = metadata;
173
+ }
174
+ }
175
+ }
176
+
177
+ /**
178
+ * Get component schema for a type
179
+ */
180
+ export function getComponentSchema(type) {
181
+ return getAllComponentSchemas()[type] || null;
182
+ }
183
+
184
+ /**
185
+ * Get registered component types
186
+ */
187
+ export function getRegisteredComponentTypes() {
188
+ return Object.keys(getAllComponentSchemas());
189
+ }
190
+
191
+ /**
192
+ * Get all component metadata (auto-discovered)
193
+ */
194
+ export function getAllComponentMetadata() {
195
+ if (!componentMetadataCache) loadAllComponents();
196
+ return componentMetadataCache;
197
+ }
198
+
199
+ /**
200
+ * Get metadata for a component type
201
+ */
202
+ export function getComponentMetadata(type) {
203
+ return getAllComponentMetadata()[type] || null;
204
+ }
205
+
206
+ /**
207
+ * Build reverse map: engagementTracking value -> component type
208
+ * e.g. { viewAllTabs: 'tabs', viewAllPanels: 'accordion', ... }
209
+ */
210
+ export function getEngagementTrackingMap() {
211
+ const metadata = getAllComponentMetadata();
212
+ const map = {};
213
+ for (const [type, meta] of Object.entries(metadata)) {
214
+ if (meta.engagementTracking) {
215
+ map[meta.engagementTracking] = type;
216
+ }
217
+ }
218
+ return map;
219
+ }
220
+
221
+ // =============================================================================
222
+ // AST EXTRACTION (acorn)
223
+ // =============================================================================
224
+
225
+ /**
226
+ * Extract all named exports from a file
227
+ */
228
+ function extractExports(filePath) {
229
+ const source = fs.readFileSync(filePath, 'utf-8');
230
+ const exports = {};
231
+
232
+ try {
233
+ const ast = acorn.parse(source, { ecmaVersion: 'latest', sourceType: 'module' });
234
+
235
+ for (const node of ast.body) {
236
+ if (node.type === 'ExportNamedDeclaration' && node.declaration?.type === 'VariableDeclaration') {
237
+ for (const decl of node.declaration.declarations) {
238
+ exports[decl.id.name] = toValue(decl.init);
239
+ }
240
+ }
241
+ }
242
+ } catch (_e) {
243
+ // Silently skip parse errors
244
+ }
245
+
246
+ return exports;
247
+ }
248
+
249
+ /**
250
+ * Convert AST node to JavaScript value
251
+ */
252
+ function toValue(node) {
253
+ if (!node) return null;
254
+
255
+ switch (node.type) {
256
+ case 'Literal':
257
+ return node.value;
258
+ case 'Identifier':
259
+ if (node.name === 'true') return true;
260
+ if (node.name === 'false') return false;
261
+ return null;
262
+ case 'ObjectExpression': {
263
+ const obj = {};
264
+ for (const p of node.properties) {
265
+ if (p.type !== 'Property') continue;
266
+ const key = p.key.name || p.key.value;
267
+ obj[key] = toValue(p.value);
268
+ }
269
+ return obj;
270
+ }
271
+ case 'ArrayExpression':
272
+ return node.elements.map(toValue);
273
+ case 'TemplateLiteral':
274
+ return node.quasis.map(q => q.value.cooked).join('');
275
+ default:
276
+ return null;
277
+ }
278
+ }
279
+
280
+ // =============================================================================
281
+ // ICON EXTRACTION (regex-based — icons are simple key→SVG maps)
282
+ // =============================================================================
283
+
284
+ let iconsCache = null;
285
+
286
+ /**
287
+ * Get all icons from DEFAULT_ICONS and course/icons.js.
288
+ * Returns { name: { category, svg, source } }
289
+ */
290
+ export function getAllIcons() {
291
+ if (iconsCache) return iconsCache;
292
+ iconsCache = {};
293
+
294
+ // Built-in icons
295
+ const iconsFile = path.join(__dirname, '../framework/js/utilities/icons.js');
296
+ if (fs.existsSync(iconsFile)) {
297
+ const builtIn = extractIconsFromSource(fs.readFileSync(iconsFile, 'utf-8'));
298
+ for (const [name, info] of Object.entries(builtIn)) {
299
+ iconsCache[name] = { ...info, source: 'built-in' };
300
+ }
301
+ }
302
+
303
+ // Custom course icons
304
+ const customFile = path.join(__dirname, '../course/icons.js');
305
+ if (fs.existsSync(customFile)) {
306
+ const custom = extractIconsFromSource(fs.readFileSync(customFile, 'utf-8'));
307
+ for (const [name, info] of Object.entries(custom)) {
308
+ iconsCache[name] = { ...info, source: 'custom' };
309
+ }
310
+ }
311
+
312
+ return iconsCache;
313
+ }
314
+
315
+ /**
316
+ * Extract icon keys, SVG content, and categories from source code.
317
+ * Parses comment lines (e.g. `// System UI`) as category headers.
318
+ * Returns { name: { category, svg } }
319
+ */
320
+ function extractIconsFromSource(source) {
321
+ const icons = {};
322
+ let currentCategory = 'Uncategorized';
323
+
324
+ // Match lines inside object literals: category comments and key-value pairs
325
+ const lines = source.split('\n');
326
+ let inObject = false;
327
+
328
+ for (const line of lines) {
329
+ const trimmed = line.trim();
330
+
331
+ // Detect start of an object (const DEFAULT_ICONS = { or export const customIcons = {)
332
+ if (/(?:const|let|var)\s+\w+\s*=\s*\{/.test(trimmed)) {
333
+ inObject = true;
334
+ currentCategory = 'Uncategorized';
335
+ continue;
336
+ }
337
+
338
+ if (!inObject) continue;
339
+
340
+ // Detect end of object
341
+ if (trimmed === '};') {
342
+ inObject = false;
343
+ continue;
344
+ }
345
+
346
+ // Category comment: `// System UI` or `// Media`
347
+ const categoryMatch = trimmed.match(/^\/\/\s*(.+)/);
348
+ if (categoryMatch) {
349
+ currentCategory = categoryMatch[1].trim();
350
+ continue;
351
+ }
352
+
353
+ // Icon entry: `'icon-name': '<path .../>',`
354
+ const iconMatch = trimmed.match(/^'([^']+)'\s*:\s*'(.+?)'\s*,?\s*$/);
355
+ if (iconMatch) {
356
+ icons[iconMatch[1]] = { category: currentCategory, svg: iconMatch[2] };
357
+ }
358
+ }
359
+
360
+ return icons;
361
+ }