diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json
new file mode 100644
index 0000000..bbc8644
--- /dev/null
+++ b/.devcontainer/devcontainer.json
@@ -0,0 +1,25 @@
+// For format details, see https://aka.ms/devcontainer.json. For config options, see the
+// README at: https://github.com/devcontainers/templates/tree/main/src/typescript-node
+{
+ "name": "Node.js & TypeScript",
+ // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
+ "image": "mcr.microsoft.com/devcontainers/typescript-node:0-20",
+ "features": {
+ "ghcr.io/devcontainers/features/python:1": {}
+ }
+
+ // Features to add to the dev container. More info: https://containers.dev/features.
+ // "features": {},
+
+ // Use 'forwardPorts' to make a list of ports inside the container available locally.
+ // "forwardPorts": [],
+
+ // Use 'postCreateCommand' to run commands after the container is created.
+ // "postCreateCommand": "yarn install",
+
+ // Configure tool-specific properties.
+ // "customizations": {},
+
+ // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
+ // "remoteUser": "root"
+}
diff --git a/.github/workflows/docker.yaml b/.github/workflows/docker.yaml
new file mode 100644
index 0000000..9d086e9
--- /dev/null
+++ b/.github/workflows/docker.yaml
@@ -0,0 +1,29 @@
+name: Docker
+
+on:
+ push:
+ branches:
+ - main
+ tags:
+ - v*
+
+jobs:
+ build-and-push:
+ name: Deploy Docker Image
+ runs-on: ubuntu-22.04
+ steps:
+ - uses: actions/checkout@v3.4.0
+ - name: Build and push
+ uses: openzim/docker-publish-action@v10
+ with:
+ image-name: openzim/freecodecamp
+ on-master: dev
+ tag-pattern: /^v([0-9.]+)$/
+ latest-on-tag: true
+ restrict-to: openzim/freecodecamp
+ registries: ghcr.io
+ credentials:
+ GHCRIO_USERNAME=${{ secrets.GHCR_USERNAME }}
+ GHCRIO_TOKEN=${{ secrets.GHCR_TOKEN }}
+ repo_description: auto
+ repo_overview: auto
\ No newline at end of file
diff --git a/.github/workflows/integration.yaml b/.github/workflows/integration.yaml
new file mode 100644
index 0000000..ddfcfe0
--- /dev/null
+++ b/.github/workflows/integration.yaml
@@ -0,0 +1,39 @@
+name: Build and test the freeCodeCamp zim file
+
+on:
+ pull_request:
+ push:
+ branches:
+ - 'main'
+jobs:
+ integration-test:
+ runs-on: ubuntu-latest
+ name: Build latest zim file
+ steps:
+ - uses: actions/checkout@v3
+ - uses: actions/setup-node@v3
+ with:
+ node-version: 20
+ cache: 'yarn'
+ cache-dependency-path: '**/yarn.lock'
+ - run: yarn install --frozen-lockfile
+ working-directory: client
+ - run: yarn lint
+ working-directory: client
+ - run: yarn build
+ working-directory: client
+
+ - uses: actions/setup-python@v4
+ with:
+ python-version: '3.11'
+ cache: 'pip' # caching pip dependencies
+
+ - run: make setup
+ - run: make fetch
+ - run: make prebuild
+
+ # Full integration test using the fetch/prebuild markdown as a fixture
+ - run: yarn test --run
+ working-directory: client
+
+ - run: make zim
\ No newline at end of file
diff --git a/.github/workflows/qa.yaml b/.github/workflows/qa.yaml
new file mode 100644
index 0000000..0c54d7d
--- /dev/null
+++ b/.github/workflows/qa.yaml
@@ -0,0 +1,39 @@
+name: QA
+
+on:
+ push:
+ branches: main
+ pull_request:
+
+env:
+ MAX_LINE_LENGTH: 88
+
+jobs:
+ check-qa:
+ runs-on: ubuntu-20.04
+ steps:
+ - uses: actions/checkout@v2
+ - uses: actions/setup-python@v4
+ with:
+ python-version: '3.11'
+ cache: 'pip' # caching pip dependencies
+
+
+ - name: Install lint requirements
+ run: |
+ pip install -r openzim/lint_requirements.txt
+
+ - name: Check black formatting
+ run: |
+ black --version
+ black --check .
+
+ - name: Check flake8 linting
+ run: |
+ flake8 --version
+ flake8 . --count --max-line-length=$MAX_LINE_LENGTH --statistics
+
+ - name: Check import order with isort
+ run: |
+ isort --version
+ isort --profile black --check .
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index b6e4761..52bb2fe 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,129 +1,18 @@
-# Byte-compiled / optimized / DLL files
-__pycache__/
-*.py[cod]
-*$py.class
-
-# C extensions
-*.so
-
-# Distribution / packaging
-.Python
-build/
-develop-eggs/
-dist/
-downloads/
-eggs/
-.eggs/
-lib/
-lib64/
-parts/
-sdist/
-var/
-wheels/
-pip-wheel-metadata/
-share/python-wheels/
-*.egg-info/
-.installed.cfg
-*.egg
-MANIFEST
-
-# PyInstaller
-# Usually these files are written by a python script from a template
-# before PyInstaller builds the exe, so as to inject date/other infos into it.
-*.manifest
-*.spec
-
-# Installer logs
-pip-log.txt
-pip-delete-this-directory.txt
-
-# Unit test / coverage reports
-htmlcov/
-.tox/
-.nox/
-.coverage
-.coverage.*
-.cache
-nosetests.xml
-coverage.xml
-*.cover
-*.py,cover
-.hypothesis/
-.pytest_cache/
-
-# Translations
-*.mo
-*.pot
-
-# Django stuff:
+# Logs
+logs
*.log
-local_settings.py
-db.sqlite3
-db.sqlite3-journal
-
-# Flask stuff:
-instance/
-.webassets-cache
-
-# Scrapy stuff:
-.scrapy
-
-# Sphinx documentation
-docs/_build/
-
-# PyBuilder
-target/
-
-# Jupyter Notebook
-.ipynb_checkpoints
-
-# IPython
-profile_default/
-ipython_config.py
-
-# pyenv
-.python-version
-
-# pipenv
-# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
-# However, in case of collaboration, if having platform-specific dependencies or dependencies
-# having no cross-platform support, pipenv may install dependencies that don't work, or not
-# install all needed dependencies.
-#Pipfile.lock
-
-# PEP 582; used by e.g. github.com/David-OConnor/pyflow
-__pypackages__/
-
-# Celery stuff
-celerybeat-schedule
-celerybeat.pid
-
-# SageMath parsed files
-*.sage.py
-
-# Environments
-.env
-.venv
-env/
-venv/
-ENV/
-env.bak/
-venv.bak/
-
-# Spyder project settings
-.spyderproject
-.spyproject
-
-# Rope project settings
-.ropeproject
-
-# mkdocs documentation
-/site
-
-# mypy
-.mypy_cache/
-.dmypy.json
-dmypy.json
-# Pyre type checker
-.pyre/
+# Editor directories and files
+.vscode/*
+!.vscode/extensions.json
+.idea
+.DS_Store
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?
+
+tmp
+build
+*.zim
\ No newline at end of file
diff --git a/.vscode/extensions.json b/.vscode/extensions.json
new file mode 100644
index 0000000..c0a6e5a
--- /dev/null
+++ b/.vscode/extensions.json
@@ -0,0 +1,3 @@
+{
+ "recommendations": ["Vue.volar", "Vue.vscode-typescript-vue-plugin"]
+}
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..dfb1704
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,19 @@
+FROM mcr.microsoft.com/devcontainers/typescript-node:20 as client
+
+WORKDIR /src
+COPY client /src
+RUN yarn install --frozen-lockfile
+RUN yarn build
+
+
+FROM python:3.11-buster
+
+WORKDIR /src
+COPY openzim/requirements.txt /src
+RUN pip install -r requirements.txt --no-cache-dir
+
+COPY openzim /src
+COPY --from=client /src /src/client
+
+
+ENTRYPOINT ["python3", "fcc2zim"]
\ No newline at end of file
diff --git a/LICENSE b/LICENSE
index f288702..94a9ed0 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,7 +1,7 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
- Copyright (C) 2007 Free Software Foundation, Inc.
+ Copyright (C) 2007 Free Software Foundation, Inc.
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
@@ -645,7 +645,7 @@ the "copyright" line and a pointer to where the full notice is found.
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
- along with this program. If not, see .
+ along with this program. If not, see .
Also add information on how to contact you by electronic and paper mail.
@@ -664,11 +664,11 @@ might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
-.
+.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
-.
+.
diff --git a/LICENSE.fcc.md b/LICENSE.fcc.md
new file mode 100644
index 0000000..0e3a1c7
--- /dev/null
+++ b/LICENSE.fcc.md
@@ -0,0 +1,13 @@
+BSD 3-Clause License
+
+Copyright (c) 2023, freeCodeCamp. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+
+Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
+
+Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
+
+Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..873b19d
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,70 @@
+SHELL=/bin/bash
+
+
+COURSES = regular-expressions \
+ basic-javascript \
+ basic-data-structures \
+ debugging \
+ functional-programming \
+ object-oriented-programming \
+ basic-algorithm-scripting \
+ intermediate-algorithm-scripting \
+ javascript-algorithms-and-data-structures-projects
+
+COURSE_CSV=$(shell sed -r 's/[[:space:]]/,/g' <<< "${COURSES}")
+
+TITLE="freeCodeCamp Javascript"
+NAME="fcc_en_javascript"
+DESCRIPTION="FCC Javascript Courses"
+LANG=eng
+CLIENTDIR=./client/dist
+TMPDIR=./tmp
+OUTPATH=./build/${LANG}.zim
+MAX_LINE_LENGTH = 88
+
+.PHONY: all setup clean client build
+
+clean:
+ rm -rf client/dist/fcc
+ rm -rf client/public/fcc
+ rm -rf ${TMPDIR}/curriculum
+ rm -rf build
+
+setup:
+ cd openzim && \
+ pip install -r requirements.txt \
+ pip install -r lint_requirements.txt
+
+lint:
+ cd openzim
+ black .
+ flake8 . --count --max-line-length=${MAX_LINE_LENGTH} --statistics
+ isort --profile black .
+
+fetch:
+ python3 openzim/fcc2zim fetch --tmpdir=${TMPDIR}
+
+prebuild:
+ python3 openzim/fcc2zim prebuild --course=${COURSE_CSV} --outdir=./client/dist/fcc --language ${LANG} --tmpdir=${TMPDIR}
+
+zim:
+ python3 openzim/fcc2zim zim --clientdir ${CLIENTDIR} --outzim ${OUTPATH} \
+ --language ${LANG} --name ${NAME} --title ${TITLE} --description ${DESCRIPTION}
+
+all: clean fetch prebuild zim
+
+build:
+ python3 openzim/fcc2zim all --clientdir ${CLIENTDIR} --outdir=./client/dist/fcc --outzim ${OUTPATH} \
+ --language ${LANG} --tmpdir=${TMPDIR} --course=${COURSE_CSV} \
+ --name ${NAME} --title ${TITLE} --description ${DESCRIPTION}
+
+docker_build:
+ docker build . -t openzim/fcc2zim
+
+docker_run:
+ docker run --rm -it -v $(PWD)/tmp:/tmp/fcc2zim openzim/fcc2zim all --clientdir ${CLIENTDIR} --outdir=./client/dist/fcc --outzim ${OUTPATH} \
+ --language ${LANG} --tmpdir=/tmp/fcc2zim --course=${COURSE_CSV} \
+ --name ${NAME} --title ${TITLE} --description ${DESCRIPTION}
+
+docker_debug:
+ docker run --rm -it --entrypoint=/bin/bash openzim/fcc2zim
\ No newline at end of file
diff --git a/README.md b/README.md
index 90d294b..c9ca34a 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,40 @@
-# freecodecamp
-FreeCodeCamp.org scraper (to ZIM)
+# FCC on Zim
+
+This project consists of two major components:
+
+- Openzim - The scripts (python) that fetch the latest FCC curriculum and package it into a format client can read, as well as our zim builder
+- Client - A vite app configured to be consumed by a Zim reader.
+
+## freeCodeCamp Zim build process
+
+This process can be broken down into 5 parts
+
+1. Build the Vite client
+1. Fetch the latest curriculum from freeCodeCamp by downloading the latest release source archive
+1. "Prebuild" the curriculum for a selected langauge and set of courses. Copy to the client directory
+1. Build a Zim file of the resulting Vite application.
+
+## Development
+
+#### Prerequsites
+
+- Node 20.x
+- Python 3
+
+This project comes with .devcontainer to help onboard new developers, with Node 20 and Python3 installed
+
+See: [`Makefile`](Makefile) for a full build process
+
+## Building with Docker
+
+- `docker build -t openzim/fcc2zim .`
+- `docker run --rm -it -v /workspaces/openzim-freecodecamp/tmp:/tmp/fcc2zim openzim/fcc2zim all \
+ --clientdir ./client/dist --outdir=./client/dist/fcc --outzim ./build/eng.zim \
+ --language eng --tmpdir=/tmp/fcc2zim \
+ --course=regular-expressions,basic-javascript,basic-data-structures,debugging,functional-programming,object-oriented-programming,basic-algorithm-scripting,intermediate-algorithm-scripting,javascript-algorithms-and-data-structures-projects \
+ --name "fcc_en_javascript" --title "freeCodeCamp Javascript" --description "FCC Javascript Courses"
+`
+
+# License
+
+This repository is licensed under GPLv3, with the exception of the freeCodeCamp curriculum which is licensed under BSD 3 Clause (see LICENSE.fcc.md).
diff --git a/client/.dockerignore b/client/.dockerignore
new file mode 100644
index 0000000..0973bc0
--- /dev/null
+++ b/client/.dockerignore
@@ -0,0 +1,3 @@
+node_modules
+dist
+*.log
\ No newline at end of file
diff --git a/client/.eslintignore b/client/.eslintignore
new file mode 100644
index 0000000..e69de29
diff --git a/client/.eslintrc.cjs b/client/.eslintrc.cjs
new file mode 100644
index 0000000..88c5018
--- /dev/null
+++ b/client/.eslintrc.cjs
@@ -0,0 +1,32 @@
+module.exports = {
+ root: true,
+ parser: 'vue-eslint-parser',
+ parserOptions: {
+ sourceType: 'module',
+ ecmaVersion: 'latest',
+ parser: '@typescript-eslint/parser',
+ },
+ extends: [
+ 'eslint:recommended',
+ // '@vue/eslint-config-standard',
+ // '@vue/eslint-config-prettier/skip-formatting',
+ 'plugin:vue/vue3-recommended',
+ // '@vue/typescript/recommended',
+ '@vue/eslint-config-typescript/recommended',
+ 'prettier',
+ ],
+ rules: {
+ '@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
+ 'no-console': 'warn',
+ 'no-debugger': 'error',
+ 'vue/multi-word-component-names': 'warn',
+ 'vue/no-reserved-component-names': 'warn',
+ 'vue/require-explicit-emits': 'warn',
+ },
+ overrides: [
+ {
+ files: ['*.cjs'],
+ env: { node: true },
+ },
+ ],
+}
diff --git a/client/.gitignore b/client/.gitignore
new file mode 100644
index 0000000..7f05860
--- /dev/null
+++ b/client/.gitignore
@@ -0,0 +1,13 @@
+node_modules
+
+public/fcc
+dist
+dist-ssr
+*.l
+*.tsbuildinfo
+
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
\ No newline at end of file
diff --git a/client/.prettierignore b/client/.prettierignore
new file mode 100644
index 0000000..6cce6a2
--- /dev/null
+++ b/client/.prettierignore
@@ -0,0 +1,5 @@
+src/assets
+dist
+public
+node_modules
+src/**/*.md
\ No newline at end of file
diff --git a/client/.prettierrc.json b/client/.prettierrc.json
new file mode 100644
index 0000000..b2095be
--- /dev/null
+++ b/client/.prettierrc.json
@@ -0,0 +1,4 @@
+{
+ "semi": false,
+ "singleQuote": true
+}
diff --git a/client/index.html b/client/index.html
new file mode 100644
index 0000000..50f44d1
--- /dev/null
+++ b/client/index.html
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+ freeCodeCamp on Kiwix
+
+
+
+
+
+
diff --git a/client/package.json b/client/package.json
new file mode 100644
index 0000000..1edc259
--- /dev/null
+++ b/client/package.json
@@ -0,0 +1,50 @@
+{
+ "name": "client",
+ "private": true,
+ "version": "0.0.0",
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "vue-tsc && vite build",
+ "preview": "vite preview",
+ "test": "vitest",
+ "lint": "eslint --ignore-path .eslintignore --ext .js,.ts,.vue ./src",
+ "format": "prettier . --write"
+ },
+ "dependencies": {
+ "@codemirror/lang-javascript": "^6.1.6",
+ "chai": "^4.3.7",
+ "codemirror": "^6.0.1",
+ "pinia": "^2.0.34",
+ "vue": "^3.2.47",
+ "vue-codemirror": "^6.1.1",
+ "vue-router": "4"
+ },
+ "devDependencies": {
+ "@types/glob": "^8.1.0",
+ "@types/marked": "^4.0.8",
+ "@typescript-eslint/eslint-plugin": "^5.58.0",
+ "@typescript-eslint/parser": "^5.58.0",
+ "@vitejs/plugin-vue": "^4.1.0",
+ "@vue/eslint-config-prettier": "^7.1.0",
+ "@vue/eslint-config-standard": "^8.0.1",
+ "@vue/eslint-config-typescript": "^11.0.2",
+ "autoprefixer": "^10.4.14",
+ "eslint": "^8.38.0",
+ "eslint-config-prettier": "^8.8.0",
+ "eslint-plugin-html": "^7.1.0",
+ "eslint-plugin-prettier": "^4.2.1",
+ "eslint-plugin-vue": "^9.10.0",
+ "glob": "^10.2.1",
+ "marked": "^4.3.0",
+ "postcss": "^8.4.23",
+ "prettier": "^2.8.7",
+ "tailwindcss": "^3.3.1",
+ "ts-node": "^10.9.1",
+ "typescript": "^4.9.3",
+ "vite": "^4.2.0",
+ "vitest": "^0.30.1",
+ "vue-eslint-parser": "^9.1.1",
+ "vue-tsc": "^1.2.0"
+ }
+}
diff --git a/client/postcss.config.js b/client/postcss.config.js
new file mode 100644
index 0000000..2e7af2b
--- /dev/null
+++ b/client/postcss.config.js
@@ -0,0 +1,6 @@
+export default {
+ plugins: {
+ tailwindcss: {},
+ autoprefixer: {},
+ },
+}
diff --git a/client/public/free-code-camp-logo.svg b/client/public/free-code-camp-logo.svg
new file mode 100644
index 0000000..be2ec54
--- /dev/null
+++ b/client/public/free-code-camp-logo.svg
@@ -0,0 +1,13 @@
+
+
\ No newline at end of file
diff --git a/client/public/vite.svg b/client/public/vite.svg
new file mode 100644
index 0000000..e7b8dfb
--- /dev/null
+++ b/client/public/vite.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/client/src/App.vue b/client/src/App.vue
new file mode 100644
index 0000000..f9cf455
--- /dev/null
+++ b/client/src/App.vue
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
diff --git a/client/src/assets/vue.svg b/client/src/assets/vue.svg
new file mode 100644
index 0000000..770e9d3
--- /dev/null
+++ b/client/src/assets/vue.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/client/src/components/challenge/ChallengeInstructions.vue b/client/src/components/challenge/ChallengeInstructions.vue
new file mode 100644
index 0000000..183acf4
--- /dev/null
+++ b/client/src/components/challenge/ChallengeInstructions.vue
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
diff --git a/client/src/main.ts b/client/src/main.ts
new file mode 100644
index 0000000..31d098a
--- /dev/null
+++ b/client/src/main.ts
@@ -0,0 +1,20 @@
+import { createApp } from 'vue'
+import { createRouter, createWebHashHistory } from 'vue-router'
+import './style.css'
+import App from './App.vue'
+import { routes } from './routes'
+
+const app = createApp(App)
+
+// 3. Create the router instance and pass the `routes` option
+// You can pass in additional options here, but let's
+// keep it simple for now.
+const router = createRouter({
+ // 4. Provide the history implementation to use. We are using the hash history for simplicity here.
+ history: createWebHashHistory(),
+ routes, // short for `routes: routes`
+})
+
+app.use(router)
+
+app.mount('#app')
diff --git a/client/src/pages/ChallengePage.vue b/client/src/pages/ChallengePage.vue
new file mode 100644
index 0000000..b51c696
--- /dev/null
+++ b/client/src/pages/ChallengePage.vue
@@ -0,0 +1,119 @@
+
+
+
+