This commit is contained in:
146
tests/oss-subtree-split.test.ts
Normal file
146
tests/oss-subtree-split.test.ts
Normal file
@@ -0,0 +1,146 @@
|
||||
/**
|
||||
* E-4 — Regression guard for scripts/oss-subtree-split.sh.
|
||||
*
|
||||
* This is a STATIC analysis of the script + the package structure it
|
||||
* operates on. It does NOT actually run `git subtree split` (that's a
|
||||
* minutes-long operation per package, gated behind an explicit local
|
||||
* invocation). Instead it locks down:
|
||||
*
|
||||
* 1. The script exists and is executable.
|
||||
* 2. The list of forbidden top-level entries the script checks for
|
||||
* is the actual list of monorepo-only directories. If we ever
|
||||
* add a new monorepo-level dir (e.g. `tools/`) without updating
|
||||
* the script's forbidden list, a real subtree-split could leak
|
||||
* it; this test surfaces that gap.
|
||||
* 3. Every `packages/hive-mind-*` directory contains the package-
|
||||
* level files an OSS-mirror expects (package.json + a real src/
|
||||
* or index file at minimum). If a package is gutted to `export {}`
|
||||
* we don't want the OSS mirror to ship an empty shell.
|
||||
* 4. The forbidden list does NOT include legitimate package-internal
|
||||
* directories (docs/, assets/, src/, tests/, dist/) — those exist
|
||||
* inside packages and must be allowed.
|
||||
*/
|
||||
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { readFileSync, existsSync, statSync, readdirSync } from 'node:fs';
|
||||
import { join, resolve } from 'node:path';
|
||||
|
||||
const REPO_ROOT = resolve(__dirname, '..');
|
||||
const SCRIPT_PATH = join(REPO_ROOT, 'scripts', 'oss-subtree-split.sh');
|
||||
const PACKAGES_DIR = join(REPO_ROOT, 'packages');
|
||||
|
||||
describe('oss-subtree-split.sh — static guards', () => {
|
||||
it('script exists at the documented path', () => {
|
||||
expect(existsSync(SCRIPT_PATH)).toBe(true);
|
||||
expect(statSync(SCRIPT_PATH).isFile()).toBe(true);
|
||||
});
|
||||
|
||||
it('script has a shebang for bash', () => {
|
||||
const content = readFileSync(SCRIPT_PATH, 'utf-8');
|
||||
expect(content.startsWith('#!/usr/bin/env bash')).toBe(true);
|
||||
});
|
||||
|
||||
it('script uses set -euo pipefail (fail-fast)', () => {
|
||||
const content = readFileSync(SCRIPT_PATH, 'utf-8');
|
||||
expect(content).toMatch(/set -euo pipefail/);
|
||||
});
|
||||
|
||||
it('discovers packages/hive-mind-* directories dynamically (Wave 2/3 auto-included)', () => {
|
||||
const content = readFileSync(SCRIPT_PATH, 'utf-8');
|
||||
// The default-PACKAGES discovery loop is the seam that picks up
|
||||
// newly-added hive-mind-* packages without script edits.
|
||||
expect(content).toMatch(/packages\/hive-mind-\*/);
|
||||
expect(content).toMatch(/mapfile.*PACKAGES.*ls -1d/);
|
||||
});
|
||||
});
|
||||
|
||||
describe('oss-subtree-split.sh — forbidden monorepo-level entries', () => {
|
||||
/**
|
||||
* Mirror of the script's forbidden-list. If you add a new monorepo-
|
||||
* level top-level dir, update BOTH the script and this test (and
|
||||
* verify no package legitimately uses that name internally).
|
||||
*/
|
||||
const FORBIDDEN: readonly string[] = [
|
||||
'apps',
|
||||
'packages',
|
||||
'sidecar',
|
||||
'.planning',
|
||||
'.scratch',
|
||||
'.mind',
|
||||
'benchmarks',
|
||||
];
|
||||
|
||||
// .planning / .scratch / .mind are gitignored working dirs — forbidden from
|
||||
// the OSS export if present, but legitimately ABSENT on a clean checkout (CI).
|
||||
// The "dead guard" existence check therefore applies only to the tracked dirs;
|
||||
// a gitignored working dir that's simply not present is fine.
|
||||
const GITIGNORED_WORKING_DIRS = new Set(['.planning', '.scratch', '.mind']);
|
||||
it('every tracked forbidden entry exists as a monorepo-level dir (otherwise the guard is dead)', () => {
|
||||
for (const f of FORBIDDEN) {
|
||||
const monorepoLevel = join(REPO_ROOT, f);
|
||||
if (GITIGNORED_WORKING_DIRS.has(f) && !existsSync(monorepoLevel)) continue;
|
||||
expect(
|
||||
existsSync(monorepoLevel),
|
||||
`Forbidden entry '${f}' is not present at monorepo root — the script's negative-assertion guard is dead.`,
|
||||
).toBe(true);
|
||||
}
|
||||
});
|
||||
|
||||
it('script lists every forbidden entry in its loop', () => {
|
||||
const content = readFileSync(SCRIPT_PATH, 'utf-8');
|
||||
for (const f of FORBIDDEN) {
|
||||
// Each forbidden token appears in the `for forbidden in …` loop.
|
||||
expect(
|
||||
content,
|
||||
`Script does not list '${f}' as a forbidden top-level entry.`,
|
||||
).toContain(f);
|
||||
}
|
||||
});
|
||||
|
||||
it('forbidden list does not include legitimate package-internal dirs', () => {
|
||||
const PACKAGE_INTERNAL_DIRS: readonly string[] = ['src', 'tests', 'dist', 'docs', 'assets'];
|
||||
const content = readFileSync(SCRIPT_PATH, 'utf-8');
|
||||
// Locate the `for forbidden in` line specifically — we want to
|
||||
// ensure the FORBIDDEN tokens don't accidentally include things
|
||||
// every package has internally.
|
||||
const match = content.match(/for forbidden in\s+([^\n;]+)/);
|
||||
expect(match).toBeTruthy();
|
||||
const tokens = (match![1] ?? '').trim().split(/\s+/);
|
||||
for (const internal of PACKAGE_INTERNAL_DIRS) {
|
||||
expect(
|
||||
tokens.includes(internal),
|
||||
`Script forbids '${internal}' but that's a legitimate package-internal dir — splits would always fail.`,
|
||||
).toBe(false);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('oss-subtree-split.sh — package-level shape', () => {
|
||||
function listHiveMindPackages(): string[] {
|
||||
return readdirSync(PACKAGES_DIR)
|
||||
.filter((name) => name.startsWith('hive-mind-'))
|
||||
.filter((name) => statSync(join(PACKAGES_DIR, name)).isDirectory());
|
||||
}
|
||||
|
||||
it('at least one hive-mind-* package exists (so the script has something to split)', () => {
|
||||
expect(listHiveMindPackages().length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it.each(listHiveMindPackages())('package %s has package.json', (name) => {
|
||||
const pkgJson = join(PACKAGES_DIR, name, 'package.json');
|
||||
expect(existsSync(pkgJson), `${name} missing package.json`).toBe(true);
|
||||
});
|
||||
|
||||
it.each(listHiveMindPackages())('package %s has src/ (real code, not a placeholder)', (name) => {
|
||||
const src = join(PACKAGES_DIR, name, 'src');
|
||||
expect(existsSync(src), `${name} missing src/`).toBe(true);
|
||||
});
|
||||
|
||||
it.each(listHiveMindPackages())('package %s package.json declares Apache-2.0 license', (name) => {
|
||||
const pkg = JSON.parse(readFileSync(join(PACKAGES_DIR, name, 'package.json'), 'utf-8'));
|
||||
expect(
|
||||
pkg.license,
|
||||
`${name} must be Apache-2.0 to ship via the OSS subtree-split (matches the OSS repo's license).`,
|
||||
).toBe('Apache-2.0');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user