Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions apps/web-docs/content/docs/cli.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ studio init [projectDir] [options]

Skips scaffolding if a `studio.config.ts` already exists. See [Getting Started](/docs/getting-started).

### Non-interactive / CI mode

`init` skips every prompt and uses defaults (project name `studio-project`, the `bento-feature` template, `en`+`ar` locales, dependencies installed) when `-y`/`--yes` is passed, **or automatically** when it detects a CI environment: `CI=true`/`CI=1`, `CIRCLECI=true`, `GITHUB_ACTIONS`, or `GITLAB_CI` is set. Useful for scaffolding a project from a script or agent without wiring `--yes` explicitly.

## `studio update`

Update an existing studio project to the latest CLI version adds the CLI dependency if missing and merges default config values into `studio.config.ts`.
Expand All @@ -45,6 +49,29 @@ studio update [projectDir] [options]
| ----------- | ------------------------- |
| `-y, --yes` | Skip confirmation prompts |

### Version gate

`update` compares your **installed** CLI version (from your own `package.json`) against the **latest published** version before doing anything. If a newer CLI is available, it refuses to proceed:

```
CLI v0.3.0 is installed, but v0.4.0 is available.
Run `npm install @stdout-design/cli@latest` first, then re-run `studio update`.
```

Upgrade the CLI first, then run `studio update` again.

### What gets merged

`update` diffs your `studio.config.ts` against the scaffold defaults for your installed version and:

- Adds any new default presets/templates you don't already have (existing entries with the same ID are left untouched).
- Flags presets whose IDs are deprecated (none currently).
- Bumps `scaffoldVersion`.
- Overwrites the static scaffold assets (`tsconfig.json`, `.gitignore`, `types.d.ts`).
- Bumps the `@stdout-design/cli` dependency in `package.json` to match.

If nothing would change, it prints "Project is already up to date." and exits without touching any files. Otherwise it prints a diff-style summary and asks for confirmation (skipped with `--yes`).

## `studio render`

Render a template single, or batch with `--data`.
Expand Down Expand Up @@ -122,6 +149,8 @@ studio dev [rootDir] [options]

`--open` is accepted but currently a no-op open `http://localhost:3030` manually.

`-p`/`--port` always wins over any environment variable if you're starting the dev server yourself (not through `studio dev`) rather than importing `startStandaloneServer`, it also reads a `PORT` environment variable when no port argument is given.

## `studio cache stats`

Show cache statistics location, compiled/pixel entry counts, disk usage, and the size limit.
Expand Down Expand Up @@ -170,3 +199,8 @@ See [AI Agents](/docs/agents).

- `0` success (including a cancelled `init`).
- `1` any error, or any failed render in a batch.

<Cards>
<Card title="Dev Server API" href="/docs/dev-server-api" />
<Card title="Core Package API" href="/docs/core" />
</Cards>
25 changes: 25 additions & 0 deletions apps/web-docs/content/docs/configuration.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ export default config;
| `locales` | `string[]` | No | Supported locale codes, e.g. `["en", "ar"]` |
| `outDir` | `string` | No | Output directory (default `./out`) |
| `fonts` | `Record<locale, FontConfig[]>` | No | Per-locale font config (see Internationalization) |
| `images` | `ImagePolicy` | No | Remote image/emoji fetch policy (see below) default denies all |
| `scaffoldVersion` | `string` | No | Written by `init`/`update`; not for manual edits |

### `templates`
Expand Down Expand Up @@ -88,6 +89,30 @@ These are just sizes name and use them however fits your workflow.

The preset selected by default in the studio and render fallbacks. If omitted, the first preset in the array is used.

### `images`

By default, remote images don't render a template's `<img src>`, `backgroundImage`, and `maskImage` URLs are **blocked until you allow them**. To load images from a CDN or your own domain, add an `allowUrl` check:

```ts
images: {
allowUrl: (url) => url.startsWith("https://cdn.example.com/"),
maxBytes: 5 * 1024 * 1024, // 5 MiB, optional
timeout: 8000, // ms, optional
fetch: myCustomFetch, // optional, defaults to globalThis.fetch
emoji: "twemoji", // optional emoji provider, or "from-font"
},
```

| Field | Type | Default | Description |
| --- | --- | --- | --- |
| `allowUrl` | `(url: string) => boolean` | blocks every URL | Return `true` for URLs you want fetched. |
| `maxBytes` | `number` | 32 MiB | Reject an image body larger than this. |
| `timeout` | `number` (ms) | 5000 | Give up fetching an image after this long. |
| `fetch` | `typeof fetch` | `globalThis.fetch` | Use a custom fetch e.g. to add auth headers or go through a proxy. |
| `emoji` | `EmojiType \| "from-font"` | off | Turn emoji into rendered glyphs. `"from-font"` uses a color emoji font you've registered instead of fetching. |

A blocked URL shows up as a clear render error naming the URL, so you know to add it to `allowUrl` rather than chasing a mystery failure.

## Validation & errors

`studio.config.ts` is loaded and validated on every command and by the dev server. Failures are reported as clear errors:
Expand Down
216 changes: 216 additions & 0 deletions apps/web-docs/content/docs/core.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
---
title: Core Package API
description: Use @stdout-design/core directly in your own scripts, servers, or build pipelines.
---

Everything the CLI and studio do to turn a template into pixels is exposed as a plain library. Reach for `@stdout-design/core` when you want to render outside the CLI a custom script, a queue worker, your own HTTP route, or a build step that needs images without shelling out.

```bash
npm install @stdout-design/core
```

## Quick start: render a component

The fastest path from a React component to PNG bytes is `renderComponent`. It validates props, renders, and writes the file for you:

```ts
import { renderComponent } from "@stdout-design/core";
import StatCard, { propsSchema } from "./templates/stat-card";

const { outputPath } = await renderComponent({
component: StatCard,
propsSchema,
props: { title: "Weekly Active Users", stat: "124.5K" },
preset: { id: "instagram-square", width: 1080, height: 1080 },
outDir: "./out",
format: "png", // "webp" | "png" | "jpeg" | "ico" | "raw"
});

console.log(outputPath); // ./out/single.default.instagram-square.0.png
```

No caching, no locale handling this is the one-shot version. For a template registered in `studio.config.ts` with locales, caching, and font resolution wired up, use `orchestrateRender` (below) or just call `runBatch`/the CLI.

## Rendering a whole project: `runBatch`

`runBatch` is what `studio render --data` runs under the hood: it loads your `studio.config.ts`, resolves the template, expands props × presets × locales × data rows, and writes a `manifest.json` alongside the images.

```ts
import { runBatch } from "@stdout-design/core";

const { manifest, elapsedMs } = await runBatch({
rootDir: process.cwd(),
templateId: "stat-card",
dataFile: "./data/stats.csv", // or pass `rows` directly as objects
presets: ["instagram-square", "x-card"],
locales: ["en", "ar"],
outDir: "./out",
failFast: false,
});

console.log(`${manifest.succeeded.length}/${manifest.totalCount} rendered`);
```

Use this when you want the CLI's batch behavior (data-driven rendering, manifest, per-cell error isolation) from inside your own script — a cron job, a CI step, an agent tool call.

## Building a custom pipeline

If `renderComponent`/`runBatch` don't fit for example, you're rendering inside a request handler and want fine control over caching and images the pieces they're built from are all exported.

### `compileTemplate`

Turns a JSX element into the intermediate form the renderer needs.

```ts
import { compileTemplate } from "@stdout-design/core";

const compiled = await compileTemplate(<StatCard title="Hello" stat="10K" />);
```

### `renderToPixels` / `measureTemplate` / `renderAutoSized`

```ts
import {
renderToPixels,
measureTemplate,
renderAutoSized,
} from "@stdout-design/core";

// Render at explicit dimensions
const { bytes, width, height } = await renderToPixels(
compiled,
{ width: 1080, height: 1080 },
{ format: "png" }
);

// Or find natural size first
const { width: w, height: h } = await measureTemplate(compiled);

// Or do both in one call — useful when a template has no fixed size
// (e.g. auto-sizing text-heavy cards)
const auto = await renderAutoSized(compiled);
```

### `registerFont`

Preload a font once so you don't re-pass its bytes on every render call:

```ts
import { registerFont } from "@stdout-design/core";

await registerFont({ name: "Inter", data: interFontBytes, weight: 700 });
```

### `orchestrateRender` / `orchestrateMeasure`

The layer that ties compile + render + caching + locale data + fonts + image policy together it's what the dev server's `/render` and `/measure` routes call, and what `runBatch` calls per cell. Reach for this when you want caching and locale support but don't want the full batch/CLI machinery:

```ts
import { orchestrateRender, openCache } from "@stdout-design/core";

const cache = await openCache(process.cwd());

const result = await orchestrateRender({
cache,
component: StatCard,
propsSchema,
props: { title: "Hello" },
templateId: "stat-card",
templateContentHash: "abc123", // any stable string identifying this template version
width: 1080,
height: 1080,
locale: "ar",
loadLocaleData: async (locale) => ({ title: "مرحبا" }),
images: { allowUrl: (url) => url.startsWith("https://cdn.example.com/") },
});

console.log(result.cacheHit, result.durationMs);
```

`orchestrateMeasure` is the same shape without `width`/`height`/`format` it returns just `{ width, height, durationMs }`.

## Loading a project's config

```ts
import { loadConfig, parseStudioConfig } from "@stdout-design/core";

// Load and validate studio.config.ts from a project root
const config = await loadConfig("./my-project");

// Or validate a config object you already have in hand
const result = parseStudioConfig(rawConfigObject);
if ("issues" in result) {
console.error(result.issues); // human-readable validation errors
} else {
console.log(result.config);
}
```

## Caching

`RenderCache` backs every render path (dev server, batch, CLI) with a two-tier cache: compiled templates in memory, rendered pixels on disk.

```ts
import { openCache } from "@stdout-design/core";

const cache = await openCache("./my-project", { cacheDir: "./.studio-cache" });

const stats = await cache.stats();
// { compiled: { entries }, pixels: { entries, sizeBytes, maxSizeBytes }, cacheDir }

await cache.clean(); // evict everything
cache.close();
```

You rarely construct `RenderCache` yourself `openCache` resolves the right cache directory for a project and wires it up. See [Caching](/docs/caching) for how cache keys and eviction work.

## Validating props

The same validation the CLI and studio use for a template's `propsSchema`:

```ts
import {
validateProps,
PropValidationError,
zodToJsonSchemaShape,
} from "@stdout-design/core";

try {
const props = validateProps(propsSchema, rawProps);
} catch (error) {
if (error instanceof PropValidationError) {
// error.issues: { field, message, suggestion?, ... }[]
console.error(error.issues);
}
}

// JSON Schema for a prop panel, form builder, or agent tool schema
const jsonSchema = zodToJsonSchemaShape(propsSchema);
```

`defineSchema` is a no-op type helper you'll see in templates it exists purely for TypeScript inference at the definition site, not for anything at runtime.

## Errors

Every error the core package throws deliberately (config, template loading, rendering, cache) is an `AppError` with a stable `code` from `ErrorCode`, so you can branch on failure type instead of parsing messages:

```ts
import { AppError, ErrorCode } from "@stdout-design/core";

try {
await loadConfig("./my-project");
} catch (error) {
if (error instanceof AppError && error.code === ErrorCode.CONFIG_NOT_FOUND) {
// handle missing config specifically
}
}
```

Common codes: `CONFIG_NOT_FOUND`, `CONFIG_INVALID`, `TEMPLATE_LOAD_FAILED`, `TEMPLATE_INVALID_EXPORT`, `TEMPLATE_NOT_FOUND`, `PROP_VALIDATION_FAILED`, `RENDER_FAILED`, `CACHE_INIT_FAILED`, `CACHE_WRITE_FAILED`.

<Cards>
<Card title="Configuration" href="/docs/configuration" />
<Card title="Dev Server API" href="/docs/dev-server-api" />
<Card title="OG Image Generation" href="/docs/og" />
<Card title="Caching" href="/docs/caching" />
</Cards>
Loading
Loading