This repository contains a Node-based tool for generating SCORM-compatible interactive learning modules from a single Markdown (.md) file.
The generated output is a SCORM ZIP that can be uploaded directly into an LMS such as Blackboard.
Features:
- Step-by-step modules that learners can complete.
- Modules are marked as complete as students progress through.
- Images, codeblocks, hyperlinks and most markdown syntax (including GitHub callout support).
- Inline quizzes using
:::quizblocks with SCORM answer persistence. - Light/dark mode.
- Automatically formatted codeblocks.
- Tracking/saving progress. Upon re-opening the module, learners are asked if they would like to continue where they left off.
- optional Bonus task support
- Optional contextual key-term definitions from
keywords.md, with related-term navigation.
Each SCORM module is generated from one Markdown file, which defines:
- The module title
- An optional cover page
- Step-by-step learning content
- Code blocks, callouts, lists, links, and images
- Optional SCORM metadata (description, course code)
The generator outputs SCORM ZIP artifacts to:
./interactive-codelab/scorm/*.zip
Each ZIP represents one complete SCORM module.
interactive-codelab/
├── content/
│ └── module.md # Markdown file used to generate the module
├── public/
│ └── assets/ # Image assets referenced by the markdown
├── scorm/
│ └── *.zip # Generated SCORM packages
Sample data is present in the project for the purpoose of testing. Before generating a new module, reset the content and assets directories.
Delete the contents of the following directories:
./interactive-codelab/public/assets/*
./interactive-codelab/content/*
Copy any image assets used by your module into:
./interactive-codelab/public/assets/
- Copy your Markdown file into:
./interactive-codelab/content/
- Rename it to:
module.md
-
Optionally add a
keywords.mdfile alongsidemodule.md. Each definition begins with a level-one heading, and its content continues until the next level-one heading, so it supports the same Markdown features as lesson content. -
Declare related-term bubbles with a dedicated HTML comment on its own line. Separate term names with commas; names that also have a
#definition become clickable bubbles:# Android Studio Android Studio is an IDE for building Android apps. <!-- related-key-terms: IDE, Android SDK, Kotlin -->
If not already installed:
npm install
Run:
npm run blackboard
The generated SCORM package will appear in:
./interactive-codelab/scorm/
Upload the generated .zip file into your LMS (e.g. Blackboard) as a SCORM package.
The first major heading (#) defines the module name.
# Exercise 1: My Android CV
Rules:
- The first level-1 heading (
#) is used as the SCORM module title - A second level-1 heading (
#) is optional and enables Bonus Tasks (see below) - Avoid additional
#headings after the bonus heading, as only the first two are used for structure
Anything after the first major heading and before the first subheading (##) becomes the cover page.
Example:
# Exercise 1: My Android CV
Welcome to Week 1 of Android development.
In this module, you will build a simple CV app using Kotlin.
This content is shown to the learner before the module starts.
You can set a banner image for the module by adding an image immediately after the main heading with the description exactly:

Notes:
- The description must be
Banner Image - The image must exist in
public/assets/ - The banner image is optional and will only be displayed if there is content on the cover page.
Use :::hint and :::solution directive blocks anywhere inside a step to add help that is hidden until the learner chooses to reveal it. This is useful for gradually offering support without immediately showing the complete answer.
:::hint
Look at which exception is thrown by `int.Parse`.
:::
:::solution
Use `int.TryParse` when invalid user input is an expected possibility.
:::Rules:
- Each hint or solution becomes its own revealable panel with a button.
- The content inside the block supports normal Markdown, including inline code, lists, links, and fenced code blocks.
- Put hints before solutions when you want learners to request small pieces of help before revealing the full answer.
Use :::tabs when a step needs to show several related files without cluttering the page with multiple separate code blocks. Each file starts with ---tab followed by the label to display on the tab. The content inside each tab supports normal Markdown, so fenced code blocks still get syntax highlighting and copy buttons.
:::tabs
---tab Program.cs
```csharp
Console.WriteLine("Hello from the main program");
```
---tab CustomerService.cs
```csharp
public class CustomerService
{
public string GetDisplayName() => "Ada Lovelace";
}
```
---tab CustomerServiceTests.cs
```csharp
[Fact]
public void GetDisplayName_returns_name()
{
Assert.Equal("Ada Lovelace", new CustomerService().GetDisplayName());
}
```
:::Rules:
- Start the group with
:::tabsand end it with:::. - Start each tab with
---tab FileName.ext. - Put each file's code or Markdown content below its
---tabline. - Use tabs only when files are related. Normal fenced code blocks that are not inside a tab block continue to render as standard code blocks.
Each module step is defined by a level-2 heading (##):
## Create the Project
Rules:
- Each
##heading becomes a separate step - Headings deeper than this (e.g.
###) do not create new steps - Use
###and deeper headings only for structure within a step - Avoid adding in text that indicates what step it is (e.g. "Step 1: "). The tool will automatically number each sub-heading as a step when compiled.
You can optionally show a suggested completion time beside the step heading in the generated module. Add the suggested time marker immediately underneath the relevant ## heading using this exact format:
## Create the Project
<!-- suggested-time: 20 minutes -->
Step content starts here.Rules:
- The marker must be the first line directly below the
##heading. - The marker must use the exact syntax
<!-- suggested-time: your time text -->. - Replace
your time textwith the text you want displayed, such as10 minutes,1 hour, or15–20 minutes. - If the marker is missing, placed elsewhere, or does not match the exact format, no suggested time is shown for that step.
- The marker is removed from the learner-facing content and displayed as a coloured pill to the right of the step heading.
- Other
<!-- -->comments outside fenced code blocks are also hidden from learner-facing content.
You can place a quiz anywhere in the module content (overview, main steps, or bonus steps) using:
:::quiz
This is a question?
- [] Answer 1
- [x] This answer is correct
- [] Answer 3
- [] Answer 4
:::Rules:
- Start with
:::quizand end with:::. - All lines before the first bullet are treated as the quiz question.
- Options must use bullet points with checkbox syntax:
- []for an incorrect option- [x]for the correct option
- At least one
- [x]option must be present.
Learner behavior:
- Selecting an option marks the quiz immediately.
- Correct answers are highlighted with a green tick.
- If the learner chooses incorrectly, their selected option is marked red, the correct answer is marked green, and all other options are greyed out.
- A Retry button appears for incorrect attempts so the learner can reset and try again.
SCORM tracking behavior:
- Quiz selections are saved in SCORM suspend data.
- Re-opening the module restores previously selected quiz answers.
- Choosing Restart from beginning clears quiz attempts along with other progress.
You can define optional bonus tasks by adding a second major heading (#) in module.md.
How it works:
- The second
#heading text becomes the Bonus section title in the left sidebar. - Any following
##headings after that second#become bonus task steps. - Bonus tasks are separate from main module progress and have their own progress indicator.
Example:
# Exercise 1: My Android CV
Welcome text for the module...
## Main step one
Main content...
## Main step two
Main content...
# Bonus Tasks
## Add an extra layout variant
Bonus content...
## Add accessibility labels
Bonus content...Behavior notes:
- If no second
#heading is present, no bonus section is shown. - Bonus tasks are unlocked from the final main step via the Bonus tasks button.
- On smaller screens, only the active track's radial progress is shown (main or bonus).
Images are supported using standard Markdown syntax:

Important rules:
- Images must be stored in
./interactive-codelab/public/assets/ - Always link using relative paths starting with
assets/ - Do not use
/assets/...(this will point to the LMS root and break)
Correct:

Incorrect:

Code blocks are fully supported. These will be formatted using Highlight.js automatically.
Use triple backticks and specify a language where possible so formatting is correct:
```kotlin
fun main() {
println("Hello World")
}
```Inline code is also supported using backticks. Inline code will not receive automatic formatting like code blocks:
`val name = "Joshua"`
The tool supports GitHub-style callouts such as Note, Warning, Important, and others.
Example:
> [!NOTE]
> This is an example.
... will produce the following:
Note
This is an example.
Supported formats are based on GitHub’s callout syntax:
https://github.com/orgs/community/discussions/16925
Standard GitHub Markdown is supported, including:
- Bold
- Italics
Inline code- Bullet lists
- Numbered lists
Example:
- Kotlin
- Android Studio
- RecyclerView
1. Create the project
2. Add resources
3. Run the app
Standard Markdown links are supported:
[Android Developer Docs](https://developer.android.com)
All links will automatically open in a new tab when clicked in the LMS.
The following placeholders will be replaced automatically with the learner’s name, with all special characters and spaces removed (as provided by the LMS):
[name]
[yourname]
Example:
Place the contents of your application into a folder called [name].
Notes:
- The substituted name contains no spaces or special characters and are converted to lowercase
You can configure SCORM metadata using HTML comments at the very top of the Markdown file.
Sets the description shown in the LMS when importing the module.
<!-- scorm:description Build a simple CV app in Android Studio. You’ll add an image, style text, and learn string resources. -->
Replaces the default “Developer Module” label in the SCORM UI.
<!-- scorm:courseCode BIT608 Mobile Application Development -->
Example with both:
<!-- scorm:description Build a simple CV app in Android Studio. You’ll add an image, style text, and learn string resources. -->
<!-- scorm:courseCode BIT608 Mobile Application Development -->
# Exercise 1: My Android CV
Both fields are optional and will be replaced by defaults if not specified.
- One
#heading only (module title) - Use
##for steps - Assets must live in
public/assets/ - Link images using
assets/filename.png - Rename the Markdown file to
module.md - Clean assets and content directories before each build
- Use HTML comments for SCORM metadata
- The second
#indicates bonus tasks, with subsequent##representing bonus steps
Place an optional guides.md beside module.md and keywords.md in content/ to add deeper walkthroughs. Each guide begins with a level-one heading whose text must match a keyword heading (case-insensitively). Only matched keywords display See it in action; unmatched guides are ignored with a development warning. Guide bodies support normal Markdown, including code, images, links, lists, tables, and callouts. Level-two (##) headings remain ordinary sections within a guide and never create lesson steps.
# Activity
## Start with the idea
An Activity provides a screen where a learner interacts with an Android app.The file is optional, and guides do not create completion items or affect SCORM progress. Exercise build workflows must copy guides.md into content/ whenever guide support is required.

