Skip to content
qino

CLI

The qino command, what each subcommand checks, and how to wire it into a build.

@qino/cms installs a qino binary with three commands. There are no flags.

Terminal
Usage: qino <command>

Commands:
  lint     Validate config, paths, and relations (no content read)
  check    Validate every content file against its schema
  build    Run lint + check, then generate types

Discovery

Every command runs from the working directory and expects:

  1. A qino/ folder.
  2. qino/index.ts that default-exports an initQino instance.
  3. Definitions exported from any .ts, .tsx, .js, or .mjs file under qino/.

Definition files are loaded with jiti, so TypeScript needs no separate compile step. See Project structure.

qino lint

Reads no content. Checks, in order:

  • The entry file exists and default-exports an instance.
  • contentFolder and mediaFolder exist and are directories.
  • At least one collection, tree, or item is defined.
  • No two primitives own overlapping paths.
  • Every primitive and every relation target was created by the same instance.

On success it prints each primitive's path.

Importing definitions never checks path conflicts, so hot reload can recreate definitions freely. Run lint or build in CI to enforce path ownership.

qino check

Reads and validates every content file against its schema:

  • Collections: every file in the directory.
  • Trees: the full walk, including anchor files and _order.json consistency.
  • Items: the single file.

Views, relation resolution, filter, sort, and augment do not run. An empty collection or tree logs a warning but does not fail. Validation errors are reported with the primitive and file path.

qino build

Runs lint, then check, then writes qino/_generated/types.d.ts. The file augments @qino/cms with a slug union per collection and tree:

qino/_generated/types.d.ts
// Generated by qino — do not edit. Run `qino build` to regenerate.

export {};

declare module "@qino/cms" {
  export type PostSlug = "hello-world" | "second-post" | (string & {});
  export type DocSlug =
    | "introduction"
    | "guides"
    | "guides/setup"
    | (string & {});

  interface QinoSlugRegistry {
    "/docs": DocSlug;
    "/posts": PostSlug;
  }
}

The write is idempotent: the file is only rewritten when its content changes. Items have no slugs and are skipped. A directory with no entries is omitted so its slug type stays string. See Types.

Exit codes

CodeWhen
0Success
1Unknown or missing command, or any thrown error

Errors are printed as Error: <message>. The messages are listed in Errors.

Wiring it in

Run build before the app builds:

package.json
{
  "scripts": {
    "prebuild": "qino build",
    "build": "next build"
  }
}

In a monorepo, run it in the package that owns qino/, since discovery is relative to the working directory.