diff --git a/install.ps1 b/install.ps1 index 5476887b6..5dad1cea7 100644 --- a/install.ps1 +++ b/install.ps1 @@ -34,6 +34,7 @@ $Platforms = [ordered]@{ pi = @{ Target = (Join-Path $HOME '.agents\skills'); Style = 'per-skill' } openclaw = @{ Target = (Join-Path $HOME '.openclaw\skills'); Style = 'folder' } antigravity = @{ Target = (Join-Path $HOME '.gemini\antigravity\skills'); Style = 'folder' } + vibe = @{ Target = (Join-Path $HOME '.vibe\skills'); Style = 'per-skill' } vscode = @{ Target = (Join-Path $HOME '.copilot\skills'); Style = 'per-skill' } hermes = @{ Target = (Join-Path $HOME '.hermes\skills'); Style = 'folder' } cline = @{ Target = (Join-Path $HOME '.cline\skills'); Style = 'folder' } diff --git a/tests/install/platform-table-consistency.test.mjs b/tests/install/platform-table-consistency.test.mjs new file mode 100644 index 000000000..659c5883e --- /dev/null +++ b/tests/install/platform-table-consistency.test.mjs @@ -0,0 +1,122 @@ +import { describe, it, expect } from 'vitest'; +import { readFileSync } from 'node:fs'; +import { dirname, resolve } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const repoRoot = resolve(__dirname, '../..'); + +const installSh = readFileSync(resolve(repoRoot, 'install.sh'), 'utf-8'); +const installPs1 = readFileSync(resolve(repoRoot, 'install.ps1'), 'utf-8'); +const readme = readFileSync(resolve(repoRoot, 'README.md'), 'utf-8'); + +/** + * Parse the platforms_table() heredoc in install.sh: + * id|$HOME/target/dir|style + */ +function parseShPlatforms(source) { + const heredoc = source.match(/platforms_table\(\)\s*\{\s*\n\s*cat <` values:" README line. */ +function parseReadmeSupportedValues(source) { + const line = source.match(/^- Supported `` values: (.+)$/m); + if (!line) return []; + return [...line[1].matchAll(/`([a-z0-9][a-z0-9-]*)`/g)].map((m) => m[1]); +} + +/** Ids referenced as `install.sh ` in the Platform Compatibility table. */ +function parseReadmeCompatTableIds(source) { + const section = source.match(/### Platform Compatibility\n([\s\S]*?)\n#{2,3} /); + if (!section) return []; + return [...section[1].matchAll(/`install\.sh ([a-z0-9][a-z0-9-]*)`/g)].map((m) => m[1]); +} + +const shRows = parseShPlatforms(installSh); +const ps1Rows = parsePs1Platforms(installPs1); + +describe('installer platform table consistency', () => { + // Guard against the parsers silently matching nothing (e.g. after a + // formatting change in either script): a regex mismatch must fail loudly + // here, not let the comparison tests pass vacuously on two empty lists. + it('parses a plausible number of platforms from both scripts', () => { + expect(shRows.length).toBeGreaterThanOrEqual(10); + expect(ps1Rows.length).toBeGreaterThanOrEqual(10); + }); + + it('install.sh and install.ps1 define the same platform ids in the same order', () => { + // Same order matters, not just the same set: both scripts number their + // interactive platform menus from the table order, so "3) opencode" must + // mean the same thing on macOS/Linux and on Windows. + expect(ps1Rows.map((r) => r.id)).toEqual(shRows.map((r) => r.id)); + }); + + it('each platform has the same link style in both scripts', () => { + const ps1ById = new Map(ps1Rows.map((r) => [r.id, r])); + for (const row of shRows) { + expect(ps1ById.get(row.id)?.style, `style for "${row.id}"`).toBe(row.style); + } + }); + + it('each platform has the same skills target dir in both scripts', () => { + const ps1ById = new Map(ps1Rows.map((r) => [r.id, r])); + for (const row of shRows) { + const ps1Row = ps1ById.get(row.id); + if (!ps1Row) continue; // id-set mismatch is reported by the test above + expect(normalizeTarget(ps1Row.target), `target for "${row.id}"`).toBe( + normalizeTarget(row.target), + ); + } + }); + + it('README "Supported values" line matches the installer table', () => { + const readmeIds = parseReadmeSupportedValues(readme); + expect(readmeIds.length).toBeGreaterThanOrEqual(10); + expect([...readmeIds].sort()).toEqual(shRows.map((r) => r.id).sort()); + }); + + it('README Platform Compatibility table only references real installer platforms', () => { + // The table may legitimately document a platform via another install + // method (e.g. vscode → auto-discovery), so this is a subset check; full + // coverage of the id list is enforced by the supported-values test above. + const tableIds = parseReadmeCompatTableIds(readme); + expect(tableIds.length).toBeGreaterThanOrEqual(10); + const shIds = new Set(shRows.map((r) => r.id)); + for (const id of tableIds) { + expect(shIds.has(id), `"install.sh ${id}" in README compatibility table`).toBe(true); + } + }); +});