mirror of
https://github.com/Matysh/houseplan-card
synced 2026-09-29 03:09:36 +00:00
106 lines
4.7 KiB
JavaScript
106 lines
4.7 KiB
JavaScript
#!/usr/bin/env node
|
|
/** Executable inventory of every persisted config/layout coordinate writer (#291). */
|
|
import { readFileSync, readdirSync } from 'node:fs';
|
|
import { resolve } from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
|
|
const defaultRoot = fileURLToPath(new URL('..', import.meta.url));
|
|
|
|
const occurrences = (source, needle) => {
|
|
const out = [];
|
|
let from = 0;
|
|
while (true) {
|
|
const index = source.indexOf(needle, from);
|
|
if (index < 0) return out;
|
|
out.push(index);
|
|
from = index + needle.length;
|
|
}
|
|
};
|
|
|
|
const requireWindow = (errors, source, index, pattern, label, before = 650) => {
|
|
const window = source.slice(Math.max(0, index - before), index + 260);
|
|
if (!pattern.test(window)) errors.push(`${label} bypasses the canonical boundary`);
|
|
};
|
|
|
|
export function checkCoordinateWriteBarriers(root = defaultRoot) {
|
|
const errors = [];
|
|
// The editor implementation is a lazy chunk, but the persisted-coordinate
|
|
// inventory remains one logical frontend. Normalize the typed host port so
|
|
// the existing boundary windows keep describing the production call sites.
|
|
const card = readFileSync(resolve(root, 'src/houseplan-card.ts'), 'utf8');
|
|
const editor = readFileSync(resolve(root, 'src/houseplan-editor-runtime.ts'), 'utf8')
|
|
.replaceAll('this.host.', 'this.');
|
|
const optimize = readFileSync(resolve(root, 'src/plan-optimize-write.ts'), 'utf8')
|
|
.replaceAll('host.', 'this.');
|
|
const frontend = `${card}\n${editor}\n${optimize}`;
|
|
|
|
const configWrites = occurrences(frontend, "type: 'houseplan/config/set'");
|
|
if (configWrites.length !== 1) errors.push(`frontend config writer inventory: ${configWrites.length}`);
|
|
for (const index of configWrites) requireWindow(
|
|
errors, frontend, index,
|
|
/const canonicalCandidate = canonicalizeConfigGeometry\(candidate\);[\s\S]*config: canonicalCandidate,[\s\S]*expected_rev: this\._cfgRev/,
|
|
'frontend config/set', 600,
|
|
);
|
|
if (occurrences(frontend, '._sendConfigCandidate(candidate)').length !== 2) {
|
|
errors.push('frontend canonical config transport callers changed');
|
|
}
|
|
|
|
const positionWrites = occurrences(frontend, "type: 'houseplan/layout/update'");
|
|
if (positionWrites.length !== 3) errors.push(`frontend position writer inventory: ${positionWrites.length}`);
|
|
for (const [ordinal, index] of positionWrites.entries()) requireWindow(
|
|
errors, frontend, index,
|
|
/const pos = canonicalizePosition\([^)]+\);[\s\S]*pos(?:[,\s}])/,
|
|
`frontend layout/update #${ordinal + 1}`,
|
|
);
|
|
|
|
const localWrites = occurrences(frontend, 'localStorage.setItem(LS_KEY');
|
|
if (localWrites.length !== 1) errors.push(`frontend local layout writer inventory: ${localWrites.length}`);
|
|
for (const index of localWrites) requireWindow(
|
|
errors, frontend, index,
|
|
/this\._layout = canonicalizeLayoutGeometry\(this\._layout\);/,
|
|
'frontend localStorage layout', 350,
|
|
);
|
|
|
|
const optimizeWrites = occurrences(frontend, "type: 'houseplan/plan/optimize'");
|
|
if (optimizeWrites.length !== 1) errors.push(`frontend Optimize writer inventory: ${optimizeWrites.length}`);
|
|
for (const index of optimizeWrites) requireWindow(
|
|
errors, frontend, index,
|
|
/config,[\s\S]*layout,[\s\S]*expected_config_rev: this\._cfgRev/,
|
|
'frontend Optimize transaction', 300,
|
|
);
|
|
|
|
const componentRoot = resolve(root, 'custom_components/houseplan');
|
|
const pythonFiles = readdirSync(componentRoot)
|
|
.filter((name) => name.endsWith('.py'));
|
|
for (const name of pythonFiles) {
|
|
const source = readFileSync(resolve(componentRoot, name), 'utf8');
|
|
if (name === 'store.py') continue;
|
|
if (name === 'trails.py') continue; // operational breadcrumb Store, not plan geometry
|
|
if (/\b(?:runtime|rt)\.(?:config_store|store)\.async_save\s*\(/.test(source)) {
|
|
errors.push(`backend direct plan Store writer: custom_components/houseplan/${name}`);
|
|
}
|
|
}
|
|
const store = readFileSync(resolve(componentRoot, 'store.py'), 'utf8');
|
|
if (!/out\["layout"\] = canonicalize_layout_geometry\(layout\)/.test(store)) {
|
|
errors.push('backend layout_store_payload bypasses lattice canonicalization');
|
|
}
|
|
if (!/canonical_config = canonicalize_config_geometry\(config\)/.test(store)) {
|
|
errors.push('backend async_save_config_state bypasses lattice canonicalization');
|
|
}
|
|
if (occurrences(store, 'runtime.store.async_save(').length !== 1
|
|
|| occurrences(store, 'runtime.config_store.async_save(').length !== 1) {
|
|
errors.push('backend central Store writer inventory changed');
|
|
}
|
|
return errors;
|
|
}
|
|
|
|
if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
|
const errors = checkCoordinateWriteBarriers();
|
|
if (errors.length) {
|
|
for (const error of errors) console.error(`coordinate write barrier: ${error}`);
|
|
process.exitCode = 1;
|
|
} else {
|
|
console.log('coordinate write barrier: OK');
|
|
}
|
|
}
|