Skip to content

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

FlagEffect
-h, --helpPrint usage and exit 0
-v, --versionPrint 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 change

Directories are walked recursively for .sjs files. Output .js (and, when enabled, .js.map) is written to the output directory.

FlagValuesDefaultEffect
--out-dir <dir>pathdistOutput directory
--source-map <mode>none | inline | externalexternalSourcemap emission
--no-cachecache onForce a cold build (ignore the .superjs/ cache)
--watchoffRecompile 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
FlagValuesDefaultEffect
--format <mode>pretty | jsonprettyDiagnostic 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
FlagValuesDefaultEffect
--out-dir <dir>pathalongside sourceDirectory 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.sjs

Once 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 add always 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)
FlagEffect
--checkDon'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
FlagValuesDefaultEffect
--format <mode>pretty | jsonprettyDiagnostic output format
--fixoffApply auto-fixes in place, then report what remains

--fix rewrites the findings that carry a safe, unambiguous fix — today no-var (varlet, L002) and no-debugger (delete the statement, L005) — writes each file back, and reports anything left unfixed.

Rules:

CodeRule
SJS-L001prefer const — a let binding never reassigned
SJS-L002no var — use let / const
SJS-L003use === / !==, not == / !=
SJS-L004prefer for…of over for…in
SJS-L005no debugger statement
SJS-L006no empty match (a match with no arms)
SJS-L007no redundant match arm (a variant handled twice)
SJS-L008prefer an arrow over a function-expression callback
SJS-L009no unused import
SJS-L010import-order (sort imports by source)
SJS-L012no unused variable, function, or class
SJS-L013no explicit dynamic (opt out with // @sjs:dynamic-ok)
SJS-L014no shadowing of an enclosing-scope binding
SJS-L015no floating promise (await/return/consume it)
SJS-L016no unhandled Result (match/return/consume it)
SJS-L017prefer returning Result over throw
SJS-L018no 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
FlagValuesDefaultEffect
--format <mode>md | jsonmdOutput format
--out-dir <dir>pathstdoutWrite one file per input instead of printing

MVP: signatures + doc comments. The full doc site (--serve, HTML, validated @example blocks) 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-insensitive

See 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 project

Templates: 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 doctor

lsp

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/stdout

It 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/
ExitMeaning
0every emitted file matches the expected tree
1a file differs, is missing, or the inputs failed to compile
2usage 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-transform and @superjs/vitest-transform will compile .sjs test files through the compiler's transform() API so they run under Jest or Vitest unchanged.
  • Native runnersuperjs test, a zero-config runner for .sjs test files (watch + coverage), is planned for Stage 5 / post-1.0.

Exit codes

CodeMeaning
0Success — no errors
1Compilation errors, or a named file was missing
2Usage error (e.g. no files passed to build/check)
70Internal 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"
  }
}
KeyTypeDefaultNotes
compilerOptions.strictbooleanfalsePromotes warnings to errors; warns on implicit dynamic
compilerOptions.noEmitOnErrorbooleanfalseSkip emit when any error is present
compilerOptions.targetES2020ESNextES2022ECMAScript output level
compilerOptions.outDirpathDefault output directory
compilerOptions.sourceMapnone | inline | externalnoneSourcemap mode
jsx.runtimeautomatic | classicautomaticJSX transform
jsx.importSourcestringreactJSX import source
pathsrecord{}tsconfig-style path mapping
output.eollf | crlf | autolfLine ending of emitted files

--strict is not a command-line flag — set compilerOptions.strict in config so every tool (CLI, CI, editor) agrees on one source of truth.

Documentation