Skip to main content

Move filesystem content to a CMS

Assess and export a filesystem-backed application as a verified portable V1 directory.

The portable export keeps the collection contract as the source of truth. It does not change the provider, contact a CMS, or edit source files.

Prepare the application

Keep the application on the filesystem provider. Make sure that content.config.ts, nuxt.config.ts, and .ginko/content-contract.json are current.

Declare CMS details that cannot be inferred safely. For example, declare a nested collection as a tree:

ts
// content.config.ts
export const docs = defineCollection({
  type: 'page',
  source: 'docs/**/*.md',
  cms: {
    type: 'tree',
    route: { allowMultipleRoots: true },
  },
})

Run the local assessment:

bash
pnpm exec ginko-content portable assess

The command reads the source files and both configuration files. It rebuilds the resolved contract and compares its hash with the generated contract. It uses the Ginko parser, schema validation, content graph, navigation builder, and portable V1 validators.

The assessment reports all detected source problems. Each diagnostic includes a code, file, field when available, and corrective action. Exit code 0 means that export can continue. Exit code 2 means that the source or contract is not portable.

Export the portable directory

Select a destination that does not exist:

bash
pnpm exec ginko-content portable export .ginko/cms-import

The command repeats the assessment. It writes the directory atomically only when the assessment passes. The result uses the existing portable V1 manifest, checksums, document codecs, and bounded directory verifier.

Applications that need programmatic control can use the Node API:

ts
import {
  assessFilesystemPortability,
  exportFilesystemToPortableDirectory,
} from '@lupinum/ginko-content/portability/node'

const assessment = await assessFilesystemPortability({ rootDir: process.cwd() })

if (assessment.ok && assessment.evidence) {
  await exportFilesystemToPortableDirectory({
    rootDir: process.cwd(),
    destination: '.ginko/cms-import',
    expectedInputHash: assessment.evidence.inputHash,
  })
}

expectedInputHash prevents export when a source or configuration input changed after assessment. The export result includes the contract hash, manifest hash, input hash, counts, collections, and locales.

Resolve unsupported source behavior

Portable V1 does not represent source draft state or standalone partial documents. The assessment refuses these documents instead of changing their meaning. .navigation.yml control files are not standalone documents. Their supported metadata is merged into the matching tree entry.

Stable $... Content references remain in MDC. The provider-independent render boundary resolves them from the content graph or the application's configured quick links. Literal code examples remain unchanged.

Site-owned public asset paths and external URLs remain authored MDC values when no field or component media policy marks them as managed assets. The export does not fetch, convert, or upload those values. A schema image field must use a supported external reference or a verified managed-asset identity. Referenced /ginko-assets/<sha256>.<ext> images are read from public/, checked for type, size, and SHA-256 integrity, and copied into the portable directory.

Custom filesystem transformers and custom search filter queries require an explicit portable representation. The assessment refuses these cases. It does not claim that application-specific queries or CMS-backed rendering were tested.

Pass the verified directory to the CMS import planner. Keep authentication, target capability checks, draft application, publication, receipts, and provider cutover in the CMS workflow.