Versioning
Every template and widget in Ceres gets a version number. The version bumps automatically when you change the source code. You never need to bump it yourself.
How it works
Each template has a version.json file:
{
"version": "1.0.101",
"digest": "a3f8b2c1"
}
Two fields:
- version is a semver number like
1.0.101 - digest is a short hash of the template's source files
When you run a build, the system:
- Computes a fresh digest (hash) of all files in the template folder (ignoring
version.jsonitself) - Compares the fresh digest with the stored one
- If they are different, it bumps the patch version (e.g.,
1.0.101becomes1.0.102) and saves the new digest - If they are the same, it keeps the current version
This means:
- Change your CSS? Version bumps.
- Change your HBS template? Version bumps.
- Run the build twice without changing anything? Version stays the same.
Why this matters
The version number ends up in the output file paths:
dist/templates/basic-invoice-example/1.0.101/bundle.js
dist/templates/basic-invoice-example/1.0.101/bundle.css
Since the version is in the path, the CDN can cache these files forever. When you change your template and the version bumps, the new files get a new path, so browsers automatically get the latest version.
JSON and HTML files (like manifest.json) are not cached. So when the manifest updates to point to a new version, browsers pick it up right away.
The digest function
The digest is an MD5 hash of all files in the template folder. It walks through every file, hashes the file path and contents, and produces an 8-character hex string.
// Simplified version of how the digest is computed:
function hashDirectory(dir) {
const hash = crypto.createHash("md5");
// Walk all files in sorted order (for stability)
for (const file of allFilesSorted) {
hash.update(relativePath);
hash.update(fileContents);
}
return hash.digest("hex").substring(0, 8);
}
The function ignores version.json itself (so bumping the version does not change the digest) and ignores node_modules and dist folders.
Manifest structure
Root template manifest
Each template has a root manifest.json at dist/templates/<name>/manifest.json:
{
"version": "1.0.101",
"manifest": "./1.0.101/manifest.json",
"assets": {
"js": "./1.0.101/bundle.js",
"css": "./1.0.101/bundle.css",
"thumbnail": "./1.0.101/thumbnail.png"
}
}
This tells the main renderer: "the latest version is 1.0.101, and here is where to find the files."
Version-specific manifest
Inside the version folder, there is another manifest.json with file hashes:
{
"version": "1.0.101",
"assets": {
"js": "bundle.js",
"css": "bundle.css",
"thumbnail": "thumbnail.png"
},
"digest": {
"js": "e4f5a6b7",
"css": "c8d9e0f1"
}
}
Main renderer manifest
The main renderer has its own manifest at dist/main-manifest.json:
{
"js": "main-renderer/renderer.a1b2c3d4.js"
}
Widgets manifest
All widgets are summarized in dist/widgets-manifest.json:
{
"invoice-status": {
"js": "widgets/invoice-status/bundle.1.0.3.js",
"css": "widgets/invoice-status/bundle.1.0.3.css",
"version": "1.0.3"
}
}
Purging old versions
If you set PURGE_OLD_ASSETS=1, the build will delete old version folders from dist/. Only the current version is kept. This is useful for keeping the deployment size small.