OpenFrontIO: npm run inst, a seven-day rule on dependencies, and placeholder keys in package.json
Project brief: Online browser-based RTS game. Players compete to expand their territory, build structures, and form strategic alliances in various maps based on real-world geography.
At a glance
- What is it?
- A browser real-time strategy game written in TypeScript, forked from WarFront.io. Its install path forbids npm install, refuses dependencies published less than seven days ago, and its development server scripts carry hardcoded placeholder credentials and API domains.
- Who is it for?
- Treat this as a source project rather than something to install from a store: AGPL-3.0 covers the code, a separate file covers assets, and the notices in the footer and loading screen have to survive in a modified build.
- Can I use it commercially?
- Yes, with strict conditions. AGPL-3.0 is a network copyleft licence: if people use a modified version over a network, for example as a hosted service, you must offer them its source code under the same licence.
- Is it still maintained?
- Yes. The repository last received commits 1 day ago.
- What is it written in?
- Mainly TypeScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on November 14, 2026, and from our analysis. They are not legal advice.
Star history
- openfrontio/OpenFrontIO2,904 ★ · +460
| Repository | 2026-07-30 | 2026-11-14 |
|---|---|---|
| openfrontio/OpenFrontIO | 2.4k | 2.9k |
460 Stars in the last 107 days, measured from the daily GitHub snapshot. Compare with other projects
Editorial analysis
npm install is forbidden here and npm run inst is the supported path
The installation section is unusually strict about one command. It says not to use npm install and not to use npm i for project dependencies, and to run this instead:
npm run instThat script exists because it runs the safer npm ci --ignore-scripts, which installs exactly the versions recorded in package-lock.json and skips lifecycle scripts. Plain npm install would resolve versions afresh and run whatever scripts a dependency ships, so the project routes around both behaviours with one alias. There is a prerequisite step too, because Node.js may bundle an older npm:
npm install --global --ignore-scripts [email protected]Then the clone, which is the only version control step in the whole guide:
git clone https://github.com/openfrontio/OpenFrontIO.git
cd OpenFrontIOWorth flagging to anyone skimming: the command used to upgrade npm globally is itself an npm install, and it carries the same --ignore-scripts guard the project insists on elsewhere. The lockfile is therefore load bearing in two ways at once, as the pin list and as the thing that makes an auditable install possible.
Dependencies younger than seven days are turned away
The policy behind the strict install goes further than lockfile pinning. The repository rejects dependency releases less than seven days old, and rejects dependencies sourced from Git, from remote URLs, from local tarballs or from directories. The instruction is to wait until a release passes the seven-day window before updating, and security exceptions require explicit maintainer review. That is a supply-chain rule with teeth: a hotfix published this morning cannot be pulled in by editing a version range. Two files carry it. The .npmrc at the root is what lets npm enforce such a rule at install time, and the Dockerfile repeats the discipline by running npm ci --ignore-scripts in every stage and setting HUSKY=0 so git hooks stay out of the image build. The engines range in package.json pins the toolchain as well, node at 24.15.0 or newer inside the 24 line and npm at 12.1.0 or newer inside the 12 line, which is a narrower window than the prose prerequisite alone suggests, since both ranges also declare an upper bound and exclude the next major.
Development mode ships placeholder credentials inside package.json
The dev server script carries its whole environment inline, which makes the security surface of local development readable in one line. start:server-dev sets GAME_ENV=dev, DOMAIN=localhost and GIT_COMMIT=DEV, and it passes two keys as literals: API_KEY set to WARNING_DEV_API_KEY_DO_NOT_USE_IN_PRODUCTION and ADMIN_BOT_API_KEY set to WARNING_DEV_ADMIN_BOT_KEY_DO_NOT_USE_IN_PRODUCTION. Both names say what they are, which is the point, so someone reading the manifest can see at a glance that no real credential belongs in that line. A third value, TURNSTILE_SITE_KEY, is set to 1x00000000000000000000AA, the shape of a fixed test value used outside production. Every one of those assignments is made through cross-env inside the script rather than read from a file, even though an example.env sits at the root, so the two mechanisms coexist and it is worth knowing which one a given script uses. dev:host sets VITE_HOST=lan instead, for testing from another machine on the network, and the dev command itself opens a browser unless SKIP_BROWSER_OPEN is set to true in the environment.
Replaying a production game requires the same commit
One workflow has a hard precondition stated plainly: to replay a production game you must be on the same commit the game was executed on. The commit for a given game is not guessed, it is looked up, through the game's own endpoint at https://api.openfront.io/game/[gameId], which returns a gitCommit value. The second half of the rule is that unfinished games cannot be replayed on localhost, so there is no partial path either. This is the practical consequence of the simulation being deterministic: replay means re-running the same inputs against the same code, and a build from another commit is a different simulation even when the map and the rules match. Two scripts make the connection possible, dev:staging and dev:prod, which point the client at api.openfront.dev or api.openfront.io while still running the server locally. A third one deserves a note: the script named tunnel is a production build followed by a server start, and the line itself opens no tunnel, so the name promises more than the command does.
A deterministic core, a compact wire format and a Go map generator
The project structure splits the game into three source trees and two supporting directories. /src/client is the frontend, /src/core is described as the deterministic game simulation, and /src/server is the backend game server, while /resources holds static assets such as images and maps. The fifth entry is the most specific: /zbin is a compact binary wire format for zod schemas, described as self-contained and zod-only, which means the encoding travels with its own schema definitions rather than relying on a shared library elsewhere in the tree. Beside those sits map-generator/, and the package scripts reveal what it is, since docs:map-generator runs go doc inside it: a Go component ships inside a TypeScript project. The root also carries client-api.json, __mocks__/ for test doubles, tests/, scripts/, proprietary/ and a .gitmodules file, so some content arrives as git submodules rather than tracked files. Tests run under vitest, twice in one command, once for the general suite and again for tests/server, while the matchmaking suites bypass vitest and run plain node against .mjs files in tests/matchmaking. The map content itself lives in /resources, which is also where images sit, and the scope the structure supports is the one the feature list describes: territorial control, an alliance system framed as mutual defence, a resource balance between expansion and defence, and maps drawn from real geography across Europe, Asia, Africa and more, played in any modern browser.
The guide says webpack, the scripts say vite
One line in the development instructions does not match the repository. The description of npm run dev says it starts the webpack dev server for the client, launches the game server with development settings, and opens a game in the default browser. The manifest tells a different story: start:client is simply vite, there is a vite.config.ts at the root and no webpack configuration file in the tree, and the build scripts run vite build rather than a webpack build. The rest of that script set is worth reading closely. build-dev runs a type check with tsc --noEmit in parallel with a development-mode vite build, and build-prod does the same with the kill-others-on-fail flag on the runner and then appends a tsx step for asset hashing, so a type error stops the build instead of shipping. The manifest is also named openfront-client even though the repository ships a server as well, and the performance and replay entry points all run through npx tsx, one for the full set, one for a whole game, one for client updates and one for replaying a recorded game. The release line explains the cadence behind those scripts: v0.34.22 published on 2026-10-01, v0.34.21 on 2026-09-30 and v0.34.20 on 2026-09-26, so version numbers move inside the 0.34 line rather than jumping majors, and the last commit on the default branch main is dated 2026-10-03.
The container serves through nginx, and its build can fail where tests cannot look
The Dockerfile is where several of these decisions become concrete. The base is node:24-slim, and npm 12.1.0 is installed globally with --ignore-scripts immediately. A build stage copies package files first for caching reasons, runs npm ci --ignore-scripts against a cache mount for /root/.npm, and copies only the inputs the build needs: tsconfig.json, vite.config.ts, eslint.config.js, index.html, client-api.json, resources, proprietary, src, zbin and scripts. The commit identity is an argument rather than a guess:
ARG GIT_COMMIT=unknown
ENV GIT_COMMIT="$GIT_COMMIT"
RUN npm run build-prodThe comment above that step explains why the copy list is exact: build-prod runs a script that emits static/asset-hashes.json and static/core-version.txt for the desktop release descriptor, and without those files the image build fails with ERR_MODULE_NOT_FOUND, which the unit suite cannot catch because only the container build runs build-prod from a copied tree. A separate production dependencies stage installs with npm ci --omit=dev --ignore-scripts. The final stage installs nginx, curl, wget, supervisor and apache2-utils, rewrites worker_connections to 8192 with sed, and copies supervisord.conf, so what ships is nginx fronting a supervised process, on a base image that still carries the Node runtime rather than a bare distribution image. Five shell scripts sit at the root for the rest of that pipeline, build.sh, build-deploy.sh, deploy.sh, update.sh and setup.sh, alongside nginx.conf and a generate-nginx-upstream.sh that produces upstream entries for it.
AGPL for code, a separate file for assets, and notices that must survive
Licensing is split three ways and the split is stated rather than assumed. Source code is under the GNU Affero General Public License v3.0, with the full requirements in LICENSE, asset licensing handled separately in LICENSE-ASSETS, and history in LICENSING.md. The copyright notices are named by location: one in the footer and one on the loading screen, both reading OpenFront and Contributors, and modified versions must preserve them in reasonably visible locations. That clause is the practical one for anyone shipping a fork or a derivative, since both notices are user interface text rather than a header in a source file, so they can be dropped by a careless rebuild. Two more provenance facts sit nearby: the project is a fork and rewrite of WarFront.io with credit given to that project, and translations are coordinated through a Crowdin project, with contributions handled through an approved-issue process described in CONTRIBUTING.md. Even the README header carries a licensing detail of its own, switching between two logo files under proprietary/images/ depending on whether the reader prefers a dark or a light colour scheme, and its badge row links both the AGPL-3.0 text and a Creative Commons BY-SA 4.0 page, while the licence section itself names AGPL-3.0 for the source and sends asset questions to a separate file, so the badge row covers more ground than the prose assigns.
Editorial conclusion
Treat this as a source project rather than something to install from a store: AGPL-3.0 covers the code, a separate file covers assets, and the notices in the footer and loading screen have to survive in a modified build. Before running it locally, check Node and npm against the engines range, use npm run inst rather than npm install, and read what start:server-dev puts in your environment, because the API and admin bot keys there are literal placeholders named to warn against production use. If your goal is to replay a recorded game, look up its gitCommit value first, because an unfinished game cannot be replayed on localhost at all.
Frequently asked questions
How do I install and run OpenFrontIO locally?
Clone https://github.com/openfrontio/OpenFrontIO.git, change into the directory, and run npm run inst rather than npm install, since that script runs npm ci --ignore-scripts to install the locked versions without lifecycle scripts. Node.js 24.15.0 or newer and npm 12.1.0 or newer are required, then npm run dev starts the client and the server together.
What kind of game is OpenFront.io?
An online browser real-time strategy game about territorial control and alliance building, where players expand territory, build structures and form alliances on maps based on real-world geography. It runs in a modern web browser, is written in TypeScript, and is a fork and rewrite of WarFront.io.
Is OpenFront.io free?
The repository states licence terms rather than prices: source code under AGPL-3.0 in LICENSE, assets under a separate LICENSE-ASSETS file, and history in LICENSING.md. What the hosted game costs is not something this repository answers.
Why does OpenFront lag, and what can I measure locally?
No performance figures are published in the repository. What it ships are measurement entry points: npm run perf for the full set, perf:game for a whole game, perf:client for client updates, and replay:game to re-run a recorded game, all of which run through tsx.
What is OpenFront on Crazy Games?
This repository does not mention Crazy Games or any portal listing. The only site it points to is openfront.io, and the only other lineage it names is WarFront.io, the project it was forked from.
Can I replay a finished OpenFront game on localhost?
Only on the same commit the game ran on, which you can read from the gitCommit value at https://api.openfront.io/game/[gameId]. Unfinished games cannot be replayed on localhost at all.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/openfrontio-openfrontio)Star history chart for your README
[](https://hysenlabs.com/projects/openfrontio-openfrontio)Was this useful?