diff --git a/scripts/proxy-install-ca.mjs b/scripts/proxy-install-ca.mjs index 4f11d25771f7..307284cdb574 100644 --- a/scripts/proxy-install-ca.mjs +++ b/scripts/proxy-install-ca.mjs @@ -1,4 +1,5 @@ #!/usr/bin/env node +// Creates the debug proxy CA and, on macOS, trusts it in the system keychain. import { spawnSync } from "node:child_process"; import process from "node:process"; import { resolveSystemBin } from "../src/infra/resolve-system-bin.js"; diff --git a/scripts/prune-docker-plugin-dist.mjs b/scripts/prune-docker-plugin-dist.mjs index 8b622d6bab06..87573924f5c1 100644 --- a/scripts/prune-docker-plugin-dist.mjs +++ b/scripts/prune-docker-plugin-dist.mjs @@ -1,3 +1,5 @@ +// Prunes omitted bundled plugin files and their unshared runtime dependencies +// from Docker-oriented production package output. import fs from "node:fs"; import path from "node:path"; import { pathToFileURL } from "node:url"; @@ -18,6 +20,9 @@ function parsePluginList(value) { ); } +/** + * Parses OPENCLAW_EXTENSIONS into the bundled plugin ids that Docker should keep. + */ export function parseDockerPluginKeepList(value) { return parsePluginList(value); } @@ -67,7 +72,9 @@ function collectPackageRuntimeClosure(repoRoot, seedPackageNames) { } seen.add(packageName); - const packageJson = readPackageJson(path.join(nodeModulePath(repoRoot, packageName), "package.json")); + const packageJson = readPackageJson( + path.join(nodeModulePath(repoRoot, packageName), "package.json"), + ); for (const dependencyName of collectRuntimeDependencyNames(packageJson)) { if (!seen.has(dependencyName)) { stack.push(dependencyName); @@ -106,7 +113,9 @@ function pruneNodeModulesForOmittedPlugins(repoRoot, bundledPluginDir, omittedPl const omittedSeeds = new Set(); for (const pluginId of omittedPluginIds) { - const packageJson = readPackageJson(path.join(repoRoot, bundledPluginDir, pluginId, "package.json")); + const packageJson = readPackageJson( + path.join(repoRoot, bundledPluginDir, pluginId, "package.json"), + ); if (typeof packageJson?.name === "string") { omittedPackageNames.add(packageJson.name); } @@ -116,7 +125,11 @@ function pruneNodeModulesForOmittedPlugins(repoRoot, bundledPluginDir, omittedPl } const keptSeeds = new Set(collectRuntimeDependencyNames(rootPackageJson)); - for (const dependencyName of collectWorkspacePackageRuntimeSeeds(repoRoot, "packages", new Set())) { + for (const dependencyName of collectWorkspacePackageRuntimeSeeds( + repoRoot, + "packages", + new Set(), + )) { keptSeeds.add(dependencyName); } for (const dependencyName of collectWorkspacePackageRuntimeSeeds( @@ -150,18 +163,25 @@ function pruneNodeModulesForOmittedPlugins(repoRoot, bundledPluginDir, omittedPl return removed; } +/** + * Removes omitted plugin dist trees plus node_modules packages not needed by kept runtime code. + */ export function pruneDockerPluginDist(params = {}) { const repoRoot = params.cwd ?? params.repoRoot ?? process.cwd(); const env = params.env ?? process.env; const bundledPluginDir = env.OPENCLAW_BUNDLED_PLUGIN_DIR ?? "extensions"; const keepPluginIds = parseDockerPluginKeepList(env.OPENCLAW_EXTENSIONS); const excludedPluginIds = collectRootPackageExcludedExtensionDirs({ cwd: repoRoot }); - const omittedPluginIds = new Set([...excludedPluginIds].filter((pluginId) => !keepPluginIds.has(pluginId))); + const omittedPluginIds = new Set( + [...excludedPluginIds].filter((pluginId) => !keepPluginIds.has(pluginId)), + ); const removed = []; removed.push(...pruneNodeModulesForOmittedPlugins(repoRoot, bundledPluginDir, omittedPluginIds)); - for (const pluginId of [...omittedPluginIds].toSorted((left, right) => left.localeCompare(right))) { + for (const pluginId of [...omittedPluginIds].toSorted((left, right) => + left.localeCompare(right), + )) { for (const pluginPath of [ path.join(bundledPluginDir, pluginId), path.join("dist", "extensions", pluginId), diff --git a/scripts/release-candidate-checklist.mjs b/scripts/release-candidate-checklist.mjs index d38066bcb8d5..6f0903a28ddb 100644 --- a/scripts/release-candidate-checklist.mjs +++ b/scripts/release-candidate-checklist.mjs @@ -1,4 +1,6 @@ #!/usr/bin/env node +// Coordinates release-candidate validation runs and emits the publish command +// only after required local, CI, npm, plugin, and E2E evidence is green. import { spawnSync } from "node:child_process"; import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs"; import { basename, join } from "node:path"; @@ -50,6 +52,9 @@ function requireValue(argv, index, flag) { return value; } +/** + * Parses release-candidate validation options and enforces publish-scope policy. + */ export function parseArgs(argv) { const args = stripLeadingPackageManagerSeparator(argv); const options = { @@ -201,6 +206,9 @@ function githubApiTimedOut(error) { ); } +/** + * Calls the GitHub REST API with the gh-auth token and a bounded timeout. + */ export async function githubApi(path, options = {}) { const token = options.token ?? run("gh", ["auth", "token"], { capture: true }).trim(); const timeoutMs = options.timeoutMs ?? githubApiTimeoutMs(); @@ -254,6 +262,9 @@ async function runArtifacts(repo, runId) { })); } +/** + * Chooses the expected artifact name, allowing one same-prefix fallback per run. + */ export function resolveArtifactName(artifacts, preferredName, prefix) { const available = artifacts .filter((artifact) => artifact.expired !== true) @@ -312,6 +323,9 @@ function runLocalGeneratedCheckIfNeeded(options) { return { status: "passed", command: "pnpm release:generated:check" }; } +/** + * Extracts a GitHub Actions run id from gh workflow dispatch output. + */ export function parseRunIdFromDispatchOutput(output) { return output.match(/actions\/runs\/([0-9]+)/u)?.[1] ?? ""; } @@ -498,6 +512,9 @@ function shellQuote(value) { return `'${String(value).replace(/'/gu, "'\\''")}'`; } +/** + * Builds the final release publish workflow command once validation evidence is ready. + */ export function buildPublishCommand(options) { const fields = [ ["tag", options.tag], diff --git a/scripts/release-preflight.mjs b/scripts/release-preflight.mjs index f55d241eb842..d1dbf86e44ba 100644 --- a/scripts/release-preflight.mjs +++ b/scripts/release-preflight.mjs @@ -1,4 +1,5 @@ #!/usr/bin/env node +// Checks or refreshes generated release artifacts before a release publish. import { runManagedCommand } from "./lib/managed-child-process.mjs"; const args = new Set(process.argv.slice(2)); diff --git a/scripts/resolve-upgrade-survivor-baselines.mjs b/scripts/resolve-upgrade-survivor-baselines.mjs index 47ed92c616ae..5636e694ed82 100644 --- a/scripts/resolve-upgrade-survivor-baselines.mjs +++ b/scripts/resolve-upgrade-survivor-baselines.mjs @@ -1,3 +1,5 @@ +// Resolves Docker upgrade-survivor baseline specs from requested tokens and +// live release history JSON captured by release workflows. import { readFileSync, writeFileSync } from "node:fs"; import { fileURLToPath } from "node:url"; import { normalizeUpgradeSurvivorBaselineSpec } from "./lib/docker-e2e-plan.mjs"; @@ -101,6 +103,9 @@ function readStableReleases(file, publishedVersions) { .toSorted((a, b) => String(b.publishedAt).localeCompare(String(a.publishedAt))); } +/** + * Expands the release-history token into recent stable plus pinned historical baselines. + */ export function resolveReleaseHistory(args) { const releasesJson = args.get("releases-json"); if (!releasesJson) { @@ -128,6 +133,9 @@ export function resolveReleaseHistory(args) { return dedupeSpecs(versions); } +/** + * Resolves the last N stable release versions from release metadata. + */ export function resolveLastStable(args, count) { const releasesJson = args.get("releases-json"); if (!releasesJson) { @@ -141,6 +149,9 @@ export function resolveLastStable(args, count) { return dedupeSpecs(releases.slice(0, count).map((release) => release.version)); } +/** + * Resolves all stable release versions at or after the requested minimum. + */ export function resolveAllSince(args, minimumVersion) { const releasesJson = args.get("releases-json"); if (!releasesJson) { @@ -155,6 +166,9 @@ export function resolveAllSince(args, minimumVersion) { ); } +/** + * Expands requested baseline tokens into normalized package/version specs. + */ export function resolveBaselines(args) { const requested = args.get("requested") ?? ""; const fallback = args.get("fallback") ?? "openclaw@latest"; diff --git a/scripts/root-dependency-ownership-audit.mjs b/scripts/root-dependency-ownership-audit.mjs index 01e2f4be44d6..0e6da3b40d50 100644 --- a/scripts/root-dependency-ownership-audit.mjs +++ b/scripts/root-dependency-ownership-audit.mjs @@ -1,5 +1,7 @@ #!/usr/bin/env node +// Audits root package runtime dependencies against source imports and bundled +// plugin ownership so extension-owned deps can move out of root. import fs from "node:fs"; import path from "node:path"; import { pathToFileURL } from "node:url"; @@ -79,6 +81,9 @@ function sectionFor(relativePath) { return section; } +/** + * Collects static and simple constant-backed package specifiers from source text. + */ export function collectModuleSpecifiers(source) { const specifiers = new Set(); for (const pattern of IMPORT_PATTERNS) { @@ -210,6 +215,9 @@ function sectionSetIsSubsetOf(sectionSet, allowed) { return sectionSet.size > 0; } +/** + * Classifies whether a root dependency is core-owned, shared, or extension-local. + */ export function classifyRootDependencyOwnership(record) { const sections = new Set(record.sections); @@ -276,6 +284,9 @@ export function classifyRootDependencyOwnership(record) { }; } +/** + * Builds dependency ownership records from root package.json and scanned imports. + */ export function collectRootDependencyOwnershipAudit(params = {}) { const repoRoot = path.resolve(params.repoRoot ?? process.cwd()); const rootPackageJson = readJson(path.join(repoRoot, "package.json")); @@ -352,6 +363,9 @@ export function collectRootDependencyOwnershipAudit(params = {}) { .toSorted((left, right) => left.depName.localeCompare(right.depName)); } +/** + * Returns actionable errors for dependencies that should not remain root-owned. + */ export function collectRootDependencyOwnershipCheckErrors(records) { return records .filter((record) => record.category === "extension_only_localizable") diff --git a/scripts/run-additional-boundary-checks.mjs b/scripts/run-additional-boundary-checks.mjs index 2a51c5b51b6a..097adada7039 100644 --- a/scripts/run-additional-boundary-checks.mjs +++ b/scripts/run-additional-boundary-checks.mjs @@ -1,4 +1,6 @@ #!/usr/bin/env node +// Runs additional architecture and boundary checks with sharding, concurrency, +// timeout handling, and grouped CI output. import { spawn } from "node:child_process"; import { performance } from "node:perf_hooks"; @@ -6,6 +8,7 @@ const DEFAULT_CHECK_TIMEOUT_MS = 10 * 60 * 1000; const DEFAULT_OUTPUT_MAX_BYTES = 512 * 1024; const TIMEOUT_KILL_GRACE_MS = 5_000; +/** Ordered list of supplemental boundary checks used by CI sharding. */ export const BOUNDARY_CHECKS = [ ["prompt:snapshots:check", "pnpm", ["prompt:snapshots:check"]], ["plugin-extension-boundary", "pnpm", ["run", "lint:plugins:no-extension-imports"]], @@ -66,10 +69,16 @@ export const BOUNDARY_CHECKS = [ ["lint:ui:no-raw-window-open", "pnpm", ["lint:ui:no-raw-window-open"]], ].map(([label, command, args]) => ({ label, command, args })); +/** + * Resolves the configured boundary-check concurrency. + */ export function resolveConcurrency(value, fallback = 4, label = "concurrency") { return resolvePositiveInteger(value, fallback, label); } +/** + * Parses positive integer CLI/env options with a fallback. + */ export function resolvePositiveInteger(value, fallback, label = "value") { if (value === undefined || value === null || value === "") { return fallback; @@ -85,6 +94,9 @@ export function resolvePositiveInteger(value, fallback, label = "value") { return parsed; } +/** + * Parses one N/TOTAL shard selector into zero-based index form. + */ export function parseShardSpec(value) { if (!value) { return null; @@ -107,6 +119,9 @@ export function parseShardSpec(value) { return { count, index: index - 1, label: `${index}/${count}` }; } +/** + * Parses a comma-separated list of N/TOTAL shard selectors. + */ export function parseShardSelection(value) { if (!value) { return null; @@ -124,6 +139,9 @@ export function parseShardSelection(value) { }); } +/** + * Selects checks whose ordinal belongs to the requested shard set. + */ export function selectChecksForShard(checks, shardSpec) { const shards = typeof shardSpec === "string" @@ -141,10 +159,16 @@ export function selectChecksForShard(checks, shardSpec) { ); } +/** + * Formats a check command for CI group output. + */ export function formatCommand({ command, args }) { return [command, ...args].join(" "); } +/** + * Keeps only the tail of noisy check output so failure logs stay bounded. + */ export function createBoundedOutputBuffer(maxBytes = DEFAULT_OUTPUT_MAX_BYTES) { const limit = Math.max(1, maxBytes); const chunks = []; @@ -247,6 +271,9 @@ function installActiveChildCleanup(activeChildren) { }; } +/** + * Runs one boundary check with timeout and process-group termination. + */ export function runSingleCheck( check, { @@ -359,6 +386,9 @@ function writeTimingSummary(results, output) { } } +/** + * Runs boundary checks with bounded concurrency and returns the failure count. + */ export async function runChecks( checks = BOUNDARY_CHECKS, { @@ -442,11 +472,7 @@ if (import.meta.url === `file://${process.argv[1]}`) { process.env.OPENCLAW_ADDITIONAL_BOUNDARY_CONCURRENCY === undefined ? "OPENCLAW_EXTENSION_BOUNDARY_CONCURRENCY" : "OPENCLAW_ADDITIONAL_BOUNDARY_CONCURRENCY"; - const concurrency = resolveConcurrency( - concurrencyRaw, - 4, - concurrencyLabel, - ); + const concurrency = resolveConcurrency(concurrencyRaw, 4, concurrencyLabel); const checkTimeoutMs = resolvePositiveInteger( process.env.OPENCLAW_ADDITIONAL_BOUNDARY_TIMEOUT_MS, DEFAULT_CHECK_TIMEOUT_MS, diff --git a/scripts/run-bundled-extension-oxlint.mjs b/scripts/run-bundled-extension-oxlint.mjs index ed0fbd5412d1..de07ae919e3e 100644 --- a/scripts/run-bundled-extension-oxlint.mjs +++ b/scripts/run-bundled-extension-oxlint.mjs @@ -1,3 +1,4 @@ +// Runs oxlint over bundled plugin source files using the shared extension lint runner. import { runExtensionOxlint } from "./lib/run-extension-oxlint.mjs"; runExtensionOxlint({