mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-15 05:36:10 +00:00
* chore(types): add declaration files for scripts/lib and scripts/e2e modules
* chore(types): add declaration files for top-level script modules (a-m)
* chore(types): add declaration files for top-level script modules (n-z)
* test: use a non-secret-shaped gateway token fixture
* test: type ci workflow guard helpers for the root test lane
* chore(tooling): typecheck root test/** with a dedicated tsgo lane
- test/tsconfig/tsconfig.test.root.json: root-test program (strict unused checks,
fixtures excluded; two Docker E2E clients that import built dist/** stay out,
same rationale as the scripts/e2e exclusion in tsconfig.scripts.json)
- tsgo:test:root wired into tsgo:test, check:test-types, scripts/check.mjs, and
the ci.yml test-types shard, mirroring the tsgo:scripts lane (#104348)
- changed-lane routing: test/**/*.ts (excluding fixtures) and the lane tsconfig
now trigger 'typecheck test root' in check:changed; previously test/ paths ran
lint only, so harness type errors surfaced first in CI (#104287 envDir case)
- burn down all 1071 latent type errors in the program: precise param/local
types across test/scripts, test/vitest, test/e2e, and transitive scripts/e2e
program members; 205 sibling .d.mts declaration files for imported .mjs
modules (committed separately); zero any, zero ts-expect-error
- resolve the pre-existing testing star-export ambiguity in
scripts/e2e/parallels/common.ts with an explicit re-export
Closes #104388
* chore(types): correct declaration fidelity per structured review
- re-derive 51 .d.mts files from implementation data flow instead of
initializers: fix a wrong never return (runTestProjectsDelegation returns
the child), add encoding-sensitive exec/spawn overloads (plain-gh), restore
the full release profile union, make parsed paths string | null, add missing
parseArgs fields via help/non-help unions, add a missing sibling declaration
(budget-number-args), drop 15 unused lint directives
- precise install-record/tuple typing removes the type-aware oxlint
regressions the first declarations caused in scripts/e2e implementations
- route .mts declaration edits under test/ to the testRoot lane and reference
the test-root project from tsconfig.projects.json so tsgo:all covers it
(closes both review findings against the lane wiring)
* chore(scripts): keep telegram runner dist typing structural for the boundary guard
* chore(types): declare runtime pack and gateway readiness exports added on main
* test: pin the importTargetPlan form of the plugin-contract plan import
The guard expectation still referenced the raw await import( form that
7ae5996bb3 (#103975) replaced with the importTargetPlan fallback helper;
the assertion fails on current main.
188 lines
6.5 KiB
TypeScript
188 lines
6.5 KiB
TypeScript
// Telegram package Docker harness.
|
|
// Runs QA live transport code against the package candidate installed in Docker.
|
|
|
|
import { randomUUID } from "node:crypto";
|
|
import fs from "node:fs/promises";
|
|
import path from "node:path";
|
|
import { pathToFileURL } from "node:url";
|
|
import type { QaProviderMode } from "../../extensions/qa-lab/src/run-config.ts";
|
|
|
|
function parseBoolean(value: string | undefined) {
|
|
const normalized = value?.trim().toLowerCase();
|
|
return normalized === "1" || normalized === "true" || normalized === "yes";
|
|
}
|
|
|
|
function splitCsv(value: string | undefined) {
|
|
return (value ?? "")
|
|
.split(",")
|
|
.map((entry) => entry.trim())
|
|
.filter((entry) => entry.length > 0);
|
|
}
|
|
|
|
function parsePositiveIntegerEnv(env: NodeJS.ProcessEnv, name: string) {
|
|
const raw = env[name]?.trim();
|
|
if (!raw) {
|
|
return undefined;
|
|
}
|
|
if (!/^\d+$/u.test(raw)) {
|
|
throw new Error(`invalid ${name}: ${raw}`);
|
|
}
|
|
const value = Number(raw);
|
|
if (!Number.isSafeInteger(value) || value <= 0) {
|
|
throw new Error(`invalid ${name}: ${raw}`);
|
|
}
|
|
return value;
|
|
}
|
|
|
|
function resolveCredentialSource(env: NodeJS.ProcessEnv) {
|
|
return env.OPENCLAW_NPM_TELEGRAM_CREDENTIAL_SOURCE ?? env.OPENCLAW_QA_CREDENTIAL_SOURCE;
|
|
}
|
|
|
|
function resolveCredentialRole(env: NodeJS.ProcessEnv) {
|
|
return env.OPENCLAW_NPM_TELEGRAM_CREDENTIAL_ROLE ?? env.OPENCLAW_QA_CREDENTIAL_ROLE;
|
|
}
|
|
|
|
function createRunId() {
|
|
return `${Date.now().toString(36)}-${randomUUID().slice(0, 8)}`;
|
|
}
|
|
|
|
function resolvePackageTelegramOutputDir(env: NodeJS.ProcessEnv, repoRoot: string) {
|
|
return (
|
|
env.OPENCLAW_NPM_TELEGRAM_OUTPUT_DIR?.trim() ||
|
|
path.join(repoRoot, ".artifacts", "qa-e2e", `npm-telegram-live-${createRunId()}`)
|
|
);
|
|
}
|
|
|
|
const DEFAULT_RTT_CHECK_ID = "telegram-mentioned-message-reply";
|
|
|
|
function resolveRttOptions(env: NodeJS.ProcessEnv, selectedScenarioIds: readonly string[] = []) {
|
|
const explicitCheckIds = splitCsv(env.OPENCLAW_NPM_TELEGRAM_RTT_CHECKS);
|
|
if (
|
|
explicitCheckIds.length === 0 &&
|
|
selectedScenarioIds.length > 0 &&
|
|
!selectedScenarioIds.includes(DEFAULT_RTT_CHECK_ID)
|
|
) {
|
|
return {};
|
|
}
|
|
const rttCount = parsePositiveIntegerEnv(env, "OPENCLAW_NPM_TELEGRAM_RTT_SAMPLES") ?? 20;
|
|
return {
|
|
rttCount,
|
|
rttTimeoutMs: parsePositiveIntegerEnv(env, "OPENCLAW_NPM_TELEGRAM_RTT_TIMEOUT_MS"),
|
|
maxRttFailures:
|
|
parsePositiveIntegerEnv(env, "OPENCLAW_NPM_TELEGRAM_RTT_MAX_FAILURES") ?? rttCount,
|
|
rttCheckIds: explicitCheckIds,
|
|
};
|
|
}
|
|
|
|
async function shouldFailPackageTelegramRun(
|
|
result: { summaryPath: string },
|
|
env: NodeJS.ProcessEnv = process.env,
|
|
) {
|
|
if (parseBoolean(env.OPENCLAW_NPM_TELEGRAM_ALLOW_FAILURES)) {
|
|
return false;
|
|
}
|
|
const { readQaSuiteFailedScenarioCountFromFile } =
|
|
await import("../../extensions/qa-lab/src/suite-summary.ts");
|
|
return (await readQaSuiteFailedScenarioCountFromFile(result.summaryPath)) > 0;
|
|
}
|
|
|
|
async function resolveTrustedOpenClawCommand(
|
|
rawCommand: string,
|
|
env: NodeJS.ProcessEnv = process.env,
|
|
) {
|
|
if (!path.isAbsolute(rawCommand)) {
|
|
throw new Error("OPENCLAW_NPM_TELEGRAM_SUT_COMMAND must be an absolute path.");
|
|
}
|
|
const commandName = path.basename(rawCommand);
|
|
if (commandName !== "openclaw" && commandName !== "openclaw.cmd") {
|
|
throw new Error(
|
|
`OPENCLAW_NPM_TELEGRAM_SUT_COMMAND must point to openclaw; got: ${commandName}`,
|
|
);
|
|
}
|
|
const npmPrefix = env.NPM_CONFIG_PREFIX?.trim();
|
|
if (!npmPrefix) {
|
|
throw new Error("Missing NPM_CONFIG_PREFIX for installed openclaw command validation.");
|
|
}
|
|
const [realCommand, realPrefix] = await Promise.all([
|
|
fs.realpath(rawCommand),
|
|
fs.realpath(npmPrefix),
|
|
]);
|
|
if (realCommand !== realPrefix && !realCommand.startsWith(`${realPrefix}${path.sep}`)) {
|
|
throw new Error("OPENCLAW_NPM_TELEGRAM_SUT_COMMAND must resolve inside NPM_CONFIG_PREFIX.");
|
|
}
|
|
return {
|
|
executablePath: rawCommand,
|
|
usePackagedPlugins: true,
|
|
} as const;
|
|
}
|
|
|
|
async function main() {
|
|
const { runTelegramQaLive } =
|
|
await import("../../extensions/qa-lab/src/live-transports/telegram/telegram-live.runtime.ts");
|
|
const rawSutOpenClawCommand = process.env.OPENCLAW_NPM_TELEGRAM_SUT_COMMAND?.trim();
|
|
if (!rawSutOpenClawCommand) {
|
|
throw new Error("Missing OPENCLAW_NPM_TELEGRAM_SUT_COMMAND.");
|
|
}
|
|
const sutOpenClawCommand = await resolveTrustedOpenClawCommand(rawSutOpenClawCommand);
|
|
|
|
const repoRoot = path.resolve(process.env.OPENCLAW_NPM_TELEGRAM_REPO_ROOT ?? process.cwd());
|
|
const outputDir = resolvePackageTelegramOutputDir(process.env, repoRoot);
|
|
const scenarioIds = splitCsv(process.env.OPENCLAW_NPM_TELEGRAM_SCENARIOS);
|
|
const result = await runTelegramQaLive({
|
|
env: process.env,
|
|
repoRoot,
|
|
outputDir,
|
|
sutOpenClawCommand,
|
|
providerMode: process.env.OPENCLAW_NPM_TELEGRAM_PROVIDER_MODE as QaProviderMode | undefined,
|
|
primaryModel: process.env.OPENCLAW_NPM_TELEGRAM_MODEL,
|
|
alternateModel: process.env.OPENCLAW_NPM_TELEGRAM_ALT_MODEL,
|
|
fastMode: parseBoolean(process.env.OPENCLAW_NPM_TELEGRAM_FAST),
|
|
scenarioIds,
|
|
...resolveRttOptions(process.env, scenarioIds),
|
|
sutAccountId: process.env.OPENCLAW_NPM_TELEGRAM_SUT_ACCOUNT,
|
|
credentialSource: resolveCredentialSource(process.env),
|
|
credentialRole: resolveCredentialRole(process.env),
|
|
});
|
|
|
|
process.stdout.write(`Package Telegram QA report: ${result.reportPath}\n`);
|
|
process.stdout.write(`Package Telegram QA summary: ${result.summaryPath}\n`);
|
|
if (await shouldFailPackageTelegramRun(result)) {
|
|
process.exitCode = 1;
|
|
}
|
|
}
|
|
|
|
async function formatRunnerErrorMessage(error: unknown) {
|
|
try {
|
|
// Widen the specifier so the source-only test-root program does not try to
|
|
// resolve dist (TS2307); the docker-e2e boundary guard requires importing
|
|
// built dist here, so the cast stays structural instead of a src reference.
|
|
const distErrorsPath = "../../dist/infra/errors.js" as string;
|
|
const { formatErrorMessage } = (await import(distErrorsPath)) as {
|
|
formatErrorMessage: (err: unknown) => string;
|
|
};
|
|
return formatErrorMessage(error);
|
|
} catch {
|
|
return error instanceof Error ? error.message : String(error);
|
|
}
|
|
}
|
|
|
|
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
|
|
main().catch(async (error: unknown) => {
|
|
process.stderr.write(
|
|
`package telegram live e2e failed: ${await formatRunnerErrorMessage(error)}\n`,
|
|
);
|
|
process.exitCode = 1;
|
|
});
|
|
}
|
|
|
|
export const testing = {
|
|
parsePositiveIntegerEnv,
|
|
resolvePackageTelegramOutputDir,
|
|
resolveCredentialRole,
|
|
resolveCredentialSource,
|
|
resolveRttOptions,
|
|
resolveTrustedOpenClawCommand,
|
|
shouldFailPackageTelegramRun,
|
|
};
|
|
export { testing as __testing };
|