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,15 @@
|
|
|
1
|
+
/* Stub Player Styles — split into per-module partials */
|
|
2
|
+
@import url('./styles/_base.css');
|
|
3
|
+
@import url('./styles/_header-bar.css');
|
|
4
|
+
@import url('./styles/_debug-panel.css');
|
|
5
|
+
@import url('./styles/_edit-mode.css');
|
|
6
|
+
|
|
7
|
+
@import url('./styles/_content-viewer.css');
|
|
8
|
+
@import url('./styles/_config-panel.css');
|
|
9
|
+
@import url('./styles/_interactions-panel.css');
|
|
10
|
+
@import url('./styles/_assessments-panel.css');
|
|
11
|
+
@import url('./styles/_interaction-editor.css');
|
|
12
|
+
@import url('./styles/_login-screen.css');
|
|
13
|
+
@import url('./styles/_catalog-panel.css');
|
|
14
|
+
@import url('./styles/_catalog-icons.css');
|
|
15
|
+
@import url('./styles/_outline-mode.css');
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* stub-player.js - Shared stub LMS player generator
|
|
3
|
+
*
|
|
4
|
+
* Used by both preview-server.js (live mode) and preview-export.js (static export)
|
|
5
|
+
*/
|
|
6
|
+
import { readFileSync, readdirSync } from 'fs';
|
|
7
|
+
import { dirname, join } from 'path';
|
|
8
|
+
import { fileURLToPath } from 'url';
|
|
9
|
+
|
|
10
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
11
|
+
const __dirname = dirname(__filename);
|
|
12
|
+
|
|
13
|
+
// CSS is loaded lazily inside generateStubPlayer() to filter by mode
|
|
14
|
+
const stylesDir = join(__dirname, 'stub-player/styles');
|
|
15
|
+
const VIEWER_STYLES = ['_base.css', '_header-bar.css', '_content-viewer.css', '_login-screen.css'];
|
|
16
|
+
|
|
17
|
+
function loadStyles(isLive) {
|
|
18
|
+
return readdirSync(stylesDir)
|
|
19
|
+
.filter(f => f.endsWith('.css'))
|
|
20
|
+
.filter(f => isLive || VIEWER_STYLES.includes(f))
|
|
21
|
+
.sort()
|
|
22
|
+
.map(f => readFileSync(join(stylesDir, f), 'utf-8'))
|
|
23
|
+
.join('\n');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// HTML template modules
|
|
27
|
+
import { generateHeaderBar } from './stub-player/header-bar.js';
|
|
28
|
+
import { generateDebugPanel } from './stub-player/debug-panel.js';
|
|
29
|
+
import { generateConfigPanel } from './stub-player/config-panel.js';
|
|
30
|
+
import { generateInteractionsPanel } from './stub-player/interactions-panel.js';
|
|
31
|
+
import { generateContentViewer } from './stub-player/content-viewer.js';
|
|
32
|
+
import { generateCatalogPanel } from './stub-player/catalog-panel.js';
|
|
33
|
+
import { generateOutlineMode } from './stub-player/outline-mode.js';
|
|
34
|
+
|
|
35
|
+
import { generateLoginScreen } from './stub-player/login-screen.js';
|
|
36
|
+
|
|
37
|
+
import { generateInteractionEditor } from './stub-player/interaction-editor.js';
|
|
38
|
+
|
|
39
|
+
import { escapeHtml } from './project-utils.js';
|
|
40
|
+
|
|
41
|
+
// Re-export for consumers that import from stub-player
|
|
42
|
+
export { escapeHtml };
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Generate the stub LMS player HTML
|
|
47
|
+
* @param {object} config - Configuration for the player
|
|
48
|
+
* @param {string} config.title - Course title
|
|
49
|
+
* @param {string} config.launchUrl - URL to load in iframe (file path or http URL)
|
|
50
|
+
* @param {string} config.storageKey - localStorage key for persistence
|
|
51
|
+
* @param {string} [config.passwordHash] - Optional SHA-256 hash of password for access
|
|
52
|
+
* @param {boolean} [config.isLive] - True for live mode (shows "Live" badge)
|
|
53
|
+
* @param {boolean} [config.liveReload] - True to enable live reload via SSE
|
|
54
|
+
* @param {string} [config.courseContent] - Markdown/HTML content for the content viewer
|
|
55
|
+
* @param {string|number} [config.startSlide] - Slide ID or index to navigate to on load
|
|
56
|
+
* @returns {string} - Complete HTML for the player page
|
|
57
|
+
*/
|
|
58
|
+
export function generateStubPlayer(config) {
|
|
59
|
+
const { title, launchUrl, storageKey, passwordHash, isLive, liveReload, courseContent, startSlide, isDesktop, moduleBasePath = '/__stub-player' } = config;
|
|
60
|
+
const hasPassword = !!passwordHash;
|
|
61
|
+
const hasContent = !!courseContent;
|
|
62
|
+
|
|
63
|
+
const stubPlayerStyles = loadStyles(isLive);
|
|
64
|
+
|
|
65
|
+
return `<!DOCTYPE html>
|
|
66
|
+
<html lang="en">
|
|
67
|
+
<head>
|
|
68
|
+
<meta charset="UTF-8">
|
|
69
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
70
|
+
<title>${escapeHtml(title)} - Preview</title>
|
|
71
|
+
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100' fill='none' stroke='%23fff' stroke-width='7' stroke-linecap='round' stroke-linejoin='round'><polyline points='25,22 5,50 25,78'/><polyline points='75,22 95,50 75,78'/><path d='M50,28 C40,28 33,36 33,45 C33,52 38,56 42,60 L42,65 L58,65 L58,60 C62,56 67,52 67,45 C67,36 60,28 50,28' stroke-width='6'/><line x1='44' y1='70' x2='56' y2='70' stroke-width='6'/><line x1='46' y1='75' x2='54' y2='75' stroke-width='6'/></svg>">
|
|
72
|
+
<style>
|
|
73
|
+
${stubPlayerStyles}
|
|
74
|
+
</style>
|
|
75
|
+
</head>
|
|
76
|
+
<body>
|
|
77
|
+
${hasPassword ? generateLoginScreen({ title: escapeHtml(title) }) : ''}
|
|
78
|
+
|
|
79
|
+
<iframe id="stub-player-course-frame" name="stub-player-course-frame"></iframe>
|
|
80
|
+
|
|
81
|
+
${isLive ? generateOutlineMode() : ''}
|
|
82
|
+
|
|
83
|
+
${generateHeaderBar({ isLive, hasContent })}
|
|
84
|
+
|
|
85
|
+
${isLive ? generateDebugPanel() : ''}
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
${hasContent ? generateContentViewer({ isLive }) : ''}
|
|
92
|
+
|
|
93
|
+
${isLive ? generateConfigPanel() : ''}
|
|
94
|
+
|
|
95
|
+
${isLive ? generateInteractionsPanel() : ''}
|
|
96
|
+
|
|
97
|
+
${isLive ? generateCatalogPanel() : ''}
|
|
98
|
+
|
|
99
|
+
${isLive ? generateInteractionEditor() : ''}
|
|
100
|
+
|
|
101
|
+
<script>
|
|
102
|
+
// Inject Configuration
|
|
103
|
+
window.STUB_CONFIG = {
|
|
104
|
+
title: ${JSON.stringify(title)},
|
|
105
|
+
launchUrl: ${JSON.stringify(launchUrl)},
|
|
106
|
+
storageKey: ${JSON.stringify(storageKey)},
|
|
107
|
+
passwordHash: ${hasPassword ? JSON.stringify(passwordHash) : 'null'},
|
|
108
|
+
isLive: ${isLive || false},
|
|
109
|
+
liveReload: ${liveReload || false},
|
|
110
|
+
startSlide: ${startSlide !== undefined ? JSON.stringify(startSlide) : 'null'},
|
|
111
|
+
courseContent: ${hasContent ? JSON.stringify(courseContent) : 'null'},
|
|
112
|
+
isDesktop: ${isDesktop || false},
|
|
113
|
+
isCI: ${!!process.env.CI}
|
|
114
|
+
};
|
|
115
|
+
</script>
|
|
116
|
+
<script type="module" src="${moduleBasePath}/${isLive ? 'app' : 'app-viewer'}.js"></script>
|
|
117
|
+
|
|
118
|
+
${liveReload ? `
|
|
119
|
+
<script>
|
|
120
|
+
// Live reload via Server-Sent Events
|
|
121
|
+
// Deferred to window.load to prevent "connection interrupted while page was loading" warnings
|
|
122
|
+
(function() {
|
|
123
|
+
let reconnectAttempts = 0;
|
|
124
|
+
const maxReconnectAttempts = 10;
|
|
125
|
+
|
|
126
|
+
function connect() {
|
|
127
|
+
const eventSource = new EventSource('/__reload');
|
|
128
|
+
|
|
129
|
+
eventSource.onmessage = function(event) {
|
|
130
|
+
if (event.data === 'reload') {
|
|
131
|
+
console.log('[Live Reload] Rebuilding complete, reloading course...');
|
|
132
|
+
// Clear error log before reload to prevent stale errors
|
|
133
|
+
if (window.stubPlayer?.clearErrors) window.stubPlayer.clearErrors();
|
|
134
|
+
const frame = document.getElementById('stub-player-course-frame');
|
|
135
|
+
if (frame) {
|
|
136
|
+
frame.contentWindow.location.reload();
|
|
137
|
+
}
|
|
138
|
+
} else if (event.data === 'connected') {
|
|
139
|
+
reconnectAttempts = 0;
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
eventSource.onerror = function() {
|
|
144
|
+
eventSource.close();
|
|
145
|
+
reconnectAttempts++;
|
|
146
|
+
if (reconnectAttempts <= maxReconnectAttempts) {
|
|
147
|
+
setTimeout(connect, 2000);
|
|
148
|
+
} else {
|
|
149
|
+
console.warn('[Live Reload] Max reconnect attempts reached. Refresh page manually.');
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
window.addEventListener('load', connect);
|
|
155
|
+
})();
|
|
156
|
+
</script>
|
|
157
|
+
` : ''}
|
|
158
|
+
</body>
|
|
159
|
+
</html>`;
|
|
160
|
+
}
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test Data Reporting
|
|
3
|
+
*
|
|
4
|
+
* Sends test data records to the configured endpoint to verify the setup works.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import fs from 'fs';
|
|
8
|
+
import path from 'path';
|
|
9
|
+
import { pathToFileURL } from 'url';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Send a test data record to the configured endpoint
|
|
13
|
+
* @param {Object} options - Command options
|
|
14
|
+
* @param {string} options.type - Type of test: 'assessment', 'objective', 'interaction'
|
|
15
|
+
* @param {string} options.message - Custom message to include
|
|
16
|
+
*/
|
|
17
|
+
export async function testDataReporting(options = {}) {
|
|
18
|
+
// Handle TLS certificate issues (corporate proxies)
|
|
19
|
+
if (options.insecure) {
|
|
20
|
+
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const cwd = process.cwd();
|
|
24
|
+
const configPath = path.join(cwd, 'course', 'course-config.js');
|
|
25
|
+
|
|
26
|
+
// Check if we're in a course project
|
|
27
|
+
if (!fs.existsSync(configPath)) {
|
|
28
|
+
console.error('❌ Error: course/course-config.js not found.');
|
|
29
|
+
console.error(' Run this command from the root of a coursecode project.');
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Load course config
|
|
34
|
+
let courseConfig;
|
|
35
|
+
try {
|
|
36
|
+
const configModule = await import(pathToFileURL(configPath).href);
|
|
37
|
+
courseConfig = configModule.courseConfig;
|
|
38
|
+
} catch (error) {
|
|
39
|
+
console.error('❌ Error loading course-config.js:', error.message);
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Check if data reporting is configured
|
|
44
|
+
const endpoint = courseConfig.environment?.dataReporting?.endpoint;
|
|
45
|
+
if (!endpoint) {
|
|
46
|
+
console.error('❌ Data reporting is not configured.');
|
|
47
|
+
console.error('');
|
|
48
|
+
console.error(' Add to course-config.js:');
|
|
49
|
+
console.error('');
|
|
50
|
+
console.error(' environment: {');
|
|
51
|
+
console.error(' dataReporting: {');
|
|
52
|
+
console.error(" endpoint: 'https://your-endpoint.workers.dev/data'");
|
|
53
|
+
console.error(' }');
|
|
54
|
+
console.error(' }');
|
|
55
|
+
process.exit(1);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Validate URL has protocol (must match production behavior)
|
|
59
|
+
if (!endpoint.startsWith('http://') && !endpoint.startsWith('https://')) {
|
|
60
|
+
console.error('❌ Invalid endpoint URL: missing protocol.');
|
|
61
|
+
console.error('');
|
|
62
|
+
console.error(` Found: ${endpoint}`);
|
|
63
|
+
console.error(` Expected: https://${endpoint}`);
|
|
64
|
+
console.error('');
|
|
65
|
+
console.error(' The endpoint must include the full URL with https:// protocol.');
|
|
66
|
+
console.error(' Update your course-config.js dataReporting.endpoint.');
|
|
67
|
+
process.exit(1);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
console.log('📊 Testing data reporting endpoint...');
|
|
71
|
+
console.log(` Endpoint: ${endpoint}`);
|
|
72
|
+
console.log('');
|
|
73
|
+
|
|
74
|
+
const recordType = options.type || 'assessment';
|
|
75
|
+
const testId = `cli-test-${Date.now()}`;
|
|
76
|
+
|
|
77
|
+
// Build test record based on type
|
|
78
|
+
let record;
|
|
79
|
+
switch (recordType) {
|
|
80
|
+
case 'objective':
|
|
81
|
+
record = {
|
|
82
|
+
type: 'objective',
|
|
83
|
+
data: {
|
|
84
|
+
objectiveId: testId,
|
|
85
|
+
completion_status: 'completed',
|
|
86
|
+
success_status: 'passed',
|
|
87
|
+
score: { scaled: 1.0 }
|
|
88
|
+
},
|
|
89
|
+
timestamp: new Date().toISOString()
|
|
90
|
+
};
|
|
91
|
+
break;
|
|
92
|
+
case 'interaction':
|
|
93
|
+
record = {
|
|
94
|
+
type: 'interaction',
|
|
95
|
+
data: {
|
|
96
|
+
interactionId: testId,
|
|
97
|
+
type: 'choice',
|
|
98
|
+
result: 'correct',
|
|
99
|
+
learner_response: 'a'
|
|
100
|
+
},
|
|
101
|
+
timestamp: new Date().toISOString()
|
|
102
|
+
};
|
|
103
|
+
break;
|
|
104
|
+
case 'assessment':
|
|
105
|
+
default:
|
|
106
|
+
record = {
|
|
107
|
+
type: 'assessment',
|
|
108
|
+
data: {
|
|
109
|
+
assessmentId: testId,
|
|
110
|
+
score: 100,
|
|
111
|
+
passed: true,
|
|
112
|
+
attemptNumber: 1,
|
|
113
|
+
totalQuestions: 5,
|
|
114
|
+
correctCount: 5,
|
|
115
|
+
timeSpent: 120
|
|
116
|
+
},
|
|
117
|
+
timestamp: new Date().toISOString()
|
|
118
|
+
};
|
|
119
|
+
break;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Build payload matching data-reporter.js format
|
|
123
|
+
const payload = {
|
|
124
|
+
records: [record],
|
|
125
|
+
sentAt: new Date().toISOString(),
|
|
126
|
+
course: {
|
|
127
|
+
title: courseConfig.metadata?.title || 'Unknown Course',
|
|
128
|
+
version: courseConfig.metadata?.version || '0.0.0',
|
|
129
|
+
id: courseConfig.metadata?.id
|
|
130
|
+
},
|
|
131
|
+
_test: {
|
|
132
|
+
source: 'coursecode-cli',
|
|
133
|
+
message: options.message || 'Test data record from coursecode CLI'
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
console.log(` Record type: ${recordType}`);
|
|
138
|
+
console.log('');
|
|
139
|
+
|
|
140
|
+
try {
|
|
141
|
+
const response = await fetch(endpoint, {
|
|
142
|
+
method: 'POST',
|
|
143
|
+
headers: {
|
|
144
|
+
'Content-Type': 'application/json'
|
|
145
|
+
},
|
|
146
|
+
body: JSON.stringify(payload)
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
if (response.ok) {
|
|
150
|
+
console.log(`✅ Success! Test ${recordType} record sent.`);
|
|
151
|
+
console.log('');
|
|
152
|
+
console.log(' Payload sent:');
|
|
153
|
+
console.log(` - Record type: ${recordType}`);
|
|
154
|
+
console.log(` - Course: ${courseConfig.metadata?.title || 'Unknown'}`);
|
|
155
|
+
console.log(` - Test ID: ${testId}`);
|
|
156
|
+
} else {
|
|
157
|
+
const errorText = await response.text();
|
|
158
|
+
console.error(`❌ Failed with status ${response.status}`);
|
|
159
|
+
console.error(` Response: ${errorText}`);
|
|
160
|
+
console.error('');
|
|
161
|
+
console.error(' Check your endpoint logs for details.');
|
|
162
|
+
process.exit(1);
|
|
163
|
+
}
|
|
164
|
+
} catch (error) {
|
|
165
|
+
console.error('❌ Network error:', error.message);
|
|
166
|
+
if (error.cause) {
|
|
167
|
+
console.error(' Cause:', error.cause.message || error.cause);
|
|
168
|
+
}
|
|
169
|
+
console.error('');
|
|
170
|
+
console.error(' Make sure:');
|
|
171
|
+
console.error(' - The endpoint URL is correct');
|
|
172
|
+
console.error(' - Your endpoint is deployed and accessible');
|
|
173
|
+
console.error(' - You have internet connectivity');
|
|
174
|
+
process.exit(1);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test Error Reporting
|
|
3
|
+
*
|
|
4
|
+
* Sends a test error to the configured endpoint to verify the setup works.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import fs from 'fs';
|
|
8
|
+
import path from 'path';
|
|
9
|
+
import { pathToFileURL } from 'url';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Send a test error/report to the configured endpoint
|
|
13
|
+
* @param {Object} options - Command options
|
|
14
|
+
* @param {string} options.type - Type of test: 'error' or 'report'
|
|
15
|
+
* @param {string} options.message - Custom message to include
|
|
16
|
+
*/
|
|
17
|
+
export async function testErrorReporting(options = {}) {
|
|
18
|
+
// Handle TLS certificate issues (corporate proxies)
|
|
19
|
+
if (options.insecure) {
|
|
20
|
+
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const cwd = process.cwd();
|
|
24
|
+
const configPath = path.join(cwd, 'course', 'course-config.js');
|
|
25
|
+
|
|
26
|
+
// Check if we're in a course project
|
|
27
|
+
if (!fs.existsSync(configPath)) {
|
|
28
|
+
console.error('❌ Error: course/course-config.js not found.');
|
|
29
|
+
console.error(' Run this command from the root of a coursecode project.');
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Load course config
|
|
34
|
+
let courseConfig;
|
|
35
|
+
try {
|
|
36
|
+
const configModule = await import(pathToFileURL(configPath).href);
|
|
37
|
+
courseConfig = configModule.courseConfig;
|
|
38
|
+
} catch (error) {
|
|
39
|
+
console.error('❌ Error loading course-config.js:', error.message);
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Check if error reporting is configured
|
|
44
|
+
const endpoint = courseConfig.environment?.errorReporting?.endpoint;
|
|
45
|
+
if (!endpoint) {
|
|
46
|
+
console.error('❌ Error reporting is not configured.');
|
|
47
|
+
console.error('');
|
|
48
|
+
console.error(' Add to course-config.js:');
|
|
49
|
+
console.error('');
|
|
50
|
+
console.error(' environment: {');
|
|
51
|
+
console.error(' errorReporting: {');
|
|
52
|
+
console.error(" endpoint: 'https://your-worker.workers.dev'");
|
|
53
|
+
console.error(' }');
|
|
54
|
+
console.error(' }');
|
|
55
|
+
process.exit(1);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Validate URL has protocol (must match production behavior)
|
|
59
|
+
if (!endpoint.startsWith('http://') && !endpoint.startsWith('https://')) {
|
|
60
|
+
console.error('❌ Invalid endpoint URL: missing protocol.');
|
|
61
|
+
console.error('');
|
|
62
|
+
console.error(` Found: ${endpoint}`);
|
|
63
|
+
console.error(` Expected: https://${endpoint}`);
|
|
64
|
+
console.error('');
|
|
65
|
+
console.error(' The endpoint must include the full URL with https:// protocol.');
|
|
66
|
+
console.error(' Update your course-config.js errorReporting.endpoint.');
|
|
67
|
+
process.exit(1);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
console.log('📧 Testing error reporting endpoint...');
|
|
71
|
+
console.log(` Endpoint: ${endpoint}`);
|
|
72
|
+
console.log('');
|
|
73
|
+
|
|
74
|
+
const isUserReport = options.type === 'report';
|
|
75
|
+
|
|
76
|
+
// Build test payload
|
|
77
|
+
const payload = isUserReport ? {
|
|
78
|
+
type: 'user_report',
|
|
79
|
+
description: options.message || 'This is a test user report from the coursecode CLI.',
|
|
80
|
+
timestamp: new Date().toISOString(),
|
|
81
|
+
url: 'cli://coursecode/test-error',
|
|
82
|
+
userAgent: `coursecode-cli/${process.env.npm_package_version || '1.0.0'}`,
|
|
83
|
+
currentSlide: 'test-slide',
|
|
84
|
+
course: {
|
|
85
|
+
title: courseConfig.metadata?.title || 'Unknown Course',
|
|
86
|
+
version: courseConfig.metadata?.version || '0.0.0',
|
|
87
|
+
id: courseConfig.metadata?.id
|
|
88
|
+
}
|
|
89
|
+
} : {
|
|
90
|
+
domain: 'cli-test',
|
|
91
|
+
operation: 'testErrorReporting',
|
|
92
|
+
message: options.message || 'This is a test error from the coursecode CLI. If you received this email, error reporting is working correctly!',
|
|
93
|
+
timestamp: new Date().toISOString(),
|
|
94
|
+
url: 'cli://coursecode/test-error',
|
|
95
|
+
userAgent: `coursecode-cli/${process.env.npm_package_version || '1.0.0'}`,
|
|
96
|
+
course: {
|
|
97
|
+
title: courseConfig.metadata?.title || 'Unknown Course',
|
|
98
|
+
version: courseConfig.metadata?.version || '0.0.0',
|
|
99
|
+
id: courseConfig.metadata?.id
|
|
100
|
+
},
|
|
101
|
+
context: {
|
|
102
|
+
testType: 'cli-verification',
|
|
103
|
+
nodeVersion: process.version
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
try {
|
|
108
|
+
const response = await fetch(endpoint, {
|
|
109
|
+
method: 'POST',
|
|
110
|
+
headers: {
|
|
111
|
+
'Content-Type': 'application/json'
|
|
112
|
+
},
|
|
113
|
+
body: JSON.stringify(payload)
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
if (response.ok) {
|
|
117
|
+
console.log('✅ Success! Test ' + (isUserReport ? 'report' : 'error') + ' sent.');
|
|
118
|
+
console.log('');
|
|
119
|
+
console.log(' Check your email inbox for the notification.');
|
|
120
|
+
console.log(' Subject will be:');
|
|
121
|
+
if (isUserReport) {
|
|
122
|
+
console.log(` "[User Report] Issue reported in ${courseConfig.metadata?.title || 'Course'}"`);
|
|
123
|
+
} else {
|
|
124
|
+
console.log(' "[Course Error] cli-test: testErrorReporting"');
|
|
125
|
+
}
|
|
126
|
+
} else {
|
|
127
|
+
const errorText = await response.text();
|
|
128
|
+
console.error(`❌ Failed with status ${response.status}`);
|
|
129
|
+
console.error(` Response: ${errorText}`);
|
|
130
|
+
console.error('');
|
|
131
|
+
console.error(' Check your Cloudflare Worker logs for details.');
|
|
132
|
+
process.exit(1);
|
|
133
|
+
}
|
|
134
|
+
} catch (error) {
|
|
135
|
+
console.error('❌ Network error:', error.message);
|
|
136
|
+
if (error.cause) {
|
|
137
|
+
console.error(' Cause:', error.cause.message || error.cause);
|
|
138
|
+
}
|
|
139
|
+
console.error('');
|
|
140
|
+
console.error(' Make sure:');
|
|
141
|
+
console.error(' - The endpoint URL is correct');
|
|
142
|
+
console.error(' - The Cloudflare Worker is deployed');
|
|
143
|
+
console.error(' - You have internet connectivity');
|
|
144
|
+
process.exit(1);
|
|
145
|
+
}
|
|
146
|
+
}
|
package/lib/token.js
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Token generator CLI - generate secure access tokens for multi-tenant deployment
|
|
3
|
+
*
|
|
4
|
+
* Usage:
|
|
5
|
+
* coursecode token # Generate random token
|
|
6
|
+
* coursecode token --add client-name # Add client to course-config
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import crypto from 'crypto';
|
|
10
|
+
import fs from 'fs';
|
|
11
|
+
import path from 'path';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Generate a cryptographically secure token
|
|
15
|
+
* @param {number} length - Token length in bytes (default: 24 = 32 chars base64url)
|
|
16
|
+
* @returns {string} URL-safe token
|
|
17
|
+
*/
|
|
18
|
+
export function generateToken(length = 24) {
|
|
19
|
+
return crypto.randomBytes(length).toString('base64url');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Token command handler
|
|
24
|
+
*/
|
|
25
|
+
export async function token(options = {}) {
|
|
26
|
+
const newToken = generateToken();
|
|
27
|
+
|
|
28
|
+
// If --add specified, add to course-config
|
|
29
|
+
if (options.add) {
|
|
30
|
+
const clientId = options.add;
|
|
31
|
+
const courseDir = path.join(process.cwd(), 'course');
|
|
32
|
+
const configPath = path.join(courseDir, 'course-config.js');
|
|
33
|
+
|
|
34
|
+
if (!fs.existsSync(configPath)) {
|
|
35
|
+
console.error('\n❌ No course-config.js found. Run from a CourseCode project directory.\n');
|
|
36
|
+
process.exit(1);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
let content = fs.readFileSync(configPath, 'utf-8');
|
|
40
|
+
|
|
41
|
+
// Check if accessControl already exists
|
|
42
|
+
if (content.includes('accessControl:')) {
|
|
43
|
+
// Add to existing clients object
|
|
44
|
+
const clientsMatch = content.match(/accessControl:\s*\{[\s\S]*?clients:\s*\{/);
|
|
45
|
+
if (clientsMatch) {
|
|
46
|
+
const insertPos = clientsMatch.index + clientsMatch[0].length;
|
|
47
|
+
const newClient = `\n '${clientId}': { token: '${newToken}' },`;
|
|
48
|
+
content = content.slice(0, insertPos) + newClient + content.slice(insertPos);
|
|
49
|
+
}
|
|
50
|
+
} else {
|
|
51
|
+
// Add accessControl section before closing brace
|
|
52
|
+
const accessControlBlock = `
|
|
53
|
+
accessControl: {
|
|
54
|
+
enabled: true,
|
|
55
|
+
clients: {
|
|
56
|
+
'${clientId}': { token: '${newToken}' }
|
|
57
|
+
}
|
|
58
|
+
},`;
|
|
59
|
+
// Find the last closing brace of courseConfig
|
|
60
|
+
const lastBrace = content.lastIndexOf('};');
|
|
61
|
+
if (lastBrace !== -1) {
|
|
62
|
+
content = content.slice(0, lastBrace) + accessControlBlock + '\n' + content.slice(lastBrace);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
fs.writeFileSync(configPath, content, 'utf-8');
|
|
67
|
+
|
|
68
|
+
console.log(`
|
|
69
|
+
✅ Added client '${clientId}' to accessControl
|
|
70
|
+
|
|
71
|
+
Token: ${newToken}
|
|
72
|
+
|
|
73
|
+
Build will generate: ${clientId}_proxy.zip
|
|
74
|
+
`);
|
|
75
|
+
} else {
|
|
76
|
+
// Just generate and print token
|
|
77
|
+
console.log(`
|
|
78
|
+
🔑 Generated access token:
|
|
79
|
+
|
|
80
|
+
${newToken}
|
|
81
|
+
|
|
82
|
+
Use with --add to add a client:
|
|
83
|
+
coursecode token --add client-name
|
|
84
|
+
`);
|
|
85
|
+
}
|
|
86
|
+
}
|