fresh start
This commit is contained in:
parent
62c3e2d368
commit
7f86647175
570 changed files with 4895 additions and 866 deletions
125
tools/hash.js
Executable file
125
tools/hash.js
Executable file
|
|
@ -0,0 +1,125 @@
|
|||
import fs from 'fs/promises';
|
||||
import path from 'path';
|
||||
import matter from 'gray-matter';
|
||||
import yaml from 'js-yaml';
|
||||
import crypto from 'crypto';
|
||||
|
||||
// Canonical hash (exclude id from frontmatter)
|
||||
function canonicalHash(meta, body) {
|
||||
const metaClone = { ...meta };
|
||||
delete metaClone.id; // Exclude id to avoid circularity
|
||||
const frontYaml = yaml.dump(metaClone, { noRefs: true, lineWidth: -1 }).trim();
|
||||
const content = frontYaml + '\n' + body.trim();
|
||||
const lines = content.split('\n');
|
||||
const trimmedLines = lines.map(line => line.replace(/\s+$/, ''));
|
||||
const normalized = trimmedLines.join('\n');
|
||||
return { hash: crypto.createHash('sha256').update(normalized).digest('hex'), normalized };
|
||||
}
|
||||
|
||||
// Compute canonical hash for a file
|
||||
async function computeHash(file) {
|
||||
const content = await fs.readFile(file, 'utf8');
|
||||
const { data: meta, content: body } = matter(content);
|
||||
// Warn about block scalars in exits.to
|
||||
if (meta.exits) {
|
||||
meta.exits.forEach((exit, i) => {
|
||||
if (typeof exit.to === 'string' && exit.to.includes('\n')) {
|
||||
console.warn(`Block scalar in exits[${i}].to: ${file}; use inline string`);
|
||||
}
|
||||
});
|
||||
}
|
||||
const { hash, normalized } = canonicalHash(meta, body);
|
||||
const expected = meta.id ? meta.id.split('@sha256:')[1] : 'none';
|
||||
console.log(`File: ${file}\nComputed: ${hash}\nExpected: ${expected}\nMatch: ${hash === expected}\nNormalized Content:\n${normalized}\n`);
|
||||
console.log(`To fix, update id to: ${meta.id.split('@sha256:')[0]}@sha256:${hash}`);
|
||||
return { file, computed: hash, expected, match: hash === expected };
|
||||
}
|
||||
|
||||
// Fix hash in file
|
||||
async function fixHash(file) {
|
||||
const content = await fs.readFile(file, 'utf8');
|
||||
const { data: meta, content: body } = matter(content);
|
||||
// Normalize exits.to to inline strings
|
||||
if (meta.exits) {
|
||||
meta.exits = meta.exits.map(exit => ({
|
||||
...exit,
|
||||
to: exit.to.replace(/\n/g, '').trim()
|
||||
}));
|
||||
}
|
||||
const { hash } = canonicalHash(meta, body);
|
||||
const kindSlug = meta.id.split('@sha256:')[0]; // Preserve 'kind:slug'
|
||||
meta.id = kindSlug + '@sha256:' + hash;
|
||||
const newContent = `---\n${yaml.dump(meta, { noRefs: true, lineWidth: -1 }).trim()}\n---\n${body.trim()}`;
|
||||
await fs.writeFile(file, newContent);
|
||||
console.log(`Fixed hash in ${file}: ${hash}`);
|
||||
}
|
||||
|
||||
// Validate mode (check without fixing)
|
||||
async function validateHashes() {
|
||||
const atlasDir = path.join(process.cwd(), 'atlas');
|
||||
const files = [];
|
||||
async function collectFiles(dir) {
|
||||
const entries = await fs.readdir(dir, { withFileTypes: true });
|
||||
entries.sort((a, b) => a.name.localeCompare(b.name));
|
||||
for (const entry of entries) {
|
||||
const fullPath = path.join(dir, entry.name);
|
||||
if (entry.isDirectory()) {
|
||||
await collectFiles(fullPath);
|
||||
} else if (entry.name.endsWith('.md')) {
|
||||
files.push(fullPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
await collectFiles(atlasDir);
|
||||
|
||||
const results = [];
|
||||
for (const file of files) {
|
||||
const result = await computeHash(file);
|
||||
results.push(result);
|
||||
}
|
||||
if (results.some(r => !r.match)) {
|
||||
console.error('Hash mismatches detected. Run `node tools/hash.js fix` to update IDs.');
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Main
|
||||
async function main() {
|
||||
const mode = process.argv[2] || 'check';
|
||||
if (mode === 'validate') {
|
||||
await validateHashes();
|
||||
return;
|
||||
}
|
||||
|
||||
const atlasDir = path.join(process.cwd(), 'atlas');
|
||||
const files = [];
|
||||
async function collectFiles(dir) {
|
||||
const entries = await fs.readdir(dir, { withFileTypes: true });
|
||||
entries.sort((a, b) => a.name.localeCompare(b.name));
|
||||
for (const entry of entries) {
|
||||
const fullPath = path.join(dir, entry.name);
|
||||
if (entry.isDirectory()) {
|
||||
await collectFiles(fullPath);
|
||||
} else if (entry.name.endsWith('.md')) {
|
||||
files.push(fullPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
await collectFiles(atlasDir);
|
||||
|
||||
const results = [];
|
||||
for (const file of files) {
|
||||
if (mode === 'fix') {
|
||||
await fixHash(file);
|
||||
} else {
|
||||
const result = await computeHash(file);
|
||||
results.push(result);
|
||||
}
|
||||
}
|
||||
if (mode === 'check' && results.some(r => !r.match)) {
|
||||
console.error('Hash mismatches detected. Run `node tools/hash.js fix` to update IDs.');
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
Loading…
Add table
Add a link
Reference in a new issue