62 lines
1.5 KiB
JavaScript
62 lines
1.5 KiB
JavaScript
// 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);
|
|
}
|