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.
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:
- A
qino/folder. qino/index.tsthat default-exports aninitQinoinstance.- Definitions exported from any
.ts,.tsx,.js, or.mjsfile underqino/.
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.
contentFolderandmediaFolderexist 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.jsonconsistency. - 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:
// 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
| Code | When |
|---|---|
0 | Success |
1 | Unknown 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:
{
"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.