|
| 1 | +#!/usr/bin/env node |
1 | 2 | import fs from "fs"; |
2 | 3 | import path from "path"; |
3 | 4 | import crypto from "crypto"; |
4 | 5 | import { fileURLToPath } from "url"; |
5 | 6 |
|
| 7 | +function die(msg) { |
| 8 | + console.error(`ERROR: ${msg}`); |
| 9 | + process.exit(1); |
| 10 | +} |
| 11 | + |
| 12 | +// --- Args: [rootDir] [outFile] |
| 13 | +const rootDirArg = process.argv[2] || "schemas/v1.0.0"; |
| 14 | +const outFileArg = process.argv[3] || "checksums.txt"; |
| 15 | + |
6 | 16 | // Resolve repo root (scripts/..) |
7 | 17 | const __filename = fileURLToPath(import.meta.url); |
8 | 18 | const __dirname = path.dirname(__filename); |
9 | 19 | const repoRoot = path.resolve(__dirname, ".."); |
10 | 20 |
|
11 | | -// Which paths to include in checksums |
12 | | -const ROOTS = [ |
13 | | - "schemas/v1.0.0" |
14 | | -]; |
15 | | - |
16 | | -// Paths to skip entirely |
17 | | -const IGNORE_PREFIXES = [ |
18 | | - ".git", |
19 | | - "node_modules", |
20 | | - ".github", |
21 | | - "checksums.txt" |
22 | | -]; |
23 | | - |
24 | | -function shouldIgnore(relPath) { |
25 | | - return IGNORE_PREFIXES.some((prefix) => { |
26 | | - return ( |
27 | | - relPath === prefix || |
28 | | - relPath.startsWith(prefix + path.sep) |
29 | | - ); |
30 | | - }); |
31 | | -} |
| 21 | +const rootAbs = path.resolve(repoRoot, rootDirArg); |
| 22 | +const outAbs = path.resolve(repoRoot, outFileArg); |
32 | 23 |
|
33 | | -function walk(relPath, acc) { |
34 | | - const absPath = path.join(repoRoot, relPath); |
35 | | - const stat = fs.statSync(absPath); |
| 24 | +const IGNORE_PREFIXES = [".git", "node_modules", ".github"]; |
36 | 25 |
|
37 | | - if (stat.isDirectory()) { |
38 | | - const entries = fs.readdirSync(absPath); |
39 | | - for (const entry of entries) { |
40 | | - const childRel = path.join(relPath, entry); |
41 | | - if (shouldIgnore(childRel)) continue; |
42 | | - walk(childRel, acc); |
43 | | - } |
44 | | - } else if (stat.isFile()) { |
45 | | - if (!shouldIgnore(relPath)) { |
46 | | - acc.push(relPath.replace(/\\/g, "/")); // normalize to POSIX-style |
47 | | - } |
48 | | - } |
| 26 | +function toPosix(p) { |
| 27 | + return p.replace(/\\/g, "/"); |
| 28 | +} |
| 29 | + |
| 30 | +function shouldIgnore(relPosixPath) { |
| 31 | + return IGNORE_PREFIXES.some( |
| 32 | + (prefix) => relPosixPath === prefix || relPosixPath.startsWith(prefix + "/") |
| 33 | + ); |
49 | 34 | } |
50 | 35 |
|
51 | | -function sha256File(relPath) { |
52 | | - const absPath = path.join(repoRoot, relPath); |
| 36 | +function sha256File(absPath) { |
53 | 37 | const buf = fs.readFileSync(absPath); |
54 | | - const hash = crypto.createHash("sha256").update(buf).digest("hex"); |
55 | | - return hash; |
| 38 | + return crypto.createHash("sha256").update(buf).digest("hex"); |
| 39 | +} |
| 40 | + |
| 41 | +function walkDir(absDir, relBasePosix, acc) { |
| 42 | + const entries = fs.readdirSync(absDir, { withFileTypes: true }) |
| 43 | + .map((e) => e.name) |
| 44 | + .sort(); |
| 45 | + |
| 46 | + for (const name of entries) { |
| 47 | + const absChild = path.join(absDir, name); |
| 48 | + const relChildPosix = toPosix(path.posix.join(relBasePosix, name)); |
| 49 | + |
| 50 | + if (shouldIgnore(relChildPosix)) continue; |
| 51 | + |
| 52 | + const stat = fs.statSync(absChild); |
| 53 | + if (stat.isDirectory()) { |
| 54 | + walkDir(absChild, relChildPosix, acc); |
| 55 | + } else if (stat.isFile()) { |
| 56 | + // Canonical scope: JSON schemas only |
| 57 | + if (relChildPosix.endsWith(".json")) acc.push(relChildPosix); |
| 58 | + } |
| 59 | + } |
56 | 60 | } |
57 | 61 |
|
58 | 62 | function main() { |
| 63 | + if (!fs.existsSync(rootAbs)) die(`Missing root directory: ${rootDirArg}`); |
| 64 | + const st = fs.statSync(rootAbs); |
| 65 | + if (!st.isDirectory()) die(`Root is not a directory: ${rootDirArg}`); |
| 66 | + |
| 67 | + const relRootPosix = toPosix(rootDirArg); |
59 | 68 | const files = []; |
60 | | - for (const root of ROOTS) { |
61 | | - const abs = path.join(repoRoot, root); |
62 | | - if (!fs.existsSync(abs)) continue; |
63 | | - walk(root, files); |
64 | | - } |
| 69 | + walkDir(rootAbs, relRootPosix, files); |
65 | 70 |
|
66 | 71 | files.sort(); |
67 | 72 |
|
68 | | - const lines = files.map((rel) => { |
69 | | - const hash = sha256File(rel); |
70 | | - return `${hash} ${rel}`; |
| 73 | + const lines = files.map((relPosix) => { |
| 74 | + const absPath = path.join(repoRoot, relPosix); |
| 75 | + const hash = sha256File(absPath); |
| 76 | + // Match common sha256sum style (binary marker *) |
| 77 | + return `${hash} *${relPosix}`; |
71 | 78 | }); |
72 | 79 |
|
73 | | - const outPath = path.join(repoRoot, "checksums.txt"); |
74 | | - fs.writeFileSync(outPath, lines.join("\n") + "\n"); |
75 | | - |
76 | | - console.log(`Wrote ${lines.length} checksums to checksums.txt`); |
| 80 | + fs.writeFileSync(outAbs, lines.join("\n") + "\n", "utf8"); |
| 81 | + console.log(`Wrote ${lines.length} checksums to ${toPosix(path.relative(repoRoot, outAbs))}`); |
77 | 82 | } |
78 | 83 |
|
79 | 84 | main(); |
0 commit comments