Version Manager Reference
src/core/version-manager.js is the single source of truth for all versioned file I/O. No other module writes version files directly — everything goes through these functions.
Directory Layout
config/versioned/
{layer}/ ← "json" or "tsx"
{Bundle}/
{Entity}/
v0/
data.json ← raw API snapshot (JSON layer) or index.tsx (TSX layer)
changes.json ← optional customization file
v1/
data.json
changes.jsonVersions are always strings: v0, v1, v2 … sorted numerically.
Version Discovery
getAllVersions(entity, layer)
Returns all version strings for an entity in ascending order (["v0", "v1", "v2"]). Returns [] if the entity directory does not exist.
getLatestVersion(entity, layer)
Returns the highest version string, e.g. "v2". Returns null if no versions exist.
getNextVersion(entity, layer)
Returns the next version string that does not yet exist. If v0 and v1 exist, returns "v2". If no versions exist, returns "v0".
versionExists(entity, version, layer)
Returns true if the version directory exists.
getVersionInfo(entity, layer)
Returns an array of info objects for every version:
[{
version: "v0",
path: "./config/versioned/json/AccountingBundle/Account/v0",
isLatest: false,
createdAt: Date | null
}]isLatest is true only for the highest version. createdAt is derived from the directory’s mtime.
Reading & Writing Version Data
loadVersionData(entity, version, layer)
Reads and parses {versionPath}/data.json. Returns null if the file does not exist or fails to parse.
saveVersionData(entity, version, layer, data)
Writes data as formatted JSON to {versionPath}/data.json, creating intermediate directories as needed. Returns the absolute path written.
Changes File I/O
hasVersionedChangesFile(entity, version, layer)
Returns true if {versionPath}/changes.json exists.
getVersionedChangesFilePath(entity, version, layer)
Returns the path string for a version’s changes.json without reading it.
loadVersionedChangesFile(entity, version, layer)
Reads and parses {versionPath}/changes.json. Returns null if absent or unparseable.
saveVersionedChangesFile(entity, version, layer, data)
Writes the changes object to {versionPath}/changes.json. Returns the path written.
findLatestVersionWithChanges(entity, layer)
Iterates versions in descending order (newest first) and returns the first version that has a changes.json file. Returns null if none have changes.
Used by generateJsonMigration when --fromVersion=latest is passed.
createVersionedChangesTemplate(entity, version, layer, data, overwrite, baseVersion)
Creates a changes.json template for a version. The template is populated from the raw API data (data.json) and, optionally, inherited from a previous version.
| Parameter | Type | Description |
|---|---|---|
entity | string | Entity path |
version | string | Target version |
layer | string | "json" or "tsx" |
data | Object | Extracted from the version’s data.json: listingFields, formFields, filterFields, showFields, requiredFields, sortableFields, implementedInterfaces, resourceName, writeableProperties, apiFilters |
overwrite | boolean | If true, overwrite an existing changes.json |
baseVersion | string|null | If set, inherit selections from this version via mergeChangesTemplates() |
Template sections created (JSON layer):
{
"listingFields": { "all": [...], "selected": [], "hide": [], "sortable": [...] },
"formModalShowFields": { "fields": [], "hide": [] },
"formModalEditFields": { "fields": [], "hide": [] },
"formModalAddFields": { "fields": [], "hide": [] },
"showFields": { "sections": {}, "hide": [] },
"formFields": { "sections": {}, "required": [], "optional": [], "hide": [] },
"filterFields": { "all": [...], "defaults": [], "hide": [] },
"sideboxFields": { "all": [], "hide": [] },
"fieldOverrides": {},
"exclusionList": { "listing": [], "forms": [], "show": [] },
"actions": { "listing": [], "showPage": [] }
}The all arrays in listingFields and filterFields are pre-populated with every available field name from the raw API data. The sortable array is pre-populated from sortableFields (the OrderFilter list from the API).
Returns the path of the created file.
mergeChangesTemplates(oldChanges, newTemplate)
Merges selections from a previous version’s changes.json (oldChanges) into a freshly-created template (newTemplate). Called automatically when baseVersion is supplied to createVersionedChangesTemplate.
What is preserved from oldChanges:
| Field | Preservation rule |
|---|---|
listingFields.selected | Kept — filtered to fields that still exist in newTemplate.listingFields.all |
listingFields.hide | Kept as-is |
listingFields.sortable | Kept — filtered to still-existing fields |
listingFields.order | Kept as-is |
listingFields.sortableKeys | Kept as-is |
listingFields.columnWidths | Kept as-is |
listingFields.alignment | Kept as-is |
listingFields.defaultOrder | Kept as-is |
listingFields.iconFields | Kept — filtered to still-existing fields |
listingFields.hideSelected | Kept — filtered to still-existing fields |
formFields.sections | Section structure preserved — each section’s fields filtered to still-existing fields; empty sections dropped |
formFields.required | Kept — filtered to still-existing fields |
formFields.optional | Kept — filtered to still-existing fields |
formFields.hide | Kept as-is |
formFields.validation | Kept as-is |
formFields.defaultValues | Kept as-is |
formFields.helperText | Kept as-is |
formModalShowFields | Kept — fields filtered to still-existing fields |
formModalEditFields | Kept — fields filtered to still-existing fields |
formModalAddFields | Kept — fields filtered to still-existing fields |
showFields.sections | Preserved — fields per section filtered to still-existing fields; empty sections dropped |
showFields.hide | Kept as-is |
filterFields.defaults | Kept — filtered to fields that still exist in newTemplate.filterFields.all |
filterFields.hide | Kept as-is |
filterFields.range | Kept as-is |
filterFields.rangeKeys | Kept as-is |
filterFields.translatable | Kept as-is |
filterFields.searchMethods | Kept as-is |
filterFields.helperText | Kept as-is |
filterFields.fieldIcon | Kept as-is |
filterFields.hideToolbar | Kept as-is |
sideboxFields | Kept as-is |
fieldOverrides | Kept as-is |
exclusionList | Kept as-is |
actions | Kept as-is |
What is NOT preserved:
- Fields that no longer exist in the new API data (filtered out automatically)
- Empty sections after filtering (dropped)
_comment,description, metadata keys
This means incremental generation is non-destructive by default. Your customizations survive API schema updates unless the field was actually removed from the API.
Path Helpers
getLayerBasePath(layer) → config/versioned/{layer}
getEntityPath(entity, layer) → config/versioned/{layer}/{Bundle}/{Entity}
Normalizes \ to / in the entity path.