package.json can’t hold docs. README can’t run scripts.
fsr puts them in the same file — so they can never drift apart again.
“Our team loves it — and nobody outside had heard of it yet. That ends now.”
Before & After
On the left, a cryptic wall of escaped one-liners. On the right, the exact same scripts — documented, sectioned, and runnable.
{
"name": "fscr",
"version": "7.3.4",
"description": "Runs the fscripts.md file",
"type": "module",
"main": "./dist/index.js",
"scripts": {
"build": "rimraf dist && mkdir -p dist/lib && cp -r lib/. dist/lib && cp index.js dist/index.js && node scripts/build-ui.mjs",
"fsr": "node dist/index.js",
"release": "yarn fsr bump && yarn build",
"release:publish": "node lib/release/publish.js",
"start": "run-s build start:run",
"start:run": "node dist/index.js",
"watch": "nodemon --watch lib --watch index.js --ext js --exec \"mkdir -p dist/lib && cp -r lib/. dist/lib && cp index.js dist/index.js && node scripts/build-ui.mjs\" --ignore 'node_modules' -I",
"test": "vitest run",
"test:watch": "vitest",
"test:ui": "vitest --ui",
"test:coverage": "vitest run --coverage",
"test:unit": "vitest run tests/unit",
"test:integration": "vitest run tests/integration",
"test:e2e": "vitest run tests/e2e",
"test:performance": "vitest run tests/performance"
}
}
- [First category of scripts](#first-category-of-scripts) - [build](#build) - [fsr](#fsr) - [release](#release) - [release:publish](#releasepublish) - [start](#start)
# First category of scripts
Welcome to your new amazing fscripts.md file. It replaces the headaches of npm scripts! But so much more.
## build
Cleans the `dist/` output directory using `rimraf`, copies the entire `lib/` into it, and copies `index.js` to `dist/index.js`.
```bash rimraf dist && mkdir -p dist/lib && cp -r lib/. dist/lib && cp index.js dist/index.js ```
## fsr
Runs the built `fsr` CLI directly from `dist/index.js`. Use this after a `build` to smoke-test the compiled output. In normal development you would run `yarn start` (which builds first) rather than this directly.
```bash node dist/index.js ```
## release
Prepares a release in two steps: first bumps the version in `package.json` (via `fsr bump`, which also handles git tagging), then rebuilds the `dist/` output so the published package reflects the new version. Run this when you are ready to cut a new version before publishing to npm.
```bash yarn fsr bump && yarn build ```
## release:publish
Executes `lib/release/publish.js`, which handles the full npm publish workflow — typically setting the dist-tag, running `npm publish`, and any post-publish steps. Run this after `release` to push the new version to the registry.
```bash node lib/release/publish.js ```
## start
Builds the project and then immediately runs the CLI. Equivalent to running `build` followed by `start:run` in sequence using `npm-run-all`'s `run-s`. Use this as the single command to go from source to a running CLI during development when you want a clean build first.
```bash run-s build start:run ```
## start:run
Runs the built CLI from `dist/index.js`. This is the second step of `start` and can also be called on its own when you know `dist/` is already up to date and you just want to relaunch the CLI without rebuilding.
```bash node dist/index.js ```
## watch
Starts a `nodemon` watcher over `lib/` and `index.js`. Whenever any `.js` file changes, it automatically syncs the changed files to `dist/` (same copy commands as `build`, but without `rimraf` so it is fast). The `-I` flag keeps nodemon from reading stdin. Use this during active development so you can run `yarn fsr` after each save without manually rebuilding.
```bash nodemon --watch lib --watch index.js --ext js --exec "mkdir -p dist/lib && cp -r lib/. dist/lib && cp index.js dist/index.js" --ignore 'node_modules' -I ```
## test
Runs the full test suite once with Vitest in non-watch (CI) mode. Exits with a non-zero code if any test fails, making it suitable for CI pipelines. Covers all test types (unit, integration, e2e, performance) in a single pass.
```bash vitest run ```
## test:watch
Starts Vitest in interactive watch mode. Re-runs only the affected tests on every file save. Use this during development to get instant feedback as you write code.
```bash vitest ```
## test:ui
Opens the Vitest browser-based UI at `localhost:51204` (or similar). Provides a visual dashboard to browse test files, inspect individual test results, and re-run tests interactively. Useful for exploring test output without reading terminal logs.
```bash vitest --ui ```
## test:coverage
Runs the full test suite once and generates a V8 code coverage report via `@vitest/coverage-v8`. Outputs a summary to the terminal and writes detailed HTML/JSON reports to `coverage/`. Use this to check overall coverage before a release or PR.
```bash vitest run --coverage ```
## test:unit
Runs only the files inside `tests/unit/` — fast, isolated unit tests for individual functions and modules with no external dependencies. Use this for a quick sanity check focused on pure logic.
```bash vitest run tests/unit ```
## test:integration
Runs only the files inside `tests/integration/` — tests that exercise multiple modules working together (e.g., the parser + runner pipeline, file I/O, or CLI command handlers). Slower than unit tests but still headless.
```bash vitest run tests/integration ```
## test:e2e
Runs only the files inside `tests/e2e/` — end-to-end tests that exercise the full CLI from the outside, simulating real user invocations. These are the slowest tests and validate the complete user-facing behavior.
```bash vitest run tests/e2e ```
## test:performance
Runs only the files inside `tests/performance/` — benchmark and regression tests that assert timing or throughput constraints. Use these to catch regressions in hot paths such as parsing, task resolution, or plugin loading.
```bash vitest run tests/performance ```
You clone a new repo. There are 40 scripts in package.json. The README mentions three of them. Nobody knows what the rest do. The one person who wrote them is on PTO.
You run build:prod thinking it’s the dev build. Staging goes down. It’s 9:23am on your first day. The Slack message appears: “Hey, why is staging down?”
You update the scripts. You forget to update the README. Three months later, nobody trusts the docs. They just try scripts and hope. This is fine. 🔥
You’re not bad at documentation. The tools just make it
impossible.
package.json
has no room for docs.
README
can’t run anything. You end up maintaining two things that
drift apart on day one — and stay that way forever.
fsr introduces fscripts.md — a Markdown file that is simultaneously your documentation and your script runner.
Write scripts in plain Markdown. Add descriptions. Organize into sections. Run them directly from the file. The docs and the runner are the same thing now. They literally cannot drift apart.
Install fsr globally. One command. Done.
Auto-imports all your package.json scripts into fscripts.md with docs stubs.
Interactive picker appears. Pick a script. Run it. No memorizing. No digging.
Features
Just run fsr. That’s it.
An interactive list of every script — with its human-readable description — appears right in your terminal. Arrow keys to navigate. Enter to run. No man pages. No README hunting. No more “which script starts the dev server?” on Slack.
$ fsr ? Choose a script to run: ❯ 🚀 start:dev — Start the dev server, hot reload 🏗️ build:prod — Full production build (grab a ☕) 🧹 clean — Remove build artifacts 🔐 encrypt:secrets — Encrypt .env before committing
Not just one-liners. Real scripts. In your Markdown.
Write complete, multi-line JavaScript directly inside fscripts.md with standard code blocks. Run with fsr run [task]. The Markdown is the script.
### check-env Validates required env vars before start. 🔍 const required = ['DATABASE_URL', 'API_KEY']; const missing = required.filter(k => !process.env[k]); if (missing.length) process.exit(1); console.log('✅ All env vars present!');
Already have scripts? Import them in one command.
Pulls every script from your package.json into a fresh fscripts.md with documentation stubs. Your workflow doesn’t change — it just gets superpowers. 💪
$ fsr generate
Built-in. No extra tools. No extra config.
# Start everything at once ⚡
$ fsr run-p start:web start:api start:worker # Run in order 🔗
$ fsr run-s clean build:prod deploy:staging
Hook into before and after any script execution.
Plugins appear right in the picker. Clear the cache before every script? Notify Slack after a deploy? Write a plugin, hook it in, done. It fires automatically, every time.
// 🧹 auto-cache-cleaner plugin export default { name: 'cache-cleaner', hooks: { 'pre-task': async ({ taskName }) => { await clearCache(); } } }
Password-protect your secrets without leaving the terminal.
$ fsr encrypt # 🔐 encrypt .env files
$ fsr decrypt # 🔓 decrypt when needed
$ fsr encryption # 🔑 interactive menu
Never accidentally commit secrets again. Never email .env files again. 😬
Diagnose and fix your environment without googling.
$ fsr doctor # run diagnostics
$ fsr doctor --fix # auto-fix what it can
$ fsr doctor --json # CI-friendly output
| package.json | README + npm | Makefile | fsr | |
|---|---|---|---|---|
| {{ row.label }} | {{ row.a }} | {{ row.b }} | {{ row.c }} | {{ row.d }} |
The others aren’t really alternatives. Devs just suffer without fsr. 😅
Built by a team that got tired of suffering 😤
“We built fsr because our own team had the same problem everyone has. The README is always lying. The package.json is always cryptic. We decided the answer wasn’t better documentation tooling OR a better script runner — it was making them the same file.”
$ npm i -g fscr # install
$ fsr generate # import your existing scripts
$ fsr # pick one and run it 🚀
No config. No setup. No more “ask Mike what the scripts do.” 🙌
fsr (Freedcamp Script Runner) is a free, open-source CLI tool
installed as the npm package fscr. It replaces your
package.json scripts with a single Markdown file
called fscripts.md that serves simultaneously as
documentation and as a script runner. Run
npm i -g fscr to install it.
Install fsr globally with npm: npm i -g fscr. After
installation, run fsr generate in any Node.js
project to automatically import your existing package.json
scripts into fscripts.md with documentation stubs. Then run
fsr to open the interactive script picker.
fscripts.md is a Markdown file that fsr uses as its source of truth. Each heading becomes a runnable script. You can write bash commands or full JavaScript blocks inside fenced code blocks, add human-readable descriptions in plain Markdown, and organize scripts into sections with a table of contents. Because docs and code live in the same file, they can never drift apart.
Yes. fsr supports parallel execution with
fsr run-p script1 script2 script3 and sequential
execution with fsr run-s script1 script2 script3.
No extra tools or configuration are needed — it is built into
fsr.
package.json scripts have no room for documentation — they are one-liners without descriptions, and docs in a README drift away almost immediately. fsr uses a Markdown file (fscripts.md) where each script has a human-readable description, can span multiple lines, supports full JavaScript, and documentation and runner code live in the same file so they literally cannot drift apart. fsr also adds an interactive picker, parallel and sequential execution, a plugin hook system, file encryption, and system diagnostics — none of which package.json supports.
Yes. fsr is completely free and open source, licensed under MIT.
There are no paid plans, no usage limits, and no enterprise
tiers. Install it with npm i -g fscr.