# Config Layers & Generation

A Zudoku configuration can be composed from multiple layers. This enables a workflow where a tool —
for example, the Zuplo portal — emits a serializable spec that `zudoku generate` turns into a typed
base config, while your hand-written `zudoku.config.ts` stays editable on top.

## Extending configurations

Use `extends` to compose your config from base layers. Like in a `tsconfig.json`, entries are module
specifiers resolved relative to the config file (extension optional):

```ts title=zudoku.config.ts
import type { ZudokuConfig } from "zudoku";

const config: ZudokuConfig = {
  extends: ["./zudoku.base"],
  site: { title: "My Override" },
};

export default config;
```

String layers are resolved **lazily**, when the config is loaded — not when it is written or
type-checked. This matters for generated layers: a tool can emit `zudoku.base.ts` just before
`zudoku dev` or `zudoku build` runs, and the committed config never needs the file to exist. If a
referenced layer is missing at load time, Zudoku fails with a hint to run `zudoku generate`.

Entries can also be inline config objects (e.g. an imported preset), and both forms can be mixed.

Layers are merged in order, with the config itself applied on top:

- **Scalars and nested objects**: later layers win, and the top-level config wins over all layers.
- **`plugins`**: concatenated in layer order, with the config's own plugins last (never replaced).
- **All other arrays** (e.g. `redirects`, `navigation`): replaced by the later layer.

Layers can themselves use `extends`; they are resolved depth-first. In dev, layer files are watched
like the config file itself, so regenerating a layer reloads the site.

## Generating a config layer

`zudoku generate` turns a serializable spec into a typed config module with real plugin imports:

```sh
zudoku generate spec.json -o zudoku.base.ts
```

The spec is a JSON projection of the Zudoku config where `plugins` are references into a curated set
of plugins instead of code:

```json title=spec.json
{
  "$schema": "https://schemas.zudoku.dev/config-spec/v1.json",
  "metadata": { "title": "Acme" },
  "apis": [{ "type": "url", "input": "https://api.acme.dev/openapi.json", "path": "api" }],
  "plugins": [
    {
      "id": "graphql",
      "options": { "type": "url", "input": "https://api.acme.dev/graphql", "path": "graphql" }
    }
  ]
}
```

The generated output is a normal, typed Zudoku config — it builds and renders like any hand-written
config:

```ts title=zudoku.base.ts
// Generated by `zudoku generate`. Do not edit.
import { graphqlPlugin } from "@zudoku/plugin-graphql";
import type { ZudokuConfig } from "zudoku";

const config: ZudokuConfig = {
  metadata: {
    title: "Acme",
  },
  apis: [
    {
      type: "url",
      input: "https://api.acme.dev/openapi.json",
      path: "api",
    },
  ],
  plugins: [
    graphqlPlugin({
      type: "url",
      input: "https://api.acme.dev/graphql",
      path: "graphql",
    }),
  ],
};

export default config;
```

A typical setup regenerates the layer right before starting Zudoku and keeps it out of version
control:

```json title=package.json
{
  "scripts": {
    "generate": "zudoku generate spec.json -o zudoku.base.ts",
    "dev": "pnpm generate && zudoku dev",
    "build": "pnpm generate && zudoku build"
  }
}
```

Config keys that hold code — `slots`, `mdx`, `customPages`, `build`, and function options — can't be
expressed in a spec. Add them in your `zudoku.config.ts` on top of the generated layer.

The `$schema` URL encodes the spec version. Specs with an unknown or unsupported version fail the
generator with a clear error, as do unknown plugin ids and invalid plugin options.

## The spec's JSON Schema

`zudoku schema` emits the JSON Schema the spec is validated against, composed from the serializable
config keys and the option schemas of all curated plugins:

```sh
zudoku schema -o config-spec.json   # or print to stdout: zudoku schema
```

Use it to validate specs in editors and UIs before running the generator. The same schema is also
available programmatically:

```ts
import { buildSpecJsonSchema, generateConfig, validateSpec } from "zudoku/codegen";
```

See the [`config-spec` example](https://github.com/zuplo/zudoku/tree/main/examples/config-spec) for
a complete project using a generated layer.
