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,237 @@
1
+ /**
2
+ * CSS Class Index
3
+ *
4
+ * Extracts all valid CSS class names and data-component values from
5
+ * framework and course CSS using PostCSS.
6
+ *
7
+ * Extracted from authoring-api.js to break a circular dependency:
8
+ * build-linter.js → authoring-api.js → (has spawn/exec/etc)
9
+ *
10
+ * Now both build-linter.js and authoring-api.js can import this
11
+ * without pulling in each other's dependency trees.
12
+ */
13
+
14
+ import fs from 'fs';
15
+ import path from 'path';
16
+ import { fileURLToPath } from 'url';
17
+ import postcss from 'postcss';
18
+
19
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
20
+ const __packageRoot = path.dirname(__dirname);
21
+
22
+ /**
23
+ * Find course root directory (cwd-first, then package root fallback).
24
+ */
25
+ function getCourseRoot() {
26
+ if (fs.existsSync(path.join(process.cwd(), 'course'))) return process.cwd();
27
+ if (fs.existsSync(path.join(process.cwd(), 'template', 'course'))) return path.join(process.cwd(), 'template');
28
+ if (fs.existsSync(path.join(__packageRoot, 'course'))) return __packageRoot;
29
+ if (fs.existsSync(path.join(__packageRoot, 'template', 'course'))) return path.join(__packageRoot, 'template');
30
+ throw new Error('No course directory found.');
31
+ }
32
+
33
+ /**
34
+ * Find framework root directory.
35
+ */
36
+ function getFrameworkRoot() {
37
+ const cwd = process.cwd();
38
+ if (fs.existsSync(path.join(cwd, 'framework'))) return cwd;
39
+ const parent = path.dirname(cwd);
40
+ if (fs.existsSync(path.join(parent, 'framework'))) return parent;
41
+ if (fs.existsSync(path.join(__packageRoot, 'framework'))) return __packageRoot;
42
+ throw new Error('Framework directory not found.');
43
+ }
44
+
45
+ /**
46
+ * Recursively collect .css files from a directory.
47
+ */
48
+ export function collectCssFiles(dir, result) {
49
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
50
+ const fullPath = path.join(dir, entry.name);
51
+ if (entry.isDirectory()) {
52
+ collectCssFiles(fullPath, result);
53
+ } else if (entry.name.endsWith('.css')) {
54
+ result.push(fullPath);
55
+ }
56
+ }
57
+ }
58
+
59
+ /**
60
+ * Extract class names and data-component values from PostCSS nodes (recursive).
61
+ */
62
+ function extractSelectorsFromNode(node, classes, dataComponents) {
63
+ if (node.type === 'rule' && node.selector) {
64
+ const classMatches = node.selector.match(/\.[\w-]+/g);
65
+ if (classMatches) {
66
+ for (const match of classMatches) {
67
+ classes.add(match.slice(1));
68
+ }
69
+ }
70
+
71
+ const dcMatches = node.selector.match(/\[data-component="([^"]+)"\]/g);
72
+ if (dcMatches) {
73
+ for (const match of dcMatches) {
74
+ const value = match.match(/\[data-component="([^"]+)"\]/);
75
+ if (value) dataComponents.add(value[1]);
76
+ }
77
+ }
78
+ }
79
+
80
+ if (node.nodes) {
81
+ for (const child of node.nodes) {
82
+ extractSelectorsFromNode(child, classes, dataComponents);
83
+ }
84
+ }
85
+ }
86
+
87
+ /**
88
+ * Extract all valid CSS class names and data-component values from framework and course CSS.
89
+ * Uses PostCSS for proper parsing — handles @media, nesting, pseudo-classes correctly.
90
+ */
91
+ export function getValidCssClasses() {
92
+ const frameworkRoot = getFrameworkRoot();
93
+ const cssDir = path.join(frameworkRoot, 'framework', 'css');
94
+
95
+ const classes = new Set();
96
+ const dataComponents = new Set();
97
+ const cssFiles = [];
98
+
99
+ // 1. Framework CSS (recursive)
100
+ if (fs.existsSync(cssDir)) {
101
+ collectCssFiles(cssDir, cssFiles);
102
+ }
103
+
104
+ // 2. Course CSS (theme + custom components)
105
+ try {
106
+ const courseRoot = getCourseRoot();
107
+ const courseDir = path.join(courseRoot, 'course');
108
+ const themeFile = path.join(courseDir, 'theme.css');
109
+ if (fs.existsSync(themeFile)) cssFiles.push(themeFile);
110
+
111
+ const customComponentsDir = path.join(courseDir, 'components');
112
+ if (fs.existsSync(customComponentsDir)) {
113
+ collectCssFiles(customComponentsDir, cssFiles);
114
+ }
115
+ } catch {
116
+ // No course directory — framework-only mode
117
+ }
118
+
119
+ // Parse each CSS file with PostCSS
120
+ for (const file of cssFiles) {
121
+ try {
122
+ const source = fs.readFileSync(file, 'utf-8');
123
+ const root = postcss.parse(source, { from: file });
124
+ extractSelectorsFromNode(root, classes, dataComponents);
125
+ } catch {
126
+ // Skip unparseable CSS files
127
+ }
128
+ }
129
+
130
+ const sortedClasses = [...classes].sort();
131
+ const sortedComponents = [...dataComponents].sort();
132
+
133
+ return {
134
+ classes: sortedClasses,
135
+ dataComponents: sortedComponents,
136
+ classCount: sortedClasses.length,
137
+ componentCount: sortedComponents.length,
138
+ cssFiles: cssFiles.map(f => path.relative(frameworkRoot, f)),
139
+ message: `${sortedClasses.length} CSS classes and ${sortedComponents.length} data-component values found across ${cssFiles.length} CSS files.`
140
+ };
141
+ }
142
+
143
+ // Files that legitimately use global element selectors (resets, typography, tokens)
144
+ const EXEMPT_FILES = new Set([
145
+ '01-base.css',
146
+ 'design-tokens.css',
147
+ 'accessibility.css',
148
+ 'accessibility-utils.css',
149
+ 'framework.css',
150
+ 'forms.css',
151
+ ]);
152
+
153
+ /**
154
+ * Check if a selector is a bare element selector (no class/ID/data-attribute qualifier).
155
+ * Returns the offending element name, or null if the selector is qualified.
156
+ *
157
+ * Examples:
158
+ * "button" → "button"
159
+ * "button[type=\"button\"]" → "button[type=\"button\"]"
160
+ * "button:disabled" → "button:disabled"
161
+ * ".btn" → null (class-qualified)
162
+ * "button.sidebar-item" → null (class-qualified)
163
+ * "[data-theme] button" → null (scoped by data-attribute)
164
+ * "#prevBtn" → null (ID-qualified)
165
+ */
166
+ function isBareElementSelector(selector) {
167
+ // Skip selectors that contain a class, ID, or data-attribute qualifier
168
+ if (/[.#]/.test(selector) || /\[data-/.test(selector)) return null;
169
+
170
+ // Check if the selector starts with or is a bare element name
171
+ // Match: element, element:pseudo, element[attr], element > element
172
+ const elementMatch = selector.trim().match(/^([a-z][a-z0-9-]*)/i);
173
+ if (elementMatch) {
174
+ return selector.trim();
175
+ }
176
+
177
+ return null;
178
+ }
179
+
180
+ /**
181
+ * Lint CSS selectors for bare element selectors in framework CSS.
182
+ * Catches rules like `button { }`, `button[type="button"] { }`, `button:disabled { }`
183
+ * that apply styles globally to all matching elements without class qualification.
184
+ *
185
+ * @returns {{ warnings: string[] }} Lint warnings
186
+ */
187
+ export function lintCssSelectors() {
188
+ const frameworkRoot = getFrameworkRoot();
189
+ const cssDir = path.join(frameworkRoot, 'framework', 'css');
190
+ const warnings = [];
191
+
192
+ if (!fs.existsSync(cssDir)) return { warnings };
193
+
194
+ const cssFiles = [];
195
+ collectCssFiles(cssDir, cssFiles);
196
+
197
+ for (const file of cssFiles) {
198
+ const basename = path.basename(file);
199
+ if (EXEMPT_FILES.has(basename)) continue;
200
+
201
+ try {
202
+ const source = fs.readFileSync(file, 'utf-8');
203
+ const root = postcss.parse(source, { from: file });
204
+ const relPath = path.relative(path.join(frameworkRoot, 'framework', 'css'), file);
205
+
206
+ root.walk(node => {
207
+ if (node.type !== 'rule' || !node.selector) return;
208
+
209
+ // Skip rules inside @keyframes (from/to are keywords, not selectors)
210
+ let parent = node.parent;
211
+ while (parent) {
212
+ if (parent.type === 'atrule' && parent.name.endsWith('keyframes')) return;
213
+ parent = parent.parent;
214
+ }
215
+
216
+ // Split comma-separated selectors and check each
217
+ const selectors = node.selector.split(',').map(s => s.trim());
218
+ for (const sel of selectors) {
219
+ const bare = isBareElementSelector(sel);
220
+ if (bare) {
221
+ const line = node.source?.start?.line || '?';
222
+ warnings.push(
223
+ `CSS global selector: '${bare}' in ${relPath}:${line}. ` +
224
+ 'Bare element selectors apply styles to ALL matching elements. ' +
225
+ 'Use a class qualifier (e.g. .btn) instead.'
226
+ );
227
+ }
228
+ }
229
+ });
230
+ } catch {
231
+ // Skip unparseable files
232
+ }
233
+ }
234
+
235
+ return { warnings };
236
+ }
237
+
package/lib/dev.js ADDED
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Dev command - start Vite development server
3
+ */
4
+
5
+ import { spawn } from 'child_process';
6
+ import path from 'path';
7
+ import { fileURLToPath } from 'url';
8
+ import { validateProject } from './project-utils.js';
9
+
10
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
11
+
12
+ export async function dev(options = {}) {
13
+ validateProject({ warnMissingRc: true });
14
+
15
+ console.log(`
16
+ 🚀 Starting CourseCode development build...
17
+
18
+ Building in watch mode - changes will auto-rebuild
19
+ Output: dist/
20
+ Press Ctrl+C to stop
21
+ `);
22
+
23
+ // Run vite build in watch mode
24
+ // Pass LMS_FORMAT if specified via --format flag
25
+ const env = { ...process.env };
26
+ // Signal to framework reporters that this is a local dev build
27
+ env.VITE_COURSECODE_LOCAL = 'true';
28
+ if (options.format) {
29
+ env.LMS_FORMAT = options.format;
30
+ console.log(` 📦 Format: ${options.format}\n`);
31
+ }
32
+
33
+ const viteBuild = spawn('npx', ['vite', 'build', '--mode', 'development', '--watch'], {
34
+ cwd: process.cwd(),
35
+ stdio: 'inherit',
36
+ shell: true,
37
+ env
38
+ });
39
+
40
+ viteBuild.on('error', (error) => {
41
+ console.error('❌ Failed to start dev server:', error.message);
42
+ process.exit(1);
43
+ });
44
+
45
+ // Handle Ctrl+C gracefully
46
+ process.on('SIGINT', () => {
47
+ console.log('\n\n👋 Stopping development server...\n');
48
+ viteBuild.kill();
49
+ process.exit(0);
50
+ });
51
+ }