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.
- package/LICENSE +21 -0
- package/README.md +322 -0
- package/THIRD_PARTY_NOTICES.md +22 -0
- package/bin/cli.js +331 -0
- package/framework/assets/logo-coursecode-black.svg +14 -0
- package/framework/assets/logo-coursecode-white.svg +14 -0
- package/framework/assets/logo-coursecode.svg +14 -0
- package/framework/css/01-base.css +160 -0
- package/framework/css/02-layout.css +499 -0
- package/framework/css/accessibility.css +834 -0
- package/framework/css/components/accordions.css +710 -0
- package/framework/css/components/assessments.css +520 -0
- package/framework/css/components/audio-player.css +570 -0
- package/framework/css/components/badges.css +80 -0
- package/framework/css/components/breadcrumbs.css +87 -0
- package/framework/css/components/buttons.css +707 -0
- package/framework/css/components/callouts.css +1280 -0
- package/framework/css/components/cards.css +475 -0
- package/framework/css/components/carousel.css +193 -0
- package/framework/css/components/checkbox-group.css +123 -0
- package/framework/css/components/checklist.css +203 -0
- package/framework/css/components/collapse.css +96 -0
- package/framework/css/components/comparison.css +33 -0
- package/framework/css/components/content-image.css +36 -0
- package/framework/css/components/document-gallery.css +425 -0
- package/framework/css/components/dropdown.css +115 -0
- package/framework/css/components/embed-frame.css +142 -0
- package/framework/css/components/engagement.css +412 -0
- package/framework/css/components/features.css +35 -0
- package/framework/css/components/flip-cards.css +253 -0
- package/framework/css/components/footer.css +353 -0
- package/framework/css/components/forms.css +294 -0
- package/framework/css/components/hero.css +216 -0
- package/framework/css/components/images.css +528 -0
- package/framework/css/components/interactive-timeline.css +274 -0
- package/framework/css/components/intro-cards.css +30 -0
- package/framework/css/components/lightbox.css +666 -0
- package/framework/css/components/loading.css +65 -0
- package/framework/css/components/modals.css +235 -0
- package/framework/css/components/notifications.css +107 -0
- package/framework/css/components/quote.css +150 -0
- package/framework/css/components/sidebar.css +684 -0
- package/framework/css/components/slide-header.css +52 -0
- package/framework/css/components/spinner.css +62 -0
- package/framework/css/components/stats.css +44 -0
- package/framework/css/components/steps.css +232 -0
- package/framework/css/components/tables.css +90 -0
- package/framework/css/components/tabs.css +347 -0
- package/framework/css/components/timeline.css +154 -0
- package/framework/css/components/toggle.css +95 -0
- package/framework/css/components/tooltip.css +226 -0
- package/framework/css/components/video-player.css +438 -0
- package/framework/css/design-tokens.css +707 -0
- package/framework/css/framework.css +86 -0
- package/framework/css/interactions/accessibility.css +75 -0
- package/framework/css/interactions/base.css +92 -0
- package/framework/css/interactions/drag-drop.css +295 -0
- package/framework/css/interactions/fill-in-the-blank.css +236 -0
- package/framework/css/interactions/hotspots.css +69 -0
- package/framework/css/interactions/index.css +45 -0
- package/framework/css/interactions/interactive-image.css +359 -0
- package/framework/css/interactions/likert.css +126 -0
- package/framework/css/interactions/matching.css +354 -0
- package/framework/css/interactions/numeric-input.css +78 -0
- package/framework/css/interactions/sequencing.css +378 -0
- package/framework/css/interactions/true-false.css +177 -0
- package/framework/css/layouts/article.css +258 -0
- package/framework/css/layouts/base.css +30 -0
- package/framework/css/layouts/canvas.css +38 -0
- package/framework/css/layouts/focused.css +236 -0
- package/framework/css/layouts/index.css +29 -0
- package/framework/css/layouts/presentation.css +191 -0
- package/framework/css/layouts/traditional.css +52 -0
- package/framework/css/responsive.css +439 -0
- package/framework/css/utilities/accessibility-utils.css +59 -0
- package/framework/css/utilities/animations.css +419 -0
- package/framework/css/utilities/borders.css +72 -0
- package/framework/css/utilities/colors.css +76 -0
- package/framework/css/utilities/container.css +46 -0
- package/framework/css/utilities/decorative.css +442 -0
- package/framework/css/utilities/display.css +257 -0
- package/framework/css/utilities/flexbox.css +80 -0
- package/framework/css/utilities/grid.css +69 -0
- package/framework/css/utilities/icons.css +534 -0
- package/framework/css/utilities/lists.css +190 -0
- package/framework/css/utilities/spacing.css +167 -0
- package/framework/css/utilities/tables.css +81 -0
- package/framework/css/utilities/typography.css +159 -0
- package/framework/css/utilities/visibility.css +117 -0
- package/framework/docs/COURSE_AUTHORING_GUIDE.md +1773 -0
- package/framework/docs/COURSE_OUTLINE_GUIDE.md +725 -0
- package/framework/docs/COURSE_OUTLINE_TEMPLATE.md +161 -0
- package/framework/docs/DATA_MODEL.md +409 -0
- package/framework/docs/FRAMEWORK_GUIDE.md +1088 -0
- package/framework/docs/USER_GUIDE.md +583 -0
- package/framework/docs/examples/cloudflare-channel-relay.js +169 -0
- package/framework/docs/examples/cloudflare-data-worker.js +102 -0
- package/framework/docs/examples/cloudflare-error-worker.js +228 -0
- package/framework/index.html +175 -0
- package/framework/js/app/AppActions.js +410 -0
- package/framework/js/app/AppState.js +225 -0
- package/framework/js/app/AppUI.js +616 -0
- package/framework/js/assessment/AssessmentActions.js +615 -0
- package/framework/js/assessment/AssessmentFactory.js +471 -0
- package/framework/js/assessment/AssessmentState.js +322 -0
- package/framework/js/assessment/AssessmentUI.js +451 -0
- package/framework/js/automation/api-engagement.js +196 -0
- package/framework/js/automation/api-interactions.js +167 -0
- package/framework/js/automation/api.js +242 -0
- package/framework/js/automation/index.js +41 -0
- package/framework/js/components/interactions/drag-drop.js +884 -0
- package/framework/js/components/interactions/fill-in.js +535 -0
- package/framework/js/components/interactions/hotspot.js +702 -0
- package/framework/js/components/interactions/interaction-base.js +511 -0
- package/framework/js/components/interactions/likert.js +301 -0
- package/framework/js/components/interactions/matching.js +699 -0
- package/framework/js/components/interactions/multiple-choice.js +377 -0
- package/framework/js/components/interactions/numeric.js +271 -0
- package/framework/js/components/interactions/sequencing.js +423 -0
- package/framework/js/components/interactions/true-false.js +241 -0
- package/framework/js/components/ui-components/accordion.js +442 -0
- package/framework/js/components/ui-components/alert.js +88 -0
- package/framework/js/components/ui-components/audio-player.js +1193 -0
- package/framework/js/components/ui-components/callout.js +121 -0
- package/framework/js/components/ui-components/carousel.js +145 -0
- package/framework/js/components/ui-components/checkbox-group.js +87 -0
- package/framework/js/components/ui-components/checklist.js +40 -0
- package/framework/js/components/ui-components/collapse.js +114 -0
- package/framework/js/components/ui-components/comparison.js +30 -0
- package/framework/js/components/ui-components/conditional-display.js +150 -0
- package/framework/js/components/ui-components/content-image.js +41 -0
- package/framework/js/components/ui-components/dropdown.js +262 -0
- package/framework/js/components/ui-components/embed-frame.js +274 -0
- package/framework/js/components/ui-components/features.js +33 -0
- package/framework/js/components/ui-components/flip-card.js +230 -0
- package/framework/js/components/ui-components/form-validator.js +76 -0
- package/framework/js/components/ui-components/hero.js +49 -0
- package/framework/js/components/ui-components/index.js +12 -0
- package/framework/js/components/ui-components/interactive-image.js +235 -0
- package/framework/js/components/ui-components/interactive-timeline.js +285 -0
- package/framework/js/components/ui-components/intro-cards.js +35 -0
- package/framework/js/components/ui-components/lightbox.js +652 -0
- package/framework/js/components/ui-components/modal.js +386 -0
- package/framework/js/components/ui-components/notifications.js +145 -0
- package/framework/js/components/ui-components/progress.js +88 -0
- package/framework/js/components/ui-components/quote.js +41 -0
- package/framework/js/components/ui-components/stats.js +33 -0
- package/framework/js/components/ui-components/steps.js +41 -0
- package/framework/js/components/ui-components/tabs.js +255 -0
- package/framework/js/components/ui-components/timeline.js +42 -0
- package/framework/js/components/ui-components/toggle-group.js +73 -0
- package/framework/js/components/ui-components/tooltip.js +458 -0
- package/framework/js/components/ui-components/value-display.js +133 -0
- package/framework/js/components/ui-components/video-player.js +686 -0
- package/framework/js/core/component-catalog.js +121 -0
- package/framework/js/core/event-bus.js +178 -0
- package/framework/js/core/interaction-catalog.js +149 -0
- package/framework/js/dev/runtime-linter.js +1725 -0
- package/framework/js/drivers/cmi5-driver.js +768 -0
- package/framework/js/drivers/driver-factory.js +77 -0
- package/framework/js/drivers/driver-interface.js +110 -0
- package/framework/js/drivers/http-driver-base.js +241 -0
- package/framework/js/drivers/lti-driver.js +508 -0
- package/framework/js/drivers/proxy-driver.js +444 -0
- package/framework/js/drivers/scorm-12-driver.js +560 -0
- package/framework/js/drivers/scorm-2004-driver.js +775 -0
- package/framework/js/drivers/scorm-driver-base.js +112 -0
- package/framework/js/engagement/engagement-manager.js +404 -0
- package/framework/js/engagement/engagement-progress.js +191 -0
- package/framework/js/engagement/engagement-trackers.js +215 -0
- package/framework/js/engagement/requirement-strategies.js +268 -0
- package/framework/js/main.js +727 -0
- package/framework/js/managers/accessibility-manager.js +499 -0
- package/framework/js/managers/assessment-manager.js +230 -0
- package/framework/js/managers/audio-manager.js +944 -0
- package/framework/js/managers/comment-manager.js +88 -0
- package/framework/js/managers/flag-manager.js +86 -0
- package/framework/js/managers/interaction-manager.js +254 -0
- package/framework/js/managers/interaction-registry.js +96 -0
- package/framework/js/managers/objective-manager.js +423 -0
- package/framework/js/managers/score-manager.js +441 -0
- package/framework/js/managers/video-manager.js +536 -0
- package/framework/js/navigation/Breadcrumbs.js +234 -0
- package/framework/js/navigation/NavigationActions.js +1132 -0
- package/framework/js/navigation/NavigationState.js +276 -0
- package/framework/js/navigation/NavigationUI.js +574 -0
- package/framework/js/navigation/document-gallery.js +357 -0
- package/framework/js/navigation/navigation-helpers.js +175 -0
- package/framework/js/navigation/navigation-validators.js +174 -0
- package/framework/js/state/index.js +8 -0
- package/framework/js/state/lms-connection.js +482 -0
- package/framework/js/state/lms-error-utils.js +58 -0
- package/framework/js/state/state-commits.js +200 -0
- package/framework/js/state/state-domains.js +86 -0
- package/framework/js/state/state-manager.js +502 -0
- package/framework/js/state/state-validation.js +311 -0
- package/framework/js/state/transaction-log.js +41 -0
- package/framework/js/state/xapi-statement-service.js +325 -0
- package/framework/js/utilities/access-control.js +99 -0
- package/framework/js/utilities/breakpoint-manager.js +315 -0
- package/framework/js/utilities/canvas-slide.js +35 -0
- package/framework/js/utilities/conditional-display.js +388 -0
- package/framework/js/utilities/course-channel.js +214 -0
- package/framework/js/utilities/course-helpers.js +420 -0
- package/framework/js/utilities/data-reporter.js +273 -0
- package/framework/js/utilities/error-reporter.js +313 -0
- package/framework/js/utilities/hotspot-helper.js +341 -0
- package/framework/js/utilities/icons.js +348 -0
- package/framework/js/utilities/logger.js +92 -0
- package/framework/js/utilities/markdown-renderer.js +45 -0
- package/framework/js/utilities/scroll-tracker.js +68 -0
- package/framework/js/utilities/ui-initializer.js +146 -0
- package/framework/js/utilities/utilities.js +293 -0
- package/framework/js/utilities/view-manager.js +227 -0
- package/framework/js/validation/html-validators.js +422 -0
- package/framework/js/validation/scorm-validators.js +438 -0
- package/framework/js/vendor/pipwerks.js +931 -0
- package/framework/scripts/generate-narration.js +629 -0
- package/framework/scripts/tts-providers/azure-provider.js +178 -0
- package/framework/scripts/tts-providers/base-provider.js +81 -0
- package/framework/scripts/tts-providers/deepgram-provider.js +135 -0
- package/framework/scripts/tts-providers/elevenlabs-provider.js +148 -0
- package/framework/scripts/tts-providers/google-provider.js +272 -0
- package/framework/scripts/tts-providers/index.js +158 -0
- package/framework/scripts/tts-providers/openai-provider.js +143 -0
- package/framework/version.json +63 -0
- package/lib/authoring-api.js +919 -0
- package/lib/build-linter.js +450 -0
- package/lib/build-packaging.js +186 -0
- package/lib/build.js +88 -0
- package/lib/cloud.js +691 -0
- package/lib/convert.js +341 -0
- package/lib/course-parser.js +936 -0
- package/lib/course-writer.js +258 -0
- package/lib/create.js +248 -0
- package/lib/css-index.js +237 -0
- package/lib/dev.js +51 -0
- package/lib/export-content.js +1246 -0
- package/lib/headless-browser.js +413 -0
- package/lib/import.js +377 -0
- package/lib/index.js +80 -0
- package/lib/info.js +79 -0
- package/lib/interaction-formatters.js +568 -0
- package/lib/manifest/cmi5-manifest.js +63 -0
- package/lib/manifest/lti-tool-config.js +53 -0
- package/lib/manifest/manifest-factory.js +99 -0
- package/lib/manifest/scorm-12-manifest.js +61 -0
- package/lib/manifest/scorm-2004-manifest.js +94 -0
- package/lib/manifest/scorm-proxy-manifest.js +104 -0
- package/lib/manifest-parser.js +96 -0
- package/lib/mcp-prompts.js +753 -0
- package/lib/mcp-server.js +316 -0
- package/lib/narration.js +53 -0
- package/lib/pdf-structure.js +142 -0
- package/lib/preview-export.js +231 -0
- package/lib/preview-routes-api.js +662 -0
- package/lib/preview-routes-editing.js +159 -0
- package/lib/preview-routes-lms.js +230 -0
- package/lib/preview-server.js +564 -0
- package/lib/project-utils.js +269 -0
- package/lib/proxy-templates/proxy.html +68 -0
- package/lib/proxy-templates/scorm-bridge.js +112 -0
- package/lib/scaffold.js +193 -0
- package/lib/schema-extractor.js +361 -0
- package/lib/slide-source-editor.js +586 -0
- package/lib/stub-player/app-viewer.js +195 -0
- package/lib/stub-player/app.js +370 -0
- package/lib/stub-player/catalog-panel.js +312 -0
- package/lib/stub-player/config-panel.js +1303 -0
- package/lib/stub-player/content-generator.js +586 -0
- package/lib/stub-player/content-viewer.js +173 -0
- package/lib/stub-player/debug-panel.js +420 -0
- package/lib/stub-player/edit-mode.js +922 -0
- package/lib/stub-player/edit-utils.js +400 -0
- package/lib/stub-player/header-bar.js +354 -0
- package/lib/stub-player/interaction-editor.js +210 -0
- package/lib/stub-player/interactions-panel.js +565 -0
- package/lib/stub-player/lms-api.js +1094 -0
- package/lib/stub-player/login-screen.js +74 -0
- package/lib/stub-player/outline-mode.js +689 -0
- package/lib/stub-player/styles/_assessments-panel.css +245 -0
- package/lib/stub-player/styles/_base.css +89 -0
- package/lib/stub-player/styles/_catalog-icons.css +96 -0
- package/lib/stub-player/styles/_catalog-panel.css +291 -0
- package/lib/stub-player/styles/_config-panel.css +636 -0
- package/lib/stub-player/styles/_content-viewer.css +834 -0
- package/lib/stub-player/styles/_debug-panel.css +576 -0
- package/lib/stub-player/styles/_edit-mode.css +128 -0
- package/lib/stub-player/styles/_header-bar.css +343 -0
- package/lib/stub-player/styles/_interaction-editor.css +140 -0
- package/lib/stub-player/styles/_interactions-panel.css +1038 -0
- package/lib/stub-player/styles/_login-screen.css +102 -0
- package/lib/stub-player/styles/_outline-mode.css +752 -0
- package/lib/stub-player/styles.css +15 -0
- package/lib/stub-player.js +160 -0
- package/lib/test-data-reporting.js +176 -0
- package/lib/test-error-reporting.js +146 -0
- package/lib/token.js +86 -0
- package/lib/upgrade.js +257 -0
- package/lib/validation-rules.js +517 -0
- package/lib/vite-plugin-content-discovery.js +296 -0
- package/package.json +108 -0
- package/schemas/XMLSchema.dtd +402 -0
- package/schemas/adlcp_v1p3.xsd +111 -0
- package/schemas/adlnav_v1p3.xsd +61 -0
- package/schemas/adlseq_v1p3.xsd +93 -0
- package/schemas/common/anyElement.xsd +27 -0
- package/schemas/common/dataTypes.xsd +138 -0
- package/schemas/common/elementNames.xsd +767 -0
- package/schemas/common/elementTypes.xsd +786 -0
- package/schemas/common/rootElement.xsd +31 -0
- package/schemas/common/vocabTypes.xsd +345 -0
- package/schemas/common/vocabValues.xsd +257 -0
- package/schemas/datatypes.dtd +203 -0
- package/schemas/ims_xml.xsd +35 -0
- package/schemas/imscp_v1p1.xsd +368 -0
- package/schemas/imsss_v1p0.xsd +67 -0
- package/schemas/imsss_v1p0auxresource.xsd +19 -0
- package/schemas/imsss_v1p0control.xsd +20 -0
- package/schemas/imsss_v1p0delivery.xsd +17 -0
- package/schemas/imsss_v1p0limit.xsd +47 -0
- package/schemas/imsss_v1p0objective.xsd +67 -0
- package/schemas/imsss_v1p0random.xsd +16 -0
- package/schemas/imsss_v1p0rollup.xsd +46 -0
- package/schemas/imsss_v1p0seqrule.xsd +108 -0
- package/schemas/imsss_v1p0util.xsd +94 -0
- package/schemas/license.txt +17 -0
- package/schemas/lom.xsd +102 -0
- package/schemas/lomCustom.xsd +62 -0
- package/schemas/lomLoose.xsd +62 -0
- package/schemas/lomStrict.xsd +62 -0
- package/schemas/xml.xsd +81 -0
- package/template/.env.example +92 -0
- package/template/course/assets/audio/example-intro.mp3 +0 -0
- package/template/course/assets/audio/example-ui-demo--compact-player.mp3 +0 -0
- package/template/course/assets/audio/example-ui-demo--demo-modal.mp3 +0 -0
- package/template/course/assets/audio/example-ui-demo--full-player.mp3 +0 -0
- package/template/course/assets/docs/example_md_1.md +39 -0
- package/template/course/assets/docs/example_md_2.md +41 -0
- package/template/course/assets/docs/example_pdf_1_thumbnail.png +0 -0
- package/template/course/assets/docs/example_pdf_2.pdf +0 -0
- package/template/course/assets/images/course-architecture.svg +36 -0
- package/template/course/assets/images/logo.svg +14 -0
- package/template/course/assets/widgets/counter-demo.html +190 -0
- package/template/course/assets/widgets/gravity-painter.html +384 -0
- package/template/course/course-config.js +539 -0
- package/template/course/icons.js +19 -0
- package/template/course/interactions/PLUGIN_GUIDE.md +97 -0
- package/template/course/slides/example-course-structure.js +138 -0
- package/template/course/slides/example-final-exam.js +144 -0
- package/template/course/slides/example-finishing.js +127 -0
- package/template/course/slides/example-interactions-showcase.js +615 -0
- package/template/course/slides/example-preview-tour.js +129 -0
- package/template/course/slides/example-remedial.js +143 -0
- package/template/course/slides/example-summary.js +103 -0
- package/template/course/slides/example-ui-showcase.js +1805 -0
- package/template/course/slides/example-welcome.js +123 -0
- package/template/course/slides/example-workflow.js +140 -0
- package/template/course/theme.css +165 -0
- package/template/eslint.config.js +47 -0
- package/template/package.json +28 -0
- package/template/vite.config.js +339 -0
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Course Writer - Unified writing utility for CourseCode
|
|
3
|
+
*
|
|
4
|
+
* Single write() function handles config-object edit operations:
|
|
5
|
+
* - config: courseConfig properties
|
|
6
|
+
* - slide: slide config in structure array
|
|
7
|
+
* - objective: objective properties
|
|
8
|
+
* - gating: gating conditions
|
|
9
|
+
*
|
|
10
|
+
* For source-text edits (interactions, assessments, templates),
|
|
11
|
+
* see slide-source-editor.js.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import fs from 'fs';
|
|
15
|
+
import path from 'path';
|
|
16
|
+
import { pathToFileURL } from 'url';
|
|
17
|
+
|
|
18
|
+
// =============================================================================
|
|
19
|
+
// PUBLIC API
|
|
20
|
+
// =============================================================================
|
|
21
|
+
|
|
22
|
+
// In-memory write mutex to prevent concurrent writes from clobbering each other.
|
|
23
|
+
// Each write() call chains onto the previous one, ensuring serial execution.
|
|
24
|
+
let _writeQueue = Promise.resolve();
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Unified write dispatcher
|
|
28
|
+
* @param {string} coursePath - Path to course directory
|
|
29
|
+
* @param {string} target - What to write: 'config' | 'slide' | 'objective' | 'gating' | 'rename-objective'
|
|
30
|
+
* @param {string} id - Identifier (path for config, slideId for slides, objectiveId, etc.)
|
|
31
|
+
* @param {*} value - New value to set
|
|
32
|
+
* @returns {Promise<{success: boolean, error?: string}>}
|
|
33
|
+
*/
|
|
34
|
+
export function write(coursePath, target, id, value) {
|
|
35
|
+
const result = _writeQueue.then(() => _doWrite(coursePath, target, id, value));
|
|
36
|
+
// Keep the queue moving even if this write fails
|
|
37
|
+
_writeQueue = result.catch(() => {});
|
|
38
|
+
return result;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async function _doWrite(coursePath, target, id, value) {
|
|
42
|
+
try {
|
|
43
|
+
switch (target) {
|
|
44
|
+
case 'config':
|
|
45
|
+
await writeConfig(coursePath, id, value);
|
|
46
|
+
break;
|
|
47
|
+
case 'slide':
|
|
48
|
+
await writeSlideConfig(coursePath, id, value);
|
|
49
|
+
break;
|
|
50
|
+
case 'objective':
|
|
51
|
+
await writeObjective(coursePath, id, value);
|
|
52
|
+
break;
|
|
53
|
+
case 'gating':
|
|
54
|
+
await writeGating(coursePath, id, value);
|
|
55
|
+
break;
|
|
56
|
+
case 'rename-objective':
|
|
57
|
+
await renameObjective(coursePath, id, value);
|
|
58
|
+
break;
|
|
59
|
+
default:
|
|
60
|
+
return { success: false, error: `Unknown target: ${target}` };
|
|
61
|
+
}
|
|
62
|
+
return { success: true };
|
|
63
|
+
} catch (err) {
|
|
64
|
+
return { success: false, error: err.message };
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
// =============================================================================
|
|
70
|
+
// TARGET HANDLERS
|
|
71
|
+
// =============================================================================
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Write config property
|
|
75
|
+
* @param {string} coursePath
|
|
76
|
+
* @param {string} propPath - Dot-notation path (e.g., 'navigation.sidebar.enabled')
|
|
77
|
+
* @param {*} value
|
|
78
|
+
*/
|
|
79
|
+
async function writeConfig(coursePath, propPath, value) {
|
|
80
|
+
const config = await loadConfig(coursePath);
|
|
81
|
+
setNestedProperty(config, propPath, value);
|
|
82
|
+
await saveConfig(coursePath, config);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Write slide config in structure array
|
|
87
|
+
* @param {string} coursePath
|
|
88
|
+
* @param {string} slideId
|
|
89
|
+
* @param {object} updates - Object with property paths and values, e.g. { 'engagement.required': true }
|
|
90
|
+
*/
|
|
91
|
+
async function writeSlideConfig(coursePath, slideId, updates) {
|
|
92
|
+
const config = await loadConfig(coursePath);
|
|
93
|
+
const slide = findStructureItem(config.structure, slideId);
|
|
94
|
+
if (!slide) throw new Error(`Slide not found: ${slideId}`);
|
|
95
|
+
|
|
96
|
+
for (const [propPath, value] of Object.entries(updates)) {
|
|
97
|
+
setNestedProperty(slide, propPath, value);
|
|
98
|
+
}
|
|
99
|
+
await saveConfig(coursePath, config);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Write objective property
|
|
104
|
+
* @param {string} coursePath
|
|
105
|
+
* @param {string} objectiveId
|
|
106
|
+
* @param {object} updates - Object with property paths and values
|
|
107
|
+
*/
|
|
108
|
+
async function writeObjective(coursePath, objectiveId, updates) {
|
|
109
|
+
const config = await loadConfig(coursePath);
|
|
110
|
+
const objective = config.objectives?.find(o => o.id === objectiveId);
|
|
111
|
+
if (!objective) throw new Error(`Objective not found: ${objectiveId}`);
|
|
112
|
+
|
|
113
|
+
for (const [propPath, value] of Object.entries(updates)) {
|
|
114
|
+
setNestedProperty(objective, propPath, value);
|
|
115
|
+
}
|
|
116
|
+
await saveConfig(coursePath, config);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Write gating conditions for a slide
|
|
121
|
+
* @param {string} coursePath
|
|
122
|
+
* @param {string} slideId
|
|
123
|
+
* @param {object} gatingConfig - Full gating object { mode, message, conditions }
|
|
124
|
+
*/
|
|
125
|
+
async function writeGating(coursePath, slideId, gatingConfig) {
|
|
126
|
+
const config = await loadConfig(coursePath);
|
|
127
|
+
const slide = findStructureItem(config.structure, slideId);
|
|
128
|
+
if (!slide) throw new Error(`Slide not found: ${slideId}`);
|
|
129
|
+
|
|
130
|
+
if (!slide.navigation) slide.navigation = {};
|
|
131
|
+
slide.navigation.gating = gatingConfig;
|
|
132
|
+
await saveConfig(coursePath, config);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Rename an objective ID and cascade to all references
|
|
137
|
+
* @param {string} coursePath
|
|
138
|
+
* @param {string} oldId
|
|
139
|
+
* @param {string} newId
|
|
140
|
+
*/
|
|
141
|
+
async function renameObjective(coursePath, oldId, newId) {
|
|
142
|
+
if (oldId === newId) return;
|
|
143
|
+
|
|
144
|
+
const config = await loadConfig(coursePath);
|
|
145
|
+
const objective = config.objectives?.find(o => o.id === oldId);
|
|
146
|
+
if (!objective) throw new Error(`Objective not found: ${oldId}`);
|
|
147
|
+
|
|
148
|
+
const conflict = config.objectives.find(o => o.id === newId);
|
|
149
|
+
if (conflict) throw new Error(`ID already exists: ${newId}`);
|
|
150
|
+
|
|
151
|
+
// Rename the objective itself
|
|
152
|
+
objective.id = newId;
|
|
153
|
+
|
|
154
|
+
// Cascade to all objectiveId references in gating conditions
|
|
155
|
+
function cascadeRename(items) {
|
|
156
|
+
for (const item of items) {
|
|
157
|
+
const conditions = item.navigation?.gating?.conditions;
|
|
158
|
+
if (conditions) {
|
|
159
|
+
for (const cond of conditions) {
|
|
160
|
+
if (cond.objectiveId === oldId) cond.objectiveId = newId;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
if (item.children) cascadeRename(item.children);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
cascadeRename(config.structure || []);
|
|
167
|
+
|
|
168
|
+
await saveConfig(coursePath, config);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
// =============================================================================
|
|
173
|
+
// CONFIG HELPERS
|
|
174
|
+
// =============================================================================
|
|
175
|
+
|
|
176
|
+
async function loadConfig(coursePath) {
|
|
177
|
+
const configFilePath = path.join(coursePath, 'course-config.js');
|
|
178
|
+
const configUrl = pathToFileURL(configFilePath).href + `?t=${Date.now()}`;
|
|
179
|
+
const configModule = await import(configUrl);
|
|
180
|
+
return configModule.courseConfig || configModule.default;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
async function saveConfig(coursePath, config) {
|
|
184
|
+
const configFilePath = path.join(coursePath, 'course-config.js');
|
|
185
|
+
const originalSource = fs.readFileSync(configFilePath, 'utf-8');
|
|
186
|
+
const headerComments = extractHeaderComments(originalSource);
|
|
187
|
+
const serialized = 'export const courseConfig = ' + serializeObject(config) + ';\n';
|
|
188
|
+
fs.writeFileSync(configFilePath, headerComments + '\n\n' + serialized, 'utf-8');
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function setNestedProperty(obj, propPath, value) {
|
|
192
|
+
const parts = propPath.split('.');
|
|
193
|
+
let current = obj;
|
|
194
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
195
|
+
if (current[parts[i]] === undefined) {
|
|
196
|
+
current[parts[i]] = {};
|
|
197
|
+
}
|
|
198
|
+
current = current[parts[i]];
|
|
199
|
+
}
|
|
200
|
+
current[parts[parts.length - 1]] = value;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function findStructureItem(structure, id) {
|
|
204
|
+
for (const item of structure) {
|
|
205
|
+
if (item.id === id) return item;
|
|
206
|
+
if (item.children) {
|
|
207
|
+
const found = findStructureItem(item.children, id);
|
|
208
|
+
if (found) return found;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
return null;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function extractHeaderComments(source) {
|
|
215
|
+
const lines = source.split('\n');
|
|
216
|
+
const commentLines = [];
|
|
217
|
+
for (const line of lines) {
|
|
218
|
+
const trimmed = line.trim();
|
|
219
|
+
if (trimmed.startsWith('/**') || trimmed.startsWith('*') || trimmed.startsWith('*/') || trimmed === '') {
|
|
220
|
+
commentLines.push(line);
|
|
221
|
+
} else {
|
|
222
|
+
break;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
while (commentLines.length > 0 && commentLines[commentLines.length - 1].trim() === '') {
|
|
226
|
+
commentLines.pop();
|
|
227
|
+
}
|
|
228
|
+
return commentLines.join('\n');
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function serializeObject(obj, indent = 0) {
|
|
232
|
+
const spaces = ' '.repeat(indent);
|
|
233
|
+
const innerSpaces = ' '.repeat(indent + 1);
|
|
234
|
+
|
|
235
|
+
if (obj === null) return 'null';
|
|
236
|
+
if (obj === undefined) return 'undefined';
|
|
237
|
+
if (typeof obj === 'boolean') return String(obj);
|
|
238
|
+
if (typeof obj === 'number') return String(obj);
|
|
239
|
+
if (typeof obj === 'string') return `'${obj.replace(/\\/g, '\\\\').replace(/'/g, "\\'")}'`;
|
|
240
|
+
if (typeof obj === 'function') return obj.toString();
|
|
241
|
+
|
|
242
|
+
if (Array.isArray(obj)) {
|
|
243
|
+
if (obj.length === 0) return '[]';
|
|
244
|
+
const items = obj.map(item => innerSpaces + serializeObject(item, indent + 1));
|
|
245
|
+
return '[\n' + items.join(',\n') + '\n' + spaces + ']';
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const entries = Object.entries(obj);
|
|
249
|
+
if (entries.length === 0) return '{}';
|
|
250
|
+
|
|
251
|
+
const props = entries.map(([key, val]) => {
|
|
252
|
+
const keyStr = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(key) ? key : `'${key}'`;
|
|
253
|
+
return innerSpaces + keyStr + ': ' + serializeObject(val, indent + 1);
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
return '{\n' + props.join(',\n') + '\n' + spaces + '}';
|
|
257
|
+
}
|
|
258
|
+
|
package/lib/create.js
ADDED
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Create command - scaffold a new CourseCode project
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import fs from 'fs';
|
|
6
|
+
import path from 'path';
|
|
7
|
+
import { fileURLToPath } from 'url';
|
|
8
|
+
import { spawn } from 'child_process';
|
|
9
|
+
|
|
10
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
11
|
+
const PACKAGE_ROOT = path.join(__dirname, '..');
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Copy directory recursively
|
|
15
|
+
*/
|
|
16
|
+
function copyDir(src, dest, options = {}) {
|
|
17
|
+
const { exclude = [] } = options;
|
|
18
|
+
|
|
19
|
+
if (!fs.existsSync(dest)) {
|
|
20
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
24
|
+
|
|
25
|
+
for (const entry of entries) {
|
|
26
|
+
const srcPath = path.join(src, entry.name);
|
|
27
|
+
const destPath = path.join(dest, entry.name);
|
|
28
|
+
|
|
29
|
+
// Check exclusions
|
|
30
|
+
if (exclude.some(pattern => entry.name.match(pattern))) {
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (entry.isDirectory()) {
|
|
35
|
+
copyDir(srcPath, destPath, options);
|
|
36
|
+
} else {
|
|
37
|
+
fs.copyFileSync(srcPath, destPath);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Run npm install in directory
|
|
44
|
+
*/
|
|
45
|
+
function npmInstall(cwd) {
|
|
46
|
+
return new Promise((resolve, reject) => {
|
|
47
|
+
const child = spawn('npm', ['install'], {
|
|
48
|
+
cwd,
|
|
49
|
+
stdio: 'inherit',
|
|
50
|
+
shell: true
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
child.on('close', (code) => {
|
|
54
|
+
if (code === 0) {
|
|
55
|
+
resolve();
|
|
56
|
+
} else {
|
|
57
|
+
reject(new Error(`npm install failed with code ${code}`));
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Initialize git repository
|
|
65
|
+
*/
|
|
66
|
+
function gitInit(cwd) {
|
|
67
|
+
return new Promise((resolve, reject) => {
|
|
68
|
+
const child = spawn('git', ['init'], {
|
|
69
|
+
cwd,
|
|
70
|
+
stdio: 'pipe',
|
|
71
|
+
shell: true
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
child.on('close', (code) => {
|
|
75
|
+
if (code === 0) {
|
|
76
|
+
resolve();
|
|
77
|
+
} else {
|
|
78
|
+
reject(new Error(`git init failed with code ${code}`));
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export async function create(name, options = {}) {
|
|
85
|
+
const targetDir = path.resolve(process.cwd(), name);
|
|
86
|
+
|
|
87
|
+
// Check if directory already exists
|
|
88
|
+
if (fs.existsSync(targetDir)) {
|
|
89
|
+
console.error(`\n❌ Directory "${name}" already exists.\n`);
|
|
90
|
+
process.exit(1);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
console.log(`\n🚀 Creating CourseCode project: ${name}\n`);
|
|
94
|
+
|
|
95
|
+
// Create project directory
|
|
96
|
+
fs.mkdirSync(targetDir, { recursive: true });
|
|
97
|
+
|
|
98
|
+
// Copy template files (course directory and vite config)
|
|
99
|
+
const templateDir = path.join(PACKAGE_ROOT, 'template');
|
|
100
|
+
console.log(' Copying template files...');
|
|
101
|
+
copyDir(templateDir, targetDir, {
|
|
102
|
+
exclude: [/^\.DS_Store$/, /^node_modules$/]
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
// Copy framework
|
|
106
|
+
const frameworkSrc = path.join(PACKAGE_ROOT, 'framework');
|
|
107
|
+
const frameworkDest = path.join(targetDir, 'framework');
|
|
108
|
+
console.log(' Copying framework...');
|
|
109
|
+
copyDir(frameworkSrc, frameworkDest, {
|
|
110
|
+
exclude: [/^\.DS_Store$/, /^dist$/]
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
// Copy SCORM schemas
|
|
114
|
+
const schemasSrc = path.join(PACKAGE_ROOT, 'schemas');
|
|
115
|
+
const schemasDest = path.join(targetDir, 'schemas');
|
|
116
|
+
console.log(' Copying LMS schemas...');
|
|
117
|
+
copyDir(schemasSrc, schemasDest);
|
|
118
|
+
|
|
119
|
+
// Copy manifest generation lib (used by vite.config.js)
|
|
120
|
+
const manifestSrc = path.join(PACKAGE_ROOT, 'lib', 'manifest');
|
|
121
|
+
const manifestDest = path.join(targetDir, 'lib', 'manifest');
|
|
122
|
+
console.log(' Copying manifest generators...');
|
|
123
|
+
copyDir(manifestSrc, manifestDest);
|
|
124
|
+
|
|
125
|
+
// Copy shared packaging utilities + proxy templates (used by vite.config.js)
|
|
126
|
+
const packagingSrc = path.join(PACKAGE_ROOT, 'lib', 'build-packaging.js');
|
|
127
|
+
const packagingDest = path.join(targetDir, 'lib', 'build-packaging.js');
|
|
128
|
+
fs.copyFileSync(packagingSrc, packagingDest);
|
|
129
|
+
|
|
130
|
+
const proxyTemplatesSrc = path.join(PACKAGE_ROOT, 'lib', 'proxy-templates');
|
|
131
|
+
const proxyTemplatesDest = path.join(targetDir, 'lib', 'proxy-templates');
|
|
132
|
+
console.log(' Copying proxy packaging templates...');
|
|
133
|
+
copyDir(proxyTemplatesSrc, proxyTemplatesDest);
|
|
134
|
+
|
|
135
|
+
// Read and customize package.json
|
|
136
|
+
const pkgPath = path.join(targetDir, 'package.json');
|
|
137
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
|
|
138
|
+
pkg.name = name;
|
|
139
|
+
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2));
|
|
140
|
+
|
|
141
|
+
// Create .coursecoderc.json to track framework version
|
|
142
|
+
const frameworkPkg = JSON.parse(fs.readFileSync(path.join(PACKAGE_ROOT, 'package.json'), 'utf-8'));
|
|
143
|
+
const coursecoderc = {
|
|
144
|
+
frameworkVersion: frameworkPkg.version,
|
|
145
|
+
createdAt: new Date().toISOString(),
|
|
146
|
+
createdWith: `coursecode@${frameworkPkg.version}`
|
|
147
|
+
};
|
|
148
|
+
fs.writeFileSync(
|
|
149
|
+
path.join(targetDir, '.coursecoderc.json'),
|
|
150
|
+
JSON.stringify(coursecoderc, null, 2)
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
console.log(' ✅ Project files created');
|
|
154
|
+
|
|
155
|
+
// If --blank, remove example files and reset config
|
|
156
|
+
if (options.blank) {
|
|
157
|
+
const { clean } = await import('./scaffold.js');
|
|
158
|
+
clean({ basePath: targetDir });
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Install dependencies
|
|
162
|
+
if (options.install !== false) {
|
|
163
|
+
console.log('\n Installing dependencies...\n');
|
|
164
|
+
try {
|
|
165
|
+
await npmInstall(targetDir);
|
|
166
|
+
console.log('\n ✅ Dependencies installed');
|
|
167
|
+
} catch (_error) {
|
|
168
|
+
console.warn('\n ⚠️ npm install failed. Run it manually.');
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Initialize git repository
|
|
173
|
+
console.log('\n Initializing git repository...');
|
|
174
|
+
try {
|
|
175
|
+
await gitInit(targetDir);
|
|
176
|
+
console.log(' ✅ Git repository initialized');
|
|
177
|
+
} catch (_error) {
|
|
178
|
+
console.warn(' ⚠️ Git init failed. You can run "git init" manually.');
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Print success message
|
|
182
|
+
if (options.blank) {
|
|
183
|
+
console.log(`
|
|
184
|
+
✅ CourseCode project "${name}" created (blank starter)!
|
|
185
|
+
|
|
186
|
+
Course files:
|
|
187
|
+
- course/course-config.js - Course metadata & structure (minimal)
|
|
188
|
+
- course/slides/intro.js - Starter slide
|
|
189
|
+
- course/theme.css - Custom styling
|
|
190
|
+
- course/assets/ - Images, audio, etc.
|
|
191
|
+
|
|
192
|
+
Next steps:
|
|
193
|
+
- Edit course-config.js with your course metadata
|
|
194
|
+
- Create slides with: coursecode new slide <id>
|
|
195
|
+
- Create assessments with: coursecode new assessment <id>
|
|
196
|
+
`);
|
|
197
|
+
} else {
|
|
198
|
+
console.log(`
|
|
199
|
+
✅ CourseCode project "${name}" created successfully!
|
|
200
|
+
|
|
201
|
+
Course files:
|
|
202
|
+
- course/course-config.js - Course metadata & structure
|
|
203
|
+
- course/slides/ - Slide content (example- files for reference)
|
|
204
|
+
- course/theme.css - Custom styling
|
|
205
|
+
- course/assets/ - Images, audio, etc.
|
|
206
|
+
|
|
207
|
+
The project includes example slides (prefixed with "example-") that
|
|
208
|
+
demonstrate framework features. When ready to build your own course:
|
|
209
|
+
- Run: coursecode clean
|
|
210
|
+
- Or create a blank project: coursecode create <name> --blank
|
|
211
|
+
`);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Prompt to start dev server
|
|
215
|
+
const shouldStart = await promptYesNo(' Start the development server? (Y/n) ');
|
|
216
|
+
|
|
217
|
+
if (shouldStart) {
|
|
218
|
+
console.log('\n Starting development server...\n');
|
|
219
|
+
const child = spawn('npx', ['coursecode', 'dev'], {
|
|
220
|
+
cwd: targetDir,
|
|
221
|
+
stdio: 'inherit',
|
|
222
|
+
shell: true
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
child.on('error', () => {
|
|
226
|
+
console.warn(' ⚠️ Failed to start dev server. Run manually:');
|
|
227
|
+
console.log(` cd ${name} && coursecode dev\n`);
|
|
228
|
+
});
|
|
229
|
+
} else {
|
|
230
|
+
console.log(`
|
|
231
|
+
To start developing:
|
|
232
|
+
|
|
233
|
+
cd ${name}
|
|
234
|
+
coursecode dev
|
|
235
|
+
`);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function promptYesNo(question) {
|
|
240
|
+
return new Promise((resolve) => {
|
|
241
|
+
process.stdout.write(question);
|
|
242
|
+
process.stdin.setEncoding('utf8');
|
|
243
|
+
process.stdin.once('data', (data) => {
|
|
244
|
+
const answer = data.trim().toLowerCase();
|
|
245
|
+
resolve(answer === '' || answer === 'y' || answer === 'yes');
|
|
246
|
+
});
|
|
247
|
+
});
|
|
248
|
+
}
|