@uniformdev/transformer
v1.1.104
Published
CLI tool for transforming Uniform.dev serialization files offline
Readme
Uniform Transformer
Bulk refactoring for Uniform serialization files — offline, dry-runnable, and fast.
@uniformdev/transformer is a command-line tool that reads a Uniform serialization folder, applies a
transformation to every file, and writes the result back. It does not connect to Uniform. It only
reads and writes files on your disk.
This project is the Node.js replacement for the decommissioned Siphon.Transform.exe.
Why this tool
A Uniform project keeps its configuration and content in thousands of YAML/JSON files. The files reference each other by id: compositions point at component types, entries point at content types, links point at project map nodes. One small schema change can touch every one of those files.
Rename a slot by hand, and you must find each composition, each composition pattern, and each component pattern that uses it. Miss one, and the project no longer deploys.
The transformer does that work for you:
| Problem | What the transformer gives you |
|---|---|
| A rename must reach hundreds of files | One command updates the definition and every reference |
| You cannot tell what a change will break | --what-if shows the full plan and writes nothing |
| Broken references are found late, at deploy time | validate checks every cross-reference and exits non-zero |
| Large projects are slow to process | Bulk commands run on all CPU cores, with the same output as a sequential run |
| A crash leaves half-written files | Every write is atomic (write to a temp file, then rename) |
| Serialization diffs are unreadable in git | unpack-serialization splits files into one fragment per item |
| Content is duplicated across pages | extract-patterns finds duplicates and turns them into patterns |
It is also a migration tool. If you moved to Uniform from another CMS, commands such as
convert-compositions-to-entries,
flatten-block-field and
generate-composition-pattern let you restructure the imported
content into the shape you actually want.
Install
Run it directly with npx (no install):
npx @uniformdev/transformer --helpOr install it:
npm install -g @uniformdev/transformer # global
npm install -D @uniformdev/transformer # per projectThe binary is named uniform-transform:
uniform-transform validate --rootDir ./contentRequires Node.js 18 or later.
Quick start
Check the project first. This is read-only.
npx @uniformdev/transformer validate --rootDir ./contentPreview a change.
--what-ifdoes everything except write.npx @uniformdev/transformer rename-slot \ --rootDir ./content \ --componentType Hero \ --slot content \ --newSlot body \ --what-ifApply it, then validate again.
npx @uniformdev/transformer rename-slot \ --rootDir ./content --componentType Hero --slot content --newSlot body npx @uniformdev/transformer validate --rootDir ./content
Tip: keep the serialization folder in git. Run the command, then read
git diff. That is the best review of what changed.
Interactive explorer
Do not remember which of the 51 commands does the thing you want, or what its switches are called? Run the binary with no arguments:
npx @uniformdev/transformer # or: uniform-transform exploreIt opens a picker over every command, grouped by what it acts on. Type to filter, fill the selected command's options in as a form, and see the exact command line before anything runs — including a one-key dry run:
❯ uniform-transform --rootDir ./content rename-slot --componentType Hero --slot content --newSlot body
[DONE] Renamed slot: 12 composition(s), 2 composition pattern(s), 0 component pattern(s) updatedThe command line is printed above and below the output, so an exploratory session ends with something you can paste into a script or a CI job. See 040-interactive-explorer.md.
Nothing about a non-interactive run changes: a bare invocation with its streams piped still prints
usage and exits 1, --help is untouched, and the explorer refuses to start (with a reason, and exit
- when there is no terminal, when
CIis set, or whenUNIFORM_TRANSFORM_NO_INTERACTIVEis set.
Serialization structure
--rootDir points at the folder that holds these directories:
| Directory | Contents |
|---|---|
| composition | Pages and layouts |
| component | Component definitions |
| contentType | Content schema definitions |
| entry | Content entries |
| componentPattern / compositionPattern | Reusable patterns |
| dataType | Data type (data resource) definitions |
| asset, category, files | Media, categorization, file assets |
| projectMapDefinition, projectMapNode | Project map (routing) |
| previewUrl, previewViewport, quirk | Preview and edge-case settings |
Each name can be changed with a switch, for example --entriesDir entries. See
001-basics.md for the full list.
A reference project: csk-marketing-site/content.
Global options
Every command accepts these:
| Switch | Description |
|---|---|
| --rootDir <path> | Required. Path to the serialization project root. |
| --what-if | Dry run. Reads, calculates and logs everything, but writes nothing. |
| --strict | Match ids and names case-sensitively. By default matching is case-insensitive. |
| --concurrency <n> | Maximum files processed in parallel. Default: number of logical CPUs. Use 1 for fully sequential work. |
| --seedDir <path> | Path to a separately deployed seed-content project. Its definitions count as known references during validate. |
| --<name>Dir <dir> | Override any serialization directory name. |
Every command logs what it does, in both normal and --what-if mode.
Safety
--what-ifon everything. No command has to be run blind.- Atomic writes. Each file goes to a temporary sibling first, then is renamed over the target. An interrupted run never leaves a half-written file.
- Duplicate-id guard. Before any file is serialized, the writer rejects arrays that hold two
elements with the same
id— including ids that differ only in case (outside--strict). A corrupt definition is never written. - Errors that matter exit non-zero. A malformed JSON/YAML file or a failed
validatefails the process, so CI stops. A missing composition, component, parameter or entry is only a warning. - Stable output. Per-file log lines always appear in disk-discovery order, so
--concurrency 8and--concurrency 1print exactly the same thing. Only the wall-clock time changes.
Performance
Commands that sweep every composition, pattern or entry run in parallel:
--concurrency 1— fully sequential.- Below
20 × vCPUfiles — one thread, overlapping (non-blocking) file I/O. 20 × vCPUfiles or more — a pool of worker processes, one per unit of concurrency. Each worker reads, transforms and writes a whole file on its own, so large documents never cross a process boundary.
validate uses a map-reduce: all files are parsed and their ids collected across
all cores, the reference registries are built once, then every file is checked across all cores
again. On a project with 100,000 entries this is the largest win.
See Performance and parallelism for details.
Commands
Run npx @uniformdev/transformer <command> --help for the exact switches of any command, or
npx @uniformdev/transformer for the interactive explorer over all of them.
| Command | What it does |
|---|---|
| explore | Opens the interactive picker: filter every command, fill its options in as a form, preview the command line, run it. Needs a terminal. |
Validation and cleanup
| Command | What it does |
|---|---|
| validate | Read-only integrity check of the whole project. Verifies file formats and that every reference resolves. Prints all problems and exits 1 on any error. |
| remove-orphan-entries | Deletes entries that no root entry, composition, pattern or project map node can reach. |
| remove-unused-content-types | Deletes content types with zero entries. |
| remove-unused-component-types | Deletes component definitions that no composition, pattern or allowedComponents list uses. |
| remove-unused-datatypes | Deletes data types whose served content types no longer exist. |
| remove-unused-slots | Deletes slot definitions that nothing fills, plus the empty slot keys on instances. |
| clear-orphan-parameters | Drops parameter values that the component definition does not declare. |
| clear-orphan-fields | Drops field values that the content type does not declare, in entries and in embedded blocks. |
| downgrade-dangling-project-map-node-links | Rewrites links to unknown project map nodes as plain URL links, keeping the path. |
Renaming and merging
| Command | What it does |
|---|---|
| rename-component | Renames a component type across definitions, filenames, allowedComponents and all compositions. Give a list to merge several types into one. |
| rename-content-type | Renames a content type across its definition, every entry and all allowedTypes references. Also merges a list of types into one. |
| rename-slot | Renames a slot on a component definition and in every place that uses it. |
Parameters and fields
| Command | What it does |
|---|---|
| add-component-parameter | Adds (or replaces) a parameter on a component definition. |
| add-contenttype-field | Adds (or replaces) a field on a content type definition. |
| remove-parameter | Removes a parameter definition and strips its values everywhere. |
| remove-field | Removes a field from content types, from every entry and from embedded blocks. |
| copy-parameter | Duplicates a parameter and carries its value into every instance. |
| copy-field | Duplicates a field and carries its value into every entry and instance. |
| extract-date-parameter / extract-date-field | Derives a new text parameter/field from a date, with a token format such as YYYY. |
| extract-regex-parameter / extract-regex-field | Derives a new text parameter/field by applying a regular expression to another one. |
| sync-select-options | Gives every matching select/multi-select the union of all declared options. |
| replace-parameter-select-option | Retargets one select option (value and/or display text) and rewrites every stored selection of the old value. |
| flatten-block-field | Dissolves a $block parameter by lifting the block content type fields onto the component. |
| convert-block-parameters-to-multiselect | Turns a $block parameter into a multi-select, with options taken from the values in use. |
| convert-block-fields-to-multiselect | The same, for a $block field on a content type. |
| set-entry-name-from-field | Sets each entry's _name from one of its own text fields. |
Slots and components
| Command | What it does |
|---|---|
| add-slot | Adds a slot to a component definition, or merges allowed components into an existing slot. |
| remove-slot | Removes a slot from the definition and from every instance, with its contents. |
| clear-slot | Empties a slot everywhere, but keeps the slot itself. |
| add-component / add-component-pattern | Inserts a component or pattern instance into existing slots across all compositions. |
| remove-component | Removes component types completely — every instance with its subtree, every allowedComponents entry, and the definition file. Works on types that are still in use. |
| update-allowed-components | Reconciles each slot's allowedComponents with the types actually placed in it. |
Propagation
| Command | What it does |
|---|---|
| propagate-root-component-property | Copies property definitions from a composition's root component to a target component type, then propagates the values. |
| propagate-root-component-slot | Copies slot definitions from the root component to a target type and moves the slot contents. |
| propagate-root-slot | Moves a named slot from composition roots into every instance of a target component type, and updates allowedComponents. |
Patterns
| Command | What it does |
|---|---|
| find-composition-pattern-candidates | Reports which compositions share the same structure, so you can see the consolidation opportunities. Read-only. |
| extract-patterns | Finds slot-less instances whose content is duplicated, mints one entry-less component pattern per group, and rewrites every match into a pattern reference. |
| extract-patterns-with-entries | Replaces atomic leaf instances that reference one entry with a generated pattern bound 1‑1 to that entry. |
| replace-component-with-pattern | Replaces all instances of a component with a pattern reference, keeping the variant. |
| apply-component-pattern | Converts instances of a component into references to an existing component pattern, with per-instance overrides. |
| apply-composition-pattern | Converts compositions of a type into thin compositions that reference a composition pattern. |
| generate-composition-pattern | Generates a composition pattern from an example composition plus entry (the inverse of apply-composition-pattern). |
Content model migration
| Command | What it does |
|---|---|
| convert-compositions-to-entries | Converts compositions into entries, and nested components into references or inline blocks. |
| split-content-type | Copies a content type into a new one and reassigns the matching entries. |
Project map and links
| Command | What it does |
|---|---|
| generate-missing-project-map-nodes | Creates group nodes for missing ancestor path segments. |
| fix-links-to-dynamic-compositions | Points link values at a dynamic composition node and lifts ${name} segments into dynamicInputValues. |
Git-friendly serialization
| Command | What it does |
|---|---|
| unpack-serialization | Splits each file into a skeleton plus one fragment per array item, recursively. Diffs, reviews and blame become per-item. |
| pack-serialization | Reassembles the skeleton and fragments into the original file. Run this before you deploy. |
Examples
Merge two component types into one, taking the union of their parameters:
uniform-transform rename-component --rootDir ./content \
--componentType "HeroBanner,LegacyHero" \
--newComponentType Hero \
--newComponentName "Hero" \
--mergeParameters --mergeSlotsDrop a parameter and all of its values:
uniform-transform remove-parameter --rootDir ./content \
--componentType Hero --parameterId "subtitle,kicker" --what-ifTurn duplicated content into patterns:
uniform-transform extract-patterns --rootDir ./content \
--excludeComponentTypes Spacer,Divider \
--minInstances 3Make the serialization diff-friendly, then restore it:
uniform-transform unpack-serialization --rootDir ./content --sourceDir ./content/component
# ... review and commit the fragments ...
uniform-transform pack-serialization --rootDir ./content --sourceDir ./content/componentGate a pipeline on project integrity:
uniform-transform validate --rootDir ./content --seedDir ./seed-content || exit 1Programmatic use
The package also exports its services, so you can build your own transformation:
import {
FileSystemService,
ComponentService,
CompositionService,
ComponentRenamerService,
Logger,
} from '@uniformdev/transformer';Exported: the type definitions, the error types, FileSystemService, ComponentService,
CompositionService, PropertyPropagatorService, SlotRenamerService, ComponentRenamerService,
ComponentAdderService, CompositionConverterService, computeGuidHash, regenerateIds,
ParameterRemoverService, FieldRemoverService and Logger.
Specifications
Each command has a specification file in the repository root, numbered 001–040. The spec is the
source of truth for behavior: it describes the problem, the switches, the rules and the edge cases,
with examples. Start with 001-basics.md.
Development
npm install
npm run dev -- validate --rootDir ./tests/fixtures # run from source
npm run build # bundle with tsup
npm run lint
npm test # unit tests
npm run test:e2e # end-to-end testsAll four checks (build, lint, test, test:e2e) must pass before a commit. When you change a
command, update its specification file too. See CLAUDE.md for the full contributor
rules, including the deterministic-id and unique-id policies.
License
MIT
