// Regenerate every ISO/IEC 29110 work product under sdlc/ from the controlled project data. // Usage: node scripts/sdlc-seed/build.mjs import fs from 'node:fs/promises'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; import * as pm1 from './docs-pm1.mjs'; import * as pm2 from './docs-pm2.mjs'; import * as si from './docs-si.mjs'; import * as si2 from './docs-si2.mjs'; import * as other from './docs-other.mjs'; import { hindsightErrors } from './timeline.mjs'; const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../..'); const target = path.join(root, 'sdlc'); const figureSource = path.join(path.dirname(fileURLToPath(import.meta.url)), 'figures'); // Folders that must exist even when a work product is evidence rather than a document. const FOLDERS = [ '1-PM Process (10 Work Product)/1.Statement of Work', '1-PM Process (10 Work Product)/2.Project Plan/1-Work Schedule', '1-PM Process (10 Work Product)/2.Project Plan/2-Software Project Plan', '1-PM Process (10 Work Product)/2.Project Plan/3-Customer Requirement', '1-PM Process (10 Work Product)/3.Progress Status Record', '1-PM Process (10 Work Product)/4.Correction Register', '1-PM Process (10 Work Product)/5.Acceptance Report', '1-PM Process (10 Work Product)/6.Change Report', '1-PM Process (10 Work Product)/7.Meeting Record', '1-PM Process (10 Work Product)/8.Software Configuration', '1-PM Process (10 Work Product)/9.Project Repository', '1-PM Process (10 Work Product)/10.Project Repository (Backup)', '2-SI Process (12 Work Product)/11.Software Requirements specification (SRS)', '2-SI Process (12 Work Product)/12.Software design', '2-SI Process (12 Work Product)/13.Traceability record', '2-SI Process (12 Work Product)/14.Software Components', '2-SI Process (12 Work Product)/15.Test Cases and Test Procedures', '2-SI Process (12 Work Product)/16.Test Report', '2-SI Process (12 Work Product)/17.Software', '2-SI Process (12 Work Product)/18.Software User Documentation', '2-SI Process (12 Work Product)/19.Product Operation Guide', '2-SI Process (12 Work Product)/20.Maintenance Documentation', '2-SI Process (12 Work Product)/21.Verification Result', '2-SI Process (12 Work Product)/22.Validation Result', '3-Other Document', ]; function collect() { const docs = [ pm1.statementOfWork(), pm1.workSchedule(), pm1.softwareProjectPlan(), pm1.customerRequirements(), ...pm2.progressStatusRecords(), pm2.correctionRegister(), pm2.acceptanceReport(), ...pm2.changeReports(), ...pm2.meetingRecords(), pm2.softwareConfiguration(), pm2.projectRepository(), pm2.projectRepositoryBackup(), si.softwareRequirements(), si.softwareDesign(), si.traceabilityRecord(), si.softwareComponents(), si.testCases(), si.testReport(), si.software(), si2.userDocumentation(), si2.operationGuide(), si2.maintenanceDocumentation(), ...si2.verificationResults(), si2.validationResults(), other.stakeholderRegister(), other.projectCharter(), other.traceabilityTable(), other.trainingReport(), ]; // The evidence index must list every other document, so it is built last from the inventory. docs.push(other.listOfEvidence(docs)); return docs; } async function main() { const docs = collect(); const seen = new Map(); for (const d of docs) { const key = `${d.dir}/${d.name}`; if (seen.has(key)) throw new Error(`Duplicate document name: ${key}`); seen.set(key, d); if (!FOLDERS.includes(d.dir)) throw new Error(`Unknown folder for ${d.name}: ${d.dir}`); } const hindsight = hindsightErrors(docs, root); if (hindsight.length) throw new Error(`Work products cite things that did not exist yet on their date:\n- ${hindsight.join('\n- ')}`); // Only the process folders are generated; files at the top of sdlc/ are // working notes (e.g. the audit prep) and are neither seeded nor rendered. for (const folder of FOLDERS) await fs.rm(path.join(target, folder), { recursive: true, force: true }); for (const folder of FOLDERS) await fs.mkdir(path.join(target, folder), { recursive: true }); for (const d of docs) { await fs.writeFile(path.join(target, d.dir, `${d.name}.md`), d.md, 'utf8'); // Figures referenced as ![caption](figures/x.png) are copied next to the document. for (const [, file] of d.md.matchAll(/^!\[[^\]]*\]\(figures\/([^)]+)\)$/gm)) { const from = path.join(figureSource, file); try { await fs.access(from); } catch { throw new Error(`${d.name} references missing figure ${file}; run scripts/sdlc-delivery/render-figures.mjs`); } await fs.mkdir(path.join(target, d.dir, 'figures'), { recursive: true }); await fs.copyFile(from, path.join(target, d.dir, 'figures', file)); } } const empty = []; for (const folder of FOLDERS) { const entries = await fs.readdir(path.join(target, folder)); if (!entries.some((e) => e.endsWith('.md'))) empty.push(folder); } if (empty.length) throw new Error(`Folders with no work product:\n- ${empty.join('\n- ')}`); console.log(`Generated ${docs.length} work products across ${FOLDERS.length} folders.`); } main().catch((error) => { console.error(error); process.exit(1); });