The Source Academy (https://sourceacademy.org/) is an immersive online experiential environment for learning programming. It is developed by a community of learners (also called "Source Academy") who use the book Structure and Interpretation of Computer Programs, JavaScript Adaptation (SICP JS). This repository houses the sources for the frontend of the Source Academy, written in ReactJS with Redux.
- Playground to write and test programs
- Built-in stepper, data visualizer, and environment visualizer to debug your programs
- Assessments (Missions/Quests/Contests) to solve challenging problems while learning about programming fundamentals
- Sessions for collaborative programming
- Integration with Google Drive and Github
- Graphic-novel-style game to provide context for themed assessments
- Grading System to grade and comment on submitted assessments
Installation of Source Academy
-
Install the version of Node.js as specified in the
.node-versionfile -
Clone this repository and navigate to it using your command line
-
Install the version of
yarnas specified inpackage.json,packageManager.We recommend using
corepackto manage the version ofyarn, you may simply runcorepack enableto complete this step. -
Run
yarn installto install dependencies.- If you are on Ubuntu and encounter the error message:
No such file or directory: 'install', you might be running the incorrect "yarn" from the cmdtest testing suite instead of the JavaScript package manager of the same name. Refer to this StackOverflow post. - If you are on the new M1 or M2 Mac chips, and encounter an error while installing
canvas, refer to this documentation to install the requisite dependencies first.
- If you are on Ubuntu and encounter the error message:
-
Run
yarn run startto start the server atlocalhost:8000. It might take a couple of minutes for the server to start. -
Point your browser to
http://localhost:8000to see your local Source Academy.
In this edition, you will only see the Playground with all its tools, but no login options or homework submission features.
If you wish to set up the GitHub or Google Drive integrations, copy the .env.example file as .env and refer to Setting up your environment below for the relevant configuration options.
To work with
the Python edition of Source Academy, you need to turn on the Conductor feature flag: Click on the top right down-arrow button, then
Settings, then Feature Flags. Toggle conductor.enable to true.
directory.language.url
https://source-academy.github.io/language-directory/directory.json
directory.plugin.url
https://source-academy.github.io/plugin-directory/directory.json
Installation of Source Academy @ NUS
- Repeat steps 1-4 above
- Copy the
.env.examplefile as.envand set the necessary variables (refer below for more information) - Run
yarn run startto start the server atlocalhost:8000
Note: It might take a couple of minutes for the server to start the first time.
The project requires some environment variables to be set to work properly. In the .env file a few things need to be set up:
REACT_APP_BACKEND_URL: The base URL of the backend. If you are testing with a local backend, the value in.env.examplematches the default development configuration of the backend.REACT_APP_USE_BACKEND: Set to false if not running together with the backend.REACT_APP_MODULE_BACKEND_URL: The base URL from which Source modules are loaded. (This is a js-slang feature, but of course it has to be configured here.) You can just use the default value in development.REACT_APP_SHAREDB_BACKEND_URL: The base URL of the ShareDB collaborative editor backend. The protocol must be HTTP or HTTPS (it will automatically be set to WS/WSS as appropriate). Must end in a trailing/.REACT_APP_SICPJS_BACKEND_URL: The base URL from which SICP JS content is loaded.
If REACT_APP_USE_BACKEND is TRUE and no backend is reachable at REACT_APP_BACKEND_URL (http://localhost:4000 by default), the frontend shows a "Source Academy is under maintenance" page instead of logging in. To run a local backend:
-
Install asdf and add the Erlang and Elixir plugins:
asdf plugin add erlang asdf plugin add elixir
-
Clone the backend and install the toolchain versions pinned in its
.tool-versions:git clone https://github.com/source-academy/backend.git cd backend asdf install -
Start a local PostgreSQL instance (only needs to be done once; on subsequent runs use
docker start sa-backend-dbinstead):docker run --name sa-backend-db -e POSTGRES_HOST_AUTH_METHOD=trust -p 5432:5432 -d postgres
-
Set up the development secrets and install dependencies:
cp config/dev.secrets.exs.example config/dev.secrets.exs mix deps.get
-
Initialize and seed the development database:
mix ecto.setup
-
Run the server:
mix phx.server
The backend is now reachable at http://localhost:4000 (Swagger docs at http://localhost:4000/swagger), and the frontend's .env.example test OAuth providers (test_admin, test_staff, test_student) will log you in against the accounts seeded above. See the backend's own README for feature-specific setup (e.g. MQTT for Sling) beyond this base configuration.
Unless you need to use the shortener locally, you can leave these values blank. Otherwise, ask your backend engineer.
REACT_APP_URL_SHORTENER_SIGNATURE: The API key for the YOURLS URL shortener.REACT_APP_URL_SHORTENER_BASE: The base URL of the YOURLS URL shortener. Unless you need to use the shortener locally, you can leave this blank. Otherwise, ask your backend engineer.
If you are testing with a local backend, the values in .env.example match the default development configuration of the backend. Otherwise, your backend engineer should provide you with the configuration for the staging and/or production backend.
n is an integer starting from 1. The numbers must be consecutive i.e. if you have 5 authentication providers, the numbers must be 1, 2, 3, 4, and 5.
REACT_APP_OAUTH2_PROVIDERn: The provider ID of the nth authentication provider. This must match the backend configuration.REACT_APP_OAUTH2_PROVIDERn_NAME: The name of the nth authentication provider shown on the login screen.REACT_APP_OAUTH2_PROVIDERn_ENDPOINT: The authentication endpoint of the nth authentication provider.
The following properties are used for the Playground Google Drive integration. You can leave them blank if you are not using or testing that feature locally.
REACT_APP_GOOGLE_CLIENT_ID: The OAuth2 client ID issued by Google.REACT_APP_GOOGLE_API_KEY: The Picker API key issued by Google.REACT_APP_GOOGLE_APP_ID: The project ID of the Google API project.
See here a guide on obtaining the above values from the Google API Console.
REACT_APP_DEPLOYMENT_NAME: The name of the Source Academy deployment. This will be shown in the/welcomeroute. Defaults to 'Source Academy'.REACT_APP_PLAYGROUND_ONLY: Whether to build the "playground-only" version, which disables the Academy components, so only the Playground is available. This is what we deploy onto GitHub Pages.REACT_APP_SHOW_RESEARCH_PROMPT: Whether to show the educational research consent prompt to users. This is mainly for instructors using their own deployment of Source Academy @ NUS to disable this prompt.
For more info on specific frontend projects, please consult our wiki.
There are a few additional environment variables that are used when building and deploying for production.
REACT_APP_VERSION: A version string shown in the console on app launch.REACT_APP_ENVIRONMENT: An environment string. Currently it is only used to differentiate different deploys in Sentry.REACT_APP_SENTRY_DSN: The Sentry DSN for error monitoring.REACT_APP_SW_EXCLUDE_REGEXES: A JSON array of regexes as strings. The service worker will ignore paths matching any of these regexes. This is used in our GitHub Pages deploy so that it does not conflict with the subsites we host on GitHub Pages.REACT_APP_CADET_LOGGER: Log server URL. To test with cadet-logger on localhost, set it tohttp://localhost:8001/assessment-logger.REACT_APP_CADET_LOGGER_INTERVAL: The interval (in ms) that the frontend should upload logs.
This project uses Knip to detect dead code — unused files, unused exports, unused (dev)dependencies, and dependencies that are imported but not declared in package.json ("unlisted aka phantom dependencies"). Knip's configuration lives in knip.json at the repo root.
- Unused files — source files with no importers anywhere in the project. Knip reports the file path; you can usually delete the file outright.
- Unused exports — functions, classes, and types exported but never imported. Knip reports the symbol and the source file:line where it's declared. The default is to remove the
exportkeyword; only delete the declaration itself if you also remove the (now-private) call sites. - Unused (dev)dependencies — entries in
dependenciesordevDependenciesthat no source file, build config, or test setup imports. Safe to delete. - Unlisted dependencies — packages that source files import by name but that are not declared in
package.json(they survive only as transitive deps). Knip calls these out so that we depend on what we directly use; relying on a transitive dep can break once the upstream changes its own dependency graph.
Run Knip as part of your normal workflow whenever you are about to remove a file, an export, or a dependency, or whenever you add a new one. It is also useful as a periodic check before opening a PR — leftover dead code accumulates quickly in a large codebase.
All Knip commands are exposed as yarn scripts:
-
yarn knip— run the default reporter. Knip exits non-zero if it finds anything, so this is safe to wire into CI. The output is grouped underUnused files,Unused exports,Unused exported types,Unused dependencies,Unused devDependencies,Unlisted dependencies, andDuplicate exports. -
yarn knip:fix— apply Knip's safe auto-fixes (for example, removing theexportkeyword from a now-unused export). This never deletes files or dependencies; review the diff afterwards. Strongly recommended to stash/commit your changes before running this, so you can easily revert if you disagree with the auto-fix.
When Knip reports something:
- Read the source for the flagged symbol to confirm Knip's static analysis is right. Knip cannot see dynamic imports, runtime reflection, side-effect-only feature flags, or entries in IDE-side indexers.
- Grep the symbol across
src/,scripts/,public/, the build configs (rsbuild.config.ts,vitest.config.ts,eslint.config.js), andtsconfig.jsonbefore treating a finding as a true positive. - For
Unlisted dependencies: if the package is genuinely imported, add it topackage.json(runtime imports go independencies; type-only imports indevDependencies) and re-runyarn install. If the import is no longer needed, remove the import line. - For
Unused (dev)dependencies: remove the entry frompackage.jsonand re-runyarn install. Don't forget to grep first — some packages are referenced by name in build configs and never imported as ES modules. - For
Unused files: delete the file. The commit message should call out the deletion explicitly so it is easy to revert. - For
Unused exports: usually the source file is still in use; just delete theexportkeyword. Reserve whole-symbol deletions for clearly internal helpers. - When Knip is wrong: if a flagged symbol is genuinely used (for example, referenced by reflection, by a feature-flag name, or by an externally-loaded Conductor plugin), use
ignoreIssuesin knip.json or JSDoc tags (like @public). If a dependency is flagged as unused but is actually needed (for example, referenced by/// <reference types="..." />directives in saga files), add the package toignoreDependenciesin knip.json with a short comment explaining why. Keep these lists minimal — each entry is a promise that the next reader doesn't have to re-verify.
The repo's knip.json has three pieces:
entry— the static entry points Knip starts analysis from. We listsrc/index.tsx(the React root),src/service-worker.ts(the PWA worker, referenced asswSrcinrsbuild.config.ts), andsrc/sw/precache.ts(imported by the service worker). Saga roots are reached transitively throughsrc/pages/createStore.ts, whichindex.tsximports.ignore— files that Knip should not try to analyse, e.g. test files, test mocks undersrc/commons/mocks/, etc.ignoreDependencies— packages that are reported by Knip as unused but that we have manually verified are genuinely used (transitive-only consumers, build-pipeline injections, CSS-only@importpaths, alias references invitest.config.ts, etc.). Add a new entry whenever Knip produces a false positive; remove an entry whenever the referenced package is finally deleted.
If you add a new entry point (e.g. a new top-level script or worker), update entry. If you add a new kind of ignored artefact, update ignore.
All sources in this repository are licensed under the Apache License Version 2.