Files
wehub-resource-sync 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
chore: import upstream snapshot with attribution
2026-07-13 12:43:05 +08:00

265 lines
8.9 KiB
JavaScript

/**
* Shared, zero-dependency discovery seam for the repo's Bun/Node workspaces and
* git submodules. This is the single source of truth the script layer reads to
* answer "which directories are workspaces?", "what package lives where?", and
* "which submodules does .gitmodules declare?" — replacing the ad-hoc glob
* walkers each script used to carry (the migration of those callers is #12333).
*
* The glob expander implements the npm/Bun `workspaces` semantics used by the
* root package.json: `*` matches a single path segment, `**` matches any number
* of segments, and a leading `!` pattern subtracts from earlier matches
* (exclude-wins, last-match-wins ordering). It is deliberately dependency-free —
* only node builtins plus reading package.json / .gitmodules — so any script or
* test can import it without pulling in the build graph.
*/
import {
existsSync,
readdirSync,
readFileSync,
statSync,
} from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
const DEFAULT_REPO_ROOT = path.resolve(
path.dirname(fileURLToPath(import.meta.url)),
"../../..",
);
function normalizePath(value) {
return value.split(path.sep).join("/");
}
function readJson(filePath) {
return JSON.parse(readFileSync(filePath, "utf8"));
}
function resolveRepoRoot(opts) {
return opts?.repoRoot ? path.resolve(opts.repoRoot) : DEFAULT_REPO_ROOT;
}
// Compile one workspace glob segment-pattern to a RegExp over the "/"-joined
// relative path. `**` spans segments (including zero), `*` stays within one.
function workspaceGlobToRegExp(glob) {
let pattern = "";
for (let i = 0; i < glob.length; i += 1) {
const char = glob[i];
// A `/**` suffix (or `/**/`) must match zero-or-more trailing segments,
// including none — so `packages/**` matches `packages` itself. Consume the
// preceding `/` with the `**` so the separator is optional too.
if (char === "/" && glob[i + 1] === "*" && glob[i + 2] === "*") {
if (glob[i + 3] === "/") {
pattern += "(?:/.*)?/";
i += 3;
} else {
pattern += "(?:/.*)?";
i += 2;
}
} else if (char === "*") {
if (glob[i + 1] === "*") {
pattern += ".*";
i += 1;
} else {
pattern += "[^/]*";
}
} else if (/[.+^${}()|[\]\\]/.test(char)) {
pattern += `\\${char}`;
} else {
pattern += char;
}
}
return new RegExp(`^${pattern}$`);
}
// Hidden and heavy build/vendor dir names that are never workspace members and
// never worth descending into. Bun/npm workspace resolution likewise ignores
// dotfile dirs, and these build outputs (e.g. a Next.js `.next/package.json`
// marker) are not real packages — so a `*` segment must skip them just as a
// `**` walk does, or a stray `.next` gets matched as a workspace.
const WALK_SKIP_DIRS = new Set([
".git",
".next",
".turbo",
"build",
"coverage",
"dist",
"node_modules",
"storybook-static",
]);
// A directory child is traversable/matchable as a workspace only if it is not
// hidden and not a build/vendor dir. Applied uniformly to `*` and `**` so both
// segment kinds agree on membership.
function isTraversableChild(name) {
return !name.startsWith(".") && !WALK_SKIP_DIRS.has(name);
}
// Walk the tree expanding one positive glob into concrete directories. A `*`
// segment enumerates children; a `**` segment matches this directory and every
// descendant directory; a literal segment descends by name.
function expandPositiveGlob(repoRoot, pattern) {
let dirs = [repoRoot];
const parts = pattern.split("/");
for (let i = 0; i < parts.length; i += 1) {
const part = parts[i];
const next = [];
for (const dir of dirs) {
if (part === "**") {
// Match zero-or-more segments: keep this dir and add all descendants.
for (const descendant of walkDirs(dir)) next.push(descendant);
continue;
}
if (part === "*") {
for (const entry of readDirEntries(dir)) {
if (entry.isDirectory() && isTraversableChild(entry.name)) {
next.push(path.join(dir, entry.name));
}
}
continue;
}
const candidate = path.join(dir, part);
try {
if (statSync(candidate).isDirectory()) next.push(candidate);
} catch {
// error-policy:J3 path does not exist — this glob branch yields nothing
}
}
dirs = next;
}
return dirs;
}
function readDirEntries(dir) {
try {
return readdirSync(dir, { withFileTypes: true });
} catch {
// error-policy:J3 unreadable dir contributes no members
return [];
}
}
// Depth-first directory list rooted at `start` (inclusive), skipping hidden and
// heavy build/vendor dirs so `**` expansion stays bounded and deterministic.
function walkDirs(start) {
const out = [start];
for (const entry of readDirEntries(start)) {
if (!entry.isDirectory()) continue;
if (!isTraversableChild(entry.name)) continue;
out.push(...walkDirs(path.join(start, entry.name)));
}
return out;
}
/**
* Expand a list of workspace glob patterns (npm/Bun `workspaces` semantics)
* into a deduped, sorted list of relative directory paths. Positive patterns
* add matches; a leading `!` pattern removes earlier matches (exclude-wins).
*/
export function expandWorkspaceGlobs(patterns, opts) {
const repoRoot = resolveRepoRoot(opts);
const matchers = patterns.map((glob) => {
const negated = glob.startsWith("!");
return {
negated,
regExp: workspaceGlobToRegExp(negated ? glob.slice(1) : glob),
};
});
function isMember(relativeDir) {
let member = false;
for (const { negated, regExp } of matchers) {
if (regExp.test(relativeDir)) member = !negated;
}
return member;
}
const dirs = new Set();
for (const glob of patterns) {
if (glob.startsWith("!")) continue;
for (const dir of expandPositiveGlob(repoRoot, glob)) {
const relativeDir = normalizePath(path.relative(repoRoot, dir));
if (!relativeDir || !isMember(relativeDir)) continue;
dirs.add(relativeDir);
}
}
return [...dirs].sort((a, b) => a.localeCompare(b));
}
/**
* List every workspace directory declared in the root package.json — expanded
* from the `workspaces` globs, negations applied, keeping only directories that
* actually contain a package.json.
*/
export function listWorkspaceDirs(opts) {
const repoRoot = resolveRepoRoot(opts);
const rootPackage = readJson(path.join(repoRoot, "package.json"));
const patterns = rootPackage.workspaces ?? [];
return expandWorkspaceGlobs(patterns, { repoRoot }).filter((relativeDir) =>
existsSync(path.join(repoRoot, relativeDir, "package.json")),
);
}
/**
* List every workspace as `{ name, dir, packageJson }`, where `name` is the
* package.json `name` field (may be undefined for a private, unnamed package)
* and `dir` is the workspace-relative directory.
*/
export function listPackages(opts) {
const repoRoot = resolveRepoRoot(opts);
return listWorkspaceDirs({ repoRoot }).map((dir) => {
const packageJson = readJson(path.join(repoRoot, dir, "package.json"));
return { name: packageJson.name, dir, packageJson };
});
}
// Minimal INI parser for .gitmodules: sections keyed by `[submodule "name"]`,
// with `path` / `url` / `branch` values. Indentation and comment lines (`#`,
// `;`) are ignored; unknown keys are dropped.
function parseGitmodules(text) {
const sections = [];
let current = null;
for (const rawLine of text.split("\n")) {
const line = rawLine.trim();
if (!line || line.startsWith("#") || line.startsWith(";")) continue;
const sectionMatch = line.match(/^\[submodule\s+"(.+)"\]$/);
if (sectionMatch) {
current = { section: sectionMatch[1] };
sections.push(current);
continue;
}
if (!current) continue;
const kvMatch = line.match(/^([A-Za-z][A-Za-z0-9_-]*)\s*=\s*(.*)$/);
if (!kvMatch) continue;
current[kvMatch[1]] = kvMatch[2].trim();
}
return sections;
}
/**
* Parse the root `.gitmodules` into `{ path, url, branch, initialized }` per
* submodule. `initialized` is true when the submodule's working tree is present
* on disk (a `.git` gitfile or non-empty checkout), false when only the gitlink
* placeholder exists. Returns an empty list when `.gitmodules` is absent.
*/
export function listSubmodules(opts) {
const repoRoot = resolveRepoRoot(opts);
const gitmodulesPath = path.join(repoRoot, ".gitmodules");
if (!existsSync(gitmodulesPath)) return [];
const sections = parseGitmodules(readFileSync(gitmodulesPath, "utf8"));
return sections
.filter((section) => typeof section.path === "string")
.map((section) => ({
path: section.path,
url: section.url,
branch: section.branch,
initialized: isSubmoduleInitialized(path.join(repoRoot, section.path)),
}));
}
function isSubmoduleInitialized(absPath) {
if (existsSync(path.join(absPath, ".git"))) return true;
const entries = readDirEntries(absPath);
return entries.length > 0;
}