hololake-system-architecture/product-source/hololake-native-desktop/scripts/compile-gls-runtime-registry.mjs

139 lines
5.4 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { createHash } from 'node:crypto'
import { readdir, readFile, writeFile } from 'node:fs/promises'
import path from 'node:path'
import process from 'node:process'
const args = new Map()
for (let index = 2; index < process.argv.length; index += 2) {
args.set(process.argv[index], process.argv[index + 1])
}
const sourceRoot = args.get('--source-root')
const sourceCommit = args.get('--source-commit')
const output = args.get('--output')
const projectionsPath = args.get('--projections')
if (!sourceRoot || !sourceCommit || !output || !projectionsPath) {
throw new Error('usage: compile-gls-runtime-registry.mjs --source-root <REPO-012/gls> --source-commit <sha> --output <registry.json> --projections <projections.json>')
}
if (!/^[a-f0-9]{40}$/.test(sourceCommit)) throw new Error('source commit must be a full SHA-1')
async function walk(directory, prefix = '') {
const entries = await readdir(directory, { withFileTypes: true })
const paths = []
for (const entry of entries) {
const relative = prefix ? `${prefix}/${entry.name}` : entry.name
if (entry.isDirectory()) paths.push(...await walk(path.join(directory, entry.name), relative))
else paths.push(relative)
}
return paths
}
function sourceRank(relative) {
if (!relative.includes('/')) return 0
if (relative.startsWith('standards/')) return 1
if (relative.includes('/notion-export/')) return 3
return 2
}
function cleanHeading(line, id) {
return line
.replace(/^#+\s*/, '')
.replaceAll('**', '')
.replace(new RegExp(`^${id}\\s*[·:-]?\\s*`), '')
.trim() || id
}
function sourceStatus(raw) {
const line = raw.split(/\r?\n/, 100).find((item) => /^\s*(?:>|[-*]\s*)?(?:状态|Status)\s*[:]/i.test(item))
if (!line) return 'UNSPECIFIED_SOURCE_STATUS'
return line.replace(/^\s*(?:>|[-*]\s*)?(?:状态|Status)\s*[:]\s*/i, '').replaceAll('`', '').trim()
}
const projectionManifest = JSON.parse(await readFile(projectionsPath, 'utf8'))
if (projectionManifest.schema !== 'hololake.gls-executable-projections/v1' || projectionManifest.source_commit !== sourceCommit) {
throw new Error('projection manifest does not match the selected REPO-012 source commit')
}
const candidates = (await walk(sourceRoot))
.map((relative) => ({ relative, match: path.basename(relative).match(/^(GLS-\d{4}).*\.hdlp$/) }))
.filter((item) => item.match)
.map((item) => ({ id: item.match[1], relative: item.relative }))
const grouped = new Map()
for (const candidate of candidates) {
const group = grouped.get(candidate.id) || []
group.push(candidate.relative)
grouped.set(candidate.id, group)
}
const protocols = []
for (const [id, sources] of [...grouped.entries()].sort(([left], [right]) => left.localeCompare(right))) {
sources.sort((left, right) => sourceRank(left) - sourceRank(right) || left.localeCompare(right))
const relative = sources[0]
const raw = await readFile(path.join(sourceRoot, relative), 'utf8')
const heading = raw.split(/\r?\n/).find((line) => line.startsWith('#') && line.includes(id))
const projection = projectionManifest.projections[id]
protocols.push({
id,
title: cleanHeading(heading || id, id),
source_status: sourceStatus(raw),
source_path: `gls/${relative}`,
source_sha256: createHash('sha256').update(raw).digest('hex'),
alternate_source_count: sources.length - 1,
projection_state: projection ? 'EXECUTABLE_PROJECTION' : 'INVENTORIED_NOT_EXECUTABLE',
projection_kind: projection?.projection_kind || null,
adapter: projection?.adapter || null,
event_kinds: projection?.event_kinds || [],
dependencies: projection?.dependencies || [],
})
}
const known = new Set(protocols.map((protocol) => protocol.id))
for (const [id, projection] of Object.entries(projectionManifest.projections)) {
if (!known.has(id)) throw new Error(`projection references an unknown protocol: ${id}`)
for (const dependency of projection.dependencies) {
if (!projectionManifest.projections[dependency]) {
throw new Error(`${id} depends on a protocol without an executable projection: ${dependency}`)
}
}
}
const visiting = new Set()
const visited = new Set()
function visit(id) {
if (visiting.has(id)) throw new Error(`executable protocol dependency cycle at ${id}`)
if (visited.has(id)) return
visiting.add(id)
for (const dependency of projectionManifest.projections[id].dependencies) visit(dependency)
visiting.delete(id)
visited.add(id)
}
for (const id of Object.keys(projectionManifest.projections)) visit(id)
const executableCount = protocols.filter((protocol) => protocol.projection_state === 'EXECUTABLE_PROJECTION').length
const registry = {
schema: 'hololake.gls-protocol-runtime-registry/v1',
record_id: 'HLP-GLS-PROTOCOL-RUNTIME-001',
source: {
repository: 'REPO-012',
commit: sourceCommit,
root: 'gls',
},
compiler: {
source_protocol_is_human_and_machine_authority: true,
raw_protocol_text_executed: false,
arbitrary_protocol_code_allowed: false,
executable_projection_requires_explicit_adapter: true,
unprojected_protocol_behavior: 'INVENTORIED_NOT_EXECUTABLE',
dependency_cycles: 'REJECT',
unknown_protocol: 'FAIL_CLOSED',
},
protocol_count: protocols.length,
executable_projection_count: executableCount,
inventoried_not_executable_count: protocols.length - executableCount,
protocols,
}
await writeFile(output, `${JSON.stringify(registry, null, 2)}\n`)
console.log(`GLS_RUNTIME_REGISTRY_COMPILED protocols=${protocols.length} executable=${executableCount} output=${output}`)