Ginko version upgrades
Apply Ginko's version-specific hard cuts, remove superseded APIs, and verify the exact installed or packed artifact.
Ginko may make hard public-contract cutovers before 1.0. Upgrade directly to the documented end state; do not preserve old and new paths in parallel.
From 0.3 to 0.4
The 0.4.0 release candidates use npm's next channel. Certify the
exact prerelease before adopting it; stable 0.4.0 follows the RC soak.
pnpm add @lupinum/ginko-content@0.4.0-rc.21. Make the Markdown profile explicit
content.markdown.plugins now has a literal empty default. Nothing is enabled
implicitly, and omitting the option is identical to plugins: []. Add only the
syntax the application owns:
export default defineNuxtConfig({
content: {
markdown: {
plugins: [
'toc',
'shiki',
'summary',
],
},
},
})The zero-option shiki entry uses Ginko's bundled light/dark Material themes.
Replace the deprecated highlight plugin name with shiki. highlight
remains a warning-only alias during 0.4.x, but configuring both names is an
error. Custom registrations use imported Shiki objects under
themes: { light, dark } and languages; replace the invalid singular theme
and langs spellings, which now fail module setup instead of falling back
silently. Add Shiki directly to the application when its config imports custom
registration objects.
Ginko and @comark/vue now use the matched Comark 0.6.2 line. Custom Comark
plugin modules still run only in the build/server filesystem profile, and their
normalized AST must satisfy Ginko's closed inert render policy. Parser support
does not authorize Vue directives, bindings, event handlers, arbitrary active
HTML, or undeclared component props and slots.
2. Treat inline Markdown as its own safe API
ContentRendererInline intentionally uses a fixed client-safe profile for SSR,
hydration, and reactive updates. It supports standard Markdown, tables, task
lists, and normalized GFM alerts. It discards comments and does not inherit
build-time plugins, Shiki, footnotes, Math, Mermaid, custom MDC components, or
markdown.tags mappings.
Use ContentRenderer with an ingested document when output needs configured
plugins or application components. The inline components prop may replace a
renderer for an already authorized tag; it cannot authorize new syntax or
expand the safety policy.
3. Install optional render integrations deliberately
Math and Mermaid are emitted through build-generated literal imports so they work in production browser chunks and packed consumers. Install only the peer for the enabled plugin:
pnpm add katex
pnpm add beautiful-mermaidMath also needs its stylesheet in Nuxt:
export default defineNuxtConfig({
css: ['katex/dist/katex.min.css'],
content: {
markdown: {
plugins: ['math'],
},
},
})Do not add Comark-specific build.transpile, Vite ssr.noExternal, or Nitro
externals.inline overrides. Ginko and Comark package their ESM boundaries
without those blanket settings.
4. Use the universal Nuxt 404 recipe
Catch-all content pages must handle both direct requests and reused-page client
navigation. Key the page by route path and throw after the awaited composable;
do not guard the missing-page check with import.meta.server:
<script setup lang="ts">
import { pages } from '~~/content.config'
definePageMeta({ key: route => route.path })
const { page } = await useContentPage(pages)
if (!page.value) {
throw createError({
statusCode: 404,
statusMessage: 'Page not found',
fatal: true,
})
}
</script>
<template>
<ContentRenderer v-if="page" :value="page" />
</template>ContentRenderer now forwards inherited Vue attributes exactly once, including
its declared class prop. Remove wrapper workarounds that existed only because
the renderer dropped that class.
5. Keep server configuration private
The public runtime projection now contains only client-owned query, search,
locale, render, and integrity facts. Code must not read provider module
specifiers, filesystem source/exclude patterns, CMS settings, agent definitions,
schema inventories, or other server/build metadata from
runtimeConfig.public.content. Server integrations read the private
runtimeConfig.content boundary or use a documented package API.
This is a removal of accidental payload exposure, not a new compatibility bridge. Do not copy the removed server objects into another public config key.
6. Rebuild generated output
Canonical Markdown normalization now closes over Comark task lists, table alignment, fenced-code metadata, named component slots, GFM alerts, and comment tuples. Comments never enter renderer, search, summary, portable, or agent text. Typed YAML values on MDC components now survive portable asset rewrite and reparse.
The content cache format changed. Delete deployment/build caches and rebuild content snapshots and search indexes after upgrading. Portable document format v1 and the CMS wire format are unchanged; no portable-file migration is required.
7. Verify the exact candidate
Run application type checks, production build/static generation, and browser navigation tests against the installed prerelease. Exercise every enabled optional Markdown plugin in a production browser, not only in Node SSR. Provider, data-source, and portability integrations should run their exported conformance contracts against the same packed tarball.
From 0.2 to 0.3
Upgrade directly from 0.2.1 to 0.3.0; there is no intermediate stable
release.
1. Update the runtime
Ginko Content now requires Node.js 22.18–22.x, 24.11–24.x, or 26+. Update development, CI, build, and production runtimes together before changing the package version.
Install the stable release:
pnpm add @lupinum/ginko-content@0.3.0Pin the exact version when certifying a deployment, provider, or CMS consumer.
2. Replace deleted composables
The app-facing composable surface is intentionally small:
useContentPageowns route-page loading and optional surroundings.useContentSearchowns headless search state and results.
Replace the deleted list, navigation, pagination, backlink, surroundings, and
direct-read composables with the equivalent one-shot async query function from
@lupinum/ginko-content/client, paired with Nuxt's useAsyncData:
import { many, navigation } from '@lupinum/ginko-content/client'
import { docs } from '~~/content.config'
const { data: pages } = await useAsyncData(
'docs:list',
() => many(docs, { sort: { order: 'asc' } }),
)
const { data: tree } = await useAsyncData(
'docs:navigation',
() => navigation(docs),
)The current helpers and their return types are in the composable API reference.
3. Read canonical route facts
Documents now expose route and localization facts through route and
resolution. Replace reads of the former top-level path, localePaths, and
variants fields with the corresponding canonical facts. Do not reconstruct
localized public paths in consumer UI or provider adapters.
Application query semantics in 0.3.0
0.3.0 makes the path vocabulary in application queries canonical:
mount-agnostic and locale-prefix-free. Paths remain locale-scoped when
translated slugs are enabled. Both by: { path } and where: { path } now
exclude the collection's route mount. Remove the mount from every such value:
// content.config.ts
export const docs = defineCollection({
type: 'page',
source: '*/1.*/**/*.md',
i18n: true,
route: { en: '/guide', de: '/leitfaden' },
})// 0.3.0-rc.4
await one(docs, { by: { path: '/guide/getting-started' } })
await one(docs, { by: { path: '/leitfaden/erste-schritte' }, locale: 'de' })
await many(docs, { where: { path: { $prefix: '/guide/deep' } } })
// 0.3.0
await one(docs, { by: { path: '/getting-started' } })
await one(docs, { by: { path: '/erste-schritte' }, locale: 'de' })
await many(docs, { where: { path: { $prefix: '/deep' } } })A collection's index document is canonical / in every locale, not the
mounted directory name.
Stale mounted values do not raise an error. They select no document.
one() returns null, and many() returns []. Offset pagination returns
an empty page with total: 0. Cursor pagination returns an empty page without
a total field. In development and during prerender, a by: { path } miss on
a value shaped like the collection mount logs an advisory hint naming the
canonical form; production is silent.
by: { route } is unchanged. It still takes the public application URL
including the collection mount and the locale prefix, so
by: { route: '/de/leitfaden/erste-schritte' } keeps working exactly as
before. Prefer it whenever you already hold a browser URL.
Filesystem canonicalKey values follow the same rule and are generated after
removing the configured source mount, so a key such as 1/1 becomes 1.
Rebuild stored snapshots and any prerelease fixtures that persisted
filesystem-generated keys.
4. Migrate providers and data sources
Existing H3-facing providers remain behind @lupinum/ginko-content/provider.
Backend-neutral CMS or remote sources can implement the bounded
ContentDataSource contract from @lupinum/ginko-content/data-source; the Ginko
binder owns timeouts, bounds, validation, cache hints, and public projection.
Run the exported provider or data-source conformance contract against the real adapter fixtures before release. Capabilities remain runtime truth: advertise only operators and pagination modes the adapter implements.
Provider wire v3 in 0.3.0-rc.4
0.3.0-rc.4 hard-cuts the prerelease provider query wire to v3. Providers must
recompile against the rc.4 types; Ginko does not negotiate or dispatch v2.
- Advertise and execute
$nindirectly. It is no longer represented as a structural negation of$in. - Return complete raw
ProviderDocumentInputidentity and route facts even when the query carriesonlyorwithout. Core validates the raw document before applying the public projection. - Return
{ result: undefined }for a missing in-processfirstresult.{ result: null }is invalid at the provider seam; the public application and HTTP boundary maps a missing single result to{ result: null }. The HTTP response is always200 application/jsonwith the full envelope; an empty body is an invalid transport response rather than a missing result. - Use the closed, non-generic provider query and site-data envelopes and closed capability declarations.
- Return
{ data, updatedAt? }from providersiteData()implementations. Remove provider response echoes ofkeyandlocale; Ginko derives them from the request. Data sources continue to echo both values for binder validation. - Read ordinary slice pagination from top-level
plan.skipandplan.limit. Explicit offset/cursor pagination is carried separately inplan.paging. - Route/reference queries retain raw
plan.resolveVariantand also carry the closed provider selector inplan.variantSelector. - Preserve provider order when no sort is supplied and reject unsupported or non-JSON query shapes, including unsafe regular-expression flags.
There is no compatibility adapter. Delete v2-specific provider fixtures and update conformance probes so each advertised operator matches a nonempty exact subset rather than merely returning successfully.
Provider wire v4 in 0.3.0
0.3.0 hard-cuts provider wire v3 to v4. There is no v3 dispatcher or
compatibility adapter.
- Import
ContentProviderQueryPlaninstead ofContentQueryPlan. - Read all pagination from
plan.pagination; top-levelskip/limitandplan.pagingare removed. - Pattern-match
plan.variant.by. Path selectors carryby: 'path'; route selectors carry ordered mounted candidates; reference selectors carry the resolved locale chain. RawresolveVariantandvariantSelectorfields no longer cross the provider boundary. - Use
toContentProviderQuery()only for context-free filters, explicit fallback chains, and mountedproviderPathselectors. Application path/route/reference requests are closed by runtime dispatch because the public helper has no collection route policy. - Return
canonicalKeyon every provider document. Core no longer derives canonical identity from the mountedcontentPath. - Read navigation locale and fallback only from
query.plan.resolveLocale; the separate navigation options argument is removed. - Treat every provider
contentPathas collection-mounted and locale-specific, but never application-locale-prefixed. - Stop importing
longestMountForPathfrom the CMS contract. Route lowering validates the resolved locale's configured mount and no longer guesses a mount from another locale. - Localized route objects must contain exactly one entry for every configured
collection locale and no unknown keys. Use a route string when every locale
shares one mount. Unlocalized collections use a string or
{ default: ... }. Every mount must be a leading-slash, site-relative path without traversal, query, fragment, or URL syntax. getCollectionPath()remains a pure collection-handle helper and cannot see module-level locale policy inherited throughi18n: true. Use the explicit collection-locali18n: { locales, defaultLocale }form for collections passed to this helper.- Filesystem
canonicalKeyvalues are now generated after removing the configured source mount, matching the mount-agnostic internalpath. This is the same rule application queries follow; see "Application query semantics in 0.3.0" above. - Navigation queries require a named collection.
.navigation.ymlfiles stay collection-neutral during ingestion and are joined to actual collection pages while building navigation. - Provider wire plans and graph-executor plans are separate phases. Providers
continue importing only
ContentProviderQueryPlan; no internal canonical plan type is public.
Every v4 query must survive an exact JSON round trip. Recompile providers against 0.3.0 and update conformance fixtures to construct already-closed route/reference selectors.
Reference metadata in 0.3.0
CONTENT_REFERENCE_PREFIX and description-based reference detection are
removed. A schema manually authored as
.describe("__nuxt_content_ref__:authors") is now an ordinary human
description. Replace it with reference('authors') or
withContentReferenceMetadata(schema, 'authors').
CONTENT_REFERENCE_METADATA_KEY also changes value, from
"__nuxt_content_ref__:" to "ginko:contentReference". This only affects
schemas that wrote the metadata key literally; reference() and
withContentReferenceMetadata() are unaffected. Descriptions never carry
reference semantics under either spelling.
Public query vocabulary in 0.3.0-rc.4
- Replace a bare locale fallback such as
fallback: 'en'withfallback: ['en']. Keep'default'when the collection default is intended. - Replace field-level negation such as
{ status: { $not: { $eq: 'draft' } } }with logical negation:{ $not: { status: { $eq: 'draft' } } }. - Replace public numeric sort directions with
ascanddesc. Numeric directions remain an internal provider-wire detail. - Import
LocalizedContentDocumentinstead of the removedLocalizedDocalias. UseContentCollectionNameorContentCollectionTargetinstead of the removedContentCollectionStringNamealias.
5. Replace the removed CMS importer
@lupinum/ginko-content/cms-import is removed without a compatibility shim.
It duplicated mapping decisions that now belong to the portable contract.
- Use
@lupinum/ginko-content/portabilityfor portable documents, codecs, validation, references, assets, manifests, and semantic comparison. - Use
@lupinum/ginko-content/portability/nodefor bounded directory reads and writes. - Keep authorization, workflow, transactions, receipts, and cleanup in Ginko CMS or another consumer-owned adapter.
Certify the CMS integration against the exact candidate tarball and record its SHA-256. A source-workspace import does not prove package compatibility.
6. Rebuild search and enable validation deliberately
Rebuild generated search output after upgrading. Pagefind now emits a locale manifest and locale-specific indexes; MiniSearch uses explicit immutable index ownership. Both return plain-text excerpts so highlighting remains consumer-owned.
Content validation defaults to report-only:
export default defineNuxtConfig({
content: {
validation: 'report',
},
})Inspect the generated report with pnpm exec ginko-content validate. Switch to
validation: 'error' only after missing links, anchors, route references, and
assets are resolved; strict mode prevents publishing an invalid snapshot.
7. Build Windows release artifacts outside Windows
With Nuxt 4.4.7–4.4.8, a production build can fail during Nitro prerendering on Windows because Nuxt's cache-driver file URL is emitted as a raw drive-letter import. This is a build-host limitation, not a content portability or production runtime limitation. Build release artifacts on Linux or macOS until the upstream Nuxt/Nitro path handling is corrected.
8. Verify the exact package
Before adopting the stable release:
- run application unit, type, build, static-generation, and browser checks;
- run provider/data-source and portability conformance tests;
- run Ginko CMS against the exact packed tarball;
- verify localized routes, selected-locale search, and all-language search;
- confirm no code imports the removed
cms-importsubpath or deleted composables.
Adopt 0.3.0 only after the exact tarball passes every required consumer.