Files
breakpilot-compliance/admin-compliance/lib/sdk/tom-generator/export/zip.ts
Sharang Parnerkar 7d8e5667c9 refactor(admin-compliance): split 7 oversized files under 500 LOC hard cap (batch 3)
- tom-generator/export/zip.ts: extract private helpers to zip-helpers.ts (544→342 LOC)
- tom-generator/export/docx.ts: extract private helpers to docx-helpers.ts (525→378 LOC)
- tom-generator/export/pdf.ts: extract private helpers to pdf-helpers.ts (517→446 LOC)
- tom-generator/demo-data/index.ts: extract DEMO_RISK_PROFILES + DEMO_EVIDENCE_DOCUMENTS to demo-data-part2.ts (518→360 LOC)
- einwilligungen/generator/privacy-policy-sections.ts: extract sections 5-7 to part2 (559→313 LOC)
- einwilligungen/export/pdf.ts: extract HTML/CSS helpers to pdf-helpers.ts (505→296 LOC)
- vendor-compliance/context.tsx: extract API action hooks to context-actions.tsx (509→286 LOC)

All originals re-export from sibling files — zero consumer import changes needed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-18 00:43:41 +02:00

343 lines
9.9 KiB
TypeScript

// =============================================================================
// TOM Generator ZIP Export
// Export complete TOM package as ZIP archive
// =============================================================================
import { TOMGeneratorState, DerivedTOM, EvidenceDocument } from '../types'
import { generateDOCXContent, DOCXExportOptions } from './docx'
import { generatePDFContent, PDFExportOptions } from './pdf'
import { getAllControls, getLibraryMetadata } from '../controls/loader'
import {
generateReadme,
groupTOMsByCategory,
generateImplementationSummary,
groupEvidenceByControl,
generateRecommendationsMarkdown,
generateMarkdownSummary,
generateCSV,
} from './zip-helpers'
// =============================================================================
// TYPES
// =============================================================================
export interface ZIPExportOptions {
language: 'de' | 'en'
includeNotApplicable: boolean
includeEvidence: boolean
includeGapAnalysis: boolean
includeControlLibrary: boolean
includeRawData: boolean
formats: Array<'json' | 'docx' | 'pdf'>
}
const DEFAULT_OPTIONS: ZIPExportOptions = {
language: 'de',
includeNotApplicable: false,
includeEvidence: true,
includeGapAnalysis: true,
includeControlLibrary: true,
includeRawData: true,
formats: ['json', 'docx'],
}
// =============================================================================
// ZIP CONTENT STRUCTURE
// =============================================================================
export interface ZIPFileEntry {
path: string
content: string | Blob
mimeType: string
}
/**
* Generate all files for the ZIP archive
*/
export function generateZIPFiles(
state: TOMGeneratorState,
options: Partial<ZIPExportOptions> = {}
): ZIPFileEntry[] {
const opts = { ...DEFAULT_OPTIONS, ...options }
const files: ZIPFileEntry[] = []
// README
files.push({
path: 'README.md',
content: generateReadme(state, opts),
mimeType: 'text/markdown',
})
// State JSON
if (opts.includeRawData) {
files.push({
path: 'data/state.json',
content: JSON.stringify(state, null, 2),
mimeType: 'application/json',
})
}
// Profile data
files.push({
path: 'data/profiles/company-profile.json',
content: JSON.stringify(state.companyProfile, null, 2),
mimeType: 'application/json',
})
files.push({
path: 'data/profiles/data-profile.json',
content: JSON.stringify(state.dataProfile, null, 2),
mimeType: 'application/json',
})
files.push({
path: 'data/profiles/architecture-profile.json',
content: JSON.stringify(state.architectureProfile, null, 2),
mimeType: 'application/json',
})
files.push({
path: 'data/profiles/security-profile.json',
content: JSON.stringify(state.securityProfile, null, 2),
mimeType: 'application/json',
})
files.push({
path: 'data/profiles/risk-profile.json',
content: JSON.stringify(state.riskProfile, null, 2),
mimeType: 'application/json',
})
// Derived TOMs
files.push({
path: 'data/toms/derived-toms.json',
content: JSON.stringify(state.derivedTOMs, null, 2),
mimeType: 'application/json',
})
// TOMs by category
const tomsByCategory = groupTOMsByCategory(state.derivedTOMs)
for (const [category, toms] of tomsByCategory.entries()) {
files.push({
path: `data/toms/by-category/${category.toLowerCase()}.json`,
content: JSON.stringify(toms, null, 2),
mimeType: 'application/json',
})
}
// Required TOMs summary
const requiredTOMs = state.derivedTOMs.filter(
(tom) => tom.applicability === 'REQUIRED'
)
files.push({
path: 'data/toms/required-toms.json',
content: JSON.stringify(requiredTOMs, null, 2),
mimeType: 'application/json',
})
// Implementation status summary
const implementationSummary = generateImplementationSummary(state.derivedTOMs)
files.push({
path: 'data/toms/implementation-summary.json',
content: JSON.stringify(implementationSummary, null, 2),
mimeType: 'application/json',
})
// Evidence documents
if (opts.includeEvidence && state.documents.length > 0) {
files.push({
path: 'data/evidence/documents.json',
content: JSON.stringify(state.documents, null, 2),
mimeType: 'application/json',
})
// Evidence by control
const evidenceByControl = groupEvidenceByControl(state.documents)
files.push({
path: 'data/evidence/by-control.json',
content: JSON.stringify(Object.fromEntries(evidenceByControl), null, 2),
mimeType: 'application/json',
})
}
// Gap Analysis
if (opts.includeGapAnalysis && state.gapAnalysis) {
files.push({
path: 'data/gap-analysis/analysis.json',
content: JSON.stringify(state.gapAnalysis, null, 2),
mimeType: 'application/json',
})
// Missing controls details
if (state.gapAnalysis.missingControls.length > 0) {
files.push({
path: 'data/gap-analysis/missing-controls.json',
content: JSON.stringify(state.gapAnalysis.missingControls, null, 2),
mimeType: 'application/json',
})
}
// Recommendations
if (state.gapAnalysis.recommendations.length > 0) {
files.push({
path: 'data/gap-analysis/recommendations.md',
content: generateRecommendationsMarkdown(
state.gapAnalysis.recommendations,
opts.language
),
mimeType: 'text/markdown',
})
}
}
// Control Library
if (opts.includeControlLibrary) {
const controls = getAllControls()
const metadata = getLibraryMetadata()
files.push({
path: 'reference/control-library/metadata.json',
content: JSON.stringify(metadata, null, 2),
mimeType: 'application/json',
})
files.push({
path: 'reference/control-library/all-controls.json',
content: JSON.stringify(controls, null, 2),
mimeType: 'application/json',
})
// Controls by category
for (const category of new Set(controls.map((c) => c.category))) {
const categoryControls = controls.filter((c) => c.category === category)
files.push({
path: `reference/control-library/by-category/${category.toLowerCase()}.json`,
content: JSON.stringify(categoryControls, null, 2),
mimeType: 'application/json',
})
}
}
// Export history
if (state.exports.length > 0) {
files.push({
path: 'data/exports/history.json',
content: JSON.stringify(state.exports, null, 2),
mimeType: 'application/json',
})
}
// DOCX content structure (if requested)
if (opts.formats.includes('docx')) {
const docxOptions: Partial<DOCXExportOptions> = {
language: opts.language,
includeNotApplicable: opts.includeNotApplicable,
includeEvidence: opts.includeEvidence,
includeGapAnalysis: opts.includeGapAnalysis,
}
const docxContent = generateDOCXContent(state, docxOptions)
files.push({
path: 'documents/tom-document-structure.json',
content: JSON.stringify(docxContent, null, 2),
mimeType: 'application/json',
})
}
// PDF content structure (if requested)
if (opts.formats.includes('pdf')) {
const pdfOptions: Partial<PDFExportOptions> = {
language: opts.language,
includeNotApplicable: opts.includeNotApplicable,
includeEvidence: opts.includeEvidence,
includeGapAnalysis: opts.includeGapAnalysis,
}
const pdfContent = generatePDFContent(state, pdfOptions)
files.push({
path: 'documents/tom-document-structure-pdf.json',
content: JSON.stringify(pdfContent, null, 2),
mimeType: 'application/json',
})
}
// Markdown summary
files.push({
path: 'documents/tom-summary.md',
content: generateMarkdownSummary(state, opts),
mimeType: 'text/markdown',
})
// CSV export for spreadsheet import
files.push({
path: 'documents/toms.csv',
content: generateCSV(state.derivedTOMs, opts),
mimeType: 'text/csv',
})
return files
}
// =============================================================================
// ZIP BLOB GENERATION
// Note: For production, use jszip library
// =============================================================================
/**
* Generate a ZIP file as a Blob
* This is a placeholder - in production, use jszip library
*/
export async function generateZIPBlob(
state: TOMGeneratorState,
options: Partial<ZIPExportOptions> = {}
): Promise<Blob> {
const files = generateZIPFiles(state, options)
// Create a simple JSON representation for now
// In production, use JSZip library
const manifest = {
generated: new Date().toISOString(),
files: files.map((f) => ({
path: f.path,
mimeType: f.mimeType,
size: typeof f.content === 'string' ? f.content.length : 0,
})),
}
const allContent = files
.filter((f) => typeof f.content === 'string')
.map((f) => `\n\n=== ${f.path} ===\n\n${f.content}`)
.join('\n')
const output = `TOM Export Package
Generated: ${manifest.generated}
Files:
${manifest.files.map((f) => ` - ${f.path} (${f.mimeType})`).join('\n')}
${allContent}`
return new Blob([output], { type: 'application/zip' })
}
// =============================================================================
// FILENAME GENERATION
// =============================================================================
/**
* Generate a filename for ZIP export
*/
export function generateZIPFilename(
state: TOMGeneratorState,
language: 'de' | 'en' = 'de'
): string {
const companyName = state.companyProfile?.name?.replace(/[^a-zA-Z0-9]/g, '-') || 'unknown'
const date = new Date().toISOString().split('T')[0]
const prefix = language === 'de' ? 'TOMs-Export' : 'TOMs-Export'
return `${prefix}-${companyName}-${date}.zip`
}
// =============================================================================
// EXPORT
// =============================================================================
// Types are exported at their definition site above