@weblakecms/cli
v0.1.2
Published
WebLake CLI — init, schemas, and content management
Readme
@weblakecms/cli
The official WebLake command-line tool. Use it to scaffold projects, export and import content schemas and items, and generate typed React block renderer components from your CMS schema definitions.
weblake <command> [sub-command] [options]Table of Contents
Installation
Global (recommended for local development)
npm install -g @weblakecms/cli
# or
pnpm add -g @weblakecms/cliMonorepo / project-local
npm install --save-dev @weblakecms/cli
# then run via
npx weblake <command>From source (development)
cd packages/cli
npm run build
node dist/index.js --helpConfiguration
The CLI reads two files from the current working directory (or any ancestor directory):
| File | Purpose |
|---|---|
| .weblake.json | Management API credentials (apiUrl, token) |
| .env.local | Delivery API credentials (WEBLAKE_API_KEY) |
Run weblake init to create both interactively. Add both files to
.gitignore — they contain secrets.
.weblake.json format
{
"apiUrl": "http://localhost:3000",
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}apiUrl— Base URL of your CMS instance (no trailing slash).token— Management JWT. Obtain it from your browser'slocalStorage(authToken) after logging into the CMS, or from the Platform UI API key section.
.env.local format
WEBLAKE_API_URL=http://localhost:3000
WEBLAKE_API_KEY=wl_live_...Only required when you also use @weblakecms/sdk in the same project.
Commands
weblake init
Interactively configures the CLI for the current workspace.
weblake initWhat it does:
- Prompts for
CMS API URL(default:http://localhost:3000). - Prompts for your
Management JWTtoken. - Prompts for your
Delivery API Key(wl_live_…). - Writes
.weblake.jsonwith the management credentials. - Writes or patches
.env.localwith the delivery credentials. - Patches
.gitignoreto exclude both files.
Example session:
$ weblake init
WebLake CLI — workspace setup
──────────────────────────────────────────
? CMS API URL: http://localhost:3000
? Management JWT (from browser localStorage > authToken): eyJ...
? Delivery API Key (wl_live_...): wl_live_abc123
✅ Written .weblake.json
✅ Written .env.local
✅ .gitignore updatedweblake schemas export
Downloads all schemas for the configured tenant as a JSON bundle.
weblake schemas export [options]Options:
| Flag | Default | Description |
|---|---|---|
| -o, --output <file> | schemas.json | Path to write the bundle |
Example:
# Export all schemas to the default file
weblake schemas export
# Export to a custom location
weblake schemas export -o ./backups/schemas-2025-01-15.jsonBundle format:
{
"version": "1.0",
"type": "weblake-schema-bundle",
"exportedAt": "2026-06-16T12:00:00.000Z",
"schemas": [
{
"slug": "blog-post",
"name": "Blog Post",
"tier": "content_type",
"currentSchema": {
"fields": [...],
"$refs": {
"link": { "name": "Link", "fields": [...] },
"button": { "name": "Button", "fields": [...] }
}
}
}
]
}- Every schema entry nests its field definitions and cross-schema references
inside
currentSchema. $refsis populated automatically by the export endpoint — it inlines all schemas referenced by field types (e.g.type: "banner") plus the built-in composite types (link,button,menu_item) so the CLI has full type information without needing a live API connection.- The bundle is suitable for version control and for importing into another WebLake instance.
Tip: The CMS Copy JSON button (on the Schemas page) emits the same bundle envelope even for a single schema — so you can always pipe its output directly to
weblake generate.
weblake schemas import
Uploads a previously exported schema bundle back into the CMS. Existing schemas with matching slugs are updated; new slugs are created.
weblake schemas import [options]Options:
| Flag | Default | Description |
|---|---|---|
| -f, --file <file> | schemas.json | Path to the bundle file |
Example:
# Import from the default file
weblake schemas import
# Import from a specific bundle
weblake schemas import -f ./backups/schemas-2025-01-15.jsonNotes:
- Field order within a schema is preserved from the bundle.
- Importing does not delete schemas that are absent from the bundle.
- The API applies upsert semantics: slug is the unique key.
weblake content export
Downloads published content items as a JSON bundle.
weblake content export [options]Options:
| Flag | Default | Description |
|---|---|---|
| -o, --output <file> | content.json | Path to write the bundle |
| -s, --schema <slug> | (all schemas) | Export only items of this schema type |
| --ids <id,...> | (all items) | Export only specific item IDs (comma-separated) |
Examples:
# Export all content
weblake content export
# Export only blog posts
weblake content export --schema blog-post -o blog-posts.json
# Export specific items
weblake content export --ids abc123,def456 -o selected.jsonBundle format:
{
"exportedAt": "2025-01-15T12:00:00.000Z",
"tenantId": "acme",
"items": [
{
"id": "abc123",
"schemaSlug": "blog-post",
"slug": "my-first-post",
"status": "published",
"data": { "title": "My First Post", ... },
"publishedAt": "2025-01-10T09:00:00.000Z"
}
]
}weblake content import
Uploads a content bundle to the CMS. Items are upserted by id; missing
items are created; existing items are updated.
weblake content import [options]Options:
| Flag | Default | Description |
|---|---|---|
| -f, --file <file> | content.json | Path to the bundle file |
Example:
# Import from the default file
weblake content import
# Import from a specific bundle
weblake content import -f ./blog-posts.jsonNotes:
- The
statusfield in the bundle is respected. Items that werepublishedin the export will be published after import. - Importing does not delete content that is absent from the bundle.
- Referenced media assets must already exist in the target tenant.
weblake generate
Generates fully-typed TypeScript/TSX block renderer components from a WebLake schema JSON definition or bundle.
weblake generate [schema-file] [options]Arguments / options:
| Argument / flag | Default | Description |
|---|---|---|
| [schema-file] | (stdin) | Path to a schema JSON file or bundle. If omitted, JSON is read from stdin. |
| -n, --name <name> | (derived from slug) | Override the display name — single-schema mode only |
| -o, --out <path> | (cwd) | Output file (single schema) or directory (bundle). See behaviour table below. |
| --no-register | (auto-register) | Skip auto-patching registry.tsx |
Accepted input formats
The command auto-detects the input format:
| JSON shape | Behaviour |
|---|---|
| { "type": "weblake-schema-bundle", "schemas": [...] } | Bundle mode — one .tsx file per schema entry |
| { "slug": "...", "fields": [...] } | Single-schema mode — one .tsx file |
| [{ "name": "...", "type": "..." }, ...] | Raw fields array — one .tsx file (slug required separately) |
-o output path behaviour
| Condition | Result |
|---|---|
| Bundle + multiple schemas + -o src/blocks/ | Writes {ComponentName}.tsx per schema into src/blocks/ |
| Bundle + one schema + -o src/blocks/Hero.tsx | Writes to that exact file (single-schema mode forwarded) |
| Single schema + -o src/blocks/Hero.tsx | Writes to that exact file |
| Single schema + no -o | Writes {ComponentName}.tsx in the current directory |
| Bundle + no -o | Writes one {ComponentName}.tsx per schema in the current directory |
Getting the schema JSON
Option A — Copy JSON from the CMS UI (recommended):
- In the CMS, go to Schemas.
- Click the 📜 Copy JSON button next to any schema — or click Export to download all schemas as a bundle.
- The clipboard always contains a
weblake-schema-bundleenvelope, even for a single schema. Paste directly into a file or pipe toweblake generate.
# macOS — paste clipboard directly to the generator
pbpaste | weblake generate -o src/blocks/Hero.tsx
# Save first, then generate
pbpaste > hero-bundle.json
weblake generate hero-bundle.json -o src/blocks/Hero.tsxOption B — Generate all blocks from a full CMS export:
# Export all schemas from the CMS
weblake schemas export -o schemas.json
# Generate a component for every schema in the bundle
weblake generate schemas.json --out src/blocks/Examples
# Single-schema bundle from the CMS → explicit output path
weblake generate hero-bundle.json -o src/blocks/Hero.tsx
# Multi-schema bundle → one file per schema in src/blocks/
weblake generate schemas-export.json --out src/blocks/
# Read from stdin
pbpaste | weblake generate --out src/blocks/
# Override the display name in the generated file header
weblake generate hero-bundle.json -n "Hero Section" -o src/blocks/Hero.tsx
# Skip registry.tsx auto-patching
weblake generate hero-bundle.json -o src/blocks/Hero.tsx --no-registerGenerated file structure
For a schema with slug hero, the command produces Hero.tsx:
/**
* Block renderer for "Hero" (schema slug: hero)
*
* Generated by: weblake generate hero-bundle.json
*
* Register this component in your app:
*
* import { createRegistry } from '@weblakecms/client';
* import { Hero } from './components/Hero';
*
* const registry = createRegistry({
* overrides: { blockRenderers: { 'hero': Hero } },
* });
* <ContentRenderer content={content} registry={registry} />
*/
import React from 'react';
import type { BlockRendererProps } from '@weblakecms/client';
import { resolveStyles, themeToCssVars, themeToWrapperStyle, layoutToCssVars, layoutToContainerStyle } from '@weblakecms/client';
import { MediaValue, LinkValue } from '@weblakecms/ui';
// ── Typed data interface ──────────────────────────────────────────────────────
export interface HeroData {
/** Title */
title?: string;
/** Subtitle */
subtitle?: string;
/** Background Image */
image?: string;
/** CTA Variant */
cta_variant?: 'primary' | 'outline';
/** Primary CTA */
cta?: LinkData;
}
// ── Component ─────────────────────────────────────────────────────────────────
export function Hero({ data: rawData, schema }: BlockRendererProps) {
const data = rawData as HeroData;
const { theme, layout } = resolveStyles(rawData);
// themeToCssVars declares --color-* and --theme-* custom properties;
// themeToWrapperStyle binds them to actual CSS attributes (background-color, color, font-family…)
const wrapperStyle = {
...themeToCssVars(theme),
...layoutToCssVars(layout),
...themeToWrapperStyle(theme),
} as React.CSSProperties;
const containerStyle = layoutToContainerStyle(layout);
return (
<div data-block="hero" data-schema={schema.slug} style={wrapperStyle}>
<div style={containerStyle}>
{/* Title */}
{data.title && <span>{String(data.title)}</span>}
{/* Subtitle */}
{data.subtitle && <span>{String(data.subtitle)}</span>}
{/* Background Image */}
<MediaValue value={data.image} name="Background Image" />
{/* Primary CTA */}
<LinkValue value={data.cta} />
</div>
</div>
);
}Field type → TypeScript type and JSX
| Field type | TypeScript type | JSX (single value) | JSX (array — isArray: true) |
|---|---|---|---|
| text, slug, email, url, color, code, date, reference | string | {data.f && <span>{String(data.f)}</span>} | {data.f?.map((item, i) => <span key={i}>{String(item)}</span>)} |
| number | number | {data.f && <span>{String(data.f)}</span>} | {data.f?.map((item, i) => <span key={i}>{String(item)}</span>)} |
| boolean | boolean | {typeof data.f === 'boolean' && <span>{data.f ? 'Yes' : 'No'}</span>} | {data.f?.map((item, i) => <span key={i}>{item ? 'Yes' : 'No'}</span>)} |
| select | Union of choice values e.g. 'sm' \| 'lg' | {data.f && <span>{String(data.f)}</span>} | {data.f?.map((item, i) => <span key={i}>{String(item)}</span>)} |
| richtext | string | <RichTextValue value={data.f} /> | {data.f?.map((item, i) => <RichTextValue key={i} value={item} />)} |
| media | string | <MediaValue value={data.f} name="..." /> | {data.f?.map((item, i) => <MediaValue key={i} value={item} name="..." />)} |
| link | LinkData | <LinkValue value={data.f} /> | {data.f?.map((item, i) => <LinkValue key={i} value={item} />)} |
| button | ButtonData | <ButtonValue value={data.f} /> | {data.f?.map((item, i) => <ButtonValue key={i} value={item} />)} |
| menu | MenuItem[] | <MenuValue value={data.f} /> | (not applicable) |
| menu_item | MenuItemData | {data.f && <MenuValue value={[data.f]} />} | <MenuValue value={data.f} /> |
| blocks (with $ref) | {RefName}Data or {RefName}Data[] | {/* TODO: render block */} | {/* TODO: render block */} |
| datasource | never (resolved into __datasource) | typed sub-component (see below) | (not applicable) |
All @weblakecms/ui components (RichTextValue, MediaValue, LinkValue,
ButtonValue, MenuValue) are imported automatically — only the ones
actually needed by the schema's fields are included.
$refs — cross-schema references
When a schema field references another schema (e.g. type: "banner" or
blockRef: "link"), the CMS export inlines the referenced schema's field
definitions into currentSchema.$refs. The generator turns each entry in
$refs into a named TypeScript interface:
/** Link (schema: link) */
export interface LinkData {
label?: string;
icon_name?: string;
url_type?: 'url' | 'ref';
href?: string;
ref_schema?: string;
ref_id?: string;
target?: '_self' | '_blank';
}When a $ref slug matches a block already implemented in @weblakecms/client
(e.g. banner, hero, sliding_banner), an informational comment is added
above the interface:
// ℹ️ Banner renderer is already available in '@weblakecms/client' — import it directly.
export interface BannerData { ... }Built-in composite types
Three composite types are recognised by name and injected automatically into
the $refs map even when not present in the exported bundle:
| Type | Interface name | Key fields |
|---|---|---|
| menu_item | MenuItemData | label, icon_name, url_type, url, ref_schema, ref_id, children (recursive) |
| link | LinkData | label, icon_name, url_type, href, ref_schema, ref_id, target |
| button | ButtonData | label, icon_name, url_type, href, ref_schema, ref_id, variant, target |
datasource fields
Fields with type: "datasource" are resolved server-side by the Delivery API
and delivered to the component under data.__datasource.<fieldName>[]. The
generator:
- Emits a typed
__datasourceblock in the main data interface. - Generates a
{FieldName}Iteminterface for each item (using the schema fields from$refswhen the source is an internal schema). - Generates a
{FieldName}Itemsub-component that renders a single item. - Emits a
.map()loop in the main component body calling the sub-component.
// Generated interface (partial)
export interface ArticleListData {
__datasource?: {
articles?: ArticlesItem[];
};
}
// Generated sub-component
function ArticlesItem({ item }: { item: ArticlesItem }) {
return (
<div>
{/* title */}
{item.title !== undefined && <span>{String(item.title)}</span>}
{/* CTA — links to the content item's path */}
{item._meta?.path && (
<a href={item._meta.path} className="...">View →</a>
)}
</div>
);
}registry.tsx auto-patching
When a registry.tsx file exists one directory above the output file
(../registry.tsx), the command automatically:
- Adds an
importline for the new component (with.jsextension for ESM compatibility). - Inserts an entry into
DEFAULT_BLOCK_RENDERERS.
Example — before:
const DEFAULT_BLOCK_RENDERERS: Record<string, BlockRendererComponent> = {
'feature-grid': FeatureGrid,
};After running weblake generate hero-bundle.json -o blocks/Hero.tsx:
import { Hero } from './blocks/Hero.js';
const DEFAULT_BLOCK_RENDERERS: Record<string, BlockRendererComponent> = {
'feature-grid': FeatureGrid,
'hero': Hero,
};If the component is already registered the command prints an info message and
skips the patch. Pass --no-register to skip this step entirely (e.g. when
the registry lives in a non-standard location or you prefer manual wiring).
weblake clientapp
Scaffolds a brand-new Next.js client site by copying the live apps/client
template from the monorepo into apps/<slug>.
Because the template is read at runtime — not embedded in the CLI package
— every improvement you make to apps/client is automatically available the
next time the command is run.
The generated app is a full monorepo workspace member so all @weblakecms/*
workspace dependencies resolve automatically without any extra configuration.
weblake clientapp <name> [options]Arguments
| Argument | Description |
|---|---|
| <name> | Display name for the new app (slugified automatically, e.g. "My Shop" → my-shop) |
Options
| Option | Default | Description |
|---|---|---|
| --api-url <url> | http://localhost:3000 | CMS / Delivery API base URL |
| --api-key <key> | (prompted) | Delivery API key (wl_live_...) — written to .env.local |
| --port <port> | 3004 | Dev-server port for the new app |
| --out <dir> | apps/<slug> in monorepo root | Override the output directory (absolute or relative to CWD) |
Examples
# Scaffold with all options — no prompts
weblake clientapp "Acme Store" \
--api-url https://cms.acme.com \
--api-key wl_live_abc123 \
--port 3010
# Scaffold with an explicit output directory
weblake clientapp my-blog --out ../projects/my-blog
# Minimal — prompts for API key, defaults to port 3004
weblake clientapp "Shore Demo"What gets created
apps/shore-demo/
├── src/
│ ├── app/ # Next.js App Router pages
│ │ ├── [[...slug]]/ # Catch-all CMS page renderer
│ │ ├── api/revalidate/ # ISR revalidation webhook
│ │ └── globals.css
│ ├── components/ # Icon + block renderer stubs
│ └── lib/weblake.ts # createWebLakeClient + createRegistry() setup
├── public/ # Static assets + SVG icon library
├── package.json # name: @weblakecms/shore-demo, port: 3004
├── next.config.ts
├── tailwind.config.ts
├── tsconfig.json
├── .env.local # WEBLAKE_API_URL + WEBLAKE_API_KEY ← gitignored
└── .gitignoreFiles never copied from the template: node_modules, .next, .turbo,
.env, .env.local, tsconfig.tsbuildinfo.
After scaffolding
# 1. Register the new workspace and install deps (from the monorepo root)
cd /path/to/weblake.shore.pt
npm install
# 2. Start the dev server
npx turbo dev --filter=@weblakecms/shore-demo
# or
cd apps/shore-demo && npx next dev --port 3004
# 3. Add custom block renderers in src/components/blocks/
weblake generate my-schema.json -o apps/shore-demo/src/components/blocks/MyBlock.tsx
# 4. Register the block in src/lib/weblake.ts using createRegistry()Typical workflow
New client site from scratch
# 1. Scaffold a new client app from the live template
weblake clientapp "My Site" --api-url http://localhost:3000 --port 3004
# 2. Install the new workspace
cd /path/to/weblake.shore.pt && npm install
# 3. Export all schemas and generate all block renderers in one step
weblake schemas export -o schemas.json
weblake generate schemas.json --out apps/my-site/src/components/blocks/
# 4. Start the dev server
npx turbo dev --filter=@weblakecms/my-siteExisting project — schema + content management
# 1. Configure the CLI for a project
cd my-weblake-project
weblake init
# 2. Export all schemas to keep them in version control
weblake schemas export -o cms/schemas.json
# 3a. Generate a single block renderer
# (use the CMS "Copy JSON" button, then paste the bundle)
pbpaste | weblake generate -o src/components/blocks/Hero.tsx
# 3b. Or generate all blocks from the export bundle at once
weblake generate cms/schemas.json --out src/components/blocks/
# 4. The component(s) are ready — customise the JSX to match your design
# 5. Export content for seeding a staging environment
weblake content export --schema blog-post -o cms/blog-posts.json
# 6. Import content into staging
WEBLAKE_TOKEN=... weblake content import -f cms/blog-posts.json