Skip to main content

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:

  1. Computes a fresh digest (hash) of all files in the template folder (ignoring version.json itself)
  2. Compares the fresh digest with the stored one
  3. If they are different, it bumps the patch version (e.g., 1.0.101 becomes 1.0.102) and saves the new digest
  4. 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.