Skill Tracer
Skills › Archify › archify › render-explainer.mjs

render-explainer.mjs

42 lines · archify/renderers/explainer/render-explainer.mjs at a406594

1import fs from 'node:fs';
2import path from 'node:path';
3import { randomUUID, createHash } from 'node:crypto';
4import { installRendererDiagnosticBoundary, throwDiagnosticError } from '../shared/diagnostics.mjs';
5import { resolveOutputPath } from '../shared/output-path.mjs';
6import { captureAtomicOutput, captureRegularFileBinding, publishRegularFileBinding, releaseRegularFileBinding, removeOwnedRegularFile } from '../shared/atomic-output.mjs';
7import { prepareExplainer, readPreparedBundle, assertPreparedDependencies } from '../../explainer/prepare.mjs';
8import { renderExplainer } from '../../explainer/render.mjs';
9installRendererDiagnosticBoundary();
10const inputPath = path.resolve(process.argv[2]);
11const sourceText = fs.readFileSync(inputPath, 'utf8');
12const bundle = process.env.ARCHIFY_EXPLAINER_PREPARED
13 ? readPreparedBundle(process.env.ARCHIFY_EXPLAINER_PREPARED, sourceText, inputPath, { expectedInputPath: process.env.ARCHIFY_EXPLAINER_SNAPSHOT_INPUT || inputPath })
14 : await prepareExplainer(sourceText, { inputPath, repoRoot: process.env.ARCHIFY_REPO_ROOT, quality: process.env.ARCHIFY_QUALITY_PROFILE });
15const request = { requestedOutput: process.argv[3], authoredOutput: bundle.document.meta.output, defaultOutput: 'explainer.html', inputPaths: [inputPath, bundle.sourcePath] };
16const { outputPath } = resolveOutputPath(request);
17fs.mkdirSync(path.dirname(outputPath), { recursive: true });
18const capture = captureAtomicOutput(outputPath, { requestedEntryPolicy: 'regular-or-absent' });
19if (capture.status !== 'captured') throwDiagnosticError('Could not capture explainer output safely.', [{code:'output/target-indeterminate',message:'Could not capture explainer output safely.',subject:{output:outputPath},evidence:{reason:capture.reason},supportedFixes:[]}]);
20let html;
21try { html = renderExplainer(bundle.document, { ...bundle, preparedBundle: bundle, pageDirectory: process.env.ARCHIFY_EXPLAINER_PAGE_DIR || path.dirname(outputPath) }); }
22catch (error) { if (error.diagnostics) error.archifyDiagnostics = error.diagnostics; throw error; }
23const bytes = Buffer.from(html);
24const candidate = path.join(path.dirname(capture.commitPath), `.archify-explainer-${randomUUID()}.html`);
25let binding;
26let owned;
27try {
28 fs.writeFileSync(candidate, bytes, { flag: 'wx', ...(capture.mode === null ? {} : {mode:capture.mode}) });
29 const stat = fs.lstatSync(candidate, {bigint:true});
30 owned = {device:stat.dev,inode:stat.ino};
31 if (capture.mode !== null) fs.chmodSync(candidate,capture.mode);
32 const staged = captureRegularFileBinding(candidate,{expectedIdentity:owned,expectedSha256:createHash('sha256').update(bytes).digest('hex'),expectedBytes:bytes.length});
33 if (staged.status !== 'captured') throw new Error('Could not bind explainer candidate.');
34 binding = staged.binding;
35 assertPreparedDependencies(bundle);
36 resolveOutputPath(request);
37 const result = publishRegularFileBinding(binding,candidate,capture.snapshot,{subject:'explainer-output'});
38 if (!['committed','committed-with-warning'].includes(result.status)) throw new Error(`Could not publish explainer output (${result.reason?.code || result.status}).`);
39} finally {
40 if(binding) releaseRegularFileBinding(binding);
41 if(owned) removeOwnedRegularFile(candidate,owned,{subject:'explainer-output'});