@tentocms/typegen
v1.0.1
Published
TypeScript type generator for TentoCMS schemas
Downloads
54
Readme
@tentocms/typegen
TypeScript type generator for TentoCMS. Generates fully-typed definitions from your CMS schemas for type-safe content fetching.
Installation
Not yet on npm.
@tentocms/typegenis not currently published to the npm registry. Until it is, install it from source: build this package from the TentoCMS monorepo and add it as a local/workspace dev dependency. The command below is the intended usage once the package is published.
npm install -D @tentocms/typegenQuick Start
npx tentocms-typegen init # Create config file
npx tentocms-typegen generate # Generate typesimport type { BlogPost } from './types/cms'
const page: BlogPost = await response.json()
console.log(page.content.author.name) // Fully typedCLI
init — layout-aware output path
init detects your project layout and writes a sensible default output into the generated tentocms.config.js:
| Detected layout | Default output |
| --- | --- |
| A ./src/ directory exists | ./src/types/cms.ts (classic layout) |
| No ./src/ directory | ./types/cms.ts |
Detection is a single check for the absence of ./src/ — there's no nuxt.config/app/-dir inspection. This avoids the old behaviour where Nuxt 4 projects (which have no src/ dir) got a default pointing at a non-existent path. Adjust the output value afterwards if you prefer a different location.
generate — flag reference
| Flag | Description | Resolution order |
| --- | --- | --- |
| -o, --output <path> | Output file path for the generated types | flag → tentocms.config.js output → ./src/types/cms.ts (fixed fallback; note this is not layout-aware — the ./src/-detection logic described above only runs inside init, not generate) |
| -k, --api-key <key> | TentoCMS API key | flag → tentocms.config.js apiKey → TENTO_API_KEY env var |
| -u, --api-url <url> | TentoCMS API base URL | flag → tentocms.config.js apiUrl → TENTO_BASE_URL env var |
| -c, --config <path> | Path to config file | Declared but currently has no effect. generate always loads ./tentocms.config.js from the current working directory (loadConfig() takes no arguments); passing -c/--config does not change which file is read. |
| -e, --env <path> | Load environment variables from this file (see below) | — |
| --env-file <path> | Alias of -e/--env (kept for older Node; Node ≥22/24 reserves a bare --env-file) | — |
| -w, --watch | Watch for schema changes and regenerate types (polling, not filesystem watching — polls the CMS API) | — |
| -i, --interval <seconds> | Polling interval in seconds, watch mode only | flag → tentocms.config.js watch.interval → 30 (default). Ignored unless -w/--watch is also set. |
init never accepts flags — only generate does.
generate — .env loading
generate resolves TENTO_API_KEY and TENTO_BASE_URL from CLI flags → tentocms.config.js → process.env. To make local runs ergonomic it also loads a .env file into process.env before reading those variables:
# Auto-loads ./.env if it exists (silent if absent)
tentocms-typegen generate
# Load an explicit env file
tentocms-typegen generate -e ./config/.env.cms
tentocms-typegen generate --env ./config/.env.cmsRules:
-e, --env <path>— load the given file (errors clearly if it doesn't exist).- No flag — auto-load
./.envfrom the current directory if present; do nothing if it's absent. - The parser is dependency-free: it skips blank lines and
#comments, parsesKEY=VALUE, and strips one surrounding pair of single/double quotes from the value. - Real environment variables are never overridden — a key already present in
process.envalways wins. This keeps the existingnode --env-file=.env node_modules/.bin/tentocms-typegen generateworkflow working (those keys are already set, so the loader is a no-op for them).
Node ≥ 22/24 note: newer Node versions reserve a bare
--env-fileon the command line for their own native loader and swallow it before it reaches this CLI. Use-e/--envinstead (an--env-filealias is registered for older Node only).
*Fields vs *Content types
For every page type, generate emits two interfaces:
*Content(e.g.BlogPostContent) — includes the base fieldsid,slug,title,seo?plus the custom fields. Use it to type a whole page object.*Fields(e.g.BlogPostFields) — only the custom fields (noid/slug/title/seo). The runtimepage.fieldsobject contains only the custom fields (id/slug/title live top-level,seolives underpage.seo), so use*Fieldsto typepage.fieldsdirectly:
import type { BlogPostContent, BlogPostFields } from './types/cms'
const page = await fetchPage() // shape ~ Page<...>
const fields: BlogPostFields = page.fields // ✅ exact custom-field shapeBoth interfaces are exported. *Content is unchanged for back-compat.
Known limitations
- Resolved-reference / repeater shapes can differ across components when the source schema models the same field differently. For example, a
backgroundfield defined as a reference in one component resolves toResolvedReference, while the same conceptual field modelled as a plain object in another component is emitted asRecord<string, unknown>. Likewise, an untyped repeater (arepeaterfield whose schema declares no sub-fields) falls back toRecord<string, unknown>[]. The generator faithfully reflects the source schema — to get consistent, fully-typed output, align the field definitions in your TentoCMS schema (use the same field type and define repeater sub-fields everywhere the field appears).
