# CLI

The mirror CLI, its commands, and their arguments.

The CLI is the tool behind the other three deliverables: it scaffolds the
config, compiles the token set, generates the utility stylesheet, writes the
Figma export, and describes the result in one document. It reads one config
file, writes everything into one directory, and keeps no other state.

```bash
pnpm mirror <command>
```

::docs-table
---
columns:
  - label: Command
  - label: Description
rows:
  - items:
      - label: init
      - label: Writes a starting `mirror.config.ts` into the current directory, and a token tree beside it if you ask for one.
        plaintext: true
  - items:
      - label: tokens
      - label: Fetches the token source and compiles it into `.maas/tokens/`, and copies the typography file in beside it for the Figma export.
        plaintext: true
  - items:
      - label: css
      - label: Generates the utility stylesheet from the compiled tokens.
        plaintext: true
  - items:
      - label: figma
      - label: Writes one DTCG file per collection mode and a styles payload into `.maas/figma/`, read from the compiled tokens alone.
        plaintext: true
  - items:
      - label: dsds
      - label: Writes one YAML document describing the system, its themes and breakpoints, and its components into `mirror.dsds.yaml`.
        plaintext: true
---
::

Nothing here prompts and nothing here writes state of its own. Both build
commands read `mirror.config.ts` from the directory they run in, and stop with
an error naming that directory if there is none.

## `mirror init`

```bash
pnpm mirror init
```

This writes a `mirror.config.ts` that extends ours, which is the smallest
config that runs.

```ts
import { extendMirrorConfig } from '@maas/mirror'

export default extendMirrorConfig({
  source: { type: 'git', repo: 'magicasaservice/mirror-tokens' },
})
```

The file is yours from there on, so a second run leaves an existing one alone
and says so. Pass `--force` to overwrite it.

### Starting from your own tokens

The config above downloads the token repository on every build. To edit the
tokens, run `mirror init --tokens`: it copies the token tree into `./tokens`
and writes a `mirror.config.ts` whose `source` reads that directory.

```bash
pnpm mirror init --tokens
```

```ts
import { extendMirrorConfig } from '@maas/mirror'

export default extendMirrorConfig({
  source: { type: 'local', path: './tokens' },
})
```

Check `./tokens` in with the rest of the project. Every machine and every CI
runner then resolves the same relative path, and `mirror tokens` compiles the
files on disk rather than fetching a ref somebody else can move.

::docs-table
---
columns:
  - label: Argument
  - label: Type
rows:
  - items:
      - label: --tokens
        description: Writes a token tree into the project and points `source` at it rather than at our repository.
      - label: boolean
  - items:
      - label: --from
        description: Which repository the tree is copied from. Takes the same string `--source` does, so a directory inside the repository and a ref both work. Defaults to `github:magicasaservice/mirror-tokens`.
      - label: string
  - items:
      - label: --dir
        description: Where the tree is written, resolved against `--cwd`. Whatever you pass is what the written config reads. Defaults to `./tokens`.
      - label: string
  - items:
      - label: --force
        description: Overwrites the config file, and writes over a token directory that already holds files.
      - label: boolean
---
::

Pass `--from` to copy a repository of your own rather than ours, and `--dir` to
land the tree somewhere other than `./tokens`.

```bash
pnpm mirror init --tokens \
  --from github:acme/design-tokens/packages/tokens#v3 \
  --dir ./design/tokens
```

That copies `packages/tokens` out of `acme/design-tokens` at `v3` into
`design/tokens`, and the config it writes reads `./design/tokens`. Nothing
records where the tree came from; from here on it lives in your own repository.

Both writes refuse without `--force`, and both are checked before the download
starts, so a run that cannot finish downloads nothing. An empty token directory
counts as absent; a directory holding anything at all is yours and is left
alone.

## `mirror tokens`

```bash
pnpm mirror tokens
```

This compiles the token source into `.maas/tokens/`, one directory per
platform. A Git source is downloaded into `<dist>/.temp` first and removed
again once the build is through, and a local source is read where it lies.

If the token source holds a typography file (`figma/type.json`, unless
`figma.typography` names another path), the run copies it verbatim into
`<dist>/tokens/figma/type.json`, so `mirror figma` can resolve text styles
from `dist` alone.

Which platforms build and where the tokens come from are both
`mirror.config.ts` decisions. See [Configuration](/tokens/configuration).

## `mirror css`

```bash
pnpm mirror css
```

This reads the compiled tokens and writes `.maas/tailwind.preset.css`, steered
by two keys under `utilities`.

`utilities.variables` decides which compiled tree is read, and defaults to
`true`. Left on, the generator reads `application.variables.js`, whose leaves
are `var(--app-*)` strings, so the emitted utilities point at custom
properties. Turned off, it reads `application.js`, whose leaves are resolved
colors, and the utilities end up with literals.

We recommend leaving it on, since utilities that point at custom properties are
what makes cascade theming work at all. A literal cannot be overridden by
loading another file, so a preset built with it off ignores every theme
override, dark mode included, and the generator prints a warning to that effect
on every run.

`utilities.targets` names the compiled targets the generator reads, in
precedence order, and defaults to `['application']`.

```ts
utilities: {
  variables: true,
  targets: ['application'],
}
```

::caution
`mirror tailwind` is a deprecated alias for `mirror css` and will be removed in
the next release. It runs the same generator and prints a notice, so rename the
script.
::

## `mirror figma`

```bash
pnpm mirror figma
```

This reads the compiled `json` tree and writes the Figma export into
`.maas/figma/`: one DTCG file per collection mode, ready for Figma’s own
variable import, plus a `styles.json` payload carrying the text and effect
styles. The collections and their modes are the ones `figma.collections` in
`mirror.config.ts` declares. Our stock config carries that key, so a config
built with `extendMirrorConfig` has it already. Without it the command stops,
and the error prints a roster read off your targets, ready to paste.

The command reads `dist` and nothing else, so the export regenerates without
refetching the token source. It needs the `json` platform in
`options.platforms`, and stops with an error naming the key without it.

## `mirror dsds`

```bash
pnpm mirror dsds --content apps/docs/content
```

This writes one YAML document describing the system, in the
[Design System Documentation Specification](https://designsystemdocspec.org)
(DSDS) format, so a tool or a model that reads DSDS reads Mirror without a
parser of its own. The document holds the system itself, read from the root
`package.json`; one theme per configured target and one breakpoint per
`breakpoint/` target, read from `mirror.config.ts`; and one component per docs
page whose frontmatter carries `component`.

A component entry is read from its page and nothing else: the title and
description, the anatomy, the data attributes, the CSS variables, the errors
and the usage guidelines. Without `--content` the document carries the system,
theme and breakpoint entries only, which is what a project with its own targets
and no component pages gets.

::docs-table
---
columns:
  - label: Argument
  - label: Type
rows:
  - items:
      - label: --content
        description: The directory of markdown pages the component entries are read from, resolved against `--cwd`. Leave it out and no component entries are written.
      - label: string
  - items:
      - label: --components
        description: The directory holding one folder per component, resolved against `--cwd`, so each component entry points at its source. Leave it out and the entries point at the installed package instead.
      - label: string
  - items:
      - label: --out
        description: Where the document is written, resolved against `--cwd`. Defaults to `mirror.dsds.yaml` in the working directory.
      - label: string
---
::

Every path in the document is relative to the directory it is written to, so
the file validates where it lies. The command prints the file it wrote and a
warning for a page that carries `component` but no `title` or `description`,
for an alternative or a `partOf` naming a page that does not exist, and for a
`checks` path that does not exist. A warning never stops the write.

The docs site regenerates the document on every build and serves it at
`/mirror.dsds.yaml`.

## Arguments

The build commands take the same two.

::docs-table
---
columns:
  - label: Argument
  - label: Type
rows:
  - items:
      - label: --cwd
        description: Project root the config file is read from, and everything else is resolved against. Defaults to the directory you ran the command in.
      - label: string
  - items:
      - label: --source
        description: Overrides the config’s `source` for this run, and nothing else.
      - label: string
---
::

A Git source is written as a provider prefix and a repository, optionally
followed by a directory inside it and by `#` and a ref.

::docs-table
---
columns:
  - label: Value
  - label: Reads as
rows:
  - items:
      - label: github:magicasaservice/mirror-tokens
        plaintext: true
      - label: The `main` branch, tokens in `tokens/`
        plaintext: true
  - items:
      - label: gitlab:acme/tokens#next
        plaintext: true
      - label: The `next` branch on GitLab
        plaintext: true
  - items:
      - label: github:acme/design/packages/tokens#v2
        plaintext: true
      - label: The `v2` ref, tokens in `packages/tokens/`
        plaintext: true
  - items:
      - label: ./tokens, ../design-tokens/tokens
        plaintext: true
      - label: A local directory
        plaintext: true
---
::

Anything starting with one of `github`, `gitlab`, `bitbucket` or `sourcehut`
followed by a colon is a repository, anything else is a path, and an
unrecognized prefix is an error rather than a path, so a typo is reported
instead of being treated as a directory name.

Only the source is overridable from outside. Targets, platforms, `dist` and
`utilities` describe what your project builds rather than which checkout builds
it, so they stay in the file where they can be reviewed.

## Further reading

- [Configuration](/tokens/configuration): every key of `mirror.config.ts`.
- [Quick start](/overview/quick-start): both commands in a build script.
