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:
69
src/lock.js
Normal file
69
src/lock.js
Normal 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 };
|
||||
Reference in New Issue
Block a user