Files
wms-app/scripts/sdlc-seed/build.mjs
T
ThanakornandClaude Opus 5.5 6c857005c0 docs(sdlc): write each work product as of its own date
- SOW, plan and design no longer cite the Aug baseline commit or the package tag
- Work Schedule status derived from its date; Test Case has no results yet
- build fails when a document cites a later commit, tag, ISS or ID (timeline.mjs)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-25 14:52:21 +07:00

101 lines
4.9 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- ')}`);
await fs.rm(target, { 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); });