426e9eeabd
Voice Workbench / headless workbench (mocked backends) (push) Has been cancelled
Voice Workbench / real acoustic lane (nightly, provisioned only) (push) Has been cancelled
ci / test (push) Has been cancelled
ci / lint-and-format (push) Has been cancelled
ci / build (push) Has been cancelled
ci / dev-startup (push) Has been cancelled
gitleaks / gitleaks (push) Has been cancelled
Markdown Links / Relative Markdown Links (push) Has been cancelled
Quality (Extended) / Homepage Build (PR smoke) (push) Has been cancelled
Quality (Extended) / Comment-only diff guard (push) Has been cancelled
Quality (Extended) / Format + Type Safety Ratchet (push) Has been cancelled
Quality (Extended) / Develop Gate (secret scan + UI determinism) (push) Has been cancelled
Quality (Extended) / Develop Gate (lint) (push) Has been cancelled
Chat shell gestures / Chat shell gesture + parity e2e (push) Has been cancelled
Cloud Gateway Discord / Test (push) Has been cancelled
Benchmark Bridge Tests / benchmark (bunx @biomejs/biome check packages/lifeops-bench/src, benchmark-lint) (push) Has been cancelled
Benchmark Bridge Tests / benchmark (bunx vitest run --config packages/lifeops-bench/vitest.config.ts --root packages/lifeops-bench --passWithNoTests, benchmark-tests) (push) Has been cancelled
Build Agent Image / build-and-push (push) Has been cancelled
Dev Smoke / bun run dev onboarding chat (push) Has been cancelled
Dev Smoke / Vite HMR dependency-level smoke (push) Has been cancelled
Electrobun Submodule Guard / electrobun gitlink is fetchable (push) Has been cancelled
Publish @elizaos/example-code / check_npm (push) Has been cancelled
Publish @elizaos/example-code / publish_npm (push) Has been cancelled
Publish @elizaos/plugin-elizacloud / verify_version (push) Has been cancelled
Publish @elizaos/plugin-elizacloud / publish_npm (push) Has been cancelled
Sandbox Live Smoke / Sandbox live smoke (push) Has been cancelled
Snap Build & Test / Build Snap (amd64) (push) Has been cancelled
Snap Build & Test / Build Snap (arm64) (push) Has been cancelled
Test Packaging / elizaos CLI global-install smoke (node + bun) (push) Has been cancelled
Cloud Gateway Webhook / Test (push) Has been cancelled
Cloud Tests / lint-and-types (push) Has been cancelled
Cloud Tests / unit-tests (push) Has been cancelled
Cloud Tests / integration-tests (push) Has been cancelled
Cloud Tests / e2e-tests (push) Has been cancelled
CodeQL Advanced / Analyze (javascript-typescript) (push) Has been cancelled
Deploy Apps Worker (Product 2) / Determine environment (push) Has been cancelled
Deploy Apps Worker (Product 2) / Deploy apps worker to apps-control host (${{ needs.determine-env.outputs.environment }}) (push) Has been cancelled
Deploy Eliza Provisioning Worker / Determine environment (push) Has been cancelled
Deploy Eliza Provisioning Worker / Deploy worker to Hetzner host (${{ needs.determine-env.outputs.environment }} @ ${{ needs.determine-env.outputs.deployment_sha }}) (push) Has been cancelled
Dev Smoke / Classify changed paths (push) Has been cancelled
supply-chain / sbom (push) Has been cancelled
supply-chain / vulnerability-scan (push) Has been cancelled
Build, Push & Deploy to Phala Cloud / build-and-push (push) Has been cancelled
Test Packaging / Validate Packaging Configs (push) Has been cancelled
Test Packaging / Build & Test PyPI Package (push) Has been cancelled
Test Packaging / PyPI on Python ${{ matrix.python }} (push) Has been cancelled
Test Packaging / Pack & Test JS Tarballs (push) Has been cancelled
UI Fixture E2E / ui-fixture-e2e (push) Has been cancelled
UI Fixture E2E / fixture-e2e (push) Has been cancelled
UI Story Gate / story-gate (push) Has been cancelled
vault-ci / test (macos-latest) (push) Has been cancelled
vault-ci / test (ubuntu-latest) (push) Has been cancelled
vault-ci / test (windows-latest) (push) Has been cancelled
vault-ci / app-core wiring tests (push) Has been cancelled
verify-patches / verify patches/CHECKSUMS.sha256 (push) Has been cancelled
Voice Benchmark Smoke / voice-emotion fixture smoke (push) Has been cancelled
Voice Benchmark Smoke / voiceagentbench fixture smoke (push) Has been cancelled
Voice Benchmark Smoke / voicebench-quality unit smoke (push) Has been cancelled
Voice Benchmark Smoke / voicebench TypeScript unit (no audio) (push) Has been cancelled
Voice Benchmark Smoke / voice bench smoke summary (push) Has been cancelled
Windows CI / windows ([bun run --cwd packages/app-core test bun run --cwd packages/elizaos test bun run --cwd packages/cloud/shared test], app-and-cli) (push) Has been cancelled
Windows CI / windows ([bun run --cwd packages/scenario-runner test bun run --cwd packages/vault test bun run --cwd packages/security test bun run --cwd plugins/plugin-coding-tools test], framework-packages) (push) Has been cancelled
Windows CI / windows ([bun run --cwd plugins/plugin-elizacloud test bun run --cwd plugins/plugin-discord test bun run --cwd plugins/plugin-anthropic test bun run --cwd plugins/plugin-openai test bun run --cwd plugins/plugin-app-control test bun run --cwd plugins/pl… (push) Has been cancelled
Windows CI / windows ([node packages/scripts/run-turbo.mjs run build --filter=@elizaos/core --filter=@elizaos/shared --filter=@elizaos/agent --concurrency=4 node packages/scripts/run-bash-linux-only.mjs scripts/verify-riscv64-buildpaths.sh node packages/scripts/run… (push) Has been cancelled
Windows CI / windows ([node packages/scripts/run-turbo.mjs run typecheck --filter=@elizaos/core --filter=@elizaos/shared --filter=@elizaos/cloud-shared --concurrency=4 bun run --cwd packages/core test bun run --cwd packages/shared test], core-runtime, 75) (push) Has been cancelled
273 lines
9.0 KiB
TypeScript
273 lines
9.0 KiB
TypeScript
/**
|
|
* Unit + live-repo-parity tests for the workspace/submodule discovery seam
|
|
* (packages/scripts/lib/workspaces.mjs). Synthetic cases exercise glob
|
|
* semantics against throwaway temp trees; parity cases assert the lib agrees
|
|
* with the real repo's package.json and .gitmodules. Deterministic, no network.
|
|
*/
|
|
|
|
import { afterEach, describe, expect, test } from "bun:test";
|
|
import fs from "node:fs";
|
|
import os from "node:os";
|
|
import path from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
import {
|
|
expandWorkspaceGlobs,
|
|
listPackages,
|
|
listSubmodules,
|
|
listWorkspaceDirs,
|
|
} from "../lib/workspaces.mjs";
|
|
|
|
const REPO_ROOT = path.resolve(
|
|
path.dirname(fileURLToPath(import.meta.url)),
|
|
"../../..",
|
|
);
|
|
|
|
const tempRoots: string[] = [];
|
|
|
|
afterEach(() => {
|
|
while (tempRoots.length > 0) {
|
|
const root = tempRoots.pop();
|
|
if (root) fs.rmSync(root, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
function makeRepo(): string {
|
|
const root = fs.mkdtempSync(path.join(os.tmpdir(), "workspaces-lib-"));
|
|
tempRoots.push(root);
|
|
return root;
|
|
}
|
|
|
|
function writeFile(root: string, relativePath: string, content: string): void {
|
|
const filePath = path.join(root, relativePath);
|
|
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
fs.writeFileSync(filePath, content);
|
|
}
|
|
|
|
function writePackage(root: string, dir: string, name?: string): void {
|
|
writeFile(
|
|
root,
|
|
path.join(dir, "package.json"),
|
|
JSON.stringify(name ? { name } : {}, null, 2),
|
|
);
|
|
}
|
|
|
|
function writeRootPackage(root: string, workspaces: string[]): void {
|
|
writeFile(
|
|
root,
|
|
"package.json",
|
|
JSON.stringify({ type: "module", workspaces }, null, 2),
|
|
);
|
|
}
|
|
|
|
describe("expandWorkspaceGlobs — glob semantics", () => {
|
|
test("simple `*` matches one segment of directories", () => {
|
|
const root = makeRepo();
|
|
fs.mkdirSync(path.join(root, "packages/a"), { recursive: true });
|
|
fs.mkdirSync(path.join(root, "packages/b"), { recursive: true });
|
|
fs.mkdirSync(path.join(root, "packages/a/nested"), { recursive: true });
|
|
|
|
const dirs = expandWorkspaceGlobs(["packages/*"], { repoRoot: root });
|
|
expect(dirs).toEqual(["packages/a", "packages/b"]);
|
|
});
|
|
|
|
test("nested `*/*` matches exactly two segments deep", () => {
|
|
const root = makeRepo();
|
|
fs.mkdirSync(path.join(root, "packages/group/one"), { recursive: true });
|
|
fs.mkdirSync(path.join(root, "packages/group/two"), { recursive: true });
|
|
fs.mkdirSync(path.join(root, "packages/solo"), { recursive: true });
|
|
|
|
const dirs = expandWorkspaceGlobs(["packages/*/*"], { repoRoot: root });
|
|
expect(dirs).toEqual(["packages/group/one", "packages/group/two"]);
|
|
});
|
|
|
|
test("`**` matches any depth including the base directory", () => {
|
|
const root = makeRepo();
|
|
fs.mkdirSync(path.join(root, "packages/a/deep/deeper"), {
|
|
recursive: true,
|
|
});
|
|
fs.mkdirSync(path.join(root, "packages/b"), { recursive: true });
|
|
|
|
const dirs = expandWorkspaceGlobs(["packages/**"], { repoRoot: root });
|
|
expect(dirs).toEqual([
|
|
"packages",
|
|
"packages/a",
|
|
"packages/a/deep",
|
|
"packages/a/deep/deeper",
|
|
"packages/b",
|
|
]);
|
|
});
|
|
|
|
test("`*` skips hidden and build-output dirs (e.g. a stray `.next` marker)", () => {
|
|
const root = makeRepo();
|
|
fs.mkdirSync(path.join(root, "examples/real"), { recursive: true });
|
|
// A Next.js build dir carries a `{"type":"commonjs"}` package.json marker;
|
|
// it is not a workspace member and must not be matched by a `*` segment.
|
|
fs.mkdirSync(path.join(root, "examples/real/.next"), { recursive: true });
|
|
fs.mkdirSync(path.join(root, "examples/real/dist"), { recursive: true });
|
|
|
|
expect(expandWorkspaceGlobs(["examples/*"], { repoRoot: root })).toEqual([
|
|
"examples/real",
|
|
]);
|
|
expect(expandWorkspaceGlobs(["examples/*/*"], { repoRoot: root })).toEqual(
|
|
[],
|
|
);
|
|
});
|
|
|
|
test("negation subtracts an earlier match (exclude-wins)", () => {
|
|
const root = makeRepo();
|
|
fs.mkdirSync(path.join(root, "packages/keep"), { recursive: true });
|
|
fs.mkdirSync(path.join(root, "packages/feed"), { recursive: true });
|
|
|
|
const dirs = expandWorkspaceGlobs(["packages/*", "!packages/feed"], {
|
|
repoRoot: root,
|
|
});
|
|
expect(dirs).toEqual(["packages/keep"]);
|
|
});
|
|
|
|
test("later positive pattern re-adds a previously negated dir (last-match-wins)", () => {
|
|
const root = makeRepo();
|
|
fs.mkdirSync(path.join(root, "packages/feed"), { recursive: true });
|
|
|
|
const dirs = expandWorkspaceGlobs(
|
|
["packages/*", "!packages/feed", "packages/feed"],
|
|
{ repoRoot: root },
|
|
);
|
|
expect(dirs).toEqual(["packages/feed"]);
|
|
});
|
|
});
|
|
|
|
describe("listWorkspaceDirs — package.json filtering", () => {
|
|
test("keeps only directories that contain a package.json", () => {
|
|
const root = makeRepo();
|
|
writeRootPackage(root, ["packages/*"]);
|
|
writePackage(root, "packages/real", "@x/real");
|
|
fs.mkdirSync(path.join(root, "packages/empty"), { recursive: true });
|
|
|
|
expect(listWorkspaceDirs({ repoRoot: root })).toEqual(["packages/real"]);
|
|
});
|
|
|
|
test("hidden and build dirs are skipped by `**` traversal", () => {
|
|
const root = makeRepo();
|
|
writeRootPackage(root, ["packages/**"]);
|
|
writePackage(root, "packages", "@x/packages-root");
|
|
writePackage(root, "packages/live", "@x/live");
|
|
writePackage(root, "packages/live/node_modules/dep", "dep");
|
|
writePackage(root, "packages/live/dist", "dist-pkg");
|
|
writePackage(root, "packages/.hidden", "hidden-pkg");
|
|
|
|
expect(listWorkspaceDirs({ repoRoot: root })).toEqual([
|
|
"packages",
|
|
"packages/live",
|
|
]);
|
|
});
|
|
|
|
test("negation excludes a workspace with a package.json", () => {
|
|
const root = makeRepo();
|
|
writeRootPackage(root, ["packages/*", "!packages/feed"]);
|
|
writePackage(root, "packages/core", "@x/core");
|
|
writePackage(root, "packages/feed", "@x/feed");
|
|
|
|
expect(listWorkspaceDirs({ repoRoot: root })).toEqual(["packages/core"]);
|
|
});
|
|
});
|
|
|
|
describe("listPackages — name mapping", () => {
|
|
test("maps each workspace dir to { name, dir, packageJson }", () => {
|
|
const root = makeRepo();
|
|
writeRootPackage(root, ["packages/*"]);
|
|
writePackage(root, "packages/alpha", "@x/alpha");
|
|
writePackage(root, "packages/beta");
|
|
|
|
const packages = listPackages({ repoRoot: root });
|
|
expect(packages).toEqual([
|
|
{
|
|
name: "@x/alpha",
|
|
dir: "packages/alpha",
|
|
packageJson: { name: "@x/alpha" },
|
|
},
|
|
{ name: undefined, dir: "packages/beta", packageJson: {} },
|
|
]);
|
|
});
|
|
});
|
|
|
|
describe("listSubmodules — .gitmodules parsing", () => {
|
|
test("parses path/url/branch and marks initialization state", () => {
|
|
const root = makeRepo();
|
|
writeFile(
|
|
root,
|
|
".gitmodules",
|
|
[
|
|
'[submodule "vendor/checked-out"]',
|
|
"\t# a comment line that must be ignored",
|
|
"\tpath = vendor/checked-out",
|
|
"\turl = https://example.com/a.git",
|
|
"\tbranch = main",
|
|
"",
|
|
'[submodule "vendor/placeholder"]',
|
|
"\tpath = vendor/placeholder",
|
|
"\turl = https://example.com/b.git",
|
|
"",
|
|
].join("\n"),
|
|
);
|
|
writeFile(root, "vendor/checked-out/.git", "gitdir: ../../.git/modules/x");
|
|
fs.mkdirSync(path.join(root, "vendor/placeholder"), { recursive: true });
|
|
|
|
expect(listSubmodules({ repoRoot: root })).toEqual([
|
|
{
|
|
path: "vendor/checked-out",
|
|
url: "https://example.com/a.git",
|
|
branch: "main",
|
|
initialized: true,
|
|
},
|
|
{
|
|
path: "vendor/placeholder",
|
|
url: "https://example.com/b.git",
|
|
branch: undefined,
|
|
initialized: false,
|
|
},
|
|
]);
|
|
});
|
|
|
|
test("returns an empty list when .gitmodules is absent", () => {
|
|
const root = makeRepo();
|
|
expect(listSubmodules({ repoRoot: root })).toEqual([]);
|
|
});
|
|
});
|
|
|
|
describe("live-repo parity", () => {
|
|
test("plugin-sql appears in listPackages with a matching dir", () => {
|
|
const pkg = listPackages({ repoRoot: REPO_ROOT }).find(
|
|
(p) => p.name === "@elizaos/plugin-sql",
|
|
);
|
|
expect(pkg).toBeDefined();
|
|
expect(pkg?.dir).toBe("plugins/plugin-sql");
|
|
});
|
|
|
|
test("packages/feed is excluded from listWorkspaceDirs", () => {
|
|
const dirs = listWorkspaceDirs({ repoRoot: REPO_ROOT });
|
|
expect(dirs).not.toContain("packages/feed");
|
|
});
|
|
|
|
test("no duplicate package names across the workspace", () => {
|
|
const names = listPackages({ repoRoot: REPO_ROOT })
|
|
.map((p) => p.name)
|
|
.filter((name): name is string => Boolean(name));
|
|
const seen = new Map<string, number>();
|
|
for (const name of names) seen.set(name, (seen.get(name) ?? 0) + 1);
|
|
const duplicates = [...seen.entries()].filter(([, count]) => count > 1);
|
|
expect(duplicates).toEqual([]);
|
|
});
|
|
|
|
test("listSubmodules entries match .gitmodules declarations", () => {
|
|
const gitmodules = fs.readFileSync(
|
|
path.join(REPO_ROOT, ".gitmodules"),
|
|
"utf8",
|
|
);
|
|
const declaredPaths = [...gitmodules.matchAll(/^\s*path\s*=\s*(.+)$/gm)].map(
|
|
(m) => m[1].trim(),
|
|
);
|
|
const libPaths = listSubmodules({ repoRoot: REPO_ROOT }).map((s) => s.path);
|
|
expect(libPaths.sort()).toEqual([...declaredPaths].sort());
|
|
});
|
|
});
|