103 lines
5.1 KiB
JavaScript
103 lines
5.1 KiB
JavaScript
// 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  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); });
|