CLI Reference
The superjs binary drives compilation, type-checking, and project setup. All
build behavior is configured through superjs.config.json —
the CLI keeps its flag surface small and reads the rest from config.
superjs <command> [files...] [flags]Global flags
| Flag | Effect |
|---|---|
-h, --help | Print usage and exit 0 |
-v, --version | Print the compiler version and exit 0 |
Commands
build
Compile .sjs source files to JavaScript.
superjs build src/ # compile every .sjs under src/ → dist/
superjs build app.sjs --watch # recompile on changeDirectories are walked recursively for .sjs files. Output .js (and, when
enabled, .js.map) is written to the output directory.
| Flag | Values | Default | Effect |
|---|---|---|---|
--out-dir <dir> | path | dist | Output directory |
--source-map <mode> | none | inline | external | external | Sourcemap emission |
--no-cache | — | cache on | Force a cold build (ignore the .superjs/ cache) |
--watch | — | off | Recompile when files change |
check
Type-check without emitting any output. Ideal for CI and editors.
superjs check src/ # human-readable diagnostics
superjs check src/ --format json # one JSON diagnostic per line| Flag | Values | Default | Effect |
|---|---|---|---|
--format <mode> | pretty | json | pretty | Diagnostic output format |
The json format emits one object per line:
{
"code": "SJS-E001",
"severity": "error",
"message": "Null or undefined assigned to non-nullable type `string`",
"file": "/abs/path/src/app.sjs",
"line": 42,
"column": 14,
"endLine": 42,
"endColumn": 21
}translate
Translate TypeScript .d.ts declaration files into SuperJS .d.sjs. Output is
written next to each source file, 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 | Values | Default | Effect |
|---|---|---|---|
--out-dir <dir> | path | alongside source | Directory for the .d.sjs output |
Enums become sum types (enum Color { Red, Green } → type Color = Red | Green),
and an intersection of object literals ({ a } & { b }) merges into one object
type. TypeScript forms SuperJS doesn't model (conditional, mapped, infer,
any, and intersections that can't be merged) degrade to dynamic, and
top-level functions, classes and namespaces are reported as not-yet-translated —
every dropped or degraded construct is surfaced as a warning, never silently lost.
add
Resolve an already-installed npm package's types into SuperJS so you can
import it with type safety. add finds the package's .d.ts (its own
types/typings entry, or the DefinitelyTyped @types/<pkg> fallback), runs
the translator, writes the result to
node_modules/@superjs/types/<pkg>/index.d.sjs, and maps the import specifier in
superjs.config.json paths.
npm install fastify # install the package as usual
superjs add fastify # → node_modules/@superjs/types/fastify/index.d.sjsOnce added, import { … } from "fastify" resolves to these types during
superjs check / superjs build — the bare specifier is matched through the
paths map add wrote.
add prints a typed-surface estimate — how much of the package's API kept a
real type vs degraded to dynamic — and records it in a surface.json sidecar
that superjs doctor reads back. The same dynamic fallbacks as translate
apply and are reported as warnings.
Today
addalways translates the package's published.d.ts. Hand-curated@superjs/types-<pkg>wrappers (richer types for the top packages) are resolved first in a later Stage 2 sprint.
format
Rewrite .sjs files in the canonical style — 2-space indent, semicolons, one
statement per line. There are no options to bikeshed.
superjs format src/ # format every .sjs under src/, in place
superjs format src/ --check # report what would change, write nothing (CI)| Flag | Effect |
|---|---|
--check | Don't write; list files that would change and exit non-zero if any |
The formatter is safe by construction: it reparses its own output and only rewrites a file when the result is provably the same program (identical AST). Anything it can't reproduce faithfully is left exactly as-is.
A gitignore-style .sjsignore at the project root excludes files from directory
walks (shared by format, lint, check, build, and doc); a file passed
explicitly on the command line is always processed.
lint
Report style findings — the SJS-L* rules. Exits non-zero when any are found,
so it gates CI.
superjs lint src/ # human-readable findings
superjs lint src/ --format json # one JSON diagnostic per line
superjs lint src/ --fix # apply auto-fixes in place| Flag | Values | Default | Effect |
|---|---|---|---|
--format <mode> | pretty | json | pretty | Diagnostic output format |
--fix | — | off | Apply auto-fixes in place, then report what remains |
--fix rewrites the findings that carry a safe, unambiguous fix — today
no-var (var → let, L002) and no-debugger (delete the statement, L005) —
writes each file back, and reports anything left unfixed.
Rules:
| Code | Rule |
|---|---|
| SJS-L001 | prefer const — a let binding never reassigned |
| SJS-L002 | no var — use let / const |
| SJS-L003 | use === / !==, not == / != |
| SJS-L004 | prefer for…of over for…in |
| SJS-L005 | no debugger statement |
| SJS-L006 | no empty match (a match with no arms) |
| SJS-L007 | no redundant match arm (a variant handled twice) |
| SJS-L008 | prefer an arrow over a function-expression callback |
| SJS-L009 | no unused import |
| SJS-L010 | import-order (sort imports by source) |
| SJS-L012 | no unused variable, function, or class |
| SJS-L013 | no explicit dynamic (opt out with // @sjs:dynamic-ok) |
| SJS-L014 | no shadowing of an enclosing-scope binding |
| SJS-L015 | no floating promise (await/return/consume it) |
| SJS-L016 | no unhandled Result (match/return/consume it) |
| SJS-L017 | prefer returning Result over throw |
| SJS-L018 | no mixed spaces and tabs in indentation |
doc
Generate API documentation from a module's exported declarations. Because SJS
types are explicit and sound, the signature is the documentation — no
@param {type} tags to drift. A leading JSDoc-style comment adds prose and tags
(@example, @since, @deprecated, @see, …).
superjs doc src/api.sjs # Markdown to stdout
superjs doc src/ --format json # machine-readable
superjs doc src/ --out-dir docs # write one .md per file| Flag | Values | Default | Effect |
|---|---|---|---|
--format <mode> | md | json | md | Output format |
--out-dir <dir> | path | stdout | Write one file per input instead of printing |
MVP: signatures + doc comments. The full doc site (
--serve, HTML, validated@exampleblocks) is later work — see ADR-009.
explain
Print the full reference for a diagnostic code.
superjs explain E001 # short form
superjs explain SJS-E001 # full code, case-insensitiveSee the complete list on the error code reference page.
init
Write a default superjs.config.json, or scaffold a starter project with a
template. Existing files are never overwritten.
superjs init # just superjs.config.json
superjs init fastify-api # scaffold a Fastify API projectTemplates: node-cli, fastify-api, workers-api, lambda-handler. Each
writes a package.json, a superjs.config.json, a starter src/*.sjs, and a
README (plus wrangler.toml for workers-api).
doctor
Report environment health: Node.js version (must be ≥ 18), compiler version,
whether a superjs.config.json is present, and a typed-surface report for every
package brought in with superjs add.
superjs doctorlsp
Start the SuperJS language server over stdio, for editor integration. The server 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 # reads/writes JSON-RPC on stdin/stdoutIt is normally launched by an editor, not by hand. The VS Code extension spawns
it 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).
stdout is the JSON-RPC channel, so the command prints nothing else there; the
process runs until the client sends exit.
verify
Recompile an input tree and byte-diff the emitted JavaScript against an expected output tree — the build-determinism gate as a user-auditable command. Exits non-zero on any difference or missing file. Source maps are disabled so the comparison is path-independent.
superjs verify src/ expected-dist/| Exit | Meaning |
|---|---|
0 | every emitted file matches the expected tree |
1 | a file differs, is missing, or the inputs failed to compile |
2 | usage error (needs both <input-dir> and <expected-dir>) |
migrate
Assisted migration of a TypeScript tree to SuperJS.
superjs migrate from-ts src/A best-effort textual pass: each .ts becomes a .sjs with any rewritten
to dynamic, and constructs that need a human rewrite — enum, namespace,
decorators, as const, non-null ! — are flagged by line in a generated
MIGRATION_REPORT.md. It does not type-check the result; run superjs check
afterward. Idempotent: a directory of already-migrated .sjs has no .ts to
process.
Planned commands
These subcommands are reserved — invoking one today prints a not-yet-implemented notice and exits non-zero.
superjs test — test runner (planned, Stage 5)
A Jest alternative for .sjs, planned along two tracks:
- Run in your existing runner —
@superjs/jest-transformand@superjs/vitest-transformwill compile.sjstest files through the compiler'stransform()API so they run under Jest or Vitest unchanged. - Native runner —
superjs test, a zero-config runner for.sjstest files (watch + coverage), is planned for Stage 5 / post-1.0.
Exit codes
| Code | Meaning |
|---|---|
0 | Success — no errors |
1 | Compilation errors, or a named file was missing |
2 | Usage error (e.g. no files passed to build/check) |
70 | Internal compiler error (reported to stderr) |
These codes are stable; scripts and CI can rely on them.
Configuration
Build options live in superjs.config.json rather than on the command line, so
one config drives build, check, and your editor identically.
{
"language": "1.0",
"compilerOptions": {
"strict": false,
"noEmitOnError": false,
"target": "ES2022",
"outDir": "dist",
"sourceMap": "none"
},
"jsx": {
"runtime": "automatic",
"importSource": "react"
},
"paths": {},
"output": {
"eol": "lf"
}
}| Key | Type | Default | Notes |
|---|---|---|---|
compilerOptions.strict | boolean | false | Promotes warnings to errors; warns on implicit dynamic |
compilerOptions.noEmitOnError | boolean | false | Skip emit when any error is present |
compilerOptions.target | ES2020…ESNext | ES2022 | ECMAScript output level |
compilerOptions.outDir | path | — | Default output directory |
compilerOptions.sourceMap | none | inline | external | none | Sourcemap mode |
jsx.runtime | automatic | classic | automatic | JSX transform |
jsx.importSource | string | react | JSX import source |
paths | record | {} | tsconfig-style path mapping |
output.eol | lf | crlf | auto | lf | Line ending of emitted files |
--strictis not a command-line flag — setcompilerOptions.strictin config so every tool (CLI, CI, editor) agrees on one source of truth.