diff --git a/website/scripts/GenerateReleaseCatalogs.ts b/website/scripts/GenerateReleaseCatalogs.ts index 4a15a735..7b0b7749 100644 --- a/website/scripts/GenerateReleaseCatalogs.ts +++ b/website/scripts/GenerateReleaseCatalogs.ts @@ -9,6 +9,14 @@ const CATALOGS_DIR = path.join(__dirname, '../../catalogs'); const OUTPUT_DIR = path.join(__dirname, '../src/data/ccc-releases'); const DELIVERY_TOOLKIT_DIR = path.join(__dirname, '../../delivery-toolkit'); +/** Must all exist for generate-release-artifacts; partial dirs are skipped (matches delivery-toolkit loadCatalog). */ +const REQUIRED_CATALOG_FILES = [ + 'metadata.yaml', + 'controls.yaml', + 'capabilities.yaml', + 'threats.yaml', +] as const; + interface CatalogDirectory { category: string; service: string; @@ -137,30 +145,29 @@ async function discoverCatalogDirectories(): Promise { const releaseDetailsPath = path.join(servicePath, 'release-details.yaml'); const hasReleaseDetails = fs.existsSync(releaseDetailsPath); - // Check if this directory has the required catalog files (controls.yaml, capabilities.yaml, etc.) - const hasControls = fs.existsSync(path.join(servicePath, 'controls.yaml')); - const hasCapabilities = fs.existsSync(path.join(servicePath, 'capabilities.yaml')); - const hasThreats = fs.existsSync(path.join(servicePath, 'threats.yaml')); - - // Only include directories that look like valid catalogs - const isValidCatalog = hasControls || hasCapabilities || hasThreats; - - if (isValidCatalog) { - catalogs.push({ - category, - service, - fullPath: servicePath, - hasReleaseDetails, - needsDevReleaseDetails: true // Always create DEV version - }); - - if (hasReleaseDetails) { - console.log(` ✅ Found with release details: ${category}/${service} (will also create DEV version)`); - } else { - console.log(` 🔧 Will create DEV version: ${category}/${service}`); - } + const missingRequired = REQUIRED_CATALOG_FILES.filter( + (f) => !fs.existsSync(path.join(servicePath, f)) + ); + + if (missingRequired.length > 0) { + console.log( + ` ⏭️ Skipping incomplete catalog: ${category}/${service} (missing: ${missingRequired.join(', ')})` + ); + continue; + } + + catalogs.push({ + category, + service, + fullPath: servicePath, + hasReleaseDetails, + needsDevReleaseDetails: true // Always create DEV version + }); + + if (hasReleaseDetails) { + console.log(` ✅ Found with release details: ${category}/${service} (will also create DEV version)`); } else { - console.log(` ⏭️ Skipping non-catalog: ${category}/${service}`); + console.log(` 🔧 Will create DEV version: ${category}/${service}`); } } } diff --git a/website/src/components/ccc/Component/index.tsx b/website/src/components/ccc/Component/index.tsx index a6ec10d5..82372183 100644 --- a/website/src/components/ccc/Component/index.tsx +++ b/website/src/components/ccc/Component/index.tsx @@ -8,7 +8,6 @@ import { ComponentPageData } from "@site/src/types/ccc"; export default function CCCComponentTemplate({ pageData }: { pageData: ComponentPageData }) { const { component } = pageData; - const latestRelease = component.releases[0]; // Releases are sorted by version, so first is latest return ( @@ -40,9 +39,9 @@ export default function CCCComponentTemplate({ pageData }: { pageData: Component
{release.metadata.release_details?.[0]?.contributors?.length > 0 ? release.metadata.release_details[0].contributors.map((contributor, index) => ) : N/A}
- {release.controls.length} - {release.threats.length} - {release.capabilities.length} + {release.controlsCount} + {release.threatsCount} + {release.capabilitiesCount} ))} diff --git a/website/src/components/ccc/Control/index.tsx b/website/src/components/ccc/Control/index.tsx index dcd1b1c0..87df7a53 100644 --- a/website/src/components/ccc/Control/index.tsx +++ b/website/src/components/ccc/Control/index.tsx @@ -1,6 +1,7 @@ import React from "react"; import Layout from "@theme/Layout"; import Link from "@docusaurus/Link"; +import useBrokenLinks from "@docusaurus/useBrokenLinks"; import { Card, CardContent, CardHeader, CardTitle } from "../../ui/card"; import { Badge } from "../../ui/badge"; import { ControlPageData } from "@site/src/types/ccc"; @@ -11,6 +12,7 @@ import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from ". export default function CCCControlTemplate({ pageData }: { pageData: ControlPageData }) { const { control, releaseTitle, releaseSlug, related_threats, related_capabilities } = pageData; + const brokenLinks = useBrokenLinks(); return ( @@ -65,28 +67,34 @@ export default function CCCControlTemplate({ pageData }: { pageData: ControlPage - {control.test_requirements.map((tr) => ( - - - - {tr.id} - - {tr.text} - - {tr.applicability?.length > 0 ? ( -
- {tr.applicability.map((level) => ( - - {level} - - ))} -
- ) : ( - - - )} -
-
- ))} + {control.test_requirements.map((tr) => { + const anchorId = tr.id.split(".").pop()?.toLowerCase(); + if (anchorId) { + brokenLinks.collectAnchor(anchorId); + } + return ( + + + + {tr.id} + + {tr.text} + + {tr.applicability?.length > 0 ? ( +
+ {tr.applicability.map((level) => ( + + {level} + + ))} +
+ ) : ( + - + )} +
+
+ ); + })}
diff --git a/website/src/components/ccc/Home/index.tsx b/website/src/components/ccc/Home/index.tsx index 14295840..195e3755 100644 --- a/website/src/components/ccc/Home/index.tsx +++ b/website/src/components/ccc/Home/index.tsx @@ -9,14 +9,6 @@ import { formatGeneratedAt } from "@site/src/utils/formatGeneratedAt"; export default function CCCHomeTemplate({ pageData }: { pageData: HomePageData }) { const { components } = pageData; - // Flatten all releases into a single array with component title - const allReleases = components.flatMap((component) => - component.releases.map((release) => ({ - ...release, - componentTitle: component.title, - slug: `/ccc/${release.metadata.id}/${release.metadata.version}`, - })) - ); // Transform components into a summary list const componentSummaries = components.map((component) => { const allDetails = component.releases.flatMap((r) => r.metadata.release_details || []); diff --git a/website/src/plugin/ccc-pages/index.ts b/website/src/plugin/ccc-pages/index.ts index 83df2d9f..467d4b56 100644 --- a/website/src/plugin/ccc-pages/index.ts +++ b/website/src/plugin/ccc-pages/index.ts @@ -198,8 +198,20 @@ function createComponentPageData(component: Component): ComponentPageData { const componentSlug = `/ccc/${component.id}`; return { - component, - related_releases: component.releases, + component: { + id: component.id, + title: component.title, + releases: component.releases.map((r) => ({ + metadata: { + id: r.metadata.id, + version: r.metadata.version, + release_details: r.metadata.release_details, + }, + controlsCount: r.controls.length, + threatsCount: r.threats.length, + capabilitiesCount: r.capabilities.length, + })), + }, releaseTitle: component.releases[0]?.metadata.title || component.title, releaseSlug: component.releases[0] ? `/ccc/${component.id}/${component.releases[0].metadata.version}` : componentSlug, slug: componentSlug, @@ -291,7 +303,7 @@ export default function pluginCCCPages(_: LoadContext): Plugin { // Create control pages for (const release of cccReleases) { for (const control of release.controls) { - const controlPageData = createControlPageData(control, release, cccReleases); + const controlPageData = createControlPageData(control, release); await pageCreator.createPage(controlPageData, controlPageData.slug, '@site/src/components/ccc/Control/index.tsx'); } } @@ -299,7 +311,7 @@ export default function pluginCCCPages(_: LoadContext): Plugin { // Create capability pages for (const release of cccReleases) { for (const capability of release.capabilities) { - const capabilityPageData = createCapabilityPageData(capability, release, cccReleases); + const capabilityPageData = createCapabilityPageData(capability, release); await pageCreator.createPage(capabilityPageData, capabilityPageData.slug, '@site/src/components/ccc/Capability/index.tsx'); } } @@ -307,7 +319,7 @@ export default function pluginCCCPages(_: LoadContext): Plugin { // Create threat pages for (const release of cccReleases) { for (const threat of release.threats) { - const threatPageData = createThreatPageData(threat, release, cccReleases); + const threatPageData = createThreatPageData(threat, release); await pageCreator.createPage(threatPageData, threatPageData.slug, '@site/src/components/ccc/Threat/index.tsx'); } } @@ -318,19 +330,29 @@ export default function pluginCCCPages(_: LoadContext): Plugin { await pageCreator.createPage(componentPageData, componentPageData.slug, '@site/src/components/ccc/Component/index.tsx'); } - // Create home page + // Create home page (slim payload: metadata only per release, not full catalogs) const homePageData: HomePageData = { - components: Object.values(components), + components: Object.values(components).map((c) => ({ + id: c.id, + title: c.title, + releases: c.releases.map((r) => ({ + metadata: { + id: r.metadata.id, + version: r.metadata.version, + release_details: r.metadata.release_details, + }, + })), + })), generatedAt: new Date().toISOString(), }; await pageCreator.createPage(homePageData, '/ccc', '@site/src/components/ccc/Home/index.tsx'); console.log('Step 2 complete: All page data created with relationships'); + // Only what client code needs (see useCCCData). Omit raw YAML and component trees — they + // duplicate cccReleases and blow Netlify build heap during globalData serialization. setGlobalData({ 'ccc-releases': cccReleases, - 'ccc-components': Object.values(components), - 'ccc-release-yaml': content }); }, }; diff --git a/website/src/plugin/cfi-pages/index.ts b/website/src/plugin/cfi-pages/index.ts index 45ffec0a..300765a5 100644 --- a/website/src/plugin/cfi-pages/index.ts +++ b/website/src/plugin/cfi-pages/index.ts @@ -222,7 +222,7 @@ async function createConfiguration( configurationResult: configResult, }; - const resultJsonPath = await createData(`cfi-config-result-${repoEntry.name}-${configFolderName}-${resultKey}.json`, JSON.stringify(resultPageData, null, 2)); + const resultJsonPath = await createData(`cfi-config-result-${repoEntry.name}-${configFolderName}-${resultKey}.json`, JSON.stringify(resultPageData)); // Add route for this ConfigurationResult page addRoute({ @@ -253,7 +253,7 @@ async function createConfiguration( configurationResultSummaries, }; - const jsonPath = await createData(`cfi-config-${repoEntry.name}-${configFolderName}.json`, JSON.stringify(pageData, null, 2)); + const jsonPath = await createData(`cfi-config-${repoEntry.name}-${configFolderName}.json`, JSON.stringify(pageData)); // Add route for this configuration page addRoute({ @@ -325,7 +325,7 @@ export default function pluginCFIPages(context: LoadContext): Plugin { generatedAt: new Date().toISOString(), }; - const homePagePath = await createData("cfi-home.json", JSON.stringify(homePageData, null, 2)); + const homePagePath = await createData("cfi-home.json", JSON.stringify(homePageData)); addRoute({ path: "/cfi", diff --git a/website/src/types/ccc.ts b/website/src/types/ccc.ts index 1348cd0b..a9c52e03 100644 --- a/website/src/types/ccc.ts +++ b/website/src/types/ccc.ts @@ -131,13 +131,35 @@ export interface ReleasePageData extends PageData { release_details: ReleaseDetails; } +/** Per-release row on the component overview table (counts only — avoids duplicating full catalogs). */ +export interface ComponentTableRelease { + metadata: Pick; + controlsCount: number; + threatsCount: number; + capabilitiesCount: number; +} + export interface ComponentPageData extends PageData { - component: Component; - related_releases: Release[]; + component: { + id: string; + title: string; + releases: ComponentTableRelease[]; + }; +} + +/** Minimal release metadata for the CCC home listing. */ +export interface HomeReleaseStub { + metadata: Pick; +} + +export interface HomeComponentStub { + id: string; + title: string; + releases: HomeReleaseStub[]; } export interface HomePageData { - components: Component[]; + components: HomeComponentStub[]; /** ISO 8601 timestamp when this page data was produced (site build time). */ generatedAt: string; } diff --git a/website/src/utils/cccDataLookup.ts b/website/src/utils/cccDataLookup.ts index 828ebf23..780bf082 100644 --- a/website/src/utils/cccDataLookup.ts +++ b/website/src/utils/cccDataLookup.ts @@ -7,14 +7,10 @@ import { Release, Control, AssessmentRequirement } from '@site/src/types/ccc'; export function useCCCData() { const cccData = usePluginData('ccc-pages') as { 'ccc-releases': Release[]; - 'ccc-components': any[]; - 'ccc-release-yaml': any[]; } | undefined; return { releases: cccData?.['ccc-releases'] || [], - components: cccData?.['ccc-components'] || [], - releaseYaml: cccData?.['ccc-release-yaml'] || [] }; }