Tooling
SuperJS ships a unified CLI that covers every stage of the development workflow: compilation, linting, formatting, and testing. There are no separate packages to install or plugins to configure.
Installation
Install the CLI from npm — a self-contained bundle with no runtime dependencies:
npm install -g @superjsorg/cliThis makes the superjs command available globally.
CLI Reference
Commands take one or more file or directory paths as positional arguments — a
directory expands to every .sjs file under it.
superjs build
Compiles .sjs files to JavaScript (plus source maps).
superjs build <files...> [--out-dir <dir>] [--source-map none|inline|external] [--watch] [--no-cache]Flags:
| Flag | Description |
|---|---|
--out-dir <dir> | Output directory (default: dist) |
--source-map <mode> | none (default), inline, or external (.js.map alongside output) |
--watch | Watch mode — recompile on file change |
--no-cache | Disable the incremental build cache |
Examples:
# Compile a single file to ./dist
superjs build src/main.sjs
# Compile a whole directory with external source maps
superjs build src --out-dir dist --source-map external
# Watch mode during development
superjs build src --out-dir dist --watchsuperjs check
Type-checks without emitting output. Exits non-zero if any errors are found.
superjs check <files...> [--format pretty|json]| Flag | Description |
|---|---|
--format <mode> | pretty (default, colored terminal) or json (machine-readable) |
superjs check src # type-check a directory
superjs check src --format json # machine-readable diagnostics for CIsuperjs translate
Translates TypeScript .d.ts declaration files into SuperJS .d.sjs, so you can
consume existing typed packages. Each file is written next to its source, or
under --out-dir.
superjs translate types.d.ts # → types.d.sjs
superjs translate types.d.ts --out-dir gen # → gen/types.d.sjs| Flag | Description |
|---|---|
--out-dir <dir> | Write the .d.sjs output to this directory instead of alongside the source |
Enums translate to sum types, and intersections of object literals merge into one
object type. TypeScript constructs SuperJS doesn't model (conditional, mapped,
infer, any, unmergeable intersections, …) degrade to dynamic, and top-level
functions/classes/namespaces are flagged as not-yet-translated — every case is
reported as a warning, never silently dropped:
$ superjs translate types.d.ts
warning: intersection `A & B` mapped to `dynamic` (reason: intersection-not-mergeable)
translated types.d.ts → types.d.sjssuperjs add
Resolves an installed npm package's types into SuperJS .d.sjs and wires them
into your project. Finds the package's .d.ts (its own types entry or the
DefinitelyTyped @types/<pkg> fallback), translates it to
node_modules/@superjs/types/<pkg>/index.d.sjs, and maps the import specifier in
superjs.config.json paths.
npm install fastify
superjs add fastify # → node_modules/@superjs/types/fastify/index.d.sjsUnmappable TypeScript forms degrade to dynamic and are reported — never
silently dropped. add prints a typed-surface estimate (how much of the API kept
a real type) and records it for superjs doctor. (Hand-curated wrappers for the
top packages land in a later Stage 2 sprint; today add always translates the
published .d.ts.)
superjs format
Rewrites .sjs files in the canonical style (2-space indent, semicolons, one
statement per line). --check reports what would change without writing — for
CI. The formatter reparses its own output and only rewrites a file when the
result is provably the same program, so it never corrupts code.
superjs format src/ # format in place
superjs format src/ --check # CI: non-zero exit if anything would changeA gitignore-style .sjsignore at the project root excludes files from directory
walks (for format, lint, check, build, and doc); explicitly-named files
are always processed. See the formatter integration guide
for the .sjsignore syntax and the Prettier / Husky coexistence story.
superjs lint
Reports style findings (SJS-L*) and exits non-zero when any are present, so it
gates CI. Current rules: prefer-const (L001), no-var (L002), ===/!==
(L003), for…of over for…in (L004), no-debugger (L005), no-empty-match
(L006), no-redundant-match-arm (L007), prefer-arrow-callback (L008),
no-unused-import (L009), import-order (L010), no-unused-var (L012), no-explicit-dynamic (L013), no-shadowing (L014), no-floating-promise (L015), no-unhandled-result (L016), prefer-result-over-throw
(L017), and no-mixed-spaces-tabs (L018) — 17 rules in total.
superjs lint src/ # human-readable
superjs lint src/ --format json # machine-readable for CI
superjs lint src/ --fix # apply auto-fixes (no-var → let, drop debugger) in placesuperjs doc
Generates API documentation from a module's exported declarations — a built-in JSDoc / TypeDoc alternative. SJS types are explicit and sound, so the signature is the documentation; a leading doc comment adds prose and tags. Outputs Markdown (default) or JSON.
superjs doc src/api.sjs # Markdown to stdout
superjs doc src/ --out-dir docs # one .md per file
superjs doc src/ --format json # machine-readable(MVP: signatures + doc comments. HTML site / --serve / validated @example
blocks are later work — see ADR-009.)
superjs explain
Prints the full spec write-up for a diagnostic code (description, example, fix).
superjs explain SJS-E001
superjs explain E007 # the SJS- prefix is optionalsuperjs init
Writes a default superjs.config.json into the current directory.
superjs doctor
Reports environment health — Node version, config presence and validity — plus a
per-package typed-surface report for everything pulled in with superjs add.
superjs lsp
Starts the SuperJS language server over stdio for editor integration. Speaks standard LSP and implements the full M1 method set: diagnostics, hover, go-to-definition, document outline, folding, completion, signature help, semantic tokens, and formatting.
superjs lsp # JSON-RPC over stdin/stdout — launched by your editorNormally an editor spawns it. The VS Code extension does so automatically; for
Neovim, Helix, or any LSP-aware editor, point the client at superjs lsp — see
the Editor Setup guide for ready-to-paste configs.
Planned commands
These are reserved and currently print a not-yet-implemented notice:
superjs test(Stage 5) — a Jest alternative for.sjs. Planned as both@superjs/jest-transform/@superjs/vitest-transform(run.sjstests in your existing Jest/Vitest) and a native zero-configsuperjs testrunner.
Full plan on the roadmap.
Project Configuration
SuperJS looks for a superjs.config.json file in the project root. All fields
are optional and have defaults. Compiler settings are nested under
compilerOptions:
{
"compilerOptions": {
"strict": false,
"target": "ES2022",
"outDir": "dist",
"sourceMap": "none",
"noEmitOnError": false
},
"jsx": {
"runtime": "automatic",
"importSource": "react"
}
}| Field | Type | Default | Description |
|---|---|---|---|
compilerOptions.strict | boolean | false | Enable strict mode (promotes SJS-W001 implicit-dynamic warnings to errors) |
compilerOptions.target | string | "ES2022" | Output target: ES2020–ES2024 or ESNext |
compilerOptions.outDir | string | — | Output directory for compiled .js files |
compilerOptions.sourceMap | string | "none" | none, inline, or external |
compilerOptions.noEmitOnError | boolean | false | Skip emitting JS if type errors are present |
jsx.runtime | string | "automatic" | automatic (react/jsx-runtime) or classic (React.createElement) |
jsx.importSource | string | "react" | Import source for the automatic JSX runtime |
The full schema (including paths, output.variants, lsp, and env) lives in
specs/config-schema.json.
CLI flags always override config file values.
Error Output
Terminal Output (default)
By default, superjs build and superjs check print colored diagnostics to the terminal:
error SJS-E001 src/main.sjs:12:5
Null/undefined assigned to non-nullable type 'string'.
warning SJS-W001 src/utils.sjs:8:10
Implicit 'dynamic' type — enable strict mode to enforce annotations.
Each diagnostic includes:
- Severity (
error/warning) - Diagnostic code (e.g.,
SJS-E001) - File path and line/column position
- Human-readable message
JSON Mode (--format json)
Pass --format json to superjs check to receive machine-readable diagnostics, suitable for editor integrations, CI pipelines, and automated tooling:
{"severity":"error","code":"SJS-E001","file":"src/main.sjs","line":12,"col":5,"message":"Null/undefined assigned to non-nullable type 'string'."}
{"severity":"warning","code":"SJS-W001","file":"src/utils.sjs","line":8,"col":10,"message":"Implicit 'dynamic' type."}Diagnostic Codes
| Code | Severity | Description |
|---|---|---|
| SJS-E001 | error | Null or undefined assigned to a non-nullable type |
| SJS-E002 | error | Type mismatch on assignment or return |
| SJS-W001 | warning | Implicit dynamic type (strict mode only) |
| SJS-E007 | error | Non-exhaustive match — a sum type variant is not handled |
SJS-E001 — Null Safety
SJS types are non-nullable by default. Use T? to declare a nullable type:
// error: 'string' is not nullable
const name: string = null // SJS-E001
// correct: use T? for nullable
const name: string? = nullSJS-E002 — Type Mismatch
const count: number = "hello" // SJS-E002: expected number, got stringSJS-W001 — Implicit Dynamic (strict mode)
// in strict mode, this emits SJS-W001
function process(data) {
return data
}
// fix: annotate the parameter
function process(data: string): string {
return data
}SJS-E007 — Non-Exhaustive Match
type Status = Active | Inactive | Pending
const s: Status = Active
// SJS-E007: 'Pending' variant not handled
const label = match s {
Active => "active",
Inactive => "inactive",
}Exit Codes
| Code | Meaning |
|---|---|
0 | Success — no errors |
1 | One or more errors found |
2 | CLI usage error (bad flags, missing arguments) |