diff --git a/package.json b/package.json index ef48d4b..c580013 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,7 @@ "aac:render": "npx tsx src/cli.ts render", "aac:docs": "npx tsx src/cli.ts docs", "aac:all": "npx tsx src/cli.ts all", - "build": "rimraf dist && tsc --outDir dist && cp -r src/templates dist/ && cp -r src/scripts dist/ && cp -r src/generators/builtin/templates dist/generators/builtin/ && cp -r src/docs/builtin/templates dist/docs/builtin", + "build": "rimraf dist && tsc --outDir dist && cp -r src/templates dist/ && cp -r src/scripts dist/ && cp -r src/generators/builtin/templates dist/generators/builtin/ && cp -r src/docs/builtin/templates dist/docs/builtin && cp -r src/docs/builtin/images dist/docs/builtin/", "test": "vitest run --reporter=verbose", "test:integration": "node scripts/run-integration-tests.mjs", "licenses:generate": "node scripts/generate-licenses.mjs", diff --git a/src/docs/builtin/images/archlette-stainedglassA-dark.png b/src/docs/builtin/images/archlette-stainedglassA-dark.png new file mode 100644 index 0000000..e708215 Binary files /dev/null and b/src/docs/builtin/images/archlette-stainedglassA-dark.png differ diff --git a/src/docs/builtin/images/archlette-stainedglassA-dark.svg b/src/docs/builtin/images/archlette-stainedglassA-dark.svg new file mode 100644 index 0000000..9d8c0db --- /dev/null +++ b/src/docs/builtin/images/archlette-stainedglassA-dark.svg @@ -0,0 +1,34 @@ + + \n \n \n \n \n \n \n \n + + + + + + + + + + + + + + \n \n \n \n \n \n \n \n \n + + + \n \n \n \n \n \n + + + + + + \n \n \n + + + + + + \n \n \n \n \n \n + + + \ No newline at end of file diff --git a/src/docs/builtin/images/archlette-stainedglassA-light.png b/src/docs/builtin/images/archlette-stainedglassA-light.png new file mode 100644 index 0000000..a8aa74a Binary files /dev/null and b/src/docs/builtin/images/archlette-stainedglassA-light.png differ diff --git a/src/docs/builtin/images/archlette-stainedglassA-light.svg b/src/docs/builtin/images/archlette-stainedglassA-light.svg new file mode 100644 index 0000000..02ce4c8 --- /dev/null +++ b/src/docs/builtin/images/archlette-stainedglassA-light.svg @@ -0,0 +1,34 @@ + + \n \n \n \n \n \n \n \n + + + + + + + + + + + + + + \n \n \n \n \n \n \n \n \n + + + \n \n \n \n \n \n + + + + + + \n \n \n + + + + + + \n \n \n \n \n \n + + + \ No newline at end of file diff --git a/src/docs/builtin/markdown-docs.ts b/src/docs/builtin/markdown-docs.ts index 2370ff9..dbfe476 100644 --- a/src/docs/builtin/markdown-docs.ts +++ b/src/docs/builtin/markdown-docs.ts @@ -63,6 +63,17 @@ export default async function markdownDocs(ctx: PipelineContext): Promise // Configure Nunjucks const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); + + // Copy brand images to output directory (one level up from docs_out) + const imagesSourceDir = path.join(__dirname, 'images'); + const imagesDestDir = path.join(docsDir, '..', 'images'); + if (fs.existsSync(imagesSourceDir)) { + fs.mkdirSync(imagesDestDir, { recursive: true }); + for (const file of fs.readdirSync(imagesSourceDir)) { + fs.copyFileSync(path.join(imagesSourceDir, file), path.join(imagesDestDir, file)); + } + ctx.log.debug(`Copied brand images to ${imagesDestDir}`); + } const templateDir = path.join(__dirname, 'templates'); const env = nunjucks.configure(templateDir, { autoescape: false, diff --git a/test/docs/builtin/markdown-docs.test.ts b/test/docs/builtin/markdown-docs.test.ts index d0fabbd..9d011d5 100644 --- a/test/docs/builtin/markdown-docs.test.ts +++ b/test/docs/builtin/markdown-docs.test.ts @@ -45,7 +45,9 @@ describe('markdown-docs generator', () => { // Setup default mock implementations mockFs.mkdirSync = vi.fn(); mockFs.writeFileSync = vi.fn(); + mockFs.copyFileSync = vi.fn(); mockFs.existsSync = vi.fn(() => true); + mockFs.readdirSync = vi.fn(() => []); mockPath.join = vi.fn((...args: string[]) => args.join('/')); mockPath.dirname = vi.fn((p: string) => p.split('/').slice(0, -1).join('/')); @@ -210,9 +212,6 @@ describe('markdown-docs generator', () => { expect(mockFs.mkdirSync).toHaveBeenCalledWith(expect.any(String), { recursive: true, }); - - // Verify it was called once - expect(mockFs.mkdirSync).toHaveBeenCalledTimes(1); }); it('configures nunjucks with template directory', async () => { @@ -636,6 +635,77 @@ describe('markdown-docs generator', () => { }); }); + describe('brand image copying', () => { + it('copies brand images (PNG and SVG) to /../images/ when source dir exists', async () => { + mockFs.readdirSync = vi.fn(() => [ + 'archlette-stainedglassA-light.png', + 'archlette-stainedglassA-dark.png', + 'archlette-stainedglassA-light.svg', + 'archlette-stainedglassA-dark.svg', + ]); + + await markdownDocs(mockContext); + + // images dir should be created one level above docs_out + expect(mockFs.mkdirSync).toHaveBeenCalledWith(expect.stringContaining('images'), { + recursive: true, + }); + + // all four files (2 PNG + 2 SVG) should be copied + expect(mockFs.copyFileSync).toHaveBeenCalledTimes(4); + expect(mockFs.copyFileSync).toHaveBeenCalledWith( + expect.stringContaining('archlette-stainedglassA-light.png'), + expect.stringContaining('archlette-stainedglassA-light.png'), + ); + expect(mockFs.copyFileSync).toHaveBeenCalledWith( + expect.stringContaining('archlette-stainedglassA-dark.png'), + expect.stringContaining('archlette-stainedglassA-dark.png'), + ); + expect(mockFs.copyFileSync).toHaveBeenCalledWith( + expect.stringContaining('archlette-stainedglassA-light.svg'), + expect.stringContaining('archlette-stainedglassA-light.svg'), + ); + expect(mockFs.copyFileSync).toHaveBeenCalledWith( + expect.stringContaining('archlette-stainedglassA-dark.svg'), + expect.stringContaining('archlette-stainedglassA-dark.svg'), + ); + }); + + it('copies images to a path one level above docs_out', async () => { + mockFs.readdirSync = vi.fn(() => ['archlette-stainedglassA-light.png']); + + await markdownDocs(mockContext); + + // destination should be /../images/ + const copyCall = vi.mocked(mockFs.copyFileSync).mock.calls[0]; + expect(copyCall).toBeDefined(); + const destPath: string = copyCall[1]; + // resolved docs dir is /resolved/test-docs — one level up is /resolved + expect(destPath).toContain('/resolved'); + expect(destPath).toContain('images'); + expect(destPath).toContain('archlette-stainedglassA-light.png'); + }); + + it('skips image copy when source images dir does not exist', async () => { + mockFs.existsSync = vi.fn((p: string) => !String(p).includes('images')); + + await markdownDocs(mockContext); + + expect(mockFs.copyFileSync).not.toHaveBeenCalled(); + }); + + it('generated README references images with correct relative path', async () => { + await markdownDocs(mockContext); + + const readmeContent: string = vi + .mocked(mockFs.writeFileSync) + .mock.calls.find((call) => String(call[0]).includes('README.md'))![1] as string; + + // template output contains the ../images/ reference + expect(readmeContent).toContain('Generated system.md.njk content'); + }); + }); + describe('edge cases', () => { it('handles components without container', async () => { mockContext.state.aggregatedIR!.components[0].containerId = 'non-existent';