diff options
Diffstat (limited to 'scripts')
| -rw-r--r-- | scripts/compile-content.ts | 113 |
1 files changed, 113 insertions, 0 deletions
diff --git a/scripts/compile-content.ts b/scripts/compile-content.ts new file mode 100644 index 0000000..b3e8989 --- /dev/null +++ b/scripts/compile-content.ts @@ -0,0 +1,113 @@ +import { createHash } from 'node:crypto' +import { + mkdirSync, + readFileSync, + readdirSync, + rmSync, + statSync, + writeFileSync +} from 'node:fs' +import { dirname, join, relative, sep } from 'node:path' +import { fileURLToPath } from 'node:url' + +import { parse as parseYaml } from 'yaml' + +import type { + ContentManifest, + ManifestEntry +} from '@hollowdark/content-system/manifest/schema' + +const HERE = dirname(fileURLToPath(import.meta.url)) +const PROJECT_ROOT = join(HERE, '..') +const CONTENT_DIR = join(PROJECT_ROOT, 'content') +const OUT_CONTENT_DIR = join(PROJECT_ROOT, 'static', 'content') +const OUT_MANIFEST_PATH = join(PROJECT_ROOT, 'static', 'content-manifest.json') + +function readPackageVersion(): string { + const pkg = JSON.parse(readFileSync(join(PROJECT_ROOT, 'package.json'), 'utf8')) as { + version: string + } + return pkg.version +} + +function walkYaml(dir: string): readonly string[] { + const out: string[] = [] + const walk = (current: string): void => { + for (const entry of readdirSync(current)) { + const full = join(current, entry) + const stat = statSync(full) + if (stat.isDirectory()) { + walk(full) + } else if (stat.isFile() && entry.endsWith('.yaml')) { + out.push(full) + } + } + } + walk(dir) + return out +} + +function toChunkId(absPath: string): string { + const rel = relative(CONTENT_DIR, absPath) + const withoutExt = rel.slice(0, -'.yaml'.length) + return withoutExt.split(sep).join('/') +} + +function shortHash(bytes: Buffer | string): string { + return createHash('sha256').update(bytes).digest('hex').slice(0, 16) +} + +function writeChunk(chunkId: string, data: unknown, version: string): ManifestEntry { + const jsonBody = JSON.stringify(data) + const hash = shortHash(jsonBody) + const segments = chunkId.split('/') + const leaf = segments[segments.length - 1] + if (leaf === undefined) { + throw new Error(`Empty chunk id from compilation input`) + } + const parentSegments = segments.slice(0, -1) + const finalName = `${leaf}.v${version}.${hash}.json` + const outPath = join(OUT_CONTENT_DIR, ...parentSegments, finalName) + mkdirSync(dirname(outPath), { recursive: true }) + writeFileSync(outPath, jsonBody, 'utf8') + + const urlPath = [...parentSegments, finalName].join('/') + return { + version, + hash, + url: `/content/${urlPath}`, + size_bytes: Buffer.byteLength(jsonBody, 'utf8') + } +} + +function compile(): void { + const version = readPackageVersion() + + rmSync(OUT_CONTENT_DIR, { recursive: true, force: true }) + mkdirSync(OUT_CONTENT_DIR, { recursive: true }) + + const yamlFiles = walkYaml(CONTENT_DIR) + const chunks: Record<string, ManifestEntry> = {} + + for (const absPath of yamlFiles) { + const raw = readFileSync(absPath, 'utf8') + const parsed = parseYaml(raw) as unknown + const chunkId = toChunkId(absPath) + chunks[chunkId] = writeChunk(chunkId, parsed, version) + } + + const manifest: ContentManifest = { + content_version: version, + app_version: version, + generated_at: new Date().toISOString(), + chunks + } + + mkdirSync(dirname(OUT_MANIFEST_PATH), { recursive: true }) + writeFileSync(OUT_MANIFEST_PATH, JSON.stringify(manifest, null, 2), 'utf8') + + const chunkCount = Object.keys(chunks).length + process.stdout.write(`Compiled ${chunkCount} content chunk${chunkCount === 1 ? '' : 's'} to ${OUT_CONTENT_DIR}\n`) +} + +compile() |
