/**
* Regenerate the classified reference documents (issue #209, ADR 0022):
* `apps/api/assets/reference-vs-nfd.docx` / `.odt`.
*
* The DOCX/ODT export of a classified page passes these to pandoc via
* `--reference-doc`; pandoc copies the reference's page setup — including
* headers and footers — into its output, which is how the VS-NfD marking
* repeats on every page in Word and LibreOffice without being deletable
* body text.
*
* The binaries are DERIVED files: base = the default reference documents of
* the PINNED pandoc (`pandoc/core:3.6`, the exact sidecar the stages run),
* plus a header and footer carrying the marking. Never edit the binaries by
* hand — edit this script and re-run it (Docker required):
*
* node apps/api/scripts/gen-classified-reference-docs.mjs
*
* The marking wording comes from @dorfteich/shared (single source, ADR
* 0022); the shared package must be built (`pnpm --filter @dorfteich/shared
* build`).
*/
import { execFileSync } from 'node:child_process';
import { mkdirSync, writeFileSync } from 'node:fs';
import { dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';
import { classificationMarking } from '@dorfteich/shared';
import { strToU8, strFromU8, unzipSync, zipSync } from 'fflate';
const PANDOC_IMAGE = 'pandoc/core:3.6';
const MARKING = classificationMarking('vs_nfd');
const outDir = join(dirname(fileURLToPath(import.meta.url)), '../assets');
function defaultReference(name) {
return execFileSync('docker', ['run', '--rm', PANDOC_IMAGE, '--print-default-data-file', name], {
maxBuffer: 64 * 1024 * 1024,
});
}
function escapeXml(value) {
return value.replace(/&/g, '&').replace(//g, '>');
}
/** DOCX: add word/header1.xml + word/footer1.xml, register them in the
* content types and document relationships, and reference them from the
* document's sectPr — Word repeats them on every page. */
function patchDocx(bytes) {
const zip = unzipSync(new Uint8Array(bytes));
const marking = escapeXml(MARKING);
const partXml = (root) =>
`\n` +
`` +
`` +
`${marking}` +
``;
zip['word/header1.xml'] = strToU8(partXml('hdr'));
zip['word/footer1.xml'] = strToU8(partXml('ftr'));
const types = strFromU8(zip['[Content_Types].xml']);
zip['[Content_Types].xml'] = strToU8(
types.replace(
'',
'' +
'' +
'',
),
);
const rels = strFromU8(zip['word/_rels/document.xml.rels']);
zip['word/_rels/document.xml.rels'] = strToU8(
rels.replace(
'',
'' +
'' +
'',
),
);
const doc = strFromU8(zip['word/document.xml']);
if (!doc.includes('')) throw new Error('reference.docx has no sectPr');
zip['word/document.xml'] = strToU8(
doc.replace(
'',
'' +
'' +
'',
),
);
return zipSync(zip);
}
/** ODT: give the Standard master page a header with the marking and put the
* marking next to the existing page number in its footer — LibreOffice
* repeats master-page headers/footers on every page. */
function patchOdt(bytes) {
const zip = unzipSync(new Uint8Array(bytes));
const marking = escapeXml(MARKING);
const styles = strFromU8(zip['styles.xml']);
if (!styles.includes('')) throw new Error('reference.odt has no footer');
const patched = styles
.replace(
'',
`${marking}`,
)
.replace(
'\n ',
`\n ${marking} · `,
);
zip['styles.xml'] = strToU8(patched);
return zipSync(zip);
}
mkdirSync(outDir, { recursive: true });
writeFileSync(join(outDir, 'reference-vs-nfd.docx'), patchDocx(defaultReference('reference.docx')));
writeFileSync(join(outDir, 'reference-vs-nfd.odt'), patchOdt(defaultReference('reference.odt')));
console.log(`generated reference-vs-nfd.docx/.odt in ${outDir} (marking: ${MARKING})`);