From 2001616d9e1a0b8148dc744354804437ac3407c9 Mon Sep 17 00:00:00 2001 From: hypercross Date: Mon, 10 Aug 2026 08:52:51 +0800 Subject: [PATCH] fix(bgm): resolve include patterns relative to package dir The default include matched every yaml across the games root, so a package could absorb a sibling game's defs (e.g. two game#main setups). Resolve patterns against the package declaration's directory instead; a leading slash marks a pattern as root-relative. --- docs/bgm-format.md | 7 +++++++ packages/bgm/src/collect.test.ts | 18 ++++++++++++++++++ packages/bgm/src/collect.ts | 16 +++++++++++++--- 3 files changed, 38 insertions(+), 3 deletions(-) diff --git a/docs/bgm-format.md b/docs/bgm-format.md index 5fa108a..5fdca3b 100644 --- a/docs/bgm-format.md +++ b/docs/bgm-format.md @@ -104,6 +104,13 @@ package. **Defaults to `./**/*.yaml`**, so all yaml in the same and sub folders is discovered with no configuration. This also matches the package declaration itself, which is fine — it's the package, not a part. +Patterns are resolved **relative to the package declaration's own directory**, +not the games root. So a package declared in `carcassonne/carcassonne.md` +with the default `./**/*.yaml` only picks up yaml under `carcassonne/` — it +never absorbs defs from a sibling game. To reach outside its folder, a +package can use a `../`-relative pattern or an absolute-from-root pattern +(e.g. `**/shared/*.yaml`). + --- ## 3. Roles diff --git a/packages/bgm/src/collect.test.ts b/packages/bgm/src/collect.test.ts index fc51a53..b7e5ec6 100644 --- a/packages/bgm/src/collect.test.ts +++ b/packages/bgm/src/collect.test.ts @@ -4,6 +4,7 @@ import { fileURLToPath } from 'node:url'; import { loadDefs, collectPackages } from './collect.js'; const fixtureRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '__fixtures__', 'harbor'); +const multiRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '__fixtures__', 'vite-build', 'games'); describe('collectPackages', () => { it('collects the harbor package from markdown code blocks', () => { @@ -60,6 +61,23 @@ describe('collectPackages', () => { }); }); + it('scopes include patterns to the package declaration directory', () => { + // Two packages share a games root. Each uses the default `./**/*.yaml` + // include, which must resolve relative to its own folder so neither + // absorbs the other's defs (both define a `game#main` setup). + const defMap = loadDefs('', multiRoot); + const packages = collectPackages(defMap, multiRoot); + + expect(packages).toHaveLength(2); + const azul = packages.find((p) => p.meta.id === 'azul')!; + const harbor = packages.find((p) => p.meta.id === 'harbor')!; + + expect([...azul.parts.keys()]).toEqual(['tile#blue']); + expect([...azul.setups.keys()]).toEqual(['game#main']); + expect([...harbor.parts.keys()]).toEqual(['token#wood']); + expect([...harbor.setups.keys()]).toEqual(['game#main']); + }); + it('throws on a duplicate type#id', () => { const defMap = loadDefs('', fixtureRoot); // Inject a duplicate part into the map under a new file name. diff --git a/packages/bgm/src/collect.ts b/packages/bgm/src/collect.ts index 2335fb0..07f9721 100644 --- a/packages/bgm/src/collect.ts +++ b/packages/bgm/src/collect.ts @@ -101,7 +101,8 @@ export function collectPackages(defMap: DefMap, rootDir: string): Package[] { const role = def.value['role'] as Role; if (role === 'package') { const pkg = asPackage(def, file); - accs.push(new PackageAcc(pkg, defMap, rootDir)); + const baseDir = path.posix.dirname(file).replace(/^\/+/, ''); + accs.push(new PackageAcc(pkg, defMap, rootDir, baseDir)); } } } @@ -125,6 +126,8 @@ class PackageAcc { readonly pkg: PackageDef, private readonly defs: DefMap, private readonly rootDir: string, + /** Path-style directory of the package declaration, e.g. `carcassonne`. */ + private readonly baseDir: string, ) {} collect() { @@ -152,10 +155,17 @@ class PackageAcc { private expandIncludes(patterns: string[]): string[] { // Match include patterns against the parsed definitions' names, which // cover both real files and markdown code blocks. Patterns are relative - // to the games root (e.g. `./**/*.yaml`). + // to the package declaration's own directory (e.g. `./**/*.yaml` means + // this package's folder and below), so a package never absorbs defs from + // a sibling game. A leading `/` marks a pattern as root-relative. + // Def names carry a leading `/` (from the empty games root), so resolved + // patterns are prefixed to match. const names = new Set(); for (const pattern of patterns) { - const matcher = picomatch(pattern, { dot: true }); + const resolved = pattern.startsWith('/') + ? pattern + : `/${path.posix.join(this.baseDir, pattern)}`; + const matcher = picomatch(resolved, { dot: true }); for (const name of this.defs.defs.keys()) { if (matcher(name)) names.add(name); }