// Runtime ESM loader for StellaOps Scanner runtime evidence // Usage: node --experimental-loader=./runtime-esm-loader.mjs app.mjs import fs from 'fs'; import path from 'path'; import crypto from 'crypto'; import { fileURLToPath, pathToFileURL } from 'url'; const outPath = process.env.SCANNER_NODE_RUNTIME_OUT || path.join(process.cwd(), 'node-runtime-evidence.ndjson'); const root = process.env.SCANNER_NODE_ROOT || process.cwd(); const loaderId = hashLoaderId(import.meta.url); function hashLoaderId(value) { return crypto.createHash('sha256').update(value || '').digest('hex'); } function scrub(p) { if (!p) return p; try { const absolute = p.startsWith('file:') ? fileURLToPath(p) : p; const rel = path.relative(root, absolute); return rel.startsWith('..') ? p : rel.split(path.sep).join('/'); } catch { return p; } } function emit(record) { try { fs.appendFileSync(outPath, JSON.stringify(record) + '\n'); } catch { // best-effort: ignore write failures } } export async function resolve(specifier, context, next) { const parent = context.parentURL ? scrub(context.parentURL) : undefined; const target = scrub(specifier); emit({ type: 'edge', from: parent, to: target, reason: 'runtime-import', loaderId }); return next(specifier, context, next); } export async function load(url, context, next) { const pathOrUrl = scrub(url); emit({ type: 'component', path: pathOrUrl, reason: 'runtime-load', loaderId }); return next(url, context, next); }