@407dev/ipolish-content-schema
v0.3.0
Published
Shared Zod schemas + inferred types for ipolish CMS-authored content (the CMS↔app contract).
Readme
@407dev/ipolish-content-schema
Single source of truth for the CMS↔app content contract — the shapes that
ipolish-cms writes into layout-component content JSON and ipolish-app reads
back. Zod schemas with inferred TypeScript types.
Why this exists
The app and CMS used to each keep their own hand-maintained copy of these shapes.
When the CMS changed vibe name/description from plain strings to localized
maps ({ en, es, ... }), the app's copy didn't follow and rendered an object as a
React child → production crash.
With one shared schema, the app imports the inferred type and runs tsc in
CI, so widening a field here makes every mismatched consumer fail to compile
instead of crashing in production. (Runtime safeParse gating is a planned Phase 2
on top of this.)
What it owns
Self-contained authored content (no Supabase/DB types):
LocalizedText/localized()— the inline translation primitive (4-level locale fallback: direct → case-insensitive → base-language →en)- Quiz content + scoring helpers (
QuizContentSchema,getQuizResultIndex, …) - Vibe content —
StoredVibe/StoredVibeSchema(loose: tolerates legacy plain-stringname/description) andVibe/VibeSchema(normalized maps) - Banner content (
BannerContentSchema)
It owns only the shared read contract. CMS-only write concerns — seed data
(DEFAULT_VIBES, DEFAULT_QUIZ_CONTENT) and normalizeVibe — live in the CMS,
built on the types exported here. Runtime-hydrated content that references
Supabase rows (picture-palettes, trending-colors, video-list) stays in the app.
Two entry points
@407dev/ipolish-content-schema— Zod schemas + types + helpers. Used by the CMS (which validates content). Requireszod.@407dev/ipolish-content-schema/types— structural types + pure helpers, no Zod at runtime and none in the type graph. Used by the app (React Native) so Zod never enters its bundle.zodis an optional peer dependency for this reason.
Publishing (public npm)
Published to the public npm registry as the scoped public package
@407dev/ipolish-content-schema. It installs over the plain npm protocol, so
consumers never clone or build it (works on any yarn/pnpm/git version, no
EAS-yarn risk) — and because it's public, no token is needed to install it.
CI publishes on a release tag (.github/workflows/publish.yml); manual fallback:
pnpm version <patch|minor> # bump + push tag
pnpm publish # prepare → tsup builds dist, then publishesPublishing requires an npm token with publish rights on the @407dev org
(NPM_TOKEN repo secret in CI; npm login locally). publishConfig.access is
public so the scoped package is published publicly rather than private.
Consuming it
No registry config and no token — it's a normal public npm package. Just depend on it by version:
"@407dev/ipolish-content-schema": "^0.3.0"A content-shape change is not done until a new version is published and green
in both ipolish-cms and ipolish-app.
Local development
Link locally instead of waiting on a publish:
pnpm dev # tsup --watch → rebuilds dist/ on every save
# app (yarn 4): yarn add @407dev/ipolish-content-schema@portal:../ipolish-content-schema
# cms (pnpm): "@407dev/ipolish-content-schema": "link:../ipolish-content-schema"Metro/Vite consume compiled dist/, so a build must always be present.
Scripts
pnpm build— emitdist/(ESM + CJS + d.ts) via tsuppnpm dev— watch buildpnpm test— schema tests (vitest)pnpm type-check—tsc --noEmit
