Some checks failed
ClawSweeper Dispatch / dispatch (push) Has been cancelled
CodeQL / Security High (actions) (push) Has been cancelled
CodeQL / Security High (channel-runtime-boundary) (push) Has been cancelled
CodeQL / Security High (core-auth-secrets) (push) Has been cancelled
CodeQL / Security High (mcp-process-tool-boundary) (push) Has been cancelled
CodeQL / Security High (network-ssrf-boundary) (push) Has been cancelled
CodeQL / Security High (plugin-trust-boundary) (push) Has been cancelled
CodeQL / Security High (process-exec-boundary) (push) Has been cancelled
Docs Sync Publish Repo / sync-publish-repo (push) Has been cancelled
Docs / docs (push) Has been cancelled
OpenClaw Stable Main Closeout / Resolve stable release closeout inputs (push) Has been cancelled
OpenClaw Stable Main Closeout / Verify stable main closeout (push) Has been cancelled
Workflow Sanity / no-tabs (push) Has been cancelled
Workflow Sanity / actionlint (push) Has been cancelled
Workflow Sanity / generated-doc-baselines (push) Has been cancelled
CI / runner-admission (push) Has been cancelled
CI / preflight (push) Has been cancelled
CI / security-fast (push) Has been cancelled
CI / pnpm-store-warmup (push) Has been cancelled
CI / build-artifacts (push) Has been cancelled
CI / native-i18n (push) Has been cancelled
CI / ${{ matrix.check_name }} (push) Has been cancelled
CI / ${{ matrix.checkName }} (push) Has been cancelled
CI / checks-node-compat-node22 (push) Has been cancelled
CI / check-bundled-channel-config-metadata (push) Has been cancelled
CI / check-dependencies (push) Has been cancelled
CI / check-guards (push) Has been cancelled
CI / check-lint (push) Has been cancelled
CI / check-prod-types (push) Has been cancelled
CI / check-shrinkwrap (push) Has been cancelled
CI / check-test-types (push) Has been cancelled
CI / check-additional-boundaries-a (push) Has been cancelled
CI / check-additional-boundaries-bcd (push) Has been cancelled
CI / check-additional-extension-bundled (push) Has been cancelled
CI / check-additional-extension-channels (push) Has been cancelled
CI / check-additional-extension-package-boundary (push) Has been cancelled
CI / check-additional-runtime-topology-architecture (push) Has been cancelled
CI / check-session-accessor-boundary (push) Has been cancelled
CI / check-session-transcript-reader-boundary (push) Has been cancelled
CI / check-docs (push) Has been cancelled
CI / skills-python (push) Has been cancelled
CI / macos-swift (push) Has been cancelled
CI / ios-build (push) Has been cancelled
CI / ci-timings-summary (push) Has been cancelled
Native App Locale Refresh / Refresh native fa (push) Has been cancelled
Native App Locale Refresh / Refresh native fr (push) Has been cancelled
Native App Locale Refresh / Refresh native hi (push) Has been cancelled
Native App Locale Refresh / Refresh native id (push) Has been cancelled
Native App Locale Refresh / Refresh native it (push) Has been cancelled
Native App Locale Refresh / Refresh native ja-JP (push) Has been cancelled
Control UI Locale Refresh / plan (push) Has been cancelled
Control UI Locale Refresh / Refresh ${{ matrix.locale }} (push) Has been cancelled
Control UI Locale Refresh / Commit control UI locale refresh (push) Has been cancelled
Live Media Runner Image / Build live media runner image (push) Has been cancelled
Native App Locale Refresh / Refresh native ar (push) Has been cancelled
Native App Locale Refresh / Refresh native de (push) Has been cancelled
Native App Locale Refresh / Refresh native es (push) Has been cancelled
Native App Locale Refresh / Refresh native ko (push) Has been cancelled
Native App Locale Refresh / Refresh native nl (push) Has been cancelled
Native App Locale Refresh / Refresh native pl (push) Has been cancelled
Native App Locale Refresh / Refresh native pt-BR (push) Has been cancelled
Native App Locale Refresh / Refresh native ru (push) Has been cancelled
Native App Locale Refresh / Refresh native sv (push) Has been cancelled
Native App Locale Refresh / Refresh native th (push) Has been cancelled
Native App Locale Refresh / Refresh native tr (push) Has been cancelled
Native App Locale Refresh / Refresh native uk (push) Has been cancelled
Native App Locale Refresh / Refresh native vi (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-CN (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-TW (push) Has been cancelled
Native App Locale Refresh / Commit native locale refresh (push) Has been cancelled
Plugin Init Scaffold Validation / Validate provider scaffold (push) Has been cancelled
Plugin NPM Release / preview_plugins_npm (push) Has been cancelled
Plugin NPM Release / Validate release publish approval (push) Has been cancelled
Plugin NPM Release / preview_plugin_pack (push) Has been cancelled
Plugin NPM Release / publish_plugins_npm (push) Has been cancelled
Sandbox Common Smoke / sandbox-common-smoke (push) Has been cancelled
Website Installer Sync / static (push) Has been cancelled
Website Installer Sync / linux-docker (push) Has been cancelled
Website Installer Sync / macos-installer (push) Has been cancelled
Website Installer Sync / windows-installer (push) Has been cancelled
Website Installer Sync / sync-website (push) Has been cancelled
Adolf is a fork/vendored clone of github.com/openclaw/openclaw (v2026.6.11), free to diverge. Tree copied sans upstream .git; upstream remote added for future syncs. Node pinned to 24 (.nvmrc); engines already require >=22.19. Preserves docs/ARCHITECTURE.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LeqyaxJF2nbRXJtae2kNB2
277 lines
8.2 KiB
JavaScript
277 lines
8.2 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
// Finds hidden local runtime sidecar loaders missing tsdown entries.
|
|
import { promises as fs } from "node:fs";
|
|
import path from "node:path";
|
|
import ts from "typescript";
|
|
import {
|
|
collectTypeScriptFilesFromRoots,
|
|
resolveRepoRoot,
|
|
runAsScript,
|
|
toLine,
|
|
unwrapExpression,
|
|
} from "./lib/ts-guard-utils.mjs";
|
|
|
|
const repoRoot = resolveRepoRoot(import.meta.url);
|
|
const defaultSourceRoots = [path.join(repoRoot, "src"), path.join(repoRoot, "extensions")];
|
|
const localRuntimeSpecifierPattern = /^\.{1,2}\/.*\.runtime\.(?:js|ts)$/;
|
|
|
|
function toPosixPath(value) {
|
|
return value.split(path.sep).join("/");
|
|
}
|
|
|
|
function normalizeRelativePath(value) {
|
|
return path.posix.normalize(toPosixPath(value).replace(/^\.\//, ""));
|
|
}
|
|
|
|
function unwrapInitializer(expression) {
|
|
let current = unwrapExpression(expression);
|
|
while (ts.isSatisfiesExpression(current)) {
|
|
current = unwrapExpression(current.expression);
|
|
}
|
|
return current;
|
|
}
|
|
|
|
function readStringLiteral(node) {
|
|
if (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node)) {
|
|
return node.text;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
function readArrayStrings(node) {
|
|
const expression = unwrapInitializer(node);
|
|
if (!ts.isArrayLiteralExpression(expression)) {
|
|
return null;
|
|
}
|
|
const values = [];
|
|
for (const element of expression.elements) {
|
|
const value = readStringLiteral(unwrapInitializer(element));
|
|
if (value === null) {
|
|
return null;
|
|
}
|
|
values.push(value);
|
|
}
|
|
return values;
|
|
}
|
|
|
|
function isCreateRequireCall(node, createRequireNames) {
|
|
return (
|
|
ts.isCallExpression(node) &&
|
|
ts.isIdentifier(node.expression) &&
|
|
createRequireNames.has(node.expression.text)
|
|
);
|
|
}
|
|
|
|
function isLocalRuntimeSpecifier(specifier) {
|
|
return localRuntimeSpecifierPattern.test(specifier);
|
|
}
|
|
|
|
function resolveRuntimeSpecifierSource(importerPath, specifier) {
|
|
const importerDir = path.posix.dirname(normalizeRelativePath(importerPath));
|
|
const resolved = path.posix.normalize(path.posix.join(importerDir, specifier));
|
|
return resolved.replace(/\.js$/, ".ts");
|
|
}
|
|
|
|
function readObjectEntrySources(entry) {
|
|
if (!entry || Array.isArray(entry) || typeof entry !== "object") {
|
|
return [];
|
|
}
|
|
return Object.values(entry).filter((value) => typeof value === "string");
|
|
}
|
|
|
|
/**
|
|
* Collects explicit source entry files from tsdown configuration.
|
|
*/
|
|
export function collectTsdownEntrySources(config) {
|
|
const configs = Array.isArray(config) ? config : [config];
|
|
return new Set(
|
|
configs.flatMap((entry) => readObjectEntrySources(entry?.entry)).map(normalizeRelativePath),
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Finds local runtime require loaders not represented as explicit tsdown entries.
|
|
*/
|
|
export function findRuntimeSidecarLoaderViolations(content, importerPath, explicitEntrySources) {
|
|
const sourceFile = ts.createSourceFile(importerPath, content, ts.ScriptTarget.Latest, true);
|
|
const createRequireNames = new Set();
|
|
const requireNames = new Set();
|
|
const stringConstants = new Map();
|
|
const stringArrays = new Map();
|
|
const forOfRuntimeValues = [];
|
|
const violations = [];
|
|
const seen = new Set();
|
|
|
|
const currentForOfValueMap = () => {
|
|
const merged = new Map();
|
|
for (const scope of forOfRuntimeValues) {
|
|
for (const [name, values] of scope) {
|
|
merged.set(name, values);
|
|
}
|
|
}
|
|
return merged;
|
|
};
|
|
|
|
const addSpecifier = (specifier, node) => {
|
|
if (!isLocalRuntimeSpecifier(specifier)) {
|
|
return;
|
|
}
|
|
const sourcePath = resolveRuntimeSpecifierSource(importerPath, specifier);
|
|
if (explicitEntrySources.has(sourcePath)) {
|
|
return;
|
|
}
|
|
const key = `${sourcePath}:${toLine(sourceFile, node)}`;
|
|
if (seen.has(key)) {
|
|
return;
|
|
}
|
|
seen.add(key);
|
|
violations.push({
|
|
line: toLine(sourceFile, node),
|
|
specifier,
|
|
sourcePath,
|
|
reason:
|
|
`hidden local runtime loader "${specifier}" resolves to ${sourcePath}, ` +
|
|
"but that source is not an explicit tsdown entry",
|
|
});
|
|
};
|
|
|
|
const readRequireArgumentSpecifiers = (node) => {
|
|
const arg = node.arguments[0];
|
|
if (!arg) {
|
|
return [];
|
|
}
|
|
const unwrapped = unwrapInitializer(arg);
|
|
const literal = readStringLiteral(unwrapped);
|
|
if (literal !== null) {
|
|
return [literal];
|
|
}
|
|
if (ts.isIdentifier(unwrapped)) {
|
|
const loopValues = currentForOfValueMap().get(unwrapped.text);
|
|
if (loopValues) {
|
|
return loopValues;
|
|
}
|
|
const constant = stringConstants.get(unwrapped.text);
|
|
if (constant !== undefined) {
|
|
return [constant];
|
|
}
|
|
}
|
|
return [];
|
|
};
|
|
|
|
const visit = (node) => {
|
|
if (ts.isImportDeclaration(node) && ts.isStringLiteral(node.moduleSpecifier)) {
|
|
if (node.moduleSpecifier.text === "node:module") {
|
|
const bindings = node.importClause?.namedBindings;
|
|
if (bindings && ts.isNamedImports(bindings)) {
|
|
for (const element of bindings.elements) {
|
|
if (
|
|
element.propertyName?.text === "createRequire" ||
|
|
element.name.text === "createRequire"
|
|
) {
|
|
createRequireNames.add(element.name.text);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && node.initializer) {
|
|
const initializer = unwrapInitializer(node.initializer);
|
|
const literal = readStringLiteral(initializer);
|
|
if (literal !== null) {
|
|
stringConstants.set(node.name.text, literal);
|
|
}
|
|
const arrayValues = readArrayStrings(initializer);
|
|
if (arrayValues) {
|
|
stringArrays.set(node.name.text, arrayValues);
|
|
}
|
|
if (isCreateRequireCall(initializer, createRequireNames)) {
|
|
requireNames.add(node.name.text);
|
|
}
|
|
}
|
|
|
|
if (ts.isForOfStatement(node)) {
|
|
const initializer = node.initializer;
|
|
const expression = unwrapInitializer(node.expression);
|
|
if (
|
|
ts.isVariableDeclarationList(initializer) &&
|
|
initializer.declarations.length === 1 &&
|
|
ts.isIdentifier(initializer.declarations[0].name) &&
|
|
ts.isIdentifier(expression)
|
|
) {
|
|
const values = stringArrays.get(expression.text);
|
|
if (values) {
|
|
forOfRuntimeValues.push(new Map([[initializer.declarations[0].name.text, values]]));
|
|
ts.forEachChild(node.statement, visit);
|
|
forOfRuntimeValues.pop();
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (
|
|
ts.isCallExpression(node) &&
|
|
ts.isIdentifier(node.expression) &&
|
|
requireNames.has(node.expression.text)
|
|
) {
|
|
for (const specifier of readRequireArgumentSpecifiers(node)) {
|
|
addSpecifier(specifier, node);
|
|
}
|
|
}
|
|
|
|
ts.forEachChild(node, visit);
|
|
};
|
|
|
|
visit(sourceFile);
|
|
return violations;
|
|
}
|
|
|
|
/**
|
|
* Collects runtime sidecar loader violations across configured roots.
|
|
*/
|
|
export async function collectRuntimeSidecarLoaderViolations(params) {
|
|
const files = await collectTypeScriptFilesFromRoots(params.sourceRoots, {
|
|
extraTestSuffixes: [".test-support.ts", ".test-helpers.ts"],
|
|
});
|
|
const violations = [];
|
|
for (const filePath of files) {
|
|
if (filePath.endsWith(".d.ts")) {
|
|
continue;
|
|
}
|
|
const relativePath = normalizeRelativePath(path.relative(params.repoRoot, filePath));
|
|
const content = await fs.readFile(filePath, "utf8");
|
|
for (const violation of findRuntimeSidecarLoaderViolations(
|
|
content,
|
|
relativePath,
|
|
params.explicitEntrySources,
|
|
)) {
|
|
violations.push({ path: relativePath, ...violation });
|
|
}
|
|
}
|
|
return violations;
|
|
}
|
|
|
|
async function main() {
|
|
const { default: tsdownConfig } = await import("../tsdown.config.ts");
|
|
const violations = await collectRuntimeSidecarLoaderViolations({
|
|
repoRoot,
|
|
sourceRoots: defaultSourceRoots,
|
|
explicitEntrySources: collectTsdownEntrySources(tsdownConfig),
|
|
});
|
|
if (violations.length === 0) {
|
|
console.log("runtime-sidecar-loaders: local runtime sidecar loaders look OK.");
|
|
return;
|
|
}
|
|
console.error("runtime-sidecar-loaders: hidden local runtime loaders found:");
|
|
for (const violation of violations) {
|
|
console.error(
|
|
`- ${violation.path}:${violation.line}: ${violation.reason}. ` +
|
|
'Use cached import("./x.runtime.js") or add the sidecar as a stable tsdown entry.',
|
|
);
|
|
}
|
|
process.exitCode = 1;
|
|
}
|
|
|
|
runAsScript(import.meta.url, main);
|