Introduction
This is the documentation for the @freecodecamp/freecodecamp-os
package.
freecodecamp-os
is a tool for creating and running interactive courses within Visual Studio Code.
The freecodecamp-os
package is to be used in conjunction with the freeCodeCamp - Courses extension, for the optimal experience.
Getting Started
Creating a New Course
Create a new project directory and install @freecodecamp/freecodecamp-os
:
mkdir <COURSE_DIR>
cd <COURSE_DIR>
npm init -y
npm install @freecodecamp/freecodecamp-os
Configuring Your Course
Create a freecodecamp.conf.json
file in the project root:
touch freecodecamp.conf.json
Add the following required configuration:
{
"version": "0.0.1",
"config": {
"projects.json": "<PROJECTS_JSON>",
"state.json": "<STATE_JSON>"
},
"curriculum": {
"locales": {
"<LOCALE>": "<LOCALE_DIR>"
}
}
}
Example
{
"version": "0.0.1",
"config": {
"projects.json": "./config/projects.json",
"state.json": "./config/state.json"
},
"curriculum": {
"locales": {
"english": "./curriculum/locales/english"
}
}
}
There are many more configuration options available. See the configuration page for more details.
Create the projects.json
file:
[
{
"id": 0,
"dashedName": "<PROJECT_DASHED_NAME>"
}
]
There are many more configuration options available. See the configuration page for more details.
Create the state.json
file:
{}
Initialise this file with the initial state of the course. If you want the course to start on a project (instead of the landing page), replace null
with the dashedName
of the project.
Create the curricula files:
mkdir <LOCALE_DIR>
touch <LOCALE_DIR>/<PROJECT_DASHED_NAME>.md
Add the Markdown content to the curricula files. See the project syntax page for more details.
Create the project boilerplate/working directory in the root:
mkdir <PROJECT_DASHED_NAME>
<COURSE_DIR>/
├── freecodecamp.conf.json
├── <PROJECTS_JSON>
├── <STATE_JSON>
└── <LOCALE_DIR>/
└── <PROJECT_DASHED_NAME>.md
If using the terminal
feature:
├── <CONFIG_BASH>/
│ ├── <CONFIG_BASH_BASHRC>
│ └── <CONFIG_BASH_SOURCERER>
├── .logs/
│ ├── .bash_history.log
│ ├── .cwd.log
│ ├── .history_cwd.log
│ ├── .next_command.log
│ ├── .temp.log
│ └── .terminal_out.log
If using the tooling
feature:
├── <CONFIG_TOOLING_HELPERS>
Create a .vscode/settings.json
file to configure the freeCodeCamp - Courses extension:
{
// Open the course when the workspace is opened
"freecodecamp-courses.autoStart": true,
// Automatically adjust the terminal logs if used
"freecodecamp-courses.prepare": "sed -i \"s#WD=.*#WD=$(pwd)#g\" ./bash/.bashrc",
// Command run in terminal on `freeCodeCamp: Develop Course`
"freecodecamp-courses.scripts.develop-course": "NODE_ENV=development npm run start",
// Command run in terminal on `freeCodeCamp: Run Course`
"freecodecamp-courses.scripts.run-course": "NODE_ENV=production npm run start",
// Preview to open when course starts
"freecodecamp-courses.workspace.previews": [
{
"open": true,
"url": "http://localhost:8080",
"showLoader": true,
"timeout": 4000
}
],
// The below settings are needed for using the terminal feature
"terminal.integrated.defaultProfile.linux": "bash",
"terminal.integrated.profiles.linux": {
"bash": {
"path": "bash",
"icon": "terminal-bash",
"args": ["--init-file", "./bash/sourcerer.sh"]
}
}
}
A few more settings are available, and can be seen and configured from the VSCode Settings UI.
CLI
Installation
Releases
Locate your platform in the releases section and download the latest version.
cargo
Requires Rust to be installed: https://www.rust-lang.org/tools/install
cargo install create-freecodecamp-os-app
Usage
To create a new course with some boilerplate:
create-freecodecamp-os-app
To add a project to an existing course:
create-freecodecamp-os-app add-project
The version of the CLI is tied to the version of freecodecamp-os
. Some options may not be available if the version of the CLI is not compatible with the version of freecodecamp-os
that is installed.
Examples
If you create a course using @freecodecamp/freecodecamp-os
, open a pull request to add it to this list.
freeCodeCamp.org
Web3
Rust
Configuration
freecodecamp.conf.json
Required Configuration
{
"version": "0.0.1",
"config": {
"projects.json": "<PROJECTS_JSON>",
"state.json": "<STATE_JSON>"
},
"curriculum": {
"locales": {
"<LOCALE>": "<LOCALE_DIR>"
}
}
}
Minimum Usable Example
{
"version": "0.0.1",
"config": {
"projects.json": "./config/projects.json",
"state.json": "./config/state.json"
},
"curriculum": {
"locales": {
"english": "./curriculum/locales/english"
}
}
}
Optional Configuration (Features)
port
By default, the server and client communicate over port 8080
. To change this, add a port
key to the configuration file:
client
assets.header
: path relative to the root of the course -string
assets.favicon
: path relative to the root of the course -string
landing.<locale>.description
: description of the course shown on the landing page -string
landing.<locale>.title
: title of the course shown on the landing page -string
landing.<locale>.faq-link
: link to the FAQ page -string
landing.<locale>.faq-text
: text to display for the FAQ link -string
static
: static resources to serve -string | string[] | Record<string, string> | Record<string, string>[]
{
"client": {
"assets": {
"header": "./client/assets/header.png",
"favicon": "./client/assets/favicon.ico"
},
"static": ["./curriculum/", { "/images": "./curriculum/images" }]
}
}
config
projects.json
: path relative to the root of the course -string
state.json
: path relative to the root of the course -string
{
"config": {
"projects.json": "./config/projects.json",
"state.json": "./config/state.json"
}
}
curriculum
locales
: an object of locale names and their corresponding paths relative to the root of the course -Record<string, string>
assertions
: an onject of locale names and their corresponding paths to a JSON file containing custom assertions -string
{
"curriculum": {
"locales": {
"english": "./curriculum/locales/english"
},
"assertions": {
"afrikaans": "./curriculum/assertions/afrikaans.json"
}
}
}
hotReload
ignore
: a list of paths to ignore when hot reloading -string[]
tooling
helpers
: path relative to the root of the course -string
plugins
: path relative to the root of the course -string
projects.json
Definitions
id
: A unique, incremental integer -number
dashedName
: The name of the project corresponding to thecurriculum/locales/<PROJECT_DASHED_NAME>.md
file -string
isIntegrated
: Whether or not to treat the project as a single-lesson project -boolean
(default:false
)isPublic
: Whether or not to enable the project for public viewing. Note: the project will still be visible on the landing page, but will be disabled -boolean
(default:false
)currentLesson
: The current lesson of the project -number
(default:1
)runTestsOnWatch
: Whether or not to run tests on file change -boolean
(default:false
)isResetEnabled
: Whether or not to enable the reset button -boolean
(default:false
)numberOfLessons
: The number of lessons in the project -number
1seedEveryLesson
: Whether or not to run the seed on lesson load -boolean
(default:false
)blockingTests
: Run tests synchronously -boolean
(default:false
)breakOnFailure
: Stop running tests on the first failure -boolean
(default:false
)
This is automagically calculated when the app is launched.
Required Configuration
[
{
"id": 0, // Unique ID
"dashedName": "<PROJECT_DASHED_NAME>"
}
]
Optional Configuration
[
{
"id": 0,
"dashedName": "learn-x-by-building-y",
"isIntegrated": false,
"isPublic": false,
"currentLesson": 1,
"runTestsOnWatch": false,
"isResetEnabled": false,
"numberOfLessons": 10,
"seedEveryLesson": false,
"blockingTests": false,
"breakOnFailure": false
}
]
.gitignore
Retaining Files When a Step is Reset
Resetting a step removes all untracked files from the project directory. To prevent this for specific files, add them to a boilerplate .gitignore
file, or the one in root.
Project Syntax
This is the Markdown syntax used to create projects in the curriculum using the default parser. The parser can be configured using the plugin-system.
Markers
# <TITLE>
# <TITLE>
The first paragraph is used as the description of the project. The first json
code block is used for extra metadata such as tags:
## <N>
## <LESSON_NUMBER>
Zero-based numbering, because of course
meta
## <N>
```json
{
"watch": ["path/relative/to/root"],
"ignore": ["path/relative/to/root"]
}
```
The meta.watch
field is used to specify specific files to watch during a lesson. The meta.ignore
field is used to specify specific files to ignore during a lesson. The watcher is affected once on lesson load.
The watch
and ignore
fields are optional. It does not make sense to provide both at the same time.
### --description--
### --description--
<DESCRIPTION_CONTENT>
### --tests--
### --tests--
<TEST_TEXT>
```js
<TEST_CODE>
```
Example
### --tests--
You should ...
```js
await new Promise(resolve => setTimeout(resolve, 2000));
assert.equal(true, true);
```
### --seed--
### --seed--
#### --"<FILE_PATH>"--
```<FILE_EXTENSION>
<FILE_CONTENT>
```
#### --cmd--
```bash
<COMMAND>
```
Example
#### --seed--
#### --"index.js"--
```js
// I am an example boilerplate file
```
#### --cmd--
```bash
npm install
```
### --hints--
### --hints--
#### 0
Markdown-valid hint
#### 1
A second Markdown hint with code:
```rust
impl Developer for Camper {
fn can_code(&self) -> bool {
true
}
}
```
#### --force--
Any seed marked with the force flag will overwrite the seedEveryLesson
configuration option. Specifically, the force flag causes the seed to run, if it were not going to, and it prevents the seed from running, if it were going to.
### --seed--
#### --force--
<!-- Rest of seed -->
## --fcc-end--
An EOF discriminator.
## --fcc-end--
Example
curriculum/locales/english/learn-x-by-building-y.md
curriculum/locales/english/learn-x-by-building-y.md
# Learn X by Building Y
In this course, you will learn X by building y.
## 0
### --description--
Declare a variable `a` with value `1`, in `index.js`.
```javascript
const a = 1;
```
### --tests--
You should declare a variable named `a`.
```js
const test = `console.assert(typeof a);`;
const filePath = 'learn-x-by-building-y/index.js';
const cb = (stdout, stderr) => {
assert.isEmpty(stderr);
assert.exists(stdout);
};
await __helpers.javascriptTest(filePath, test, cb);
```
You should give `a` a value of `1`.
```js
const test = `console.assert(a === 1, \`expected \${a} to equal 1\`);`;
const filePath = 'learn-x-by-building-y/index.js';
const cb = (stdout, stderr) => {
assert.isEmpty(stderr);
assert.exists(stdout);
};
await __helpers.javascriptTest(filePath, test, cb);
```
### --seed--
#### --"index.js"--
```js
// I am an example boilerplate file
```
## 1
```json
{
"watch": ["learn-x-by-building-y/test/index.js"]
}
```
### --description--
Create a new directory named `test`, and create a file `test/index.ts`.
Then add the following:
```ts
const test: string = 'test';
```
### --tests--
You should ...
```js
await new Promise(resolve => setTimeout(resolve, 2000));
assert.equal(true, true);
```
### --seed--
#### --"index.js"--
```javascript
// I am an example boilerplate file
const a = 1;
```
## 2
### --description--
Description.
### --tests--
You should ...
```js
await new Promise(resolve => setTimeout(resolve, 2000));
assert.equal(true, true);
```
### --seed--
#### --cmd--
```bash
echo "I should run first"
```
#### --cmd--
```bash
mkdir test
```
#### --cmd--
```bash
touch test/index.ts
```
#### --"test/index.ts"--
```ts
const test: string = 'test';
```
## 3
### --description--
Description.
### --tests--
You should ...
```js
await new Promise(resolve => setTimeout(resolve, 2000));
assert.equal(true, true);
```
## 4
### --description--
Description.
### --tests--
You should ...
```js
await new Promise(resolve => setTimeout(resolve, 2000));
assert.equal(true, true);
```
## 5
### --description--
Description.
### --tests--
This test will pass after 5 seconds.
```js
await new Promise(resolve => setTimeout(resolve, 5000));
assert.equal(1, 1);
```
## --fcc-end--
It is also possible to add the seed for a lesson in a separate file named <PROJECT_DASHED_NAME>-seed.md
within the locales
directory.
curriculum/locales/english/learn-x-by-building-y-seed.md
curriculum/locales/english/learn-x-by-building-y-seed.md
## 0
### --seed--
#### --"index.js"--
```javascript
// Seed in a separate file
```
## --fcc-end--
If seed for the same lesson is included in both the project file and a seed file, the seed in the project file will be used.
freeCodeCamp - Courses
The freeCodeCamp - Courses VSCode Extension makes working with freecodecamp-os
in VSCode feature-rich.
Features
Commands
freeCodeCamp: Develop Course
Runs the develop-course
script in the freecodecamp.conf.json
of the current workspace. Also, enables debug-level logging in the terminal by setting NODE_ENV=development
.
Also, with NODE_ENV=development
, your workspace is validated following: https://github.com/freeCodeCamp/freeCodeCampOS/blob/main/.freeCodeCamp/tooling/validate.js
freeCodeCamp: Run Course
Runs the run-course
script in the freecodecamp.conf.json
of the current workspace.
freeCodeCamp: Shutdown Course
Disposes all terminals, and closes the visible text editors.
freeCodeCamp: Open Course
Within a new directory, this command shows the available courses to clone and then clones the selected course in the current workspace.
Lifecycle
The lifecycle of a lesson follows:
- Server parses lesson from curriculum Markdown file
- Server sends client lesson data
- Client renders lesson as HTML
Lesson
Lifecycle
The lifecycle of the testing system follows:
- Server parses test from curriculum Markdown file
- Server evaluates any
--before-all--
ops
- If any
--before-all--
ops fail, an error is printed to the console - If any
--before-all--
ops fail, the tests stop running
- Server evaluates all tests in parallel1
- Server evaluates any
--before-each--
ops- If any
--before-each--
ops fail, test code is not run
- If any
- Server evaluates the test
- Server evaluates any
--after-each--
ops
- Server evaluates any
- Server evaluates any
--after-all--
ops
- If any
--after-all--
ops fail, an error is printed to the console
Tests can be configured to run in order, in a blocking fashion with the blockingTests
configuration option.
Test Utilities
The test utilities are exported/global objects available in the test runner. These are referred to as "helpers", and the included helpers are exported from https://github.com/freeCodeCamp/freeCodeCampOS/blob/main/.freeCodeCamp/tooling/test-utils.js.
Many of the exported functions are convinience wrappers around Nodejs' fs
and child_process
modules. Specifically, they make use of the global ROOT
variable to run the functions relative to the root of the workspace.
controlWrapper
Wraps a function in an interval to retry until it does not throw or times out.
function controlWrapper(
cb: () => any,
{ timeout = 10_000, stepSize = 250 }
): Promise<ReturnType<cb> | null>;
The callback function must throw for the control wrapper to re-try.
const cb = async () => {
const flakyFetch = await fetch('http://localhost:3123');
return flakyFetch.json();
};
const result = await __helpers.controlWrapper(cb);
getBashHistory
Get the .logs/.bash_history.log
file contents
function getBashHistory(): Promise<string>;
const bashHistory = await __helpers.getBashHistory();
getCommandOutput
Returns the output of a command called from the given path relative to the root of the workspace.
function getCommandOutput(
command: string,
path = ''
): Promise<{ stdout: string; stderr: string } | Error>;
const { stdout, stderr } = await __helpers.getCommandOutput('ls');
getCWD
Get the .logs/.cwd.log
file contents
function getCWD(): Promise<string>;
const cwd = await __helpers.getCWD();
getLastCommand
Get the \(n^{th}\) latest line from .logs/.bash_history.log
.
function getLastCommand(n = 0): Promise<string>;
const lastCommand = await __helpers.getLastCommand();
getLastCWD
Get the \(n^{th}\) latest line from .logs/.cwd.log
.
function getLastCWD(n = 0): Promise<string>;
const lastCWD = await __helpers.getLastCWD();
getTemp
Get the .logs/.temp.log
file contents.
function getTemp(): Promise<string>;
const temp = await __helpers.getTemp();
Use the output of the .temp.log
file at your own risk. This file is raw input from the terminal including ANSI escape codes.
Output varies depending on emulator, terminal size, order text is typed, etc. For more info, see https://github.com/freeCodeCamp/solana-curriculum/issues/159
getTerminalOutput
Get the .logs/.terminal_out.log
file contents.
function getTerminalOutput(): Promise<string>;
const terminalOutput = await __helpers.getTerminalOutput();
importSansCache
Import a module side-stepping Nodejs' cache - cache-busting imports.
function importSansCache(path: string): Promise<any>;
const { exportedFile } = await __helpers.importSansCache(
'learn-x-by-building-y/index.js'
);
Globals
None of the globals are within the __helpers
module.
chai
assert
The assert
module: https://www.chaijs.com/api/assert/
expect
The expect
module: https://www.chaijs.com/api/bdd/
config as chaiConfig
The config
module: https://www.chaijs.com/guide/styles/#configuration
AssertionError
This is the AssertionError
class from the assert
module.
logover
The logger used by freecodecamp-os
: https://www.npmjs.com/package/logover
This is mostly useful for debugging, as any logs will be output in the freeCodeCamp terminal.
ROOT
The root of the workspace.
watcher
The Chokidar FSWatcher
instance.
This is useful if you want to stop watching a directory during a test:
const DIRECTORY_PATH_RELATIVE_TO_ROOT = "example";
watcher.unwatch(DIRECTORY_PATH_RELATIVE_TO_ROOT);
// Do something
watcher.add(DIRECTORY_PATH_RELATIVE_TO_ROOT);
Collisions
As the tests are run in the eval
ed context of the freecodecamp-os/.freeCodeCamp/tooling/tests/test-worker.js
module, there is the possibility that variable naming collisions will occur. To avoid this, it is recommended to prefix object names with __
(dunder).
Lifecycle
Resetting can follow one of two lifecycles:
- Whole project reset
- Lesson reset
Whole Project
A whole project reset is only invoked when the Reset
button is clicked in the client.
This will run a git clean
on the project directory - removing all files (tracked and untracked), but resetting them to their last committed state.
Then, the seed of each lesson will be run in order.
Lesson
A lesson reset only happens when either seedEveryLesson
is set to true
in the project config, or the force flag is set on a given lessons seed.
This will only run the seed for the current lesson.
Reset
Plugin System
The plugin system is a way to hook into events during the runtime of the application.
Plugins are defined within the JS file specified by the tooling.plugins
configuration option.
Hooks
onTestsStart
Called when the tests start running, before any --before-
hooks.
onTestsEnd
Called when the tests finish running, after all --after-
hooks.
onProjectStart
Called when the project first loads, before any tests are run, and only happens once per project.
onProjectFinished
Called when the project is finished, after all tests are run and passed, and only happens once per project.
onLessonPassed
Called when a lesson passes, after all tests are run and passed, and only happens once per lesson.
onLessonFailed
Called when a lesson fails, after all tests are run and any fail.
onLessonLoad
Called once when a lesson is loaded, after the onProjectStart
if the first lesson.
Parser
It is possible to define a custom parser for the curriculum files. This is useful when the curriculum files are not in the default format described in the project syntax section.
The first parameter of the parser functions is the project dashed name. This is the same as the dashedName
field in the projects.json
file.
It is up to the parser to read, parse, and return the data in the format expected by the application.
getProjectMeta
(projectDashedName: string) =>
Promise<{
title: string;
description: string;
numberOfLessons: number;
tags: string[];
}>;
The title
, tags
, and description
fields are expected to be either plain strings, or HTML strings which are then rendered in the client.
getLesson
This function can be called multiple times per lesson. Therefore, it is expected to be idempotent.
(projectDashedName: string, lessonNumber: number) =>
Promise<{
meta?: { watch?: string[]; ignore?: string[] };
description: string;
tests: [[string, string]];
hints: string[];
seed: [{ filePath: string; fileSeed: string } | string];
isForce?: boolean;
beforeAll?: string;
afterAll?: string;
beforeEach?: string;
afterEach?: string;
}>;
The meta
field is expected to be an object with either a watch
or ignore
field. The watch
field is expected to be an array of strings, and the ignore
field is expected to be an array of strings.
The description
field is expected to be either a plain string, or an HTML string which is then rendered in the client.
The tests[][0]
field is the test text, and the tests[][1]
field is the test code. The test text is expected to be either a plain string, or an HTML string.
The hints
field is expected to be an array of plain strings, or an array of HTML strings.
The seed[].filePath
field is the relative path to the file from the workspace root. The seed[].fileSeed
field is the file content to be written to the file.
The seed[]
field can also be a plain string, which is then treated as a bash
command to be run in the workspace root.
An example of this can be seen in the default parser used: https://github.com/freeCodeCamp/freeCodeCampOS/blob/main/.freeCodeCamp/plugin/index.js
Example
import { pluginEvents } from "@freecodecamp/freecodecamp-os/.freeCodeCamp/plugin/index.js";
pluginEvents.onTestsStart = async (project, testsState) => {
console.log('onTestsStart');
};
pluginEvents.onTestsEnd = async (project, testsState) => {
console.log('onTestsEnd');
};
pluginEvents.onProjectStart = async project => {
console.log('onProjectStart');
};
pluginEvents.onProjectFinished = async project => {
console.log('onProjectFinished');
};
pluginEvents.onLessonFailed = async project => {
console.log('onLessonFailed');
};
pluginEvents.onLessonPassed = async project => {
console.log('onLessonPassed');
};
Client Injection
With the static
config option, you can add a /script/injectable.js
script to be injected in the head
of the client.
There is a reserved websocket event (__run-client-code
) that can be used to send code from the client to the server to be run in the server's context. The code has access to a few globals:
ROOT
: The root of the coursejoin
: The Node.jspath
module function
This enables scripts like the following to be run:
client/injectable.js
function checkForToken() {
const serverTokenCode = `
try {
const {readFile} = await import('fs/promises');
const tokenFile = await readFile(join(ROOT, 'config/token.txt'));
const token = tokenFile.toString();
console.log(token);
__result = token;
} catch (e) {
console.error(e);
__result = null;
}`;
socket.send(
JSON.stringify({
event: '__run-client-code',
data: serverTokenCode
})
);
}
async function askForToken() {
const modal = document.createElement('dialog');
const p = document.createElement('p');
p.innerText = 'Enter your token';
p.style.color = 'black';
const input = document.createElement('input');
input.type = 'text';
input.id = 'token-input';
input.style.color = 'black';
const button = document.createElement('button');
button.innerText = 'Submit';
button.style.color = 'black';
button.onclick = async () => {
const token = input.value;
const serverTokenCode = `
try {
const {writeFile} = await import('fs/promises');
await writeFile(join(ROOT, 'config/token.txt'), '${token}');
__result = true;
} catch (e) {
console.error(e);
__result = false;
}`;
socket.send(
JSON.stringify({
event: '__run-client-code',
data: serverTokenCode
})
);
modal.close();
};
modal.appendChild(p);
modal.appendChild(input);
modal.appendChild(button);
document.body.appendChild(modal);
modal.showModal();
}
const socket = new WebSocket(
`${window.location.protocol === 'https:' ? 'wss:' : 'ws:'}//${
window.location.host
}`
);
window.onload = function () {
socket.onmessage = function (event) {
const parsedData = JSON.parse(event.data);
if (
parsedData.event === 'RESPONSE' &&
parsedData.data.event === '__run-client-code'
) {
if (parsedData.data.error) {
console.log(parsedData.data.error);
return;
}
const { __result } = parsedData.data;
if (!__result) {
askForToken();
return;
}
window.__token = __result;
}
};
let interval;
interval = setInterval(() => {
if (socket.readyState === 1) {
clearInterval(interval);
checkForToken();
}
}, 1000);
};
Contributing
Local Development
- Open
freeCodeCampOS/self
as a new workspace in VSCode - Run
npm i
- Run
freeCodeCamp: Develop Course
in the command palette
Gitpod
- Open the project in Gitpod:
Opening a Pull Request
- Fork the repository
- Push your changes to your fork
- Open a pull request with the recommended style
Commit Message
<type>(<scope>): <description>
Pull Request Title
<type>(<scope>): <description>
Pull Request Body
Answer the following questions:
- What does this pull request do?
- How should this be manually tested?
- Any background context you want to provide?
- What are the relevant issues?
- Screenshots (if appropriate)
Types
fix
feat
refactor
chore
Scopes
Any top-level directory or config file. Changing a package should have a scope of dep
or deps
.
Documentation
This documention is built using mdBook. Read their documentation to install the latest version.
Also, the documentation uses mdbook-admonish
:
cargo install mdbook-admonish
Serve the Documentation
cd docs
mdbook serve
This will spin up a local server at http://localhost:3000
. Also, this has hot-reloading, so any changes you make will be reflected in the browser.
Build the Documentation
cd docs
mdbook build
CLI (create-freecodecamp-os-app
)
The CLI is written in Rust, and is located in the cli
directory.
Development
$ cd cli
cli$ cargo run
Flight Manual
Release
Releases are done manually through the GitHub Actions.
Making a Release
In the Actions
tab, select the Publish to npm
workflow. Then, select Run workflow
.
Changelog
[3.6.0] -
Add
- Use bash's
script
command to record terminal input and output- Gated behind a feature flag
- Existing
.logs/
files will be deprecated in favour ofscript
command in4.0
[3.5.1] - 2024-03-19
Fix
- Remove
watcher
from context in worker
[3.5.0] - 2024-03-18
Add
meta
togetLesson
meta.watch
andmeta.ignore
to alter watch behaviour when lesson loads
Fix
- Add
/
to end of.git
indefaultPathsToIgnore
to prevent files starting with.git
from being ignored - Trim
description
andtitle
fields from parser
[3.4.1] - 2024-03-11
Fix
- Convert
Error
class to object in worker before sending to parent
[3.4.0] - 2024-03-05
Add
pluginEvents.onLessonLoad
- Loader showing progress for reset step
[3.3.0] - 2024-02-15
Add
tags
togetProjectMeta
[3.2.0] - 2024-02-12
Add
- Parser API for curriculum files
pluginEvents.getProjectMeta
pluginEvents.getLesson
Fix
- Refactor original regex Markdown parser to use
marked.lexer
- This allows for more complex Markdown files to be parsed
- Render Markdown in server
- Pass HTML string to client to render
[3.1.0] - 2024-02-05
Add
client.landing.locale.title
field for landing pageh1
Fix
- Give
.description
element amax-width
of750px
- Add
ws
as dependency
[3.0.0] - 2024-02-01
Change
- Remove
freecodecamp.conf.json
fields controlled byfreecodecamp-courses
extension - Allow hints for integrated projects
- Replace use of
FCC_OS_PORT
withport
field infreecodecamp.conf.json
- Make
version
field required infreecodecamp.conf.json
- Move project title and description to curriculum markdown files
- Rename
.terminal-out.log
to.terminal_out.log
Migration Guide
- Configure the following settings for the freeCodeCamp - Courses extension, and remove them from the
freecodecamp.conf.json
file:
"freecodecamp-courses.autoStart"
"freecodecamp-courses.path"
"freecodecamp-courses.prepare"
"freecodecamp-courses.scripts.develop-course"
"freecodecamp-courses.scripts.run-course"
"freecodecamp-courses.workspace.files"
"freecodecamp-courses.workspace.previews"
"freecodecamp-courses.workspace.terminals"
- Instead of
FCC_OS_PORT
environment variable, useport
field infreecodecamp.conf.json
file - Add a SemVer compliant
version
field tofreecodecamp.conf.json
file - Remove the
title
anddescription
fields in theproject.json
, and add thedescription
to each corresponding Markdown file immediately after thetitle
- Rename the
.terminal-out.log
file to.terminal_out.log
[2.1.0] - 2024-01-23
Add
- Worker threads to run tests in parallel
### --after-each--
to run code after each testCancel Tests
button that terminates all workers- Plugin system for events:
onTestsStart
onTestsEnd
onProjectStart
onLessonPassed
onLessonFailed
onProjectFinished
/script/injectable.js
static file to inject a JS script into the client__run-client-code
websocket event to run code in the server's context- Add
create-freecodecamp-os-app
cli for creating the boilerplate
Update
- dependency @types/node to v18.18.13
- dependency @types/react-dom to v18.2.17
- dependency typescript to v5.3.2
- dependency ts-loader to v9.5.1
- dependency marked-highlight to v2.0.7
- dependency marked to v9.1.6
- babel monorepo to v7.23.3
- dependency @types/prismjs to v1.26.3
- dependency @types/react to v18.2.36
- actions/setup-node digest to 1a4442c (#380)
- dependency chai to v4.3.10
- dependency @types/marked to v5.0.2
[2.0.0 - deprecated] - 2023-09-25
Add
- make
watcher
global process.env.FCC_OS_PORT || 8080
for server listen port- working
hints
Change
- remove
__helpers.makeDirectory
- remove
__helpers.runCommand
- remove
__helpers.writeJsonFile
- remove
__helpers.getDirectory
- remove
__helpers.getFile
- remove
__helpers.getJsonFile
- remove
__helpers.copyDirectory
- remove
__helpers.copyProjectFiles
- remove
__helpers.fileExists
- update
controlWrapper
to match documented API - start lessons at
0
instead of1
- remove landing page topic (
h2
) config.path
is no longer required- Remove
postinstall
script - Tests no longer have
--before-all--
context
Update
- dependency babel-loader to v9.1.2
- dependency marked to v9
- Markedjs had multiple major releases within 2 months
- dependency typescript to v5.0.4
- dependency webpack-cli to v5.1.1
Migration Guide
- Refactor tests to use Nodejs API instead of removed
__helpers
functions. - Change all lesson numbers to be zero-based (start at
0
) - Manually build client before running tooling server (
npm run build:client
)- Suggestion: Add
cd ./node_modules/@freecodecamp/freecodecamp-os/ && npm run build:client
tofreecodecamp.conf.json > prepare
- Suggestion: Add
- Change
--before-all--
into--before-each--
- Probably remove
--after-all--
- No longer use
global
in tests
- Probably remove
[1.10.0] - 2023-08-08
Add
config.client.static
to serve files (e.g. images) in client
[1.9.2] - 2023-06-17
Fix
- remove seeding files from watch during seeding
Update
- react monorepo (#313)
- github actions (#311)
- react monorepo
- dependency @types/node to v18.16.18
[1.9.1] - 2023-05-30
Fix
- fix
1.9.0
introduced bug of hanging tests
Update
- dependency webpack-dev-server to v4.15.0
- dependency @types/react to v18.2.6
- dependency @types/marked to v4.3.0
- react monorepo
- dependency @types/node to v18.16.5
- dependency @babel/core to v7.21.8
- babel monorepo to v7.21.5
- pin dependencies (#241)
[1.9.0] - 2023-05-20
Fix
- adjust build path
- set
$HOME
for Gitpod
Add
- add
blockingTests
flag - add
breakOnFailure
flag
Bugs
- when
blockingTests && breakOnFailure
, proceeding tests appear to hang in client
[1.8.4] - 2023-04-19
Fix
- seed files on lesson (#237)
Update
- dependency webpack-dev-server to v4.13.3
- dependency html-webpack-plugin to v5.5.1
- dependency @types/react to v18.0.35
- dependency @types/node to v18.15.11
- babel monorepo to v7.21.4
[1.8.3] - 2023-03-30
Fix
- adjust import pathing (#225)
Update
- dependency @types/node to v18.15.10
- dependency marked to v4.3.0
- dependency nodemon to v2.0.22
- dependency @types/node to v18.15.9
- dependency @types/node to v18.15.8
- dependency @types/react to v18.0.29
- dependency webpack-dev-server to v4.13.1
- dependency webpack-dev-server to v4.13.0
- dependency style-loader to v3.3.2
- dependency @types/node to v18.15.3
- dependency @babel/core to v7.21.3
- dependency @types/node to v18.15.0
- dependency nodemon to v2.0.21
- dependency @types/node to v18.14.6
[1.8.2] - 2023-03-03
Fix
- parse crlf line endings (#210)
[1.8.1] - 2023-03-02
Fix
- use node_module pathing (#209)
[1.8.0] - 2023-03-02
Change
- do not copy
.freeCodeCamp
into root (#208)
Update
- dependency @types/node to v18.14.2
- babel monorepo to v7.21.0
- dependency @types/node to v18.14.1
- dependency @types/node to v18.14.0
- dependency @types/react-dom to v18.0.11
- dependency @types/react to v18.0.28
- dependency @types/node to v18.13.0
[1.7.3] - 2023-02-08
Fix
- handle completed projects (#197)
- set default terminal (#200)
Update
- dependency @types/node to v18.11.19
- dependency typescript to v4.9.5
[1.7.2] - 2023-01-31
Fix
- apply css to all code blocks (#196)
[1.7.1] - 2023-01-31
Add
- validate curriculum on develop (#182)
Update
- dependency @types/react to v18.0.27
- dependency marked to v4.2.12
- dependency @babel/core to v7.20.12
- dependency @types/node to v18.11.18
- dependency marked to v4.2.5
- dependency @types/react-dom to v18.0.10
- dependency @babel/core to v7.20.7
- dependency @types/node to v18.11.17
- dependency css-loader to v6.7.3
- dependency @types/node to v18.11.16
[1.7.0] - 2023-01-31
Add
- create cache-busting helper (#181)
Update
- dependency @types/node to v18.11.13
- dependency typescript to v4.9.4
- dependency marked to v4.2.4
- dependency @types/node to v18.11.12
[1.6.9] - 2022-12-08
Fix
- pass WebSocket to reset function (#174)
[1.6.8] - 2022-12-07
Change
- remove
.env
file from npm (#173)
[1.6.7] - 2022-12-07
Add
- create mkdir helper function (#172)
[1.6.6] - 2022-12-06
Fix
- handle file parser errors (#166)
Update
- readme docs (#160)
- dependency ts-loader to v9.4.2
- dependency @types/react to v18.0.26
- dependency @types/node to v18.11.10
- dependency @babel/core to v7.20.5
- dependency typescript to v4.9.3
- dependency marked to v4.2.3
- dependency @types/react-dom to v18.0.9
- dependency css-loader to v6.7.2
- dependency chai to v4.3.7
- dependency marked to v4.2.2
- babel monorepo
- dependency @types/react to v18.0.25
- dependency @types/node to v18.11.9
- dependency @types/node to v18.11.8
- dependency @types/node to v18.11.7
- dependency @babel/plugin-syntax-import-assertions to v7.20.0
- dependency @types/react-dom to v18.0.8
- dependency @types/react to v18.0.24
- dependency @babel/core to v7.19.6
- dependency @babel/preset-env to v7.19.4
- dependency express to v4.18.2
- dependency typescript to v4.8.4
- dependency marked to v4.1.1
- babel monorepo to v7.19.3
[1.6.5] - 2022-09-28
Fix
- reset project, prevent lesson under/over -flow (#139)
- patch and enable reset button (#133)
- step skipping buttons (#138)
- docker settings (#137)
Add
- feat: add script for camper info (#132)
Update
- dependency ts-loader to v9.4.1
- dependency webpack-dev-server to v4.11.1
- dependency @types/react to v18.0.21
Change
- updated styles for error page (#131)
[1.6.4] - 2022-09-19
Add
- progress to projects (#121)
Change
- disable reset button (#128)
[1.6.3] - 2022-09-19
Add
- create test-util to get .temp.log file (#127)
Update
- dependency nodemon to v2.0.20
- dependency @types/react to v18.0.20
- babel monorepo to v7.19.1
- pin dependency logover to 2.0.0
[1.6.2] - 2022-09-16
Fix
- fetch project on hot-reload
[1.6.1] - 2022-09-15
Fix
- optional chaining to hot-reload in server (#119)
[1.6.0] - 2022-09-14
Add
- versioning
- hot-ignore (#118)
[1.5.5] - 2022-09-12
Change
- replace spark with fade in (#116)
Update
- dependency webpack-dev-server to v4.11.0
- dependency @types/react to v18.0.19
- babel monorepo to v7.19.0
- dependency typescript to v4.8.3
- dependency @types/react to v18.0.18
- dependency @types/marked to v4.0.7
- pin dependency @types/node to 18.7.15
[1.5.4] - 2022-09-09
Change
- UI revamp (#108)
[1.5.3] - 2022-09-07
Fix
- correctly show/hide files in production mode (#107)
[1.5.2] - 2022-09-07
Fix
- add controlWrapper function (#106)
[1.5.1] - 2022-09-07
Fix
- fix npm deps and installation (#105)
[1.5.0] - 2022-09-06
Add
- before-all and before-each hooks (#82)
- git build script for multiple projects (#58)
- loader config for version 1.1.1 (#44)
Change
- all major changes (#97)
- with small improvements and updates (#54)
- Improved client styling (#42)
Fix
- ignoring of Mac files (#60)
- pinning Ubuntu to version 20.04 (#53)
- updating of dependency marked to v4.0.19 (598e2e7)
- updating of dependency logover to v1.3.5 (1866487)
- updating of dependency logover to v1.3.4 (513d189)
- updating of dependency ws to v8.8.1 (ece46b5)
- updating of dependency marked to v4.0.18 (a13a865)
- updating of dependency logover to v1.3.2 (e1e6b10)
- updating of dependency nodemon to v2.0.19 (459d450)
- updating of dependency marked to v4.0.17 (bce9486)
- updating of dependency ws to v8.8.0 (fc52646)
- updating of dependency ws to v8.7.0 (36fc630)
- updating of dependency ws to v8.6.0 (9ad962a)
- updating of dependency express to v4.18.1 (0b7e21f)
- updating of dependency prismjs to v1.28.0 (29734ff)
- updating of dependency marked to v4.0.14 (17856ed)
- updating of dependency marked to v4.0.13 (e482c37)
- pinning of dependency nodemon to v2.0.15 (73e53e5)
Update
- dependency marked to v4.0.19 (598e2e7)
- dependency @types/react to v18.0.17 (4ca81e9)
- dependency @types/react to v17.0.48 (7e68a76)
- dependency @types/react to v17.0.47 (f419a80)
- dependency @babel/core to v7.18.5 (f94eec6)
- dependency @types/react to v17.0.45 (f44ab1c)
- dependency @types/react-dom to v17.0.17 (014323f)
- dependency @types/react-dom to v17.0.16 (f496e14)
- dependency ts-loader to v9.3.1 (a4bb535)
- dependency nodemon to v2.0.18 (c1540dd)
- dependency typescript to v4.7.4 (a2ba270)
- dependency typescript to v4.7.3 (f5cb880)
- dependency typescript to v4.7.2 (29e5897)
- dependency ts-loader to v9.3.0 (5081424)
- dependency typescript to v4.6.4 (b1e8fe5)
- dependency nodemon to v2.0.16 (68b6da7)
- dependency @types/react-dom to v17.0.16 (f496e14)
- dependency webpack-cli to v4.10.0 (85e4326)
- dependency webpack-dev-server to v4.10.0 (b36bd79)
Roadmap
For the most part, this roadmap outlines todos for freecodecamp-os
. If this roadmap is empty, then there are no todos 🎉
Documentation
Features
- Loader to show progress of "Reset Step"
- Crowdin translation integration