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
package/lib/import.js ADDED
@@ -0,0 +1,377 @@
1
+ /**
2
+ * Import Command
3
+ *
4
+ * Imports a PowerPoint file as a CourseCode presentation course.
5
+ * Uses PowerPoint (via AppleScript on macOS) to export slides as PNGs,
6
+ * extracts text to markdown, and scaffolds a complete course project.
7
+ *
8
+ * Also supports --slides-dir for pre-exported slide images (no PowerPoint needed).
9
+ */
10
+
11
+ import fs from 'fs';
12
+ import fsp from 'fs/promises';
13
+ import path from 'path';
14
+ import { execSync } from 'child_process';
15
+ import { fileURLToPath } from 'url';
16
+ import { create } from './create.js';
17
+
18
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
19
+ const PACKAGE_ROOT = path.join(__dirname, '..');
20
+
21
+ // ─── Core Logic ──────────────────────────────────────────────────────────────
22
+
23
+ /**
24
+ * Import a PowerPoint into an existing course directory (in-place).
25
+ * Pure logic — no process.exit(), no console output.
26
+ *
27
+ * @param {string} pptxPath - Absolute path to .pptx file
28
+ * @param {string} courseDir - Absolute path to course/ directory (e.g., /project/course)
29
+ * @param {Object} [options]
30
+ * @param {string} [options.slidesDir] - Pre-exported slide images directory (skips PowerPoint export)
31
+ * @returns {Promise<{slideCount: number, sourceFile: string, textExtracted: boolean}>}
32
+ */
33
+ export async function importInPlace(pptxPath, courseDir, options = {}) {
34
+ const ext = path.extname(pptxPath).toLowerCase();
35
+ if (ext !== '.pptx') {
36
+ throw new Error('Only .pptx files are supported.');
37
+ }
38
+ if (!fs.existsSync(pptxPath)) {
39
+ throw new Error(`File not found: ${pptxPath}`);
40
+ }
41
+
42
+ // ── Acquire slide images ────────────────────────────────────
43
+ let pngFiles;
44
+ let tempDir = null;
45
+
46
+ if (options.slidesDir) {
47
+ const slidesDir = path.resolve(options.slidesDir);
48
+ if (!fs.existsSync(slidesDir)) {
49
+ throw new Error(`Slides directory not found: ${slidesDir}`);
50
+ }
51
+ pngFiles = await findExportedPngs(slidesDir);
52
+ } else {
53
+ if (process.platform !== 'darwin') {
54
+ throw new Error(
55
+ 'Automated PowerPoint export is only supported on macOS. ' +
56
+ 'Export slides to PNG manually and use the slidesDir option.'
57
+ );
58
+ }
59
+ if (!detectPowerPoint()) {
60
+ throw new Error(
61
+ 'Microsoft PowerPoint not found. ' +
62
+ 'Export slides to PNG manually and use the slidesDir option.'
63
+ );
64
+ }
65
+
66
+ tempDir = path.join(PACKAGE_ROOT, '.tmp-slide-export');
67
+ await fsp.mkdir(tempDir, { recursive: true });
68
+
69
+ try {
70
+ exportSlidesToPng(pptxPath, tempDir);
71
+ } catch (error) {
72
+ await fsp.rm(tempDir, { recursive: true, force: true });
73
+ throw new Error(`PowerPoint export failed: ${error.message}`);
74
+ }
75
+
76
+ pngFiles = await findExportedPngs(tempDir);
77
+ }
78
+
79
+ if (pngFiles.length === 0) {
80
+ if (tempDir) await fsp.rm(tempDir, { recursive: true, force: true });
81
+ throw new Error('No slide images found (.png, .jpg, .jpeg).');
82
+ }
83
+
84
+ // ── Copy images to assets/slides/ ───────────────────────────
85
+ const assetsDir = path.join(courseDir, 'assets', 'slides');
86
+ await fsp.mkdir(assetsDir, { recursive: true });
87
+
88
+ for (const png of pngFiles) {
89
+ const imgExt = path.extname(png.name).toLowerCase();
90
+ const destName = `slide-${String(png.num).padStart(2, '0')}${imgExt}`;
91
+ await fsp.copyFile(png.path, path.join(assetsDir, destName));
92
+ }
93
+
94
+ // ── Clear existing slides ───────────────────────────────────
95
+ const slidesDir = path.join(courseDir, 'slides');
96
+ await fsp.mkdir(slidesDir, { recursive: true });
97
+ const existingSlides = await fsp.readdir(slidesDir);
98
+ for (const file of existingSlides) {
99
+ if (file.endsWith('.html') || file.endsWith('.js')) {
100
+ await fsp.unlink(path.join(slidesDir, file));
101
+ }
102
+ }
103
+
104
+ // ── Generate slide HTML files ───────────────────────────────
105
+ for (let i = 0; i < pngFiles.length; i++) {
106
+ const slideNum = i + 1;
107
+ const paddedNum = String(slideNum).padStart(2, '0');
108
+ const imgExt = path.extname(pngFiles[i].name).toLowerCase();
109
+ const html = generateSlideHtml(paddedNum, imgExt);
110
+ await fsp.writeFile(
111
+ path.join(slidesDir, `slide-${paddedNum}.html`),
112
+ html,
113
+ 'utf-8'
114
+ );
115
+ }
116
+
117
+ // ── Extract text to references ──────────────────────────────
118
+ let textExtracted = false;
119
+ try {
120
+ const markdown = await extractText(pptxPath);
121
+ if (markdown) {
122
+ const refsDir = path.join(courseDir, 'references', 'converted');
123
+ await fsp.mkdir(refsDir, { recursive: true });
124
+ await fsp.writeFile(
125
+ path.join(refsDir, `${path.basename(pptxPath, ext)}.md`),
126
+ markdown,
127
+ 'utf-8'
128
+ );
129
+ textExtracted = true;
130
+ }
131
+ } catch {
132
+ // Text extraction is best-effort
133
+ }
134
+
135
+ // ── Write course-config.js ──────────────────────────────────
136
+ const name = path.basename(pptxPath, ext).toLowerCase().replace(/[^a-z0-9]+/g, '-');
137
+ const configContent = generateCourseConfig(name, pngFiles.length);
138
+ await fsp.writeFile(
139
+ path.join(courseDir, 'course-config.js'),
140
+ configContent,
141
+ 'utf-8'
142
+ );
143
+
144
+ // ── Cleanup ─────────────────────────────────────────────────
145
+ if (tempDir) {
146
+ await fsp.rm(tempDir, { recursive: true, force: true });
147
+ }
148
+
149
+ return {
150
+ slideCount: pngFiles.length,
151
+ sourceFile: path.basename(pptxPath),
152
+ textExtracted
153
+ };
154
+ }
155
+
156
+ // ─── CLI Entry Point ─────────────────────────────────────────────────────────
157
+
158
+ /**
159
+ * CLI import command — creates a new project and imports PowerPoint into it.
160
+ */
161
+ export async function importPresentation(source, options = {}) {
162
+ const sourcePath = path.resolve(source);
163
+ const ext = path.extname(sourcePath).toLowerCase();
164
+
165
+ if (ext !== '.pptx') {
166
+ console.error('\n❌ Only .pptx files are supported.\n');
167
+ process.exit(1);
168
+ }
169
+
170
+ if (!fs.existsSync(sourcePath)) {
171
+ console.error(`\n❌ File not found: ${sourcePath}\n`);
172
+ process.exit(1);
173
+ }
174
+
175
+ const name = options.name || path.basename(sourcePath, ext).toLowerCase().replace(/[^a-z0-9]+/g, '-');
176
+
177
+ console.log(`\n📊 Importing PowerPoint: ${path.basename(sourcePath)}`);
178
+ console.log(` Project name: ${name}\n`);
179
+
180
+ // Create blank project (no example slides — they'd just be deleted)
181
+ console.log(' ⏳ Creating course project...\n');
182
+ await create(name, { blank: true, install: options.install });
183
+
184
+ const targetDir = path.resolve(process.cwd(), name);
185
+ const courseDir = path.join(targetDir, 'course');
186
+
187
+ // Import presentation in-place
188
+ console.log('\n ⏳ Importing presentation slides...');
189
+
190
+ try {
191
+ const result = await importInPlace(sourcePath, courseDir, {
192
+ slidesDir: options.slidesDir
193
+ });
194
+
195
+ console.log(` ✅ ${result.slideCount} presentation slides created`);
196
+ if (result.textExtracted) {
197
+ console.log(' ✅ Text extracted to markdown');
198
+ }
199
+
200
+ console.log(`
201
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
202
+ ✅ Presentation imported successfully!
203
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
204
+
205
+ ${result.slideCount} slides from "${result.sourceFile}" → "${name}/"
206
+
207
+ Next steps:
208
+
209
+ cd ${name}
210
+ coursecode preview # Preview the presentation
211
+ coursecode build # Build SCORM/cmi5 package
212
+
213
+ Enhance with AI:
214
+ - Add assessments between slides
215
+ - Replace image slides with interactive HTML
216
+ - Add engagement tracking requirements
217
+ - Reference: course/references/converted/ for slide text
218
+
219
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
220
+ `);
221
+ } catch (error) {
222
+ console.error(`\n❌ Import failed: ${error.message}\n`);
223
+ process.exit(1);
224
+ }
225
+ }
226
+
227
+ // ─── Helpers ─────────────────────────────────────────────────────────────────
228
+
229
+ /**
230
+ * Check if Microsoft PowerPoint is installed (macOS)
231
+ */
232
+ function detectPowerPoint() {
233
+ const appPaths = [
234
+ '/Applications/Microsoft PowerPoint.app',
235
+ path.join(process.env.HOME, 'Applications/Microsoft PowerPoint.app')
236
+ ];
237
+ return appPaths.some(p => fs.existsSync(p));
238
+ }
239
+
240
+ /**
241
+ * Export PPTX slides to PNG via AppleScript (macOS)
242
+ */
243
+ function exportSlidesToPng(pptxPath, outputDir) {
244
+ const script = `
245
+ tell application "Microsoft PowerPoint"
246
+ activate
247
+ open POSIX file "${pptxPath}"
248
+ delay 2
249
+ save active presentation in POSIX file "${outputDir}" as save as PNG
250
+ close active presentation saving no
251
+ end tell
252
+ `;
253
+
254
+ try {
255
+ execSync(`osascript -e '${script.replace(/'/g, "'\\''")}'`, {
256
+ timeout: 60000,
257
+ stdio: 'pipe'
258
+ });
259
+ } catch (error) {
260
+ throw new Error(`AppleScript failed: ${error.stderr?.toString() || error.message}`);
261
+ }
262
+ }
263
+
264
+ /**
265
+ * Find exported image files in directory, sorted by slide number.
266
+ * Supports PNG, JPG, JPEG.
267
+ */
268
+ async function findExportedPngs(dir) {
269
+ const images = [];
270
+ const imageExts = ['.png', '.jpg', '.jpeg'];
271
+
272
+ async function scan(scanDir) {
273
+ const entries = await fsp.readdir(scanDir, { withFileTypes: true });
274
+ for (const entry of entries) {
275
+ const fullPath = path.join(scanDir, entry.name);
276
+ if (entry.isDirectory()) {
277
+ await scan(fullPath);
278
+ } else {
279
+ const ext = path.extname(entry.name).toLowerCase();
280
+ if (imageExts.includes(ext)) {
281
+ const match = entry.name.match(/(\d+)/);
282
+ const num = match ? parseInt(match[1], 10) : 0;
283
+ images.push({ path: fullPath, name: entry.name, num });
284
+ }
285
+ }
286
+ }
287
+ }
288
+
289
+ await scan(dir);
290
+
291
+ // If no numbers found, assign sequential numbers by alphabetical order
292
+ if (images.every(img => img.num === 0)) {
293
+ images.sort((a, b) => a.name.localeCompare(b.name));
294
+ images.forEach((img, i) => { img.num = i + 1; });
295
+ } else {
296
+ images.sort((a, b) => a.num - b.num);
297
+ }
298
+
299
+ return images;
300
+ }
301
+
302
+ /**
303
+ * Extract text content from PPTX as markdown
304
+ */
305
+ async function extractText(pptxPath) {
306
+ const PptxParser = (await import('node-pptx-parser')).default;
307
+
308
+ const parser = new PptxParser(pptxPath);
309
+ const textContent = await parser.extractText();
310
+
311
+ let markdown = '';
312
+ let slideNum = 0;
313
+
314
+ for (const slide of textContent) {
315
+ slideNum++;
316
+ markdown += `# Slide ${slideNum}\n\n`;
317
+
318
+ if (slide.text && slide.text.length > 0) {
319
+ for (const text of slide.text) {
320
+ if (text && text.trim()) {
321
+ markdown += `${text.trim()}\n\n`;
322
+ }
323
+ }
324
+ }
325
+
326
+ markdown += '---\n\n';
327
+ }
328
+
329
+ return markdown.trim();
330
+ }
331
+
332
+ /**
333
+ * Generate HTML for a single slide (image wrapper)
334
+ */
335
+ function generateSlideHtml(paddedNum, imgExt = '.png') {
336
+ return `<img src="assets/slides/slide-${paddedNum}${imgExt}" alt="Slide ${parseInt(paddedNum, 10)}" class="img-contain">
337
+ `;
338
+ }
339
+
340
+ /**
341
+ * Generate course-config.js for presentation import
342
+ */
343
+ function generateCourseConfig(name, slideCount) {
344
+ const title = name
345
+ .replace(/-/g, ' ')
346
+ .replace(/\b\w/g, c => c.toUpperCase());
347
+
348
+ const slides = [];
349
+ for (let i = 1; i <= slideCount; i++) {
350
+ const padded = String(i).padStart(2, '0');
351
+ slides.push(` { id: 'slide-${padded}', title: 'Slide ${i}', file: 'slides/slide-${padded}.html' }`);
352
+ }
353
+
354
+ return `/**
355
+ * Course Configuration
356
+ * Imported from PowerPoint presentation
357
+ */
358
+ export const courseConfig = {
359
+ title: '${title}',
360
+ source: 'powerpoint-import',
361
+ layout: 'presentation',
362
+
363
+ navigation: {
364
+ sidebar: { enabled: false },
365
+ breadcrumbs: { enabled: false }
366
+ },
367
+
368
+ slideDefaults: {
369
+ contentWidth: 'full'
370
+ },
371
+
372
+ structure: [
373
+ ${slides.join(',\n')}
374
+ ]
375
+ };
376
+ `;
377
+ }
package/lib/index.js ADDED
@@ -0,0 +1,80 @@
1
+ /**
2
+ * CourseCode Framework - Library Entry Point
3
+ *
4
+ * This module exports the core utilities for programmatic course building.
5
+ */
6
+
7
+ // Stub Player - generates browser-based course player
8
+ export { generateStubPlayer } from './stub-player.js';
9
+
10
+ // Manifest Generation - creates SCORM/cmi5 manifests
11
+ export { generateManifest, getSchemaFiles } from './manifest/manifest-factory.js';
12
+
13
+ // Content Parsing - unified parser for courses
14
+ export {
15
+ parseCourse,
16
+ parseSlideSource,
17
+ extractAssessment,
18
+ extractNarration,
19
+ extractInteractions,
20
+ parseElements,
21
+ resolveElementByPath
22
+ } from './course-parser.js';
23
+
24
+ // Build utilities
25
+ export { build } from './build.js';
26
+ export {
27
+ createStandardPackage,
28
+ createProxyPackage,
29
+ createRemotePackage,
30
+ createExternalPackagesForClients,
31
+ validateExternalHostingConfig
32
+ } from './build-packaging.js';
33
+
34
+ // Build Linter - validate course configuration
35
+ export {
36
+ lintCourse,
37
+ lint
38
+ } from './build-linter.js';
39
+
40
+ // Shared Validation Rules (used by both browser and Node.js linters)
41
+ export {
42
+ flattenStructure,
43
+ validateAssessmentConfig,
44
+ validateQuestionConfig,
45
+ validateEngagement,
46
+ validateRequirementConfig,
47
+ validateGlobalConfig,
48
+ formatLintResults
49
+ } from './validation-rules.js';
50
+
51
+ // Re-export path utilities for template access
52
+ import { fileURLToPath } from 'url';
53
+ import { dirname, join } from 'path';
54
+
55
+ const __filename = fileURLToPath(import.meta.url);
56
+ const __dirname = dirname(__filename);
57
+
58
+ /**
59
+ * Get the absolute path to the template directory
60
+ * @returns {string} Path to template directory
61
+ */
62
+ export function getTemplatePath() {
63
+ return join(__dirname, '..', 'template');
64
+ }
65
+
66
+ /**
67
+ * Get the absolute path to the framework directory
68
+ * @returns {string} Path to framework directory
69
+ */
70
+ export function getFrameworkPath() {
71
+ return join(__dirname, '..', 'framework');
72
+ }
73
+
74
+ /**
75
+ * Get the absolute path to the schemas directory
76
+ * @returns {string} Path to schemas directory
77
+ */
78
+ export function getSchemasPath() {
79
+ return join(__dirname, '..', 'schemas');
80
+ }
package/lib/info.js ADDED
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Info command - show info about current project
3
+ */
4
+
5
+ import fs from 'fs';
6
+ import path from 'path';
7
+ import { fileURLToPath } from 'url';
8
+
9
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
10
+ const PACKAGE_ROOT = path.join(__dirname, '..');
11
+
12
+ export async function info() {
13
+ const cwd = process.cwd();
14
+
15
+ // CLI info
16
+ const cliPkg = JSON.parse(fs.readFileSync(path.join(PACKAGE_ROOT, 'package.json'), 'utf-8'));
17
+
18
+ console.log(`
19
+ 📦 CourseCode CLI v${cliPkg.version}
20
+ `);
21
+
22
+ // Check if in a project
23
+ const rcPath = path.join(cwd, '.coursecoderc.json');
24
+ const courseConfigPath = path.join(cwd, 'course', 'course-config.js');
25
+
26
+ if (!fs.existsSync(rcPath) && !fs.existsSync(path.join(cwd, 'course'))) {
27
+ console.log(` Not in a CourseCode project directory.
28
+
29
+ Create a new project:
30
+ coursecode create my-course
31
+ `);
32
+ return;
33
+ }
34
+
35
+ console.log(` Project directory: ${cwd}`);
36
+
37
+ // Read .coursecoderc.json if exists
38
+ if (fs.existsSync(rcPath)) {
39
+ const rcConfig = JSON.parse(fs.readFileSync(rcPath, 'utf-8'));
40
+ console.log(` Framework version: ${rcConfig.frameworkVersion}`);
41
+ if (rcConfig.createdAt) {
42
+ console.log(` Created: ${new Date(rcConfig.createdAt).toLocaleDateString()}`);
43
+ }
44
+ if (rcConfig.upgradedAt) {
45
+ console.log(` Last upgraded: ${new Date(rcConfig.upgradedAt).toLocaleDateString()}`);
46
+ }
47
+ }
48
+
49
+ // Read course config for metadata
50
+ if (fs.existsSync(courseConfigPath)) {
51
+ const configContent = fs.readFileSync(courseConfigPath, 'utf-8');
52
+
53
+ // Extract metadata with regex
54
+ const titleMatch = configContent.match(/title:\s*["']([^"']+)["']/);
55
+ const versionMatch = configContent.match(/version:\s*["']([^"']+)["']/);
56
+
57
+ if (titleMatch || versionMatch) {
58
+ console.log('');
59
+ console.log(' Course info:');
60
+ if (titleMatch) console.log(` Title: ${titleMatch[1]}`);
61
+ if (versionMatch) console.log(` Version: ${versionMatch[1]}`);
62
+ }
63
+ }
64
+
65
+ // Check for dist
66
+ const distDir = path.join(cwd, 'dist');
67
+ if (fs.existsSync(distDir)) {
68
+ const files = fs.readdirSync(distDir);
69
+ console.log(`\n Build output: dist/ (${files.length} files)`);
70
+ }
71
+
72
+ // Check for ZIP
73
+ const zipFiles = fs.readdirSync(cwd).filter(f => f.endsWith('.zip'));
74
+ if (zipFiles.length > 0) {
75
+ console.log(` Course package: ${zipFiles[zipFiles.length - 1]}`);
76
+ }
77
+
78
+ console.log('');
79
+ }