Files
santifer--career-ops/application-answers.mjs
T
wehub-resource-sync d083df1fdb
CodeQL Analysis / Analyze (javascript-typescript) (push) Failing after 2s
Web CI / web typecheck + build (push) Failing after 1s
Release Please / release-please (push) Failing after 1s
CodeQL Analysis / Analyze (go) (push) Failing after 16s
chore: import upstream snapshot with attribution
2026-07-13 12:02:43 +08:00

225 lines
6.8 KiB
JavaScript

#!/usr/bin/env node
import { readFileSync, writeFileSync } from 'fs';
import { resolve } from 'path';
import { fileURLToPath } from 'url';
export const APPLICATION_ANSWERS_HEADING = '## Application Answers';
const VALID_STATES = new Set(['filled', 'submitted']);
function inline(value) {
return String(value ?? '').replace(/\s+/g, ' ').trim();
}
function valueText(value) {
if (Array.isArray(value)) return value.map(inline).filter(Boolean).join(', ');
return String(value ?? '').trim();
}
function pick(object, keys) {
for (const key of keys) {
const value = object?.[key];
if (Array.isArray(value)) {
if (value.length > 0) return value;
continue;
}
if (value !== undefined && value !== null && String(value).trim()) return value;
}
return '';
}
function list(value) {
return Array.isArray(value) ? value : [];
}
function normalizeState(state) {
const normalized = inline(state || 'filled').toLowerCase();
if (!VALID_STATES.has(normalized)) {
throw new Error(`Application answer state must be one of: ${[...VALID_STATES].join(', ')}`);
}
return normalized;
}
function normalizeDate(date) {
return inline(date || new Date().toISOString().slice(0, 10));
}
function quoteBlock(value) {
const text = String(value ?? '').replace(/\r\n/g, '\n').trim();
if (!text) return '> Not recorded.';
return text.split('\n').map((line) => `> ${line}`).join('\n');
}
function qaLines(entries, { labelKeys, valueKeys, fallback }) {
if (entries.length === 0) return ['- None captured.'];
return entries.flatMap((entry, index) => {
const label = inline(pick(entry, labelKeys)) || `${fallback} ${index + 1}`;
const answer = pick(entry, valueKeys);
return [
`${index + 1}. **${label}**`,
'',
quoteBlock(answer),
'',
];
}).slice(0, -1);
}
function compactLines(entries, { labelKeys, valueKeys, fallback }) {
if (entries.length === 0) return ['- None captured.'];
return entries.map((entry, index) => {
const label = inline(pick(entry, labelKeys)) || `${fallback} ${index + 1}`;
const value = valueText(pick(entry, valueKeys)) || 'Not recorded';
return `${index + 1}. **${label}:** ${value}`;
});
}
function fileLines(entries) {
if (entries.length === 0) return ['- None captured.'];
return entries.map((entry, index) => {
const label = inline(pick(entry, ['field', 'name', 'label', 'type'])) || `File ${index + 1}`;
const file = inline(pick(entry, ['path', 'file', 'filename', 'url'])) || 'Not recorded';
const version = inline(pick(entry, ['version', 'variant']));
return `${index + 1}. **${label}:** ${version ? `${file} (${version})` : file}`;
});
}
export function normalizeApplicationAnswersSnapshot(snapshot = {}) {
return {
date: normalizeDate(snapshot.date),
state: normalizeState(snapshot.state),
freeText: list(snapshot.freeText ?? snapshot.freeTextAnswers ?? snapshot.answers),
selections: list(snapshot.selections ?? snapshot.selectedOptions),
fieldValues: list(snapshot.fieldValues ?? snapshot.otherFields ?? snapshot.fields),
files: list(snapshot.files ?? snapshot.uploads ?? snapshot.filesUsed),
};
}
export function formatApplicationAnswersSection(snapshot = {}) {
const normalized = normalizeApplicationAnswersSnapshot(snapshot);
const lines = [
APPLICATION_ANSWERS_HEADING,
'',
`**Date:** ${normalized.date}`,
`**State:** ${normalized.state}`,
'',
'### Free-text answers',
'',
...qaLines(normalized.freeText, {
labelKeys: ['question', 'field', 'label', 'prompt'],
valueKeys: ['answer', 'response', 'value', 'text'],
fallback: 'Answer',
}),
'',
'### Selections made',
'',
...compactLines(normalized.selections, {
labelKeys: ['question', 'field', 'label', 'prompt'],
valueKeys: ['selection', 'selected', 'answer', 'value', 'options'],
fallback: 'Selection',
}),
'',
'### Other field values',
'',
...compactLines(normalized.fieldValues, {
labelKeys: ['question', 'field', 'label', 'prompt'],
valueKeys: ['answer', 'response', 'value', 'text'],
fallback: 'Field',
}),
'',
'### Files used',
'',
...fileLines(normalized.files),
];
return `${lines.join('\n').replace(/\n{3,}/g, '\n\n').trim()}\n`;
}
export function upsertApplicationAnswersSection(reportText, snapshot = {}) {
const report = String(reportText ?? '').replace(/\r\n/g, '\n');
const section = formatApplicationAnswersSection(snapshot).trimEnd();
const heading = /^## Application Answers\s*$/m.exec(report);
if (!heading) {
return `${report.trimEnd()}\n\n${section}\n`;
}
const start = heading.index;
const afterHeading = start + heading[0].length;
const nextHeading = /^## .+$/m.exec(report.slice(afterHeading));
const end = nextHeading ? afterHeading + nextHeading.index : report.length;
const before = report.slice(0, start).trimEnd();
const after = report.slice(end).trimStart();
return [before, section, after].filter(Boolean).join('\n\n') + '\n';
}
function parseArgs(argv) {
const args = {};
for (let i = 0; i < argv.length; i += 1) {
const arg = argv[i];
if (arg === '--help' || arg === '-h') args.help = true;
else if (arg.startsWith('--')) {
const value = argv[i + 1];
if (!value || value.startsWith('--')) {
throw new Error(`Missing value for ${arg}`);
}
args[arg.slice(2)] = value;
i += 1;
}
}
return args;
}
function usage() {
return [
'Usage: node application-answers.mjs --report <report.md> --input <answers.json> [--state filled|submitted] [--date YYYY-MM-DD]',
'',
'The input JSON may contain: freeText, selections, fieldValues, files, date, state.',
].join('\n');
}
async function main() {
let args;
try {
args = parseArgs(process.argv.slice(2));
} catch (err) {
console.error(`${err.message}\n\n${usage()}`);
process.exitCode = 1;
return;
}
if (args.help) {
console.log(usage());
return;
}
if (!args.report || !args.input) {
console.error(usage());
process.exitCode = 1;
return;
}
const inputText = args.input === '-' ? readFileSync(0, 'utf-8') : readFileSync(resolve(args.input), 'utf-8');
const input = JSON.parse(inputText);
const snapshot = {
...input,
date: args.date || input.date,
state: args.state || input.state,
};
const reportPath = resolve(args.report);
const updated = upsertApplicationAnswersSection(readFileSync(reportPath, 'utf-8'), snapshot);
writeFileSync(reportPath, updated, 'utf-8');
const normalized = normalizeApplicationAnswersSnapshot(snapshot);
console.log(JSON.stringify({ report: reportPath, date: normalized.date, state: normalized.state }, null, 2));
}
if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
main().catch((err) => {
console.error(err.message);
process.exitCode = 1;
});
}