feat(bgm): add board game manifest loader
Parse yaml/json/toml and markdown code blocks into packages, expanding $variants via typed-csv and collecting parts, surfaces, and setups by include patterns. Ships zod validation, a vitest config, and 16 tests.
This commit is contained in:
@@ -0,0 +1,74 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { scanMarkdown } from './markdown.js';
|
||||
|
||||
describe('scanMarkdown', () => {
|
||||
it('extracts a fenced code block with a file= name', () => {
|
||||
const md = [
|
||||
'# Title',
|
||||
'',
|
||||
'```yaml file=parts/cargo.yaml',
|
||||
'role: part',
|
||||
'```',
|
||||
'',
|
||||
'text after',
|
||||
].join('\n');
|
||||
|
||||
const { files, fences } = scanMarkdown(md, 'harbor/harbor.md');
|
||||
|
||||
expect(fences).toHaveLength(1);
|
||||
expect(fences[0]).toMatchObject({
|
||||
info: 'yaml file=parts/cargo.yaml',
|
||||
content: 'role: part',
|
||||
startLine: 3,
|
||||
endLine: 5,
|
||||
});
|
||||
expect(files).toHaveLength(1);
|
||||
expect(files[0]).toMatchObject({
|
||||
name: 'harbor/parts/cargo.yaml',
|
||||
kind: 'yaml',
|
||||
text: 'role: part',
|
||||
source: 'harbor/harbor.md:3-5',
|
||||
});
|
||||
});
|
||||
|
||||
it('auto-names a block without file= from its content hash', () => {
|
||||
const md = '```yaml\nrole: part\n```';
|
||||
const { files } = scanMarkdown(md, 'harbor/harbor.md');
|
||||
expect(files).toHaveLength(1);
|
||||
expect(files[0]!.name).toMatch(/^harbor\/[0-9a-f]{8}\.yaml$/);
|
||||
expect(files[0]!.kind).toBe('yaml');
|
||||
});
|
||||
|
||||
it('ignores non-definition languages', () => {
|
||||
const md = '```js\nconst x = 1;\n```';
|
||||
const { files, fences } = scanMarkdown(md, 'harbor/harbor.md');
|
||||
expect(files).toHaveLength(0);
|
||||
expect(fences).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('ignores indented code blocks', () => {
|
||||
const md = ' role: part\n';
|
||||
const { files } = scanMarkdown(md, 'harbor/harbor.md');
|
||||
expect(files).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('names a csv block with file= as csv', () => {
|
||||
const md = '```csv file=parts/seats.csv\nseat,x\nstring,number\n0,40\n```';
|
||||
const { files } = scanMarkdown(md, 'harbor/harbor.md');
|
||||
expect(files[0]).toMatchObject({ name: 'harbor/parts/seats.csv', kind: 'csv' });
|
||||
});
|
||||
|
||||
it('tracks line numbers across multiple blocks', () => {
|
||||
const md = [
|
||||
'```yaml file=a.yaml',
|
||||
'role: part',
|
||||
'```',
|
||||
'',
|
||||
'```yaml file=b.yaml',
|
||||
'role: part',
|
||||
'```',
|
||||
].join('\n');
|
||||
const { fences } = scanMarkdown(md, 'harbor/harbor.md');
|
||||
expect(fences.map((f) => f.startLine)).toEqual([1, 5]);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user