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 @@
+
\ 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 @@
+
\ 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';