@pactosigna/records 0.1.6 → 0.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +260 -10
- package/dist/generate.d.ts.map +1 -1
- package/dist/generators/cer-generator.d.ts +10 -0
- package/dist/generators/cer-generator.d.ts.map +1 -0
- package/dist/generators/csf-generator.d.ts +10 -0
- package/dist/generators/csf-generator.d.ts.map +1 -0
- package/dist/generators/uef-generator.d.ts +15 -0
- package/dist/generators/uef-generator.d.ts.map +1 -0
- package/dist/generators/vv-generator.d.ts +10 -0
- package/dist/generators/vv-generator.d.ts.map +1 -0
- package/dist/github/changelog.d.ts +23 -0
- package/dist/github/changelog.d.ts.map +1 -1
- package/dist/github/drm-parser.d.ts +58 -0
- package/dist/github/drm-parser.d.ts.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +260 -6
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -876,7 +876,7 @@ ${probabilityLabels[labelIndex]}`,
|
|
|
876
876
|
};
|
|
877
877
|
}
|
|
878
878
|
|
|
879
|
-
//
|
|
879
|
+
// ../../node_modules/.pnpm/@pactosigna+schemas@0.1.19/node_modules/@pactosigna/schemas/dist/index.js
|
|
880
880
|
import { z as z2 } from "zod";
|
|
881
881
|
import { z as z22 } from "zod";
|
|
882
882
|
import { z as z3 } from "zod";
|
|
@@ -5351,11 +5351,257 @@ function generateSdd(input) {
|
|
|
5351
5351
|
});
|
|
5352
5352
|
}
|
|
5353
5353
|
|
|
5354
|
+
// src/generators/vv-generator.ts
|
|
5355
|
+
function generateVv(input) {
|
|
5356
|
+
const protocols = readDocuments(input.rootDir, "docs/test/protocols");
|
|
5357
|
+
const reports = readDocuments(input.rootDir, "docs/test/reports");
|
|
5358
|
+
const plans = readDocuments(input.rootDir, "docs/software/plans").filter(
|
|
5359
|
+
(d) => d.frontmatter.id.startsWith("STP-")
|
|
5360
|
+
);
|
|
5361
|
+
const docs = [...plans, ...protocols, ...reports];
|
|
5362
|
+
if (docs.length === 0) {
|
|
5363
|
+
console.warn("VV: no approved/effective V&V documents found \u2014 skipping");
|
|
5364
|
+
return null;
|
|
5365
|
+
}
|
|
5366
|
+
const content = [];
|
|
5367
|
+
content.push({ text: "Table of Contents", style: "h2", margin: [0, 0, 0, 10] });
|
|
5368
|
+
content.push({
|
|
5369
|
+
ol: docs.map((d) => ({
|
|
5370
|
+
text: `${d.frontmatter.id}: ${d.frontmatter.title}`,
|
|
5371
|
+
margin: [0, 2, 0, 2]
|
|
5372
|
+
}))
|
|
5373
|
+
});
|
|
5374
|
+
content.push({ text: "", pageBreak: "after" });
|
|
5375
|
+
for (const doc of docs) {
|
|
5376
|
+
content.push({
|
|
5377
|
+
text: `${doc.frontmatter.id}: ${doc.frontmatter.title}`,
|
|
5378
|
+
style: "h2",
|
|
5379
|
+
margin: [0, 10, 0, 6]
|
|
5380
|
+
});
|
|
5381
|
+
content.push(...markdownToPdfmake(doc.body));
|
|
5382
|
+
content.push({ text: "", margin: [0, 20, 0, 0] });
|
|
5383
|
+
}
|
|
5384
|
+
return buildDocumentDefinition({
|
|
5385
|
+
config: input.config,
|
|
5386
|
+
rootDir: input.rootDir,
|
|
5387
|
+
recordTitle: "Verification & Validation",
|
|
5388
|
+
recordId: "VV",
|
|
5389
|
+
version: input.version,
|
|
5390
|
+
date: input.date,
|
|
5391
|
+
revisionHistory: input.revisionHistory,
|
|
5392
|
+
content
|
|
5393
|
+
});
|
|
5394
|
+
}
|
|
5395
|
+
|
|
5396
|
+
// src/generators/uef-generator.ts
|
|
5397
|
+
function generateUef(input) {
|
|
5398
|
+
const plans = readDocuments(input.rootDir, "docs/usability");
|
|
5399
|
+
const useSpecs = readDocuments(input.rootDir, "docs/usability/use-specifications");
|
|
5400
|
+
const taskAnalyses = readDocuments(input.rootDir, "docs/usability/task-analyses");
|
|
5401
|
+
const evaluations = readDocuments(input.rootDir, "docs/usability/evaluations");
|
|
5402
|
+
const docs = [...plans, ...useSpecs, ...taskAnalyses, ...evaluations];
|
|
5403
|
+
if (docs.length === 0) {
|
|
5404
|
+
console.warn("UEF: no approved/effective usability documents found \u2014 skipping");
|
|
5405
|
+
return null;
|
|
5406
|
+
}
|
|
5407
|
+
const content = [];
|
|
5408
|
+
content.push({ text: "Table of Contents", style: "h2", margin: [0, 0, 0, 10] });
|
|
5409
|
+
content.push({
|
|
5410
|
+
ol: docs.map((d) => ({
|
|
5411
|
+
text: `${d.frontmatter.id}: ${d.frontmatter.title}`,
|
|
5412
|
+
margin: [0, 2, 0, 2]
|
|
5413
|
+
}))
|
|
5414
|
+
});
|
|
5415
|
+
content.push({ text: "", pageBreak: "after" });
|
|
5416
|
+
for (const doc of docs) {
|
|
5417
|
+
content.push({
|
|
5418
|
+
text: `${doc.frontmatter.id}: ${doc.frontmatter.title}`,
|
|
5419
|
+
style: "h2",
|
|
5420
|
+
margin: [0, 10, 0, 6]
|
|
5421
|
+
});
|
|
5422
|
+
content.push(...markdownToPdfmake(doc.body));
|
|
5423
|
+
content.push({ text: "", margin: [0, 20, 0, 0] });
|
|
5424
|
+
}
|
|
5425
|
+
return buildDocumentDefinition({
|
|
5426
|
+
config: input.config,
|
|
5427
|
+
rootDir: input.rootDir,
|
|
5428
|
+
recordTitle: "Usability Engineering File",
|
|
5429
|
+
recordId: "UEF",
|
|
5430
|
+
version: input.version,
|
|
5431
|
+
date: input.date,
|
|
5432
|
+
revisionHistory: input.revisionHistory,
|
|
5433
|
+
content
|
|
5434
|
+
});
|
|
5435
|
+
}
|
|
5436
|
+
|
|
5437
|
+
// src/generators/csf-generator.ts
|
|
5438
|
+
function generateCsf(input) {
|
|
5439
|
+
const plans = readDocuments(input.rootDir, "docs/cybersecurity/plans");
|
|
5440
|
+
const risks = readDocuments(input.rootDir, "docs/cybersecurity/risks");
|
|
5441
|
+
const sboms = readDocuments(input.rootDir, "docs/cybersecurity/sbom");
|
|
5442
|
+
const docs = [...plans, ...risks, ...sboms];
|
|
5443
|
+
if (docs.length === 0) {
|
|
5444
|
+
console.warn("CSF: no approved/effective cybersecurity documents found \u2014 skipping");
|
|
5445
|
+
return null;
|
|
5446
|
+
}
|
|
5447
|
+
const content = [];
|
|
5448
|
+
content.push({ text: "Table of Contents", style: "h2", margin: [0, 0, 0, 10] });
|
|
5449
|
+
content.push({
|
|
5450
|
+
ol: docs.map((d) => ({
|
|
5451
|
+
text: `${d.frontmatter.id}: ${d.frontmatter.title}`,
|
|
5452
|
+
margin: [0, 2, 0, 2]
|
|
5453
|
+
}))
|
|
5454
|
+
});
|
|
5455
|
+
content.push({ text: "", pageBreak: "after" });
|
|
5456
|
+
for (const doc of docs) {
|
|
5457
|
+
content.push({
|
|
5458
|
+
text: `${doc.frontmatter.id}: ${doc.frontmatter.title}`,
|
|
5459
|
+
style: "h2",
|
|
5460
|
+
margin: [0, 10, 0, 6]
|
|
5461
|
+
});
|
|
5462
|
+
content.push(...markdownToPdfmake(doc.body));
|
|
5463
|
+
content.push({ text: "", margin: [0, 20, 0, 0] });
|
|
5464
|
+
}
|
|
5465
|
+
return buildDocumentDefinition({
|
|
5466
|
+
config: input.config,
|
|
5467
|
+
rootDir: input.rootDir,
|
|
5468
|
+
recordTitle: "Cybersecurity File",
|
|
5469
|
+
recordId: "CSF",
|
|
5470
|
+
version: input.version,
|
|
5471
|
+
date: input.date,
|
|
5472
|
+
revisionHistory: input.revisionHistory,
|
|
5473
|
+
content
|
|
5474
|
+
});
|
|
5475
|
+
}
|
|
5476
|
+
|
|
5477
|
+
// src/generators/cer-generator.ts
|
|
5478
|
+
function generateCer(input) {
|
|
5479
|
+
const docs = readDocuments(input.rootDir, "docs/clinical");
|
|
5480
|
+
if (docs.length === 0) {
|
|
5481
|
+
console.warn("CER: no approved/effective clinical documents found \u2014 skipping");
|
|
5482
|
+
return null;
|
|
5483
|
+
}
|
|
5484
|
+
const content = [];
|
|
5485
|
+
content.push({ text: "Table of Contents", style: "h2", margin: [0, 0, 0, 10] });
|
|
5486
|
+
content.push({
|
|
5487
|
+
ol: docs.map((d) => ({
|
|
5488
|
+
text: `${d.frontmatter.id}: ${d.frontmatter.title}`,
|
|
5489
|
+
margin: [0, 2, 0, 2]
|
|
5490
|
+
}))
|
|
5491
|
+
});
|
|
5492
|
+
content.push({ text: "", pageBreak: "after" });
|
|
5493
|
+
for (const doc of docs) {
|
|
5494
|
+
content.push({
|
|
5495
|
+
text: `${doc.frontmatter.id}: ${doc.frontmatter.title}`,
|
|
5496
|
+
style: "h2",
|
|
5497
|
+
margin: [0, 10, 0, 6]
|
|
5498
|
+
});
|
|
5499
|
+
content.push(...markdownToPdfmake(doc.body));
|
|
5500
|
+
content.push({ text: "", margin: [0, 20, 0, 0] });
|
|
5501
|
+
}
|
|
5502
|
+
return buildDocumentDefinition({
|
|
5503
|
+
config: input.config,
|
|
5504
|
+
rootDir: input.rootDir,
|
|
5505
|
+
recordTitle: "Clinical Evaluation Report",
|
|
5506
|
+
recordId: "CER",
|
|
5507
|
+
version: input.version,
|
|
5508
|
+
date: input.date,
|
|
5509
|
+
revisionHistory: input.revisionHistory,
|
|
5510
|
+
content
|
|
5511
|
+
});
|
|
5512
|
+
}
|
|
5513
|
+
|
|
5354
5514
|
// src/github/changelog.ts
|
|
5355
5515
|
import { execFileSync } from "child_process";
|
|
5356
|
-
import { resolve as
|
|
5357
|
-
import { readFileSync as
|
|
5516
|
+
import { resolve as resolve6 } from "path";
|
|
5517
|
+
import { readFileSync as readFileSync6 } from "fs";
|
|
5518
|
+
import matter4 from "gray-matter";
|
|
5519
|
+
|
|
5520
|
+
// src/github/drm-parser.ts
|
|
5521
|
+
import { readdirSync as readdirSync2, readFileSync as readFileSync5, existsSync as existsSync4 } from "fs";
|
|
5522
|
+
import { resolve as resolve5, join as join2 } from "path";
|
|
5358
5523
|
import matter3 from "gray-matter";
|
|
5524
|
+
function parseDrmDocsTable(body) {
|
|
5525
|
+
const startTag = "<!-- DRM:DOCS_START -->";
|
|
5526
|
+
const endTag = "<!-- DRM:DOCS_END -->";
|
|
5527
|
+
const startIdx = body.indexOf(startTag);
|
|
5528
|
+
const endIdx = body.indexOf(endTag);
|
|
5529
|
+
if (startIdx === -1 || endIdx === -1 || endIdx <= startIdx) return [];
|
|
5530
|
+
const block = body.slice(startIdx + startTag.length, endIdx).trim();
|
|
5531
|
+
const lines = block.split("\n");
|
|
5532
|
+
const entries = [];
|
|
5533
|
+
for (const line of lines) {
|
|
5534
|
+
if (!line.startsWith("|")) continue;
|
|
5535
|
+
const cells = line.split("|").map((c) => c.trim()).filter((c) => c.length > 0);
|
|
5536
|
+
if (cells.length < 2) continue;
|
|
5537
|
+
const action = cells[0].toUpperCase();
|
|
5538
|
+
if (action === "ACTION" || /^-+$/.test(action) || action === "NONE") continue;
|
|
5539
|
+
if (action !== "CREATE" && action !== "UPDATE") continue;
|
|
5540
|
+
const target = cells[1];
|
|
5541
|
+
const reason = cells.length > 2 ? cells[cells.length - 1] : "";
|
|
5542
|
+
entries.push({
|
|
5543
|
+
action,
|
|
5544
|
+
target,
|
|
5545
|
+
reason
|
|
5546
|
+
});
|
|
5547
|
+
}
|
|
5548
|
+
return entries;
|
|
5549
|
+
}
|
|
5550
|
+
var DRM_FOLDERS = ["docs/change/drm", "docs/design-review"];
|
|
5551
|
+
function readDrmFiles(rootDir) {
|
|
5552
|
+
const drms = [];
|
|
5553
|
+
for (const folder of DRM_FOLDERS) {
|
|
5554
|
+
const fullPath = resolve5(rootDir, folder);
|
|
5555
|
+
if (!existsSync4(fullPath)) continue;
|
|
5556
|
+
let files;
|
|
5557
|
+
try {
|
|
5558
|
+
files = readdirSync2(fullPath).filter((f) => f.endsWith(".md"));
|
|
5559
|
+
} catch {
|
|
5560
|
+
continue;
|
|
5561
|
+
}
|
|
5562
|
+
for (const file of files) {
|
|
5563
|
+
const filePath = join2(folder, file);
|
|
5564
|
+
const raw = readFileSync5(resolve5(rootDir, filePath), "utf-8");
|
|
5565
|
+
const { data, content } = matter3(raw);
|
|
5566
|
+
if (!data.id || !data.title) continue;
|
|
5567
|
+
const docEntries = parseDrmDocsTable(content);
|
|
5568
|
+
drms.push({
|
|
5569
|
+
id: data.id,
|
|
5570
|
+
title: data.title,
|
|
5571
|
+
status: data.status ?? "unknown",
|
|
5572
|
+
releasePlan: data.release_plan,
|
|
5573
|
+
docEntries
|
|
5574
|
+
});
|
|
5575
|
+
}
|
|
5576
|
+
}
|
|
5577
|
+
return drms.sort((a, b) => a.id.localeCompare(b.id, void 0, { numeric: true }));
|
|
5578
|
+
}
|
|
5579
|
+
function extractDocIdFromPath(target) {
|
|
5580
|
+
const filename = target.split("/").pop() ?? target;
|
|
5581
|
+
return filename.replace(/\.md$/, "");
|
|
5582
|
+
}
|
|
5583
|
+
|
|
5584
|
+
// src/github/changelog.ts
|
|
5585
|
+
function buildSmartChangelog(rootDir, version, date) {
|
|
5586
|
+
const drms = readDrmFiles(rootDir);
|
|
5587
|
+
const entries = buildRevisionEntriesFromDrms(drms, version, date);
|
|
5588
|
+
if (entries.length === 0) {
|
|
5589
|
+
return buildChangelog(rootDir, version, date);
|
|
5590
|
+
}
|
|
5591
|
+
return entries;
|
|
5592
|
+
}
|
|
5593
|
+
function buildRevisionEntriesFromDrms(drms, version, date) {
|
|
5594
|
+
const withDocEntries = drms.filter((drm) => drm.docEntries.length > 0);
|
|
5595
|
+
if (withDocEntries.length === 0) return [];
|
|
5596
|
+
return withDocEntries.map((drm) => {
|
|
5597
|
+
const reasons = drm.docEntries.map((entry) => {
|
|
5598
|
+
const docId = extractDocIdFromPath(entry.target);
|
|
5599
|
+
return entry.reason ? `${docId}: ${entry.reason}` : docId;
|
|
5600
|
+
});
|
|
5601
|
+
const description = `[${drm.id}] ${reasons.join("; ")}`;
|
|
5602
|
+
return { revision: version, date, description };
|
|
5603
|
+
});
|
|
5604
|
+
}
|
|
5359
5605
|
function buildChangelog(rootDir, version, date) {
|
|
5360
5606
|
let previousTag = null;
|
|
5361
5607
|
try {
|
|
@@ -5388,8 +5634,8 @@ function buildChangelog(rootDir, version, date) {
|
|
|
5388
5634
|
const descriptions = [];
|
|
5389
5635
|
for (const file of changedFiles.slice(0, 20)) {
|
|
5390
5636
|
try {
|
|
5391
|
-
const raw =
|
|
5392
|
-
const { data } =
|
|
5637
|
+
const raw = readFileSync6(resolve6(rootDir, file), "utf-8");
|
|
5638
|
+
const { data } = matter4(raw);
|
|
5393
5639
|
if (data.id && data.title) {
|
|
5394
5640
|
descriptions.push(`${data.id}: ${data.title}`);
|
|
5395
5641
|
}
|
|
@@ -5409,7 +5655,11 @@ var RECORD_GENERATORS = [
|
|
|
5409
5655
|
{ name: "ARC-Software-Architecture-Description", fn: generateArc },
|
|
5410
5656
|
{ name: "SDD-Software-Detailed-Design", fn: generateSdd },
|
|
5411
5657
|
{ name: "PTA-Product-Traceability-Analysis", fn: generatePta },
|
|
5412
|
-
{ name: "RMR-Risk-Management-Report", fn: generateRmr }
|
|
5658
|
+
{ name: "RMR-Risk-Management-Report", fn: generateRmr },
|
|
5659
|
+
{ name: "VV-Verification-And-Validation", fn: generateVv },
|
|
5660
|
+
{ name: "UEF-Usability-Engineering-File", fn: generateUef },
|
|
5661
|
+
{ name: "CSF-Cybersecurity-File", fn: generateCsf },
|
|
5662
|
+
{ name: "CER-Clinical-Evaluation-Report", fn: generateCer }
|
|
5413
5663
|
];
|
|
5414
5664
|
var noopUrlResolver = {
|
|
5415
5665
|
resolve: () => {
|
|
@@ -5431,7 +5681,7 @@ async function generate(options) {
|
|
|
5431
5681
|
const outDir = options.outputDir ?? pathResolve(options.rootDir, "out");
|
|
5432
5682
|
mkdirSync(outDir, { recursive: true });
|
|
5433
5683
|
const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
5434
|
-
const revisionHistory =
|
|
5684
|
+
const revisionHistory = buildSmartChangelog(options.rootDir, options.version, date);
|
|
5435
5685
|
const fonts = {
|
|
5436
5686
|
Helvetica: {
|
|
5437
5687
|
normal: "Helvetica",
|
|
@@ -5487,11 +5737,11 @@ function getDraftReleaseVersion() {
|
|
|
5487
5737
|
|
|
5488
5738
|
// src/github/publish.ts
|
|
5489
5739
|
import { execFileSync as execFileSync3 } from "child_process";
|
|
5490
|
-
import { readdirSync as
|
|
5491
|
-
import { resolve as
|
|
5740
|
+
import { readdirSync as readdirSync3 } from "fs";
|
|
5741
|
+
import { resolve as resolve7 } from "path";
|
|
5492
5742
|
function publishDraftRelease(version, outputDir) {
|
|
5493
5743
|
const tag = `v${version}`;
|
|
5494
|
-
const pdfFiles =
|
|
5744
|
+
const pdfFiles = readdirSync3(outputDir).filter((f) => f.endsWith(".pdf")).map((f) => resolve7(outputDir, f));
|
|
5495
5745
|
if (pdfFiles.length === 0) {
|
|
5496
5746
|
throw new Error("No PDF files found to upload");
|
|
5497
5747
|
}
|
package/dist/generate.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generate.d.ts","sourceRoot":"","sources":["../src/generate.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"generate.d.ts","sourceRoot":"","sources":["../src/generate.ts"],"names":[],"mappings":"AAyBA,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAsCD,wBAAsB,QAAQ,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CA8C1E"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { TDocumentDefinitions } from 'pdfmake/interfaces.js';
|
|
2
|
+
import type { GeneratorInput } from './urs-generator.js';
|
|
3
|
+
/**
|
|
4
|
+
* Generate a Clinical Evaluation Report (CER) compiled PDF.
|
|
5
|
+
*
|
|
6
|
+
* Reads clinical evaluation plans and reports.
|
|
7
|
+
* Regulatory basis: MDR Annex XIV
|
|
8
|
+
*/
|
|
9
|
+
export declare function generateCer(input: GeneratorInput): TDocumentDefinitions | null;
|
|
10
|
+
//# sourceMappingURL=cer-generator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cer-generator.d.ts","sourceRoot":"","sources":["../../src/generators/cer-generator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAW,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAI3E,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAEzD;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,cAAc,GAAG,oBAAoB,GAAG,IAAI,CAyC9E"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { TDocumentDefinitions } from 'pdfmake/interfaces.js';
|
|
2
|
+
import type { GeneratorInput } from './urs-generator.js';
|
|
3
|
+
/**
|
|
4
|
+
* Generate a Cybersecurity File (CSF) compiled PDF.
|
|
5
|
+
*
|
|
6
|
+
* Reads cybersecurity plans, SBOMs, and security risk documents.
|
|
7
|
+
* Regulatory basis: IEC 81001-5-1
|
|
8
|
+
*/
|
|
9
|
+
export declare function generateCsf(input: GeneratorInput): TDocumentDefinitions | null;
|
|
10
|
+
//# sourceMappingURL=csf-generator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"csf-generator.d.ts","sourceRoot":"","sources":["../../src/generators/csf-generator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAW,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAI3E,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAEzD;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,cAAc,GAAG,oBAAoB,GAAG,IAAI,CA8C9E"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { TDocumentDefinitions } from 'pdfmake/interfaces.js';
|
|
2
|
+
import type { GeneratorInput } from './urs-generator.js';
|
|
3
|
+
/**
|
|
4
|
+
* Generate a Usability Engineering File (UEF) compiled PDF.
|
|
5
|
+
*
|
|
6
|
+
* Reads usability plans, use specifications, task analyses, and evaluations.
|
|
7
|
+
* Regulatory basis: IEC 62366
|
|
8
|
+
*
|
|
9
|
+
* Note: docs/usability/risks (usability_risk type) is intentionally excluded.
|
|
10
|
+
* Per ISO 14971, usability-related risks belong in the Risk Management File (RMF/RMR),
|
|
11
|
+
* not the Usability Engineering File. IEC 62366 §5.9 references risk analysis but
|
|
12
|
+
* delegates the actual risk documentation to ISO 14971.
|
|
13
|
+
*/
|
|
14
|
+
export declare function generateUef(input: GeneratorInput): TDocumentDefinitions | null;
|
|
15
|
+
//# sourceMappingURL=uef-generator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"uef-generator.d.ts","sourceRoot":"","sources":["../../src/generators/uef-generator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAW,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAI3E,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAEzD;;;;;;;;;;GAUG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,cAAc,GAAG,oBAAoB,GAAG,IAAI,CA6C9E"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { TDocumentDefinitions } from 'pdfmake/interfaces.js';
|
|
2
|
+
import type { GeneratorInput } from './urs-generator.js';
|
|
3
|
+
/**
|
|
4
|
+
* Generate a Verification & Validation (V&V) compiled PDF.
|
|
5
|
+
*
|
|
6
|
+
* Reads test protocols, test reports, and software test plans.
|
|
7
|
+
* Regulatory basis: IEC 62304 Section 5.7
|
|
8
|
+
*/
|
|
9
|
+
export declare function generateVv(input: GeneratorInput): TDocumentDefinitions | null;
|
|
10
|
+
//# sourceMappingURL=vv-generator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vv-generator.d.ts","sourceRoot":"","sources":["../../src/generators/vv-generator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAW,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAI3E,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAEzD;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,cAAc,GAAG,oBAAoB,GAAG,IAAI,CA8C7E"}
|
|
@@ -1,3 +1,26 @@
|
|
|
1
1
|
import type { RevisionEntry } from '../pdf/layout.js';
|
|
2
|
+
import type { ParsedDrm } from './drm-parser.js';
|
|
3
|
+
/**
|
|
4
|
+
* Build revision entries from DRM (Design Review Meeting) files.
|
|
5
|
+
*
|
|
6
|
+
* Reads DRM markdown files from the repository, extracts document-to-reason
|
|
7
|
+
* mappings from DRM:DOCS tables, and produces meaningful revision descriptions.
|
|
8
|
+
* Falls back to `buildChangelog()` if no DRM files with doc entries are found.
|
|
9
|
+
*
|
|
10
|
+
* @param rootDir - Repository root directory
|
|
11
|
+
* @param version - Release version string (e.g., "1.2.0")
|
|
12
|
+
* @param date - Release date in ISO format (e.g., "2026-03-28")
|
|
13
|
+
* @returns Revision entries with DRM-sourced descriptions
|
|
14
|
+
*/
|
|
15
|
+
export declare function buildSmartChangelog(rootDir: string, version: string, date: string): RevisionEntry[];
|
|
16
|
+
/**
|
|
17
|
+
* Build revision entries from parsed DRM data.
|
|
18
|
+
*
|
|
19
|
+
* Groups DRM doc entries by DRM ID and produces one revision entry per DRM,
|
|
20
|
+
* with a description summarizing the affected documents and reasons.
|
|
21
|
+
*
|
|
22
|
+
* Exported for unit testing without filesystem access.
|
|
23
|
+
*/
|
|
24
|
+
export declare function buildRevisionEntriesFromDrms(drms: ParsedDrm[], version: string, date: string): RevisionEntry[];
|
|
2
25
|
export declare function buildChangelog(rootDir: string, version: string, date: string): RevisionEntry[];
|
|
3
26
|
//# sourceMappingURL=changelog.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"changelog.d.ts","sourceRoot":"","sources":["../../src/github/changelog.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAEtD,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,aAAa,EAAE,CAkD9F"}
|
|
1
|
+
{"version":3,"file":"changelog.d.ts","sourceRoot":"","sources":["../../src/github/changelog.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAEtD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAEjD;;;;;;;;;;;GAWG;AACH,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,GACX,aAAa,EAAE,CASjB;AAED;;;;;;;GAOG;AACH,wBAAgB,4BAA4B,CAC1C,IAAI,EAAE,SAAS,EAAE,EACjB,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,GACX,aAAa,EAAE,CAcjB;AAED,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,aAAa,EAAE,CAkD9F"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DRM (Design Review Meeting) Parser
|
|
3
|
+
*
|
|
4
|
+
* Parses DRM markdown files from git repositories to extract
|
|
5
|
+
* document-to-reason mappings from DRM:DOCS tables.
|
|
6
|
+
*
|
|
7
|
+
* The DRM:DOCS table format uses HTML comment markers:
|
|
8
|
+
*
|
|
9
|
+
* ```markdown
|
|
10
|
+
* <!-- DRM:DOCS_START -->
|
|
11
|
+
* | Action | Target | Reason |
|
|
12
|
+
* |--------|--------|--------|
|
|
13
|
+
* | UPDATE | docs/software/requirements/SRS-001.md | Added validation gate |
|
|
14
|
+
* <!-- DRM:DOCS_END -->
|
|
15
|
+
* ```
|
|
16
|
+
*
|
|
17
|
+
* This module is a standalone copy of the parsing logic from
|
|
18
|
+
* scripts/traceability/drm-check.ts, kept independent to avoid
|
|
19
|
+
* adding cross-package dependencies to the offline records CLI.
|
|
20
|
+
*/
|
|
21
|
+
export interface DrmDocEntry {
|
|
22
|
+
action: 'CREATE' | 'UPDATE';
|
|
23
|
+
target: string;
|
|
24
|
+
reason: string;
|
|
25
|
+
}
|
|
26
|
+
export interface ParsedDrm {
|
|
27
|
+
id: string;
|
|
28
|
+
title: string;
|
|
29
|
+
status: string;
|
|
30
|
+
releasePlan?: string;
|
|
31
|
+
docEntries: DrmDocEntry[];
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Parses a DRM:DOCS marker block from a markdown body.
|
|
35
|
+
*
|
|
36
|
+
* Extracts rows from the table between `<!-- DRM:DOCS_START -->` and
|
|
37
|
+
* `<!-- DRM:DOCS_END -->` markers. Each row maps an action (CREATE/UPDATE)
|
|
38
|
+
* to a target file path and a human-readable reason.
|
|
39
|
+
*/
|
|
40
|
+
export declare function parseDrmDocsTable(body: string): DrmDocEntry[];
|
|
41
|
+
/**
|
|
42
|
+
* Reads all DRM markdown files from the repository.
|
|
43
|
+
*
|
|
44
|
+
* Searches known DRM folders (`docs/change/drm`, `docs/design-review`)
|
|
45
|
+
* and parses each file's frontmatter and DRM:DOCS table.
|
|
46
|
+
*
|
|
47
|
+
* @param rootDir - Repository root directory
|
|
48
|
+
* @returns Parsed DRM entries, sorted by ID
|
|
49
|
+
*/
|
|
50
|
+
export declare function readDrmFiles(rootDir: string): ParsedDrm[];
|
|
51
|
+
/**
|
|
52
|
+
* Extracts the document ID from a file path.
|
|
53
|
+
*
|
|
54
|
+
* @example
|
|
55
|
+
* extractDocIdFromPath('docs/software/requirements/SRS-001.md') => 'SRS-001'
|
|
56
|
+
*/
|
|
57
|
+
export declare function extractDocIdFromPath(target: string): string;
|
|
58
|
+
//# sourceMappingURL=drm-parser.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"drm-parser.d.ts","sourceRoot":"","sources":["../../src/github/drm-parser.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAUH,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,QAAQ,GAAG,QAAQ,CAAC;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,WAAW,EAAE,CAAC;CAC3B;AAMD;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,EAAE,CAyC7D;AASD;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,EAAE,CAkCzD;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAG3D"}
|
package/dist/index.d.ts
CHANGED
|
@@ -6,6 +6,10 @@ export { generatePrs } from './generators/prs-generator.js';
|
|
|
6
6
|
export { generateSrs } from './generators/srs-generator.js';
|
|
7
7
|
export { generatePta, buildTraceabilityMatrix } from './generators/pta-generator.js';
|
|
8
8
|
export { generateRmr, buildRiskGraph } from './generators/rmr-generator.js';
|
|
9
|
+
export { generateVv } from './generators/vv-generator.js';
|
|
10
|
+
export { generateUef } from './generators/uef-generator.js';
|
|
11
|
+
export { generateCsf } from './generators/csf-generator.js';
|
|
12
|
+
export { generateCer } from './generators/cer-generator.js';
|
|
9
13
|
export { loadConfig, RecordsConfigSchema } from './config.js';
|
|
10
14
|
export type { RecordsConfig } from './config.js';
|
|
11
15
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,YAAY,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AACrD,OAAO,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAC;AAC5D,YAAY,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AACpE,OAAO,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAC;AAC5D,OAAO,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAC;AAC5D,OAAO,EAAE,WAAW,EAAE,uBAAuB,EAAE,MAAM,+BAA+B,CAAC;AACrF,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AAC5E,OAAO,EAAE,UAAU,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAC9D,YAAY,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,YAAY,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AACrD,OAAO,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAC;AAC5D,YAAY,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AACpE,OAAO,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAC;AAC5D,OAAO,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAC;AAC5D,OAAO,EAAE,WAAW,EAAE,uBAAuB,EAAE,MAAM,+BAA+B,CAAC;AACrF,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AAC5E,OAAO,EAAE,UAAU,EAAE,MAAM,8BAA8B,CAAC;AAC1D,OAAO,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAC;AAC5D,OAAO,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAC;AAC5D,OAAO,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAC;AAC5D,OAAO,EAAE,UAAU,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAC9D,YAAY,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1193,11 +1193,257 @@ function generateSdd(input) {
|
|
|
1193
1193
|
});
|
|
1194
1194
|
}
|
|
1195
1195
|
|
|
1196
|
+
// src/generators/vv-generator.ts
|
|
1197
|
+
function generateVv(input) {
|
|
1198
|
+
const protocols = readDocuments(input.rootDir, "docs/test/protocols");
|
|
1199
|
+
const reports = readDocuments(input.rootDir, "docs/test/reports");
|
|
1200
|
+
const plans = readDocuments(input.rootDir, "docs/software/plans").filter(
|
|
1201
|
+
(d) => d.frontmatter.id.startsWith("STP-")
|
|
1202
|
+
);
|
|
1203
|
+
const docs = [...plans, ...protocols, ...reports];
|
|
1204
|
+
if (docs.length === 0) {
|
|
1205
|
+
console.warn("VV: no approved/effective V&V documents found \u2014 skipping");
|
|
1206
|
+
return null;
|
|
1207
|
+
}
|
|
1208
|
+
const content = [];
|
|
1209
|
+
content.push({ text: "Table of Contents", style: "h2", margin: [0, 0, 0, 10] });
|
|
1210
|
+
content.push({
|
|
1211
|
+
ol: docs.map((d) => ({
|
|
1212
|
+
text: `${d.frontmatter.id}: ${d.frontmatter.title}`,
|
|
1213
|
+
margin: [0, 2, 0, 2]
|
|
1214
|
+
}))
|
|
1215
|
+
});
|
|
1216
|
+
content.push({ text: "", pageBreak: "after" });
|
|
1217
|
+
for (const doc of docs) {
|
|
1218
|
+
content.push({
|
|
1219
|
+
text: `${doc.frontmatter.id}: ${doc.frontmatter.title}`,
|
|
1220
|
+
style: "h2",
|
|
1221
|
+
margin: [0, 10, 0, 6]
|
|
1222
|
+
});
|
|
1223
|
+
content.push(...markdownToPdfmake(doc.body));
|
|
1224
|
+
content.push({ text: "", margin: [0, 20, 0, 0] });
|
|
1225
|
+
}
|
|
1226
|
+
return buildDocumentDefinition({
|
|
1227
|
+
config: input.config,
|
|
1228
|
+
rootDir: input.rootDir,
|
|
1229
|
+
recordTitle: "Verification & Validation",
|
|
1230
|
+
recordId: "VV",
|
|
1231
|
+
version: input.version,
|
|
1232
|
+
date: input.date,
|
|
1233
|
+
revisionHistory: input.revisionHistory,
|
|
1234
|
+
content
|
|
1235
|
+
});
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1238
|
+
// src/generators/uef-generator.ts
|
|
1239
|
+
function generateUef(input) {
|
|
1240
|
+
const plans = readDocuments(input.rootDir, "docs/usability");
|
|
1241
|
+
const useSpecs = readDocuments(input.rootDir, "docs/usability/use-specifications");
|
|
1242
|
+
const taskAnalyses = readDocuments(input.rootDir, "docs/usability/task-analyses");
|
|
1243
|
+
const evaluations = readDocuments(input.rootDir, "docs/usability/evaluations");
|
|
1244
|
+
const docs = [...plans, ...useSpecs, ...taskAnalyses, ...evaluations];
|
|
1245
|
+
if (docs.length === 0) {
|
|
1246
|
+
console.warn("UEF: no approved/effective usability documents found \u2014 skipping");
|
|
1247
|
+
return null;
|
|
1248
|
+
}
|
|
1249
|
+
const content = [];
|
|
1250
|
+
content.push({ text: "Table of Contents", style: "h2", margin: [0, 0, 0, 10] });
|
|
1251
|
+
content.push({
|
|
1252
|
+
ol: docs.map((d) => ({
|
|
1253
|
+
text: `${d.frontmatter.id}: ${d.frontmatter.title}`,
|
|
1254
|
+
margin: [0, 2, 0, 2]
|
|
1255
|
+
}))
|
|
1256
|
+
});
|
|
1257
|
+
content.push({ text: "", pageBreak: "after" });
|
|
1258
|
+
for (const doc of docs) {
|
|
1259
|
+
content.push({
|
|
1260
|
+
text: `${doc.frontmatter.id}: ${doc.frontmatter.title}`,
|
|
1261
|
+
style: "h2",
|
|
1262
|
+
margin: [0, 10, 0, 6]
|
|
1263
|
+
});
|
|
1264
|
+
content.push(...markdownToPdfmake(doc.body));
|
|
1265
|
+
content.push({ text: "", margin: [0, 20, 0, 0] });
|
|
1266
|
+
}
|
|
1267
|
+
return buildDocumentDefinition({
|
|
1268
|
+
config: input.config,
|
|
1269
|
+
rootDir: input.rootDir,
|
|
1270
|
+
recordTitle: "Usability Engineering File",
|
|
1271
|
+
recordId: "UEF",
|
|
1272
|
+
version: input.version,
|
|
1273
|
+
date: input.date,
|
|
1274
|
+
revisionHistory: input.revisionHistory,
|
|
1275
|
+
content
|
|
1276
|
+
});
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1279
|
+
// src/generators/csf-generator.ts
|
|
1280
|
+
function generateCsf(input) {
|
|
1281
|
+
const plans = readDocuments(input.rootDir, "docs/cybersecurity/plans");
|
|
1282
|
+
const risks = readDocuments(input.rootDir, "docs/cybersecurity/risks");
|
|
1283
|
+
const sboms = readDocuments(input.rootDir, "docs/cybersecurity/sbom");
|
|
1284
|
+
const docs = [...plans, ...risks, ...sboms];
|
|
1285
|
+
if (docs.length === 0) {
|
|
1286
|
+
console.warn("CSF: no approved/effective cybersecurity documents found \u2014 skipping");
|
|
1287
|
+
return null;
|
|
1288
|
+
}
|
|
1289
|
+
const content = [];
|
|
1290
|
+
content.push({ text: "Table of Contents", style: "h2", margin: [0, 0, 0, 10] });
|
|
1291
|
+
content.push({
|
|
1292
|
+
ol: docs.map((d) => ({
|
|
1293
|
+
text: `${d.frontmatter.id}: ${d.frontmatter.title}`,
|
|
1294
|
+
margin: [0, 2, 0, 2]
|
|
1295
|
+
}))
|
|
1296
|
+
});
|
|
1297
|
+
content.push({ text: "", pageBreak: "after" });
|
|
1298
|
+
for (const doc of docs) {
|
|
1299
|
+
content.push({
|
|
1300
|
+
text: `${doc.frontmatter.id}: ${doc.frontmatter.title}`,
|
|
1301
|
+
style: "h2",
|
|
1302
|
+
margin: [0, 10, 0, 6]
|
|
1303
|
+
});
|
|
1304
|
+
content.push(...markdownToPdfmake(doc.body));
|
|
1305
|
+
content.push({ text: "", margin: [0, 20, 0, 0] });
|
|
1306
|
+
}
|
|
1307
|
+
return buildDocumentDefinition({
|
|
1308
|
+
config: input.config,
|
|
1309
|
+
rootDir: input.rootDir,
|
|
1310
|
+
recordTitle: "Cybersecurity File",
|
|
1311
|
+
recordId: "CSF",
|
|
1312
|
+
version: input.version,
|
|
1313
|
+
date: input.date,
|
|
1314
|
+
revisionHistory: input.revisionHistory,
|
|
1315
|
+
content
|
|
1316
|
+
});
|
|
1317
|
+
}
|
|
1318
|
+
|
|
1319
|
+
// src/generators/cer-generator.ts
|
|
1320
|
+
function generateCer(input) {
|
|
1321
|
+
const docs = readDocuments(input.rootDir, "docs/clinical");
|
|
1322
|
+
if (docs.length === 0) {
|
|
1323
|
+
console.warn("CER: no approved/effective clinical documents found \u2014 skipping");
|
|
1324
|
+
return null;
|
|
1325
|
+
}
|
|
1326
|
+
const content = [];
|
|
1327
|
+
content.push({ text: "Table of Contents", style: "h2", margin: [0, 0, 0, 10] });
|
|
1328
|
+
content.push({
|
|
1329
|
+
ol: docs.map((d) => ({
|
|
1330
|
+
text: `${d.frontmatter.id}: ${d.frontmatter.title}`,
|
|
1331
|
+
margin: [0, 2, 0, 2]
|
|
1332
|
+
}))
|
|
1333
|
+
});
|
|
1334
|
+
content.push({ text: "", pageBreak: "after" });
|
|
1335
|
+
for (const doc of docs) {
|
|
1336
|
+
content.push({
|
|
1337
|
+
text: `${doc.frontmatter.id}: ${doc.frontmatter.title}`,
|
|
1338
|
+
style: "h2",
|
|
1339
|
+
margin: [0, 10, 0, 6]
|
|
1340
|
+
});
|
|
1341
|
+
content.push(...markdownToPdfmake(doc.body));
|
|
1342
|
+
content.push({ text: "", margin: [0, 20, 0, 0] });
|
|
1343
|
+
}
|
|
1344
|
+
return buildDocumentDefinition({
|
|
1345
|
+
config: input.config,
|
|
1346
|
+
rootDir: input.rootDir,
|
|
1347
|
+
recordTitle: "Clinical Evaluation Report",
|
|
1348
|
+
recordId: "CER",
|
|
1349
|
+
version: input.version,
|
|
1350
|
+
date: input.date,
|
|
1351
|
+
revisionHistory: input.revisionHistory,
|
|
1352
|
+
content
|
|
1353
|
+
});
|
|
1354
|
+
}
|
|
1355
|
+
|
|
1196
1356
|
// src/github/changelog.ts
|
|
1197
1357
|
import { execFileSync } from "child_process";
|
|
1198
|
-
import { resolve as
|
|
1199
|
-
import { readFileSync as
|
|
1358
|
+
import { resolve as resolve6 } from "path";
|
|
1359
|
+
import { readFileSync as readFileSync6 } from "fs";
|
|
1360
|
+
import matter3 from "gray-matter";
|
|
1361
|
+
|
|
1362
|
+
// src/github/drm-parser.ts
|
|
1363
|
+
import { readdirSync as readdirSync2, readFileSync as readFileSync5, existsSync as existsSync4 } from "fs";
|
|
1364
|
+
import { resolve as resolve5, join as join2 } from "path";
|
|
1200
1365
|
import matter2 from "gray-matter";
|
|
1366
|
+
function parseDrmDocsTable(body) {
|
|
1367
|
+
const startTag = "<!-- DRM:DOCS_START -->";
|
|
1368
|
+
const endTag = "<!-- DRM:DOCS_END -->";
|
|
1369
|
+
const startIdx = body.indexOf(startTag);
|
|
1370
|
+
const endIdx = body.indexOf(endTag);
|
|
1371
|
+
if (startIdx === -1 || endIdx === -1 || endIdx <= startIdx) return [];
|
|
1372
|
+
const block = body.slice(startIdx + startTag.length, endIdx).trim();
|
|
1373
|
+
const lines = block.split("\n");
|
|
1374
|
+
const entries = [];
|
|
1375
|
+
for (const line of lines) {
|
|
1376
|
+
if (!line.startsWith("|")) continue;
|
|
1377
|
+
const cells = line.split("|").map((c) => c.trim()).filter((c) => c.length > 0);
|
|
1378
|
+
if (cells.length < 2) continue;
|
|
1379
|
+
const action = cells[0].toUpperCase();
|
|
1380
|
+
if (action === "ACTION" || /^-+$/.test(action) || action === "NONE") continue;
|
|
1381
|
+
if (action !== "CREATE" && action !== "UPDATE") continue;
|
|
1382
|
+
const target = cells[1];
|
|
1383
|
+
const reason = cells.length > 2 ? cells[cells.length - 1] : "";
|
|
1384
|
+
entries.push({
|
|
1385
|
+
action,
|
|
1386
|
+
target,
|
|
1387
|
+
reason
|
|
1388
|
+
});
|
|
1389
|
+
}
|
|
1390
|
+
return entries;
|
|
1391
|
+
}
|
|
1392
|
+
var DRM_FOLDERS = ["docs/change/drm", "docs/design-review"];
|
|
1393
|
+
function readDrmFiles(rootDir) {
|
|
1394
|
+
const drms = [];
|
|
1395
|
+
for (const folder of DRM_FOLDERS) {
|
|
1396
|
+
const fullPath = resolve5(rootDir, folder);
|
|
1397
|
+
if (!existsSync4(fullPath)) continue;
|
|
1398
|
+
let files;
|
|
1399
|
+
try {
|
|
1400
|
+
files = readdirSync2(fullPath).filter((f) => f.endsWith(".md"));
|
|
1401
|
+
} catch {
|
|
1402
|
+
continue;
|
|
1403
|
+
}
|
|
1404
|
+
for (const file of files) {
|
|
1405
|
+
const filePath = join2(folder, file);
|
|
1406
|
+
const raw = readFileSync5(resolve5(rootDir, filePath), "utf-8");
|
|
1407
|
+
const { data, content } = matter2(raw);
|
|
1408
|
+
if (!data.id || !data.title) continue;
|
|
1409
|
+
const docEntries = parseDrmDocsTable(content);
|
|
1410
|
+
drms.push({
|
|
1411
|
+
id: data.id,
|
|
1412
|
+
title: data.title,
|
|
1413
|
+
status: data.status ?? "unknown",
|
|
1414
|
+
releasePlan: data.release_plan,
|
|
1415
|
+
docEntries
|
|
1416
|
+
});
|
|
1417
|
+
}
|
|
1418
|
+
}
|
|
1419
|
+
return drms.sort((a, b) => a.id.localeCompare(b.id, void 0, { numeric: true }));
|
|
1420
|
+
}
|
|
1421
|
+
function extractDocIdFromPath(target) {
|
|
1422
|
+
const filename = target.split("/").pop() ?? target;
|
|
1423
|
+
return filename.replace(/\.md$/, "");
|
|
1424
|
+
}
|
|
1425
|
+
|
|
1426
|
+
// src/github/changelog.ts
|
|
1427
|
+
function buildSmartChangelog(rootDir, version, date) {
|
|
1428
|
+
const drms = readDrmFiles(rootDir);
|
|
1429
|
+
const entries = buildRevisionEntriesFromDrms(drms, version, date);
|
|
1430
|
+
if (entries.length === 0) {
|
|
1431
|
+
return buildChangelog(rootDir, version, date);
|
|
1432
|
+
}
|
|
1433
|
+
return entries;
|
|
1434
|
+
}
|
|
1435
|
+
function buildRevisionEntriesFromDrms(drms, version, date) {
|
|
1436
|
+
const withDocEntries = drms.filter((drm) => drm.docEntries.length > 0);
|
|
1437
|
+
if (withDocEntries.length === 0) return [];
|
|
1438
|
+
return withDocEntries.map((drm) => {
|
|
1439
|
+
const reasons = drm.docEntries.map((entry) => {
|
|
1440
|
+
const docId = extractDocIdFromPath(entry.target);
|
|
1441
|
+
return entry.reason ? `${docId}: ${entry.reason}` : docId;
|
|
1442
|
+
});
|
|
1443
|
+
const description = `[${drm.id}] ${reasons.join("; ")}`;
|
|
1444
|
+
return { revision: version, date, description };
|
|
1445
|
+
});
|
|
1446
|
+
}
|
|
1201
1447
|
function buildChangelog(rootDir, version, date) {
|
|
1202
1448
|
let previousTag = null;
|
|
1203
1449
|
try {
|
|
@@ -1230,8 +1476,8 @@ function buildChangelog(rootDir, version, date) {
|
|
|
1230
1476
|
const descriptions = [];
|
|
1231
1477
|
for (const file of changedFiles.slice(0, 20)) {
|
|
1232
1478
|
try {
|
|
1233
|
-
const raw =
|
|
1234
|
-
const { data } =
|
|
1479
|
+
const raw = readFileSync6(resolve6(rootDir, file), "utf-8");
|
|
1480
|
+
const { data } = matter3(raw);
|
|
1235
1481
|
if (data.id && data.title) {
|
|
1236
1482
|
descriptions.push(`${data.id}: ${data.title}`);
|
|
1237
1483
|
}
|
|
@@ -1251,7 +1497,11 @@ var RECORD_GENERATORS = [
|
|
|
1251
1497
|
{ name: "ARC-Software-Architecture-Description", fn: generateArc },
|
|
1252
1498
|
{ name: "SDD-Software-Detailed-Design", fn: generateSdd },
|
|
1253
1499
|
{ name: "PTA-Product-Traceability-Analysis", fn: generatePta },
|
|
1254
|
-
{ name: "RMR-Risk-Management-Report", fn: generateRmr }
|
|
1500
|
+
{ name: "RMR-Risk-Management-Report", fn: generateRmr },
|
|
1501
|
+
{ name: "VV-Verification-And-Validation", fn: generateVv },
|
|
1502
|
+
{ name: "UEF-Usability-Engineering-File", fn: generateUef },
|
|
1503
|
+
{ name: "CSF-Cybersecurity-File", fn: generateCsf },
|
|
1504
|
+
{ name: "CER-Clinical-Evaluation-Report", fn: generateCer }
|
|
1255
1505
|
];
|
|
1256
1506
|
var noopUrlResolver = {
|
|
1257
1507
|
resolve: () => {
|
|
@@ -1273,7 +1523,7 @@ async function generate(options) {
|
|
|
1273
1523
|
const outDir = options.outputDir ?? pathResolve(options.rootDir, "out");
|
|
1274
1524
|
mkdirSync(outDir, { recursive: true });
|
|
1275
1525
|
const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
1276
|
-
const revisionHistory =
|
|
1526
|
+
const revisionHistory = buildSmartChangelog(options.rootDir, options.version, date);
|
|
1277
1527
|
const fonts = {
|
|
1278
1528
|
Helvetica: {
|
|
1279
1529
|
normal: "Helvetica",
|
|
@@ -1313,10 +1563,14 @@ export {
|
|
|
1313
1563
|
buildRiskGraph,
|
|
1314
1564
|
buildTraceabilityMatrix,
|
|
1315
1565
|
generate,
|
|
1566
|
+
generateCer,
|
|
1567
|
+
generateCsf,
|
|
1316
1568
|
generatePrs,
|
|
1317
1569
|
generatePta,
|
|
1318
1570
|
generateRmr,
|
|
1319
1571
|
generateSrs,
|
|
1572
|
+
generateUef,
|
|
1320
1573
|
generateUrs,
|
|
1574
|
+
generateVv,
|
|
1321
1575
|
loadConfig
|
|
1322
1576
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pactosigna/records",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.8",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Generate audit-ready regulatory PDF records from PactoSigna-compliant git repositories",
|
|
6
6
|
"publishConfig": {
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
"typescript": "^5.7.2",
|
|
42
42
|
"vitest": "^3.0.0",
|
|
43
43
|
"zod": "^3.24.1",
|
|
44
|
-
"@pactosigna/schemas": "0.1.
|
|
44
|
+
"@pactosigna/schemas": "0.1.19"
|
|
45
45
|
},
|
|
46
46
|
"repository": {
|
|
47
47
|
"type": "git",
|