# Migration helpers

`@richscripts/richtexteditor/migration` is a server-safe CommonJS entry point for moving common configuration and content from TinyMCE, CKEditor 5, Froala, Quill, Tiptap, or Lexical.

```js
const { createMigrationReport, migrateConfig, migrateEditor } = require("@richscripts/richtexteditor/migration");

const plan = migrateConfig("tinymce", legacyConfig);
console.table(plan.unmapped);

const result = migrateEditor("tinymce", legacyEditor, richTextEditor);
console.table(result.warnings);

const report = createMigrationReport("tinymce", { config: legacyConfig, content: legacyHtml });
console.table(report.blockers);
if (!report.readyForCutover) process.exitCode = 1;
```

- HTML-native editors transfer their HTML for RichTextEditor's normal loading pipeline.
- Quill Delta, Tiptap JSON, and Lexical JSON receive semantic HTML conversion for common blocks and marks, including mixed multi-level ordered/bullet lists and source checklist state.
- TinyMCE, CKEditor, and Froala HTML passes through without schema rewriting, while private runtime bookkeeping attributes (`data-mce-*`, `data-ck-*`, CKEditor list IDs, and `data-fr-*`) are removed before handoff.
- Unknown toolbar items, embeds, nodes, and marks are reported. They are not silently presented as migrated.
- `createMigrationReport` combines configuration and content findings into blockers, warnings, and a stable manual-review checklist for a pre-cutover inventory.
- Custom plugins, schemas, collaboration state, comments, tracked changes, uploads, permissions, and application data remain explicit integration work.

Always run representative production documents through both editors and compare saved output before cutover.

## Custom structured-model converters

Use explicit programmatic converters to resolve inventoried Quill embeds or Tiptap/Lexical nodes and marks without forking the adapter:

```js
const report = createMigrationReport("quill", {
  content: legacyDelta,
  converters: {
    quill: {
      embeds: {
        mention(value) {
          const id = escapeAttribute(value.id);
          const label = escapeHtml(value.label);
          return `<span data-mention="${id}">@${label}</span>`;
        }
      }
    }
  }
});

console.table(report.appliedConverters);
```

Registries support `quill.embeds`, `tiptap.nodes`, `tiptap.marks`, and `lexical.nodes`. A converter returns an HTML string or `{ html, warnings }`. Returning `null` deliberately falls through to the normal unsupported-content blocker. Successful applications are counted in `appliedConverters`; each entry also includes stable source-model `paths` and the converter receives the current `context.path`.

Converter output is trusted application code and is not sanitized by the migration helper. Escape external values, sanitize generated HTML under your own policy, and keep representative-output review in the cutover gate.

## File and batch inventory CLI

The package also installs `rte-migration-report`. A single-document inventory accepts an HTML file for TinyMCE, CKEditor, or Froala, and a JSON export for Quill, Tiptap, or Lexical:

```sh
rte-migration-report --source quill --config quill-config.json --content article.delta.json --output report.json
```

For a batch, create a manifest. File references are resolved relative to the manifest, so the inventory is portable in CI:

```json
{
  "entries": [
    { "id": "terms", "source": "ckeditor", "configFile": "config.json", "contentFile": "terms.html" },
    { "id": "guide", "source": "tiptap", "contentFile": "guide.json" }
  ]
}
```

```sh
rte-migration-report --input migration-inventory.json --output migration-report.json
```

To reuse the programmatic converter registry in single-file or batch CI runs, opt into a local CommonJS module:

```sh
rte-migration-report --input migration-inventory.json --converters ./migration-converters.cjs --converter-sha256 "$REVIEWED_SHA256" --output migration-report.json
```

The module must export the same `quill`/`tiptap`/`lexical` registry object documented above. The CLI resolves this path from its invoking working directory and validates every registry group and converter function before reading the inventory. This flag executes local code with the permissions of the CLI process; use only a reviewed, trusted module from your repository. Pin reviewed bytes with `--converter-sha256 <64-character digest>`: a malformed value or content mismatch fails before the module can execute. Without `--converters`, the CLI remains data-only and preserves every unsupported-content blocker.

CLI artifacts use report schema version `2` and include a stable `generator` object with the producing `@richscripts/richtexteditor` package version. Both single-document and batch output include `converterModule`: `null` for the default data-only path, or a non-sensitive file label, SHA-256 digest, and `hashVerifiedBeforeExecution` flag. Store the generator version and digest with CI artifacts and review schema-version changes before consuming new report fields. The label intentionally omits the local absolute path; the digest proves byte identity, not that the module is safe or correct.

Reports intentionally omit a current-time field, so identical inputs, package code, and converter bytes serialize identically. Let the CI artifact store provide creation/retention timestamps; this keeps readiness diffs meaningful and reproducible.

Print the packaged Draft 2020-12 JSON Schema with `rte-migration-report --print-schema`. The schema covers both single-document and batch artifacts, including generator identity, converter provenance, structured findings, aggregate totals, and nested document reports. The command is standalone so schema discovery cannot accidentally execute converters or read migration inputs.

Validate a stored artifact with `rte-migration-report --validate-report ./migration-report.json`. This dependency-free mode checks schema/generator compatibility, document field shapes, readiness-versus-blocker consistency, and batch totals/unsupported inventories against the nested reports. It does not load converter modules or source migration files. A valid artifact exits `0` and emits compact JSON containing its exact-byte SHA-256; malformed, incompatible, or internally inconsistent artifacts exit `1` with the failed invariant.

For promotion gates, add `--report-sha256 <64-character digest>`. The CLI compares the expected digest with the stored bytes before JSON parsing or validation and rejects mismatches. The output records `hashVerifiedBeforeValidation` so downstream jobs can distinguish a computed digest from an enforced pin. SHA-256 establishes byte identity and detects unintended artifact changes; it does not identify or authenticate a signer, and validation still does not replace content-fidelity acceptance.

To gate only newly introduced migration debt, add `--baseline-report ./accepted-baseline.json` to `--validate-report`. Both artifacts are validated first. Comparison output lists new blocker messages and new/increased unsupported findings by document ID, source, kind, name, added count, and exact new source paths. Exit `0` means no new detected debt; exit `2` means regressions were found; exit `1` means an artifact or option was invalid. Existing accepted blockers remain visible in the reports, and a regression-free comparison is not production acceptance.

Each document report includes a structured `unsupportedContent` inventory whose entries identify the unsupported `kind` (`node`, `mark`, or `embed`), name, occurrence count, and stable source-model paths such as `ops[4].insert.mention` or `$.content[2].marks[0]`. Batch output also provides document totals, `summary.unsupportedByDocument`, and source/kind/name rollups in `summary.unsupportedTotals`, including affected-document counts. CI and migration owners can locate, size, and assign custom conversion work without parsing warning text.

The command exits `0` when every report has no automated blocker, `2` when blockers are present, and `1` for invalid input or I/O errors. Use `--allow-blockers` when generating an informational artifact should not fail CI. A zero exit code is not production acceptance: every report retains the manual-review checklist.
