Add lock-file load and stable ID assignment

Lock-file determinism guarantees re-bakes do not renumber existing
tiles. Atlas_id mismatch is a hard-fail to prevent silent corruption.
Deleted tile IDs move to deleted[] and are not recycled.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Axel Meyer
2026-05-21 22:40:50 +02:00
parent b64e3e87a9
commit 83499850fb
3 changed files with 153 additions and 0 deletions

69
src/lock.js Normal file
View File

@@ -0,0 +1,69 @@
// src/lock.js
// Load + update + serialize the tiles.atlas.lock.json file.
const fs = require('node:fs');
function loadLock(lockPath) {
if (!fs.existsSync(lockPath)) {
return null; // signal: fresh bake
}
const raw = JSON.parse(fs.readFileSync(lockPath, 'utf8'));
if (typeof raw.atlas_id !== 'string') {
throw new Error(`lock: missing atlas_id in ${lockPath}`);
}
return {
atlas_id: raw.atlas_id,
bindings: raw.bindings || {},
deleted: raw.deleted || [],
next_id: raw.next_id || 1,
};
}
function assignIds(sources, existingLock, atlasId) {
if (existingLock && existingLock.atlas_id !== atlasId) {
throw new Error(
`lock: atlas_id mismatch (lock has '${existingLock.atlas_id}', `
+ `cli passed '${atlasId}'); refusing to bake`
);
}
const bindings = existingLock ? { ...existingLock.bindings } : {};
const deletedSet = new Set(existingLock ? existingLock.deleted : []);
let nextId = existingLock ? existingLock.next_id : 1;
const presentNames = new Set(sources.map(s => s.name));
const tiles = [];
// Assign IDs to all sources (existing → stable, new → next_id++)
for (const source of sources) {
let id = bindings[source.name];
if (id === undefined) {
id = nextId++;
bindings[source.name] = id;
}
tiles.push({ ...source, id });
}
// Detect deletions: previously-bound names not in sources
for (const name of Object.keys(bindings)) {
if (!presentNames.has(name)) {
deletedSet.add(bindings[name]);
delete bindings[name];
}
}
return {
tiles,
lock: {
atlas_id: atlasId,
bindings,
deleted: [...deletedSet].sort((a, b) => a - b),
next_id: nextId,
},
};
}
function serializeLock(lock) {
return JSON.stringify(lock, null, 2);
}
module.exports = { loadLock, assignIds, serializeLock };