fsr — Freedcamp Script Runner | Document & Run npm Scripts from One Markdown File
npm i -g fscr free & open source

Your scripts. Documented. Runnable. In one Markdown file.

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.”

fsr — interactive picker
$ fsr
? Choose a script to run: (Use arrow keys)
{{ item.pointer }} {{ item.emoji }} {{ item.name }} — {{ item.desc }}

Before & After

Same scripts. One you can actually read. 📖

On the left, a cryptic wall of escaped one-liners. On the right, the exact same scripts — documented, sectioned, and runnable.

BEFORE 😵 package.json
{
  "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"
  }
}
AFTER ✨ fscripts.md
-   [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 ```

Sound familiar? 👀

🕵️

The Mystery Scripts

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.

💀

The Wrong Deploy

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?”

💔

The Drift

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.

One file. Docs AND runner. Problem solved. 🎉

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.

STEP 1
npm i -g fscr

Install fsr globally. One command. Done.

STEP 2 📝
fsr generate

Auto-imports all your package.json scripts into fscripts.md with docs stubs.

STEP 3 🎯
fsr

Interactive picker appears. Pick a script. Run it. No memorizing. No digging.

Features

Everything your scripts deserved all along 💅

🎯

The Interactive Picker

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
🤯

Full JavaScript in Markdown

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!');

Auto-Generate from package.json

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

Parallel & Sequential

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
🔌

Plugin System

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();
    }
  }
}
🔐

File Encryption

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. 😬

🩺

System Doctor

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
💅

And more…

📑 Auto Table of Contents fsr toc
🏷️ Version bump + beautify fsr bump
📈 Smart package upgrades fsr upgrade
🐚 Shell tab completions fsr completion
📊 Cache management fsr cache
🌿 Git branch validation fsr branch
🗑️ Clear task history fsr clear

The honest comparison 👀

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.”
The fsr team 🛠️
Our whole team uses fsr on every project. We wouldn’t ship it if we didn’t.

Three commands. That’s it. 🎯

$ 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.” 🙌

Frequently Asked Questions

What is fsr?

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.

How do I install fsr?

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.

What is fscripts.md?

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.

Can fsr run scripts in parallel?

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.

What is the difference between fsr and npm scripts in package.json?

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.

Is fsr free?

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.