radroots_sdk_artifact_lib.mjs (2517B)
1 import { lstatSync, readFileSync, realpathSync } from "node:fs"; 2 import { isAbsolute, resolve } from "node:path"; 3 import { spawnSync } from "node:child_process"; 4 5 export const ARTIFACT_ROUTES = Object.freeze({ 6 typescript: Object.freeze({ 7 target: "typescript", 8 language: "typescript", 9 output: "contracts/provenance/sdk/typescript.json", 10 }), 11 wasm: Object.freeze({ 12 target: "wasm", 13 language: "javascript", 14 output: "contracts/provenance/sdk/wasm.json", 15 }), 16 swift: Object.freeze({ 17 target: "ffi", 18 language: "swift", 19 output: "contracts/provenance/sdk/swift.json", 20 }), 21 kotlin: Object.freeze({ 22 target: "ffi", 23 language: "kotlin", 24 output: "contracts/provenance/sdk/kotlin.json", 25 }), 26 }); 27 28 export function validateConsumerRoot(root) { 29 const marker = readFileSync(resolve(root, ".radroots-consumer-root"), "utf8"); 30 if (marker !== "sdk\n") { 31 throw new Error("consumer marker must contain exactly sdk followed by LF"); 32 } 33 } 34 35 export function resolveCanonicalSourceRoot(raw) { 36 if (!raw || !isAbsolute(raw)) { 37 throw new Error("RADROOTS_LIB_SOURCE_ROOT must be an absolute path"); 38 } 39 const requested = resolve(raw); 40 const metadata = lstatSync(requested); 41 if (!metadata.isDirectory() || metadata.isSymbolicLink()) { 42 throw new Error("RADROOTS_LIB_SOURCE_ROOT must be a real directory"); 43 } 44 const canonical = realpathSync(requested); 45 if (canonical !== requested) { 46 throw new Error("RADROOTS_LIB_SOURCE_ROOT must not traverse symlinks"); 47 } 48 return canonical; 49 } 50 51 export function artifactRoute(name) { 52 const route = ARTIFACT_ROUTES[name]; 53 if (!route) { 54 throw new Error(`unsupported SDK artifact route: ${name ?? "<missing>"}`); 55 } 56 return route; 57 } 58 59 export function run(command, args, options = {}) { 60 const result = spawnSync(command, args, { 61 cwd: options.cwd, 62 env: options.env, 63 stdio: options.capture ? "pipe" : "inherit", 64 encoding: options.capture ? "utf8" : undefined, 65 }); 66 if (result.error) { 67 throw result.error; 68 } 69 if (result.status !== 0) { 70 throw new Error(`${command} exited with status ${result.status ?? "unknown"}`); 71 } 72 return options.capture ? result.stdout.trim() : ""; 73 } 74 75 export function sourceDateEpoch(sourceRoot, revision, env) { 76 const value = run( 77 "git", 78 ["show", "-s", "--format=%ct", revision], 79 { cwd: sourceRoot, env, capture: true }, 80 ); 81 if (!/^[0-9]+$/.test(value)) { 82 throw new Error("canonical source commit timestamp is invalid"); 83 } 84 return value; 85 }