mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-12 21:53:00 -06:00
refactor(scripts): adopt shared scaffolding (#118514)
* refactor(scripts): adopt shared scaffolding * fix(scripts): satisfy strict tooling checks * fix(scripts): preserve scaffolding contracts
This commit is contained in:
committed by
GitHub
parent
757d220203
commit
ea2c6a63c9
@@ -2,10 +2,10 @@
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import process from "node:process";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { runAndroidSigningCommandSync } from "./lib/android-release-signing-process.mjs";
|
||||
|
||||
const rootDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
||||
import { parseFlagArgs, stringFlag } from "./lib/arg-utils.mjs";
|
||||
import { resolveRepoRoot } from "./lib/repo-root.mjs";
|
||||
const rootDir = resolveRepoRoot(import.meta.url);
|
||||
const defaultManifestPath = path.join(rootDir, "apps", "android", "Config", "ReleaseSigning.json");
|
||||
const requiredPropertyNames = [
|
||||
"OPENCLAW_ANDROID_STORE_FILE",
|
||||
@@ -46,33 +46,43 @@ function parseArgs(argv) {
|
||||
keystorePath: process.env.OPENCLAW_ANDROID_UPLOAD_KEYSTORE || "",
|
||||
propertiesPath: process.env.OPENCLAW_ANDROID_SIGNING_PROPERTIES || "",
|
||||
};
|
||||
|
||||
for (let index = 0; index < argv.length; index += 1) {
|
||||
const arg = argv[index];
|
||||
if (arg === "--mode") {
|
||||
options.mode = readOptionValue(argv, index, arg);
|
||||
index += 1;
|
||||
} else if (arg === "--manifest") {
|
||||
options.manifestPath = path.resolve(readOptionValue(argv, index, arg));
|
||||
index += 1;
|
||||
} else if (arg === "--workspace") {
|
||||
options.workspace = path.resolve(readOptionValue(argv, index, arg));
|
||||
index += 1;
|
||||
} else if (arg === "--materialized-dir") {
|
||||
options.materializedDir = path.resolve(readOptionValue(argv, index, arg));
|
||||
index += 1;
|
||||
} else if (arg === "--keystore") {
|
||||
options.keystorePath = path.resolve(readOptionValue(argv, index, arg));
|
||||
index += 1;
|
||||
} else if (arg === "--properties") {
|
||||
options.propertiesPath = path.resolve(readOptionValue(argv, index, arg));
|
||||
index += 1;
|
||||
} else if (arg === "-h" || arg === "--help") {
|
||||
usage();
|
||||
process.exit(0);
|
||||
} else {
|
||||
throw new Error(`Unknown argument: ${arg}`);
|
||||
}
|
||||
const helpIndex = argv.findIndex((arg) => arg === "-h" || arg === "--help");
|
||||
parseFlagArgs(
|
||||
helpIndex === -1 ? argv : argv.slice(0, helpIndex),
|
||||
options,
|
||||
[
|
||||
stringFlag("--mode", "mode", {
|
||||
allowInline: false,
|
||||
missingValueMessage: "Missing value for --mode.",
|
||||
rejectShortOptions: true,
|
||||
repeatable: true,
|
||||
}),
|
||||
...[
|
||||
["--manifest", "manifestPath"],
|
||||
["--workspace", "workspace"],
|
||||
["--materialized-dir", "materializedDir"],
|
||||
["--keystore", "keystorePath"],
|
||||
["--properties", "propertiesPath"],
|
||||
].map(([flag, key]) =>
|
||||
stringFlag(flag, key, {
|
||||
allowInline: false,
|
||||
missingValueMessage: `Missing value for ${flag}.`,
|
||||
rejectShortOptions: true,
|
||||
repeatable: true,
|
||||
transform: path.resolve,
|
||||
}),
|
||||
),
|
||||
],
|
||||
{
|
||||
ignoreDoubleDash: false,
|
||||
onUnhandledArg(arg) {
|
||||
throw new Error(`Unknown argument: ${arg}`);
|
||||
},
|
||||
},
|
||||
);
|
||||
if (helpIndex !== -1) {
|
||||
usage();
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
if (!options.mode) {
|
||||
@@ -82,14 +92,6 @@ function parseArgs(argv) {
|
||||
return options;
|
||||
}
|
||||
|
||||
function readOptionValue(argv, index, option) {
|
||||
const value = argv[index + 1] ?? "";
|
||||
if (!value || value.startsWith("-")) {
|
||||
throw new Error(`Missing value for ${option}.`);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
function requireString(value, key) {
|
||||
if (typeof value !== "string" || value.trim() === "") {
|
||||
throw new Error(`Android release signing manifest missing ${key}.`);
|
||||
|
||||
@@ -12,9 +12,9 @@ import {
|
||||
import { visitModuleSpecifiers } from "./lib/guard-inventory-utils.mjs";
|
||||
import { optionalBundledClusterSet } from "./lib/optional-bundled-clusters.mjs";
|
||||
import { escapeRegExp } from "./lib/regexp.mjs";
|
||||
import { resolveRepoRoot } from "./lib/repo-root.mjs";
|
||||
import { toLine } from "./lib/ts-guard-utils.mjs";
|
||||
|
||||
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
||||
const repoRoot = resolveRepoRoot(import.meta.url);
|
||||
const srcRoot = path.join(repoRoot, "src");
|
||||
const extensionsRoot = path.join(repoRoot, BUNDLED_PLUGIN_ROOT_DIR);
|
||||
const testRoot = path.join(repoRoot, "test");
|
||||
|
||||
@@ -4,12 +4,12 @@
|
||||
import { spawnSync } from "node:child_process";
|
||||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath, pathToFileURL } from "node:url";
|
||||
import { pathToFileURL } from "node:url";
|
||||
import { runManagedCommand } from "./lib/managed-child-process.mjs";
|
||||
import { assertRealOutputRoot } from "./lib/output-root-guard.mjs";
|
||||
import { resolveRepoRoot } from "./lib/repo-root.mjs";
|
||||
import { listGeneratedExtensionAssetSources } from "./lib/static-extension-assets.mjs";
|
||||
|
||||
const rootDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
||||
const rootDir = resolveRepoRoot(import.meta.url);
|
||||
const VALID_PHASES = new Set(["build", "copy"]);
|
||||
// Each complete bundled-plugin asset generator gets the same 10-minute build ceiling.
|
||||
const BUNDLED_PLUGIN_ASSET_HOOK_TIMEOUT_MS = 600_000;
|
||||
|
||||
@@ -5,10 +5,10 @@ import { promises as fs } from "node:fs";
|
||||
import path from "node:path";
|
||||
import ts from "typescript";
|
||||
import { visitModuleSpecifiers } from "./lib/guard-inventory-utils.mjs";
|
||||
import { resolveRepoRoot } from "./lib/repo-root.mjs";
|
||||
import {
|
||||
collectTypeScriptFiles,
|
||||
getPropertyNameText,
|
||||
resolveRepoRoot,
|
||||
runAsScript,
|
||||
toLine,
|
||||
} from "./lib/ts-guard-utils.mjs";
|
||||
|
||||
@@ -5,9 +5,9 @@ import { spawnSync as defaultSpawnSync } from "node:child_process";
|
||||
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath, pathToFileURL } from "node:url";
|
||||
|
||||
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
||||
import { pathToFileURL } from "node:url";
|
||||
import { resolveRepoRoot } from "./lib/repo-root.mjs";
|
||||
const repoRoot = resolveRepoRoot(import.meta.url);
|
||||
const tmpDir = process.env.TMPDIR || process.env.TEMP || process.env.TMP || os.tmpdir();
|
||||
const MAX_RSS_MARKER = "__OPENCLAW_MAX_RSS_KB__=";
|
||||
const DEFAULT_COMMAND_TIMEOUT_MS = 60_000;
|
||||
|
||||
@@ -2,10 +2,9 @@
|
||||
// Verifies each generated Control UI sidecar encodes the final emitted asset bytes.
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { brotliDecompressSync, gunzipSync } from "node:zlib";
|
||||
|
||||
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
||||
import { resolveRepoRoot } from "./lib/repo-root.mjs";
|
||||
const repoRoot = resolveRepoRoot(import.meta.url);
|
||||
const assetsDir = path.join(repoRoot, "dist", "control-ui", "assets");
|
||||
const errors = [];
|
||||
let checked = 0;
|
||||
|
||||
@@ -12,7 +12,8 @@ import {
|
||||
mergeLegacyObjectPropertyValues,
|
||||
mergeLegacyPathBranchAssignments,
|
||||
} from "./lib/legacy-store-path-domain.mjs";
|
||||
import { resolveRepoRoot, runAsScript, toLine, unwrapExpression } from "./lib/ts-guard-utils.mjs";
|
||||
import { resolveRepoRoot } from "./lib/repo-root.mjs";
|
||||
import { runAsScript, toLine, unwrapExpression } from "./lib/ts-guard-utils.mjs";
|
||||
|
||||
const databaseFirstLegacyStoreSourceRoots = ["src", "extensions", "packages"];
|
||||
const databaseFirstNativeSourceRoots = ["apps/macos/Sources/OpenClaw"];
|
||||
|
||||
@@ -3,12 +3,12 @@
|
||||
import fs from "node:fs";
|
||||
import { createRequire } from "node:module";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { resolveRepoRoot } from "./lib/repo-root.mjs";
|
||||
|
||||
const require = createRequire(import.meta.url);
|
||||
const ts = require("typescript");
|
||||
|
||||
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
||||
const repoRoot = resolveRepoRoot(import.meta.url);
|
||||
const SCAN_ROOTS = ["src", "extensions", "packages"];
|
||||
const SOURCE_FILE_RE = /\.(?:ts|tsx)$/;
|
||||
const SKIP_PATH_RE =
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
// the source checkout copied or mounted as the app under test.
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { laneResources, laneWeight } from "./lib/docker-e2e-plan.mjs";
|
||||
import {
|
||||
allReleasePathLanes,
|
||||
@@ -12,8 +11,8 @@ import {
|
||||
publicInstallerLanes,
|
||||
tailLanes,
|
||||
} from "./lib/docker-e2e-scenarios.mjs";
|
||||
|
||||
const ROOT_DIR = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
||||
import { resolveRepoRoot } from "./lib/repo-root.mjs";
|
||||
const ROOT_DIR = resolveRepoRoot(import.meta.url);
|
||||
const errors = [];
|
||||
const packageJson = JSON.parse(readText("package.json"));
|
||||
const packageScripts = new Set(Object.keys(packageJson.scripts ?? {}));
|
||||
|
||||
@@ -2,9 +2,8 @@
|
||||
// Runs duplicate-code detection with repo-specific excludes.
|
||||
import { spawnSync } from "node:child_process";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
||||
import { resolveRepoRoot } from "./lib/repo-root.mjs";
|
||||
const repoRoot = resolveRepoRoot(import.meta.url);
|
||||
const jscpdBin = path.join(repoRoot, "node_modules", "jscpd", "bin", "jscpd");
|
||||
|
||||
const targets = [
|
||||
|
||||
@@ -4,12 +4,8 @@
|
||||
import { promises as fs } from "node:fs";
|
||||
import path from "node:path";
|
||||
import ts from "typescript";
|
||||
import {
|
||||
collectTypeScriptFilesFromRoots,
|
||||
resolveRepoRoot,
|
||||
runAsScript,
|
||||
toLine,
|
||||
} from "./lib/ts-guard-utils.mjs";
|
||||
import { resolveRepoRoot } from "./lib/repo-root.mjs";
|
||||
import { collectTypeScriptFilesFromRoots, runAsScript, toLine } from "./lib/ts-guard-utils.mjs";
|
||||
|
||||
const repoRoot = resolveRepoRoot(import.meta.url);
|
||||
const defaultRoots = [path.join(repoRoot, "src"), path.join(repoRoot, "extensions")];
|
||||
|
||||
@@ -16,6 +16,7 @@ import os from "node:os";
|
||||
import path, { dirname, join, resolve } from "node:path";
|
||||
import pMap from "p-map";
|
||||
import { parsePositiveInt } from "./lib/numeric-options.mjs";
|
||||
import { resolveRepoRoot } from "./lib/repo-root.mjs";
|
||||
import {
|
||||
forwardSignalToVitestProcessGroup,
|
||||
installVitestProcessGroupCleanup,
|
||||
@@ -23,7 +24,7 @@ import {
|
||||
} from "./vitest-process-group.mjs";
|
||||
|
||||
const require = createRequire(import.meta.url);
|
||||
const repoRoot = resolve(import.meta.dirname, "..");
|
||||
const repoRoot = resolveRepoRoot(import.meta.url);
|
||||
const tscBin = require.resolve("typescript/bin/tsc");
|
||||
const nativePreviewPackageJsonPath = require.resolve("@typescript/native-preview/package.json");
|
||||
const nativePreviewPackageJson = JSON.parse(readFileSync(nativePreviewPackageJsonPath, "utf8"));
|
||||
|
||||
@@ -15,8 +15,9 @@ import {
|
||||
resolveRepoSpecifier,
|
||||
writeLine,
|
||||
} from "./lib/guard-inventory-utils.mjs";
|
||||
import { resolveRepoRoot } from "./lib/repo-root.mjs";
|
||||
import { listGeneratedExtensionAssetSources } from "./lib/static-extension-assets.mjs";
|
||||
import { resolveRepoRoot, runAsScript } from "./lib/ts-guard-utils.mjs";
|
||||
import { runAsScript } from "./lib/ts-guard-utils.mjs";
|
||||
|
||||
const repoRoot = resolveRepoRoot(import.meta.url);
|
||||
// Generated bundles are validated at their build owner; they are not bounded authored source.
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
||||
import { resolveRepoRoot } from "./lib/repo-root.mjs";
|
||||
const repoRoot = resolveRepoRoot(import.meta.url);
|
||||
|
||||
const LOCAL_WILDCARD_REEXPORT_PATTERN = /^\s*export\s+(?:type\s+)?\*\s+from\s+["'](?:\.{1,2}\/)/u;
|
||||
|
||||
|
||||
@@ -4,10 +4,10 @@
|
||||
import { promises as fs } from "node:fs";
|
||||
import { createRequire } from "node:module";
|
||||
import path from "node:path";
|
||||
import { resolveRepoRoot } from "./lib/repo-root.mjs";
|
||||
import {
|
||||
collectTypeScriptFilesFromRoots,
|
||||
getPropertyNameText,
|
||||
resolveRepoRoot,
|
||||
runAsScript,
|
||||
toLine,
|
||||
unwrapExpression,
|
||||
|
||||
@@ -11,7 +11,8 @@ import {
|
||||
runBaselineInventoryCheck,
|
||||
resolveRepoSpecifier,
|
||||
} from "./lib/guard-inventory-utils.mjs";
|
||||
import { resolveRepoRoot, runAsScript } from "./lib/ts-guard-utils.mjs";
|
||||
import { resolveRepoRoot } from "./lib/repo-root.mjs";
|
||||
import { runAsScript } from "./lib/ts-guard-utils.mjs";
|
||||
|
||||
const repoRoot = resolveRepoRoot(import.meta.url);
|
||||
const baselinePath = path.join(
|
||||
|
||||
@@ -3,16 +3,15 @@
|
||||
// Verifies plugin SDK subpath exports and generated entrypoint metadata.
|
||||
import { readFileSync } from "node:fs";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import ts from "typescript";
|
||||
import { normalizeRepoPath, visitModuleSpecifiers } from "./lib/guard-inventory-utils.mjs";
|
||||
import { resolveRepoRoot } from "./lib/repo-root.mjs";
|
||||
import {
|
||||
collectTypeScriptFilesFromRoots,
|
||||
resolveSourceRoots,
|
||||
toLine,
|
||||
} from "./lib/ts-guard-utils.mjs";
|
||||
|
||||
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
||||
const repoRoot = resolveRepoRoot(import.meta.url);
|
||||
const scanRoots = resolveSourceRoots(repoRoot, [
|
||||
"src",
|
||||
"packages",
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
||||
import { resolveRepoRoot } from "./lib/repo-root.mjs";
|
||||
const repoRoot = resolveRepoRoot(import.meta.url);
|
||||
const extensionsRoot = path.join(repoRoot, "extensions");
|
||||
|
||||
const WILDCARD_PLUGIN_SDK_REEXPORT_PATTERN =
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath, pathToFileURL } from "node:url";
|
||||
|
||||
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
||||
import { pathToFileURL } from "node:url";
|
||||
import { resolveRepoRoot } from "./lib/repo-root.mjs";
|
||||
const repoRoot = resolveRepoRoot(import.meta.url);
|
||||
const schemaDir = path.join(repoRoot, "packages/gateway-protocol/src/schema");
|
||||
const failures = [];
|
||||
const read = (relativePath) => fs.readFileSync(path.join(repoRoot, relativePath), "utf8");
|
||||
|
||||
@@ -4,12 +4,12 @@ import { spawnSync } from "node:child_process";
|
||||
import fs from "node:fs";
|
||||
import { createRequire } from "node:module";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { resolveRepoRoot } from "./lib/repo-root.mjs";
|
||||
|
||||
const require = createRequire(import.meta.url);
|
||||
const ts = require("typescript");
|
||||
|
||||
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
||||
const repoRoot = resolveRepoRoot(import.meta.url);
|
||||
const descriptorPath = "src/gateway/methods/core-descriptors.ts";
|
||||
|
||||
function runGit(args) {
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
import { promises as fs } from "node:fs";
|
||||
import path from "node:path";
|
||||
import ts from "typescript";
|
||||
import { resolveRepoRoot } from "./lib/repo-root.mjs";
|
||||
import {
|
||||
collectTypeScriptFilesFromRoots,
|
||||
resolveRepoRoot,
|
||||
runAsScript,
|
||||
toLine,
|
||||
unwrapExpression,
|
||||
|
||||
@@ -3,10 +3,10 @@
|
||||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import ts from "typescript";
|
||||
import { resolveRepoRoot } from "./lib/repo-root.mjs";
|
||||
import {
|
||||
collectFileViolations,
|
||||
getPropertyNameText,
|
||||
resolveRepoRoot,
|
||||
resolveSourceRoots,
|
||||
runAsScript,
|
||||
toLine,
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
|
||||
import path from "node:path";
|
||||
import ts from "typescript";
|
||||
import { resolveRepoRoot } from "./lib/repo-root.mjs";
|
||||
import {
|
||||
collectFileViolations,
|
||||
resolveRepoRoot,
|
||||
resolveSourceRoots,
|
||||
runAsScript,
|
||||
toLine,
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import ts from "typescript";
|
||||
import { resolveRepoRoot } from "./lib/repo-root.mjs";
|
||||
import {
|
||||
collectFileViolations,
|
||||
resolveRepoRoot,
|
||||
resolveSourceRoots,
|
||||
runAsScript,
|
||||
toLine,
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
// Prevents Telegram runtime imports from grammy type-only modules.
|
||||
import { readdirSync, readFileSync } from "node:fs";
|
||||
import path from "node:path";
|
||||
|
||||
const repoRoot = path.resolve(import.meta.dirname, "..");
|
||||
import { resolveRepoRoot } from "./lib/repo-root.mjs";
|
||||
const repoRoot = resolveRepoRoot(import.meta.url);
|
||||
const telegramRoot = path.join(repoRoot, "extensions/telegram");
|
||||
const importSpecifierPatterns = [
|
||||
/\bimport\s+(?:type\s+)?[\s\S]*?\bfrom\s*["']([^"']+)["']/gu,
|
||||
|
||||
@@ -2,11 +2,10 @@
|
||||
|
||||
// Enforces core tsgo project boundaries and sparse-checkout safety.
|
||||
import { spawnSync } from "node:child_process";
|
||||
import path from "node:path";
|
||||
import { resolveRepoToolBinPath } from "./lib/local-heavy-check-runtime.mjs";
|
||||
import { createManagedCommandInvocation } from "./lib/managed-child-process.mjs";
|
||||
|
||||
const repoRoot = path.resolve(import.meta.dirname, "..");
|
||||
import { resolveRepoRoot } from "./lib/repo-root.mjs";
|
||||
const repoRoot = resolveRepoRoot(import.meta.url);
|
||||
const tsgoPath = resolveRepoToolBinPath("tsgo", { cwd: repoRoot });
|
||||
|
||||
const coreGraphs = [
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
// Checks core web-fetch surfaces for provider-owned Firecrawl coupling.
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { resolveRepoRoot } from "./lib/repo-root.mjs";
|
||||
import { collectSourceFileContents } from "./lib/source-file-scan-cache.mjs";
|
||||
import { runAsScript } from "./lib/ts-guard-utils.mjs";
|
||||
|
||||
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
||||
const repoRoot = resolveRepoRoot(import.meta.url);
|
||||
const scanExtensions = new Set([".ts", ".js", ".mjs", ".cjs"]);
|
||||
const ignoredDirNames = new Set([
|
||||
".artifacts",
|
||||
|
||||
@@ -3,12 +3,11 @@
|
||||
// Inventories core web-search surfaces that still mention bundled providers.
|
||||
import { promises as fs } from "node:fs";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { diffInventoryEntries, runBaselineInventoryCheck } from "./lib/guard-inventory-utils.mjs";
|
||||
import { resolveRepoRoot } from "./lib/repo-root.mjs";
|
||||
import { collectSourceFileContents } from "./lib/source-file-scan-cache.mjs";
|
||||
import { runAsScript } from "./lib/ts-guard-utils.mjs";
|
||||
|
||||
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
||||
const repoRoot = resolveRepoRoot(import.meta.url);
|
||||
const baselinePath = path.join(
|
||||
repoRoot,
|
||||
"test",
|
||||
|
||||
+18
-20
@@ -1,5 +1,6 @@
|
||||
// Runs the repository check lanes selected by CLI arguments.
|
||||
import { performance } from "node:perf_hooks";
|
||||
import { booleanFlag, parseFlagArgs } from "./lib/arg-utils.mjs";
|
||||
import { printTimingSummary } from "./lib/check-timing-summary.mjs";
|
||||
import { runManagedCommand } from "./lib/managed-child-process.mjs";
|
||||
|
||||
@@ -24,26 +25,23 @@ export function usage() {
|
||||
* Parses aggregate check runner arguments.
|
||||
*/
|
||||
function parseCheckArgs(argv) {
|
||||
const args = {
|
||||
help: false,
|
||||
includeArchitecture: false,
|
||||
includeTestTypes: false,
|
||||
timed: false,
|
||||
};
|
||||
for (const arg of argv) {
|
||||
if (arg === "--timed") {
|
||||
args.timed = true;
|
||||
} else if (arg === "--include-architecture") {
|
||||
args.includeArchitecture = true;
|
||||
} else if (arg === "--include-test-types") {
|
||||
args.includeTestTypes = true;
|
||||
} else if (arg === "--help" || arg === "-h") {
|
||||
args.help = true;
|
||||
} else {
|
||||
throw new Error(`unknown argument: ${arg}\n\n${usage()}`);
|
||||
}
|
||||
}
|
||||
return args;
|
||||
return parseFlagArgs(
|
||||
argv,
|
||||
{ help: false, includeArchitecture: false, includeTestTypes: false, timed: false },
|
||||
[
|
||||
booleanFlag("--timed", "timed", true, { repeatable: true }),
|
||||
booleanFlag("--include-architecture", "includeArchitecture", true, { repeatable: true }),
|
||||
booleanFlag("--include-test-types", "includeTestTypes", true, { repeatable: true }),
|
||||
booleanFlag("--help", "help", true, { repeatable: true }),
|
||||
booleanFlag("-h", "help", true, { repeatable: true }),
|
||||
],
|
||||
{
|
||||
ignoreDoubleDash: false,
|
||||
onUnhandledArg(arg) {
|
||||
throw new Error(`unknown argument: ${arg}\n\n${usage()}`);
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -4,12 +4,12 @@
|
||||
import { execFileSync } from "node:child_process";
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath, pathToFileURL } from "node:url";
|
||||
import { pathToFileURL } from "node:url";
|
||||
import { renderDocsHeadingMap } from "./docs-list.js";
|
||||
import { repairMintlifyAccordionIndentation } from "./lib/mintlify-accordion.mjs";
|
||||
import { resolveRepoRoot } from "./lib/repo-root.mjs";
|
||||
|
||||
const HERE = path.dirname(fileURLToPath(import.meta.url));
|
||||
const ROOT = path.resolve(HERE, "..");
|
||||
const ROOT = resolveRepoRoot(import.meta.url);
|
||||
const SOURCE_DOCS_DIR = path.join(ROOT, "docs");
|
||||
const SOURCE_CONFIG_PATH = path.join(SOURCE_DOCS_DIR, "docs.json");
|
||||
const INTERNAL_DOCS_DIRS = ["internal"];
|
||||
|
||||
@@ -12,10 +12,9 @@ import { spawnSync } from "node:child_process";
|
||||
import fs from "node:fs";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { resolveRepoRoot } from "./lib/repo-root.mjs";
|
||||
|
||||
const scriptDir = path.dirname(fileURLToPath(import.meta.url));
|
||||
const repoRoot = path.resolve(scriptDir, "..");
|
||||
const repoRoot = resolveRepoRoot(import.meta.url);
|
||||
const engine = process.env.OPENCLAW_SANDBOX_E2E_ENGINE?.trim() || "docker";
|
||||
const image = process.env.OPENCLAW_SANDBOX_E2E_IMAGE?.trim() || "e2e-sleep:latest";
|
||||
const useSudo = process.env.OPENCLAW_SANDBOX_E2E_SUDO === "1";
|
||||
|
||||
@@ -4,10 +4,10 @@
|
||||
import { spawnSync } from "node:child_process";
|
||||
import { existsSync } from "node:fs";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath, pathToFileURL } from "node:url";
|
||||
import { pathToFileURL } from "node:url";
|
||||
import { readPositiveEnvInt } from "./lib/numeric-options.mjs";
|
||||
|
||||
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
||||
import { resolveRepoRoot } from "./lib/repo-root.mjs";
|
||||
const repoRoot = resolveRepoRoot(import.meta.url);
|
||||
const entryCandidates = ["dist/entry.js", "dist/entry.mjs"];
|
||||
const startupMetadataPath = "dist/cli-startup-metadata.json";
|
||||
const DEFAULT_BUILD_TIMEOUT_MS = 10 * 60 * 1000;
|
||||
|
||||
@@ -4,14 +4,14 @@
|
||||
import { spawnSync } from "node:child_process";
|
||||
import { existsSync, readdirSync } from "node:fs";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath, pathToFileURL } from "node:url";
|
||||
import { pathToFileURL } from "node:url";
|
||||
import {
|
||||
collectBundledPluginBuildEntries,
|
||||
NON_PACKAGED_BUNDLED_PLUGIN_DIRS,
|
||||
} from "./lib/bundled-plugin-build-entries.mjs";
|
||||
import { readPositiveEnvInt } from "./lib/numeric-options.mjs";
|
||||
|
||||
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
||||
import { resolveRepoRoot } from "./lib/repo-root.mjs";
|
||||
const repoRoot = resolveRepoRoot(import.meta.url);
|
||||
const DEFAULT_BUILD_TIMEOUT_MS = 10 * 60 * 1000;
|
||||
|
||||
/**
|
||||
|
||||
@@ -2,12 +2,13 @@
|
||||
// Ensures Playwright Chromium is installed or a usable system browser is available.
|
||||
import { spawnSync as spawnSyncImpl } from "node:child_process";
|
||||
import { existsSync as existsSyncImpl, realpathSync } from "node:fs";
|
||||
import { dirname, resolve } from "node:path";
|
||||
import { resolve } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { chromium } from "playwright";
|
||||
import { resolveRepoRoot } from "./lib/repo-root.mjs";
|
||||
import { resolvePnpmRunner } from "./pnpm-runner.mjs";
|
||||
|
||||
const repoRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
||||
const repoRoot = resolveRepoRoot(import.meta.url);
|
||||
const playwrightInstallBaseArgs = ["--dir", "ui", "exec", "playwright", "install"];
|
||||
const executableOverrideEnvKey = "PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH";
|
||||
const chromiumPackageNames = ["chromium-browser", "chromium"];
|
||||
|
||||
@@ -9,9 +9,9 @@ import { pathToFileURL } from "node:url";
|
||||
import { resolveRepoToolBinPath } from "./lib/local-heavy-check-runtime.mjs";
|
||||
import { repairMintlifyAccordionIndentation } from "./lib/mintlify-accordion.mjs";
|
||||
import { outputTail } from "./lib/output-tail.mjs";
|
||||
import { resolveRepoRoot } from "./lib/repo-root.mjs";
|
||||
import { buildCmdExeCommandLine, resolveWindowsCmdExePath } from "./windows-cmd-helpers.mjs";
|
||||
|
||||
const ROOT = path.resolve(import.meta.dirname, "..");
|
||||
const ROOT = resolveRepoRoot(import.meta.url);
|
||||
const CHECK = process.argv.includes("--check");
|
||||
const DOCS_FORMAT_MAX_BUFFER_BYTES = 1024 * 1024 * 16;
|
||||
const DOCS_FORMAT_MAX_COMMAND_LINE_BYTES = 24 * 1024;
|
||||
|
||||
@@ -19,13 +19,6 @@ export function releaseProfileForTarget(
|
||||
readPackageJson?: (sha: string) => string,
|
||||
): "beta" | "stable";
|
||||
export function releaseEvidenceVerificationArgs(parentRunId: unknown): string[];
|
||||
export function runGhRead(
|
||||
args: string[],
|
||||
params?: {
|
||||
execFileSyncImpl?: (...args: unknown[]) => unknown;
|
||||
timeoutMs?: number;
|
||||
},
|
||||
): string;
|
||||
export function shouldDeleteTemporaryWorkflowRef(params: {
|
||||
keepBranch: boolean;
|
||||
dryRun: boolean;
|
||||
|
||||
@@ -5,6 +5,7 @@ import { existsSync, mkdtempSync, rmSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { pathToFileURL } from "node:url";
|
||||
import { execGhRead } from "./lib/plain-gh.mjs";
|
||||
|
||||
const WORKFLOW = "full-release-validation.yml";
|
||||
const TRUSTED_WORKFLOW_PATH = `.github/workflows/${WORKFLOW}`;
|
||||
@@ -13,6 +14,12 @@ const RELEASE_EVIDENCE_VERIFIER_PATHS = [
|
||||
".agents/skills/release-openclaw-ci/scripts/release-ci-summary.mjs",
|
||||
];
|
||||
const GH_READ_TIMEOUT_MS = 60_000;
|
||||
const GH_READ_OPTIONS = {
|
||||
encoding: "utf8",
|
||||
killSignal: "SIGKILL",
|
||||
stdio: ["ignore", "pipe", "inherit"],
|
||||
timeout: GH_READ_TIMEOUT_MS,
|
||||
};
|
||||
const RELEASE_BRANCH_PATTERN =
|
||||
/^(?:release\/[0-9]{4}\.[0-9]+\.[0-9]+|extended-stable\/[0-9]{4}\.[0-9]+\.33)$/u;
|
||||
const RELEASE_TAG_PATTERN = /^v[0-9]{4}\.[0-9]+\.[0-9]+(?:-(?:alpha|beta)\.[0-9]+)?$/u;
|
||||
@@ -62,17 +69,6 @@ function runStatus(command, args, options = {}) {
|
||||
});
|
||||
}
|
||||
|
||||
export function runGhRead(args, params = {}) {
|
||||
const execFileSyncImpl = params.execFileSyncImpl ?? execFileSync;
|
||||
const output = execFileSyncImpl("gh", args, {
|
||||
encoding: "utf8",
|
||||
killSignal: "SIGKILL",
|
||||
stdio: ["ignore", "pipe", "inherit"],
|
||||
timeout: params.timeoutMs ?? GH_READ_TIMEOUT_MS,
|
||||
});
|
||||
return typeof output === "string" ? output.trim() : "";
|
||||
}
|
||||
|
||||
function readOptionValue(argv, index, optionName) {
|
||||
const value = argv[index + 1];
|
||||
if (value === undefined || value === "" || value.startsWith("-")) {
|
||||
@@ -264,20 +260,23 @@ function collectRunId(dispatchOutput) {
|
||||
}
|
||||
|
||||
function findLatestRunId(branch, sha) {
|
||||
const json = runGhRead([
|
||||
"run",
|
||||
"list",
|
||||
"--workflow",
|
||||
WORKFLOW,
|
||||
"--branch",
|
||||
branch,
|
||||
"--event",
|
||||
"workflow_dispatch",
|
||||
"--limit",
|
||||
"20",
|
||||
"--json",
|
||||
"databaseId,headSha,createdAt",
|
||||
]);
|
||||
const json = execGhRead(
|
||||
[
|
||||
"run",
|
||||
"list",
|
||||
"--workflow",
|
||||
WORKFLOW,
|
||||
"--branch",
|
||||
branch,
|
||||
"--event",
|
||||
"workflow_dispatch",
|
||||
"--limit",
|
||||
"20",
|
||||
"--json",
|
||||
"databaseId,headSha,createdAt",
|
||||
],
|
||||
GH_READ_OPTIONS,
|
||||
);
|
||||
const runs = JSON.parse(json);
|
||||
const match = runs.find((runItem) => runItem.headSha === sha);
|
||||
return match?.databaseId ? String(match.databaseId) : "";
|
||||
@@ -288,7 +287,7 @@ function readWorkflowRun(parentRunId, workflowSha) {
|
||||
throw new Error("parent run ID must be a positive decimal");
|
||||
}
|
||||
const workflowRun = JSON.parse(
|
||||
runGhRead(["api", `repos/openclaw/openclaw/actions/runs/${parentRunId}`]),
|
||||
execGhRead(["api", `repos/openclaw/openclaw/actions/runs/${parentRunId}`], GH_READ_OPTIONS),
|
||||
);
|
||||
if (workflowRun.head_sha !== workflowSha) {
|
||||
throw new Error(
|
||||
|
||||
@@ -5,6 +5,7 @@ import { execFileSync } from "node:child_process";
|
||||
import { appendFile, mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import process from "node:process";
|
||||
import { parseFlagArgs, stringFlag } from "./lib/arg-utils.mjs";
|
||||
|
||||
/**
|
||||
* Dependency evidence reports generated for release artifacts.
|
||||
@@ -379,14 +380,6 @@ async function generateDependencyReleaseEvidence({
|
||||
return { manifest, counts, outputDir };
|
||||
}
|
||||
|
||||
function readOptionValue(argv, index, optionName, { allowEmpty = false } = {}) {
|
||||
const value = argv[index + 1];
|
||||
if (value === undefined || value.startsWith("-") || (!allowEmpty && value === "")) {
|
||||
throw new Error(`Expected ${optionName} <value>.`);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
function usage() {
|
||||
return `Usage: node scripts/generate-dependency-release-evidence.mjs --output-dir <dir> --release-ref <ref> --npm-dist-tag <tag> [options]
|
||||
|
||||
@@ -414,60 +407,34 @@ export function parseArgs(argv) {
|
||||
githubOutput: process.env.GITHUB_OUTPUT,
|
||||
githubStepSummary: process.env.GITHUB_STEP_SUMMARY,
|
||||
};
|
||||
const seen = new Set();
|
||||
const setOnce = (flag, key, value) => {
|
||||
if (seen.has(flag)) {
|
||||
throw new Error(`${flag} was provided more than once.`);
|
||||
}
|
||||
seen.add(flag);
|
||||
options[key] = value;
|
||||
};
|
||||
for (let index = 0; index < argv.length; index += 1) {
|
||||
const arg = argv[index];
|
||||
if (arg === "--") {
|
||||
continue;
|
||||
}
|
||||
if (arg === "-h" || arg === "--help") {
|
||||
return { ...options, help: true };
|
||||
}
|
||||
if (arg === "--root") {
|
||||
setOnce(arg, "rootDir", readOptionValue(argv, index, arg));
|
||||
index += 1;
|
||||
continue;
|
||||
}
|
||||
if (arg === "--output-dir") {
|
||||
setOnce(arg, "outputDir", readOptionValue(argv, index, arg));
|
||||
index += 1;
|
||||
continue;
|
||||
}
|
||||
if (arg === "--release-ref") {
|
||||
setOnce(arg, "releaseRef", readOptionValue(argv, index, arg));
|
||||
index += 1;
|
||||
continue;
|
||||
}
|
||||
if (arg === "--npm-dist-tag") {
|
||||
setOnce(arg, "npmDistTag", readOptionValue(argv, index, arg));
|
||||
index += 1;
|
||||
continue;
|
||||
}
|
||||
if (arg === "--base-ref") {
|
||||
setOnce(arg, "baseRef", readOptionValue(argv, index, arg));
|
||||
index += 1;
|
||||
continue;
|
||||
}
|
||||
if (arg === "--github-output") {
|
||||
setOnce(arg, "githubOutput", readOptionValue(argv, index, arg, { allowEmpty: true }));
|
||||
index += 1;
|
||||
continue;
|
||||
}
|
||||
if (arg === "--github-step-summary") {
|
||||
setOnce(arg, "githubStepSummary", readOptionValue(argv, index, arg, { allowEmpty: true }));
|
||||
index += 1;
|
||||
continue;
|
||||
}
|
||||
throw new Error(`Unsupported argument: ${arg}`);
|
||||
}
|
||||
return options;
|
||||
const helpIndex = argv.findIndex((arg) => arg === "-h" || arg === "--help");
|
||||
const parsed = parseFlagArgs(
|
||||
helpIndex === -1 ? argv : argv.slice(0, helpIndex),
|
||||
options,
|
||||
[
|
||||
["--root", "rootDir", false],
|
||||
["--output-dir", "outputDir", false],
|
||||
["--release-ref", "releaseRef", false],
|
||||
["--npm-dist-tag", "npmDistTag", false],
|
||||
["--base-ref", "baseRef", false],
|
||||
["--github-output", "githubOutput", true],
|
||||
["--github-step-summary", "githubStepSummary", true],
|
||||
].map(([flag, key, allowEmpty]) =>
|
||||
stringFlag(flag, key, {
|
||||
allowEmpty,
|
||||
allowInline: false,
|
||||
missingValueMessage: `Expected ${flag} <value>.`,
|
||||
rejectShortOptions: true,
|
||||
}),
|
||||
),
|
||||
{
|
||||
duplicateOptionMessage: (flag) => `${flag} was provided more than once.`,
|
||||
onUnhandledArg(arg) {
|
||||
throw new Error(`Unsupported argument: ${arg}`);
|
||||
},
|
||||
},
|
||||
);
|
||||
return helpIndex === -1 ? parsed : { ...parsed, help: true };
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
// Generates Swift constants for the host environment security policy.
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { loadHostEnvSecurityPolicy } from "../src/infra/host-env-security-policy.js";
|
||||
import { resolveRepoRoot } from "./lib/repo-root.mjs";
|
||||
|
||||
const args = new Set(process.argv.slice(2));
|
||||
const checkOnly = args.has("--check");
|
||||
@@ -14,8 +14,7 @@ if (checkOnly && args.has("--write")) {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const here = path.dirname(fileURLToPath(import.meta.url));
|
||||
const repoRoot = path.resolve(here, "..");
|
||||
const repoRoot = resolveRepoRoot(import.meta.url);
|
||||
const policyPath = path.join(repoRoot, "src", "infra", "host-env-security-policy.json");
|
||||
const outputPath = path.join(
|
||||
repoRoot,
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import process from "node:process";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
const rootDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
||||
import { parseFlagArgs, stringFlag } from "./lib/arg-utils.mjs";
|
||||
import { resolveRepoRoot } from "./lib/repo-root.mjs";
|
||||
const rootDir = resolveRepoRoot(import.meta.url);
|
||||
const defaultManifestPath = path.join(rootDir, "apps", "ios", "Config", "AppStoreSigning.json");
|
||||
|
||||
function validateAppGroupId(value, context) {
|
||||
@@ -31,38 +31,41 @@ validates the checked-in manifest and renders local release xcconfig settings.
|
||||
}
|
||||
|
||||
function parseArgs(argv) {
|
||||
let mode = "";
|
||||
let manifestPath = defaultManifestPath;
|
||||
|
||||
for (let i = 0; i < argv.length; i += 1) {
|
||||
const arg = argv[i];
|
||||
if (arg === "--mode") {
|
||||
mode = readOptionValue(argv, i, arg);
|
||||
i += 1;
|
||||
} else if (arg === "--manifest") {
|
||||
manifestPath = path.resolve(readOptionValue(argv, i, arg));
|
||||
i += 1;
|
||||
} else if (arg === "-h" || arg === "--help") {
|
||||
usage();
|
||||
process.exit(0);
|
||||
} else {
|
||||
throw new Error(`Unknown argument: ${arg}`);
|
||||
}
|
||||
const options = { manifestPath: defaultManifestPath, mode: "" };
|
||||
const helpIndex = argv.findIndex((arg) => arg === "-h" || arg === "--help");
|
||||
parseFlagArgs(
|
||||
helpIndex === -1 ? argv : argv.slice(0, helpIndex),
|
||||
options,
|
||||
[
|
||||
stringFlag("--mode", "mode", {
|
||||
allowInline: false,
|
||||
missingValueMessage: "Missing value for --mode.",
|
||||
rejectShortOptions: true,
|
||||
repeatable: true,
|
||||
}),
|
||||
stringFlag("--manifest", "manifestPath", {
|
||||
allowInline: false,
|
||||
missingValueMessage: "Missing value for --manifest.",
|
||||
rejectShortOptions: true,
|
||||
repeatable: true,
|
||||
transform: path.resolve,
|
||||
}),
|
||||
],
|
||||
{
|
||||
ignoreDoubleDash: false,
|
||||
onUnhandledArg(arg) {
|
||||
throw new Error(`Unknown argument: ${arg}`);
|
||||
},
|
||||
},
|
||||
);
|
||||
if (helpIndex !== -1) {
|
||||
usage();
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
if (!mode) {
|
||||
if (!options.mode) {
|
||||
throw new Error("Missing required --mode.");
|
||||
}
|
||||
|
||||
return { mode, manifestPath };
|
||||
}
|
||||
|
||||
function readOptionValue(argv, index, option) {
|
||||
const value = argv[index + 1] ?? "";
|
||||
if (!value || value.startsWith("-")) {
|
||||
throw new Error(`Missing value for ${option}.`);
|
||||
}
|
||||
return value;
|
||||
return options;
|
||||
}
|
||||
|
||||
function readManifest(manifestPath) {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#!/usr/bin/env node
|
||||
import { existsSync, lstatSync, mkdirSync, readdirSync, writeFileSync } from "node:fs";
|
||||
import path from "node:path";
|
||||
|
||||
const repoRoot = path.resolve(import.meta.dirname, "..");
|
||||
import { resolveRepoRoot } from "./lib/repo-root.mjs";
|
||||
const repoRoot = resolveRepoRoot(import.meta.url);
|
||||
const iosRoot = path.join(repoRoot, "apps", "ios");
|
||||
const outputPath = path.join(iosRoot, "SwiftSources.input.xcfilelist");
|
||||
|
||||
|
||||
@@ -17,12 +17,24 @@ export function stripLeadingPackageManagerSeparator(argv: string[]): string[];
|
||||
export function stringFlag<T extends FlagArgs>(
|
||||
flag: string,
|
||||
key: string,
|
||||
options?: { rejectShortOptions?: boolean },
|
||||
options?: {
|
||||
allowEmpty?: boolean;
|
||||
allowInline?: boolean;
|
||||
missingValueMessage?: string;
|
||||
rejectShortOptions?: boolean;
|
||||
repeatable?: boolean;
|
||||
transform?: (value: string) => unknown;
|
||||
},
|
||||
): FlagSpec<T>;
|
||||
export function stringListFlag<T extends FlagArgs>(
|
||||
flag: string,
|
||||
key: string,
|
||||
options?: { rejectShortOptions?: boolean },
|
||||
options?: {
|
||||
allowEmpty?: boolean;
|
||||
allowInline?: boolean;
|
||||
missingValueMessage?: string;
|
||||
rejectShortOptions?: boolean;
|
||||
},
|
||||
): FlagSpec<T>;
|
||||
export function intFlag<T extends FlagArgs>(
|
||||
flag: string,
|
||||
@@ -33,6 +45,7 @@ export function booleanFlag<T extends FlagArgs>(
|
||||
flag: string,
|
||||
key: string,
|
||||
value?: unknown,
|
||||
options?: { repeatable?: boolean },
|
||||
): FlagSpec<T>;
|
||||
export function parseFlagArgs<T extends FlagArgs>(
|
||||
argv: readonly string[],
|
||||
@@ -40,6 +53,7 @@ export function parseFlagArgs<T extends FlagArgs>(
|
||||
specs: readonly FlagSpec<T>[],
|
||||
options?: {
|
||||
allowUnknownOptions?: boolean;
|
||||
duplicateOptionMessage?: (flag: string) => string;
|
||||
ignoreDoubleDash?: boolean;
|
||||
onUnhandledArg?: (arg: string, args: T) => "handled" | void;
|
||||
},
|
||||
|
||||
+22
-16
@@ -1,4 +1,7 @@
|
||||
// Shared argument parsing helpers for repository scripts.
|
||||
function failFlagParse(message) {
|
||||
throw new Error(message);
|
||||
}
|
||||
/**
|
||||
* Read a flag value from `--flag value` or `--flag=value` arguments.
|
||||
* @internal Shared repository-script contract.
|
||||
@@ -25,7 +28,7 @@ export function stripLeadingPackageManagerSeparator(argv) {
|
||||
}
|
||||
|
||||
function isMissingStringFlagValue(value, options = {}) {
|
||||
if (!value) {
|
||||
if (value === undefined || (!value && options.allowEmpty !== true)) {
|
||||
return true;
|
||||
}
|
||||
if (value.startsWith("--")) {
|
||||
@@ -35,10 +38,10 @@ function isMissingStringFlagValue(value, options = {}) {
|
||||
}
|
||||
|
||||
function consumeStringFlag(argv, index, flag, options = {}) {
|
||||
const inlineValue = readInlineFlagValue(argv[index], flag);
|
||||
const inlineValue = options.allowInline === false ? null : readInlineFlagValue(argv[index], flag);
|
||||
if (inlineValue !== null) {
|
||||
if (isMissingStringFlagValue(inlineValue, options)) {
|
||||
throw new Error(`${flag} requires a value`);
|
||||
failFlagParse(options.missingValueMessage ?? `${flag} requires a value`);
|
||||
}
|
||||
return {
|
||||
nextIndex: index,
|
||||
@@ -50,7 +53,7 @@ function consumeStringFlag(argv, index, flag, options = {}) {
|
||||
}
|
||||
const value = argv[index + 1];
|
||||
if (isMissingStringFlagValue(value, options)) {
|
||||
throw new Error(`${flag} requires a value`);
|
||||
failFlagParse(options.missingValueMessage ?? `${flag} requires a value`);
|
||||
}
|
||||
return {
|
||||
nextIndex: index + 1,
|
||||
@@ -66,7 +69,7 @@ function consumeIntFlag(argv, index, flag, options = {}) {
|
||||
const parsed = parseIntegerFlagValue(raw.value, flag);
|
||||
const min = options.min ?? Number.NEGATIVE_INFINITY;
|
||||
if (parsed < min) {
|
||||
throw new Error(`${flag} must be at least ${min}`);
|
||||
failFlagParse(`${flag} must be at least ${min}`);
|
||||
}
|
||||
return {
|
||||
nextIndex: raw.nextIndex,
|
||||
@@ -83,7 +86,7 @@ function readFlagOptionValue(argv, index, flag) {
|
||||
const inlineValue = readInlineFlagValue(argv[index], flag);
|
||||
if (inlineValue !== null) {
|
||||
if (!inlineValue) {
|
||||
throw new Error(`${flag} requires a value`);
|
||||
failFlagParse(`${flag} requires a value`);
|
||||
}
|
||||
return { nextIndex: index, value: inlineValue };
|
||||
}
|
||||
@@ -92,7 +95,7 @@ function readFlagOptionValue(argv, index, flag) {
|
||||
}
|
||||
const value = argv[index + 1];
|
||||
if (!value || value.startsWith("--")) {
|
||||
throw new Error(`${flag} requires a value`);
|
||||
failFlagParse(`${flag} requires a value`);
|
||||
}
|
||||
return { nextIndex: index + 1, value };
|
||||
}
|
||||
@@ -100,11 +103,11 @@ function readFlagOptionValue(argv, index, flag) {
|
||||
function parseIntegerFlagValue(raw, flag) {
|
||||
const text = String(raw).trim();
|
||||
if (!/^-?\d+$/u.test(text)) {
|
||||
throw new Error(`${flag} must be an integer`);
|
||||
failFlagParse(`${flag} must be an integer`);
|
||||
}
|
||||
const parsed = Number(text);
|
||||
if (!Number.isSafeInteger(parsed)) {
|
||||
throw new Error(`${flag} must be a safe integer`);
|
||||
failFlagParse(`${flag} must be a safe integer`);
|
||||
}
|
||||
return parsed;
|
||||
}
|
||||
@@ -120,9 +123,9 @@ export function stringFlag(flag, key, options = {}) {
|
||||
return {
|
||||
flag,
|
||||
nextIndex: option.nextIndex,
|
||||
repeatable: false,
|
||||
repeatable: options.repeatable === true,
|
||||
apply(target) {
|
||||
target[key] = option.value;
|
||||
target[key] = options.transform ? options.transform(option.value) : option.value;
|
||||
},
|
||||
};
|
||||
},
|
||||
@@ -184,7 +187,7 @@ export function intFlag(flag, key, options) {
|
||||
}
|
||||
|
||||
/** Create a flag spec that assigns a fixed boolean-like value when present. */
|
||||
export function booleanFlag(flag, key, value = true) {
|
||||
export function booleanFlag(flag, key, value = true, options = {}) {
|
||||
return {
|
||||
consume(argv, index) {
|
||||
if (argv[index] !== flag) {
|
||||
@@ -193,7 +196,7 @@ export function booleanFlag(flag, key, value = true) {
|
||||
return {
|
||||
flag,
|
||||
nextIndex: index,
|
||||
repeatable: false,
|
||||
repeatable: options.repeatable === true,
|
||||
apply(target) {
|
||||
target[key] = value;
|
||||
},
|
||||
@@ -218,11 +221,14 @@ export function parseFlagArgs(argv, args, specs, options = {}) {
|
||||
continue;
|
||||
}
|
||||
if (typeof option.flag !== "string" || !option.flag) {
|
||||
throw new Error("parseFlagArgs specs must declare a flag for consumed options");
|
||||
failFlagParse("parseFlagArgs specs must declare a flag for consumed options");
|
||||
}
|
||||
if (option.repeatable !== true) {
|
||||
if (seenFlags.has(option.flag)) {
|
||||
throw new Error(`${option.flag} was provided more than once`);
|
||||
failFlagParse(
|
||||
options.duplicateOptionMessage?.(option.flag) ??
|
||||
`${option.flag} was provided more than once`,
|
||||
);
|
||||
}
|
||||
seenFlags.add(option.flag);
|
||||
}
|
||||
@@ -239,7 +245,7 @@ export function parseFlagArgs(argv, args, specs, options = {}) {
|
||||
continue;
|
||||
}
|
||||
if (!options.allowUnknownOptions && arg.startsWith("-")) {
|
||||
throw new Error(`Unknown option: ${arg}`);
|
||||
failFlagParse(`Unknown option: ${arg}`);
|
||||
}
|
||||
}
|
||||
return args;
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
// Shared scanner for guard scripts that reject disallowed source callsites.
|
||||
import { promises as fs } from "node:fs";
|
||||
import path from "node:path";
|
||||
import {
|
||||
collectTypeScriptFilesFromRoots,
|
||||
resolveRepoRoot,
|
||||
resolveSourceRoots,
|
||||
} from "./ts-guard-utils.mjs";
|
||||
import { resolveRepoRoot } from "./repo-root.mjs";
|
||||
import { collectTypeScriptFilesFromRoots, resolveSourceRoots } from "./ts-guard-utils.mjs";
|
||||
|
||||
/** Run a callsite guard over TypeScript roots and exit non-zero on violations. */
|
||||
export async function runCallsiteGuard(params) {
|
||||
|
||||
@@ -10,11 +10,8 @@ import {
|
||||
resolveRepoSpecifier,
|
||||
writeLine,
|
||||
} from "./guard-inventory-utils.mjs";
|
||||
import {
|
||||
collectTypeScriptFilesFromRoots,
|
||||
resolveRepoRoot,
|
||||
resolveSourceRoots,
|
||||
} from "./ts-guard-utils.mjs";
|
||||
import { resolveRepoRoot } from "./repo-root.mjs";
|
||||
import { collectTypeScriptFilesFromRoots, resolveSourceRoots } from "./ts-guard-utils.mjs";
|
||||
|
||||
const repoRoot = resolveRepoRoot(import.meta.url);
|
||||
const DEFAULT_BOUNDARY_SOURCE_MAX_BYTES = 2 * 1024 * 1024;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Builds shared repo/source-root context for pairing guard scripts.
|
||||
import path from "node:path";
|
||||
import { resolveRepoRoot, resolveSourceRoots } from "./ts-guard-utils.mjs";
|
||||
import { resolveRepoRoot } from "./repo-root.mjs";
|
||||
import { resolveSourceRoots } from "./ts-guard-utils.mjs";
|
||||
|
||||
/** Create repo root and source root helpers for pairing guard scanners. */
|
||||
export function createPairingGuardContext(importMetaUrl) {
|
||||
|
||||
@@ -4,6 +4,12 @@ import type {
|
||||
ExecFileSyncOptionsWithStringEncoding,
|
||||
} from "node:child_process";
|
||||
|
||||
type ExecGhReadImpl = (
|
||||
command: string,
|
||||
args: readonly string[],
|
||||
options: ExecFileSyncOptions,
|
||||
) => string | Uint8Array<ArrayBuffer>;
|
||||
|
||||
export function plainGhEnv(env?: NodeJS.ProcessEnv): {
|
||||
[key: string]: string | undefined;
|
||||
};
|
||||
@@ -20,6 +26,26 @@ export function execPlainGh(
|
||||
args: readonly string[],
|
||||
options?: ExecFileSyncOptions,
|
||||
): string | Uint8Array<ArrayBuffer>;
|
||||
export function execGhRead(
|
||||
args: readonly string[],
|
||||
options: ExecFileSyncOptionsWithStringEncoding,
|
||||
params?: { execFileSyncImpl?: ExecGhReadImpl },
|
||||
): string;
|
||||
export function execGhRead(
|
||||
args: readonly string[],
|
||||
options?: ExecFileSyncOptionsWithBufferEncoding,
|
||||
params?: { execFileSyncImpl?: ExecGhReadImpl },
|
||||
): Uint8Array<ArrayBuffer>;
|
||||
export function execGhRead(
|
||||
args: readonly string[],
|
||||
options?: ExecFileSyncOptions,
|
||||
params?: { execFileSyncImpl?: ExecGhReadImpl },
|
||||
): string | Uint8Array<ArrayBuffer>;
|
||||
export function execGhJson(
|
||||
args: readonly string[],
|
||||
options?: ExecFileSyncOptions,
|
||||
params?: { execFileSyncImpl?: ExecGhReadImpl },
|
||||
): unknown;
|
||||
export function execGhApiRead(
|
||||
endpoint: string,
|
||||
options: ExecFileSyncOptionsWithStringEncoding,
|
||||
|
||||
@@ -88,13 +88,22 @@ export function execPlainGh(args, options = {}) {
|
||||
});
|
||||
}
|
||||
|
||||
export function execGhApiRead(endpoint, options = {}) {
|
||||
export function execGhRead(args, options = {}, params = {}) {
|
||||
const env = plainGhEnv(options.env ?? process.env);
|
||||
// Keep reads on the normal PATH shim; OPENCLAW_GH_BIN pins maintainer writes.
|
||||
// Reads stay on the cache-aware PATH shim; the explicit binary is reserved for writes.
|
||||
delete env.OPENCLAW_GH_BIN;
|
||||
return execFileSync("gh", ["api", endpoint, "--method", "GET"], {
|
||||
const execFileSyncImpl = params.execFileSyncImpl ?? execFileSync;
|
||||
return execFileSyncImpl("gh", args, {
|
||||
...options,
|
||||
env,
|
||||
maxBuffer: options.maxBuffer ?? PLAIN_GH_MAX_BUFFER_BYTES,
|
||||
});
|
||||
}
|
||||
|
||||
export function execGhJson(args, options = {}, params = {}) {
|
||||
return JSON.parse(execGhRead(args, { ...options, encoding: "utf8" }, params));
|
||||
}
|
||||
|
||||
export function execGhApiRead(endpoint, options = {}) {
|
||||
return execGhRead(["api", endpoint, "--method", "GET"], options);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
/** Resolves the repository root by walking upward from the caller module. */
|
||||
export function resolveRepoRoot(importMetaUrl: string): string;
|
||||
@@ -0,0 +1,20 @@
|
||||
import { existsSync } from "node:fs";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
/** Resolves the repository root by walking upward from the caller module. */
|
||||
export function resolveRepoRoot(importMetaUrl) {
|
||||
let dir = path.dirname(fileURLToPath(importMetaUrl));
|
||||
const { root } = path.parse(dir);
|
||||
while (dir !== root) {
|
||||
if (
|
||||
existsSync(path.join(dir, ".git")) ||
|
||||
(existsSync(path.join(dir, "package.json")) &&
|
||||
existsSync(path.join(dir, "pnpm-workspace.yaml")))
|
||||
) {
|
||||
return dir;
|
||||
}
|
||||
dir = path.dirname(dir);
|
||||
}
|
||||
return path.resolve(path.dirname(fileURLToPath(importMetaUrl)), "..", "..");
|
||||
}
|
||||
@@ -1,17 +1,7 @@
|
||||
// Parses report CLI output arguments and writes optional artifacts.
|
||||
import { mkdir, writeFile } from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
|
||||
/**
|
||||
* Parses shared `--root`, `--json`, and `--markdown` flags for report scripts.
|
||||
*/
|
||||
function readReportOptionValue(argv, index, optionName) {
|
||||
const value = argv[index + 1];
|
||||
if (value === undefined || value === "" || value.startsWith("-")) {
|
||||
throw new Error(`Expected ${optionName} <value>.`);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
import { parseFlagArgs, stringFlag } from "./arg-utils.mjs";
|
||||
|
||||
export function parseReportCliArgs(argv) {
|
||||
const options = {
|
||||
@@ -19,37 +9,27 @@ export function parseReportCliArgs(argv) {
|
||||
jsonPath: null,
|
||||
markdownPath: null,
|
||||
};
|
||||
const seen = new Set();
|
||||
const setOnce = (flag, key, value) => {
|
||||
if (seen.has(flag)) {
|
||||
throw new Error(`${flag} was provided more than once.`);
|
||||
}
|
||||
seen.add(flag);
|
||||
options[key] = value;
|
||||
};
|
||||
for (let index = 0; index < argv.length; index += 1) {
|
||||
const arg = argv[index];
|
||||
if (arg === "--") {
|
||||
continue;
|
||||
}
|
||||
if (arg === "--root") {
|
||||
setOnce(arg, "rootDir", readReportOptionValue(argv, index, arg));
|
||||
index += 1;
|
||||
continue;
|
||||
}
|
||||
if (arg === "--json") {
|
||||
setOnce(arg, "jsonPath", readReportOptionValue(argv, index, arg));
|
||||
index += 1;
|
||||
continue;
|
||||
}
|
||||
if (arg === "--markdown") {
|
||||
setOnce(arg, "markdownPath", readReportOptionValue(argv, index, arg));
|
||||
index += 1;
|
||||
continue;
|
||||
}
|
||||
throw new Error(`Unsupported argument: ${arg}`);
|
||||
}
|
||||
return options;
|
||||
return parseFlagArgs(
|
||||
argv,
|
||||
options,
|
||||
[
|
||||
["--root", "rootDir"],
|
||||
["--json", "jsonPath"],
|
||||
["--markdown", "markdownPath"],
|
||||
].map(([flag, key]) =>
|
||||
stringFlag(flag, key, {
|
||||
allowInline: false,
|
||||
missingValueMessage: `Expected ${flag} <value>.`,
|
||||
rejectShortOptions: true,
|
||||
}),
|
||||
),
|
||||
{
|
||||
duplicateOptionMessage: (flag) => `${flag} was provided more than once.`,
|
||||
onUnhandledArg(arg) {
|
||||
throw new Error(`Unsupported argument: ${arg}`);
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
/**
|
||||
* Resolves the repository root by walking upward from the caller module.
|
||||
*/
|
||||
export function resolveRepoRoot(importMetaUrl: string): string;
|
||||
/**
|
||||
* Converts repo-relative source roots into absolute paths.
|
||||
*/
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Shared TypeScript AST and source-file helpers for guard scripts.
|
||||
import { existsSync, promises as fs } from "node:fs";
|
||||
import { promises as fs } from "node:fs";
|
||||
import { createRequire } from "node:module";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
@@ -14,25 +14,6 @@ function getTypeScript() {
|
||||
|
||||
const baseTestSuffixes = [".test.ts", ".test-utils.ts", ".test-harness.ts", ".e2e-harness.ts"];
|
||||
|
||||
/**
|
||||
* Resolves the repository root by walking upward from the caller module.
|
||||
*/
|
||||
export function resolveRepoRoot(importMetaUrl) {
|
||||
// Walk up from the caller's directory until we find the repo root (.git).
|
||||
// This handles callers at any depth (scripts/*.mjs, scripts/lib/*.mjs, etc.)
|
||||
// instead of assuming a fixed number of parent traversals.
|
||||
let dir = path.dirname(fileURLToPath(importMetaUrl));
|
||||
const { root } = path.parse(dir);
|
||||
while (dir !== root) {
|
||||
if (existsSync(path.join(dir, ".git"))) {
|
||||
return dir;
|
||||
}
|
||||
dir = path.dirname(dir);
|
||||
}
|
||||
// Fallback: two levels up (original behavior).
|
||||
return path.resolve(path.dirname(fileURLToPath(importMetaUrl)), "..", "..");
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts repo-relative source roots into absolute paths.
|
||||
*/
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
import fs from "node:fs";
|
||||
import { createRequire } from "node:module";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath, pathToFileURL } from "node:url";
|
||||
import { pathToFileURL } from "node:url";
|
||||
import { booleanFlag, parseFlagArgs } from "./lib/arg-utils.mjs";
|
||||
import {
|
||||
deprecatedBarrelPluginSdkEntrypoints,
|
||||
deprecatedPublicPluginSdkEntrypoints,
|
||||
@@ -13,8 +14,9 @@ import {
|
||||
privateLocalOnlyPluginSdkEntrypoints,
|
||||
publicPluginSdkEntrypoints,
|
||||
} from "./lib/plugin-sdk-entries.mjs";
|
||||
import { resolveRepoRoot } from "./lib/repo-root.mjs";
|
||||
|
||||
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
||||
const repoRoot = resolveRepoRoot(import.meta.url);
|
||||
const require = createRequire(import.meta.url);
|
||||
let ts;
|
||||
|
||||
@@ -30,19 +32,21 @@ Options:
|
||||
}
|
||||
|
||||
function parsePluginSdkSurfaceReportArgs(argv) {
|
||||
const args = { check: false, help: false };
|
||||
for (const arg of argv) {
|
||||
if (arg === "--check") {
|
||||
args.check = true;
|
||||
continue;
|
||||
}
|
||||
if (arg === "--help" || arg === "-h") {
|
||||
args.help = true;
|
||||
continue;
|
||||
}
|
||||
throw new Error(`Unknown plugin SDK surface report option: ${arg}`);
|
||||
}
|
||||
return args;
|
||||
return parseFlagArgs(
|
||||
argv,
|
||||
{ check: false, help: false },
|
||||
[
|
||||
booleanFlag("--check", "check", true, { repeatable: true }),
|
||||
booleanFlag("--help", "help", true, { repeatable: true }),
|
||||
booleanFlag("-h", "help", true, { repeatable: true }),
|
||||
],
|
||||
{
|
||||
ignoreDoubleDash: false,
|
||||
onUnhandledArg(arg) {
|
||||
throw new Error(`Unknown plugin SDK surface report option: ${arg}`);
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
const publicEntrypointSet = new Set(publicPluginSdkEntrypoints);
|
||||
const localOnlyEntrypointSet = new Set(privateLocalOnlyPluginSdkEntrypoints);
|
||||
|
||||
@@ -12,9 +12,9 @@ import {
|
||||
} from "./lib/local-heavy-check-runtime.mjs";
|
||||
import { parsePositiveInt } from "./lib/numeric-options.mjs";
|
||||
import { pluginSdkEntrypoints, productionPluginSdkEntrypoints } from "./lib/plugin-sdk-entries.mjs";
|
||||
import { resolveRepoRoot } from "./lib/repo-root.mjs";
|
||||
import { resolveWindowsTaskkillPath } from "./lib/windows-taskkill.mjs";
|
||||
|
||||
const repoRoot = resolve(import.meta.dirname, "..");
|
||||
const repoRoot = resolveRepoRoot(import.meta.url);
|
||||
const runTsgoScript = path.join(repoRoot, "scripts/run-tsgo.mjs");
|
||||
const TYPE_INPUT_EXTENSIONS = new Set([".ts", ".tsx", ".d.ts", ".js", ".mjs", ".json"]);
|
||||
const VALID_MODES = new Set(["all", "package-boundary"]);
|
||||
|
||||
@@ -12,8 +12,8 @@ import {
|
||||
shouldAcquireLocalHeavyCheckLockForTsgo,
|
||||
} from "./lib/local-heavy-check-runtime.mjs";
|
||||
import { createManagedCommandInvocation } from "./lib/managed-child-process.mjs";
|
||||
|
||||
const repoRoot = path.resolve(import.meta.dirname, "..");
|
||||
import { resolveRepoRoot } from "./lib/repo-root.mjs";
|
||||
const repoRoot = resolveRepoRoot(import.meta.url);
|
||||
const artifactRoot = path.resolve(repoRoot, ".artifacts/tsgo-profile");
|
||||
const tsgoPath = resolveRepoToolBinPath("tsgo", { cwd: repoRoot });
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { execFileSync } from "node:child_process";
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath, pathToFileURL } from "node:url";
|
||||
import { pathToFileURL } from "node:url";
|
||||
import { resolveRepoRoot } from "./lib/repo-root.mjs";
|
||||
|
||||
const MODEL_CATALOG_MIN_VERSION = "2026.7.0";
|
||||
export const MODEL_CATALOG_MIN_MODELS = 200;
|
||||
@@ -14,7 +15,7 @@ const PRICING_FETCH_TIMEOUT_MS = 60_000;
|
||||
const MAX_PRICING_CATALOG_BYTES = 5 * 1024 * 1024;
|
||||
const BUNDLE_SIZE_WARNING_BYTES = 2 * 1024 * 1024;
|
||||
const CLIENT_BUNDLE_LIMIT_BYTES = 4 * 1024 * 1024;
|
||||
const defaultRootDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
||||
const defaultRootDir = resolveRepoRoot(import.meta.url);
|
||||
|
||||
function requireOptionValue(args, index, flag) {
|
||||
const value = args[index + 1]?.trim();
|
||||
|
||||
@@ -4,8 +4,14 @@ import { spawnSync } from "node:child_process";
|
||||
import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
|
||||
import path from "node:path";
|
||||
import { pathToFileURL } from "node:url";
|
||||
import {
|
||||
booleanFlag,
|
||||
parseFlagArgs,
|
||||
stringFlag,
|
||||
stripLeadingPackageManagerSeparator,
|
||||
} from "./lib/arg-utils.mjs";
|
||||
|
||||
interface Options {
|
||||
type Options = {
|
||||
beta: string;
|
||||
model: string;
|
||||
providerMode: string;
|
||||
@@ -13,7 +19,7 @@ interface Options {
|
||||
repo: string;
|
||||
skipParallels: boolean;
|
||||
skipTelegram: boolean;
|
||||
}
|
||||
};
|
||||
|
||||
export type RunOptions = {
|
||||
capture?: boolean;
|
||||
@@ -52,6 +58,8 @@ Options:
|
||||
|
||||
export function parseArgs(argv: string[]): Options {
|
||||
const args = stripLeadingPackageManagerSeparator(argv);
|
||||
const terminatorIndex = args.indexOf("--");
|
||||
const cliArgs = terminatorIndex === -1 ? args : args.slice(0, terminatorIndex);
|
||||
const options: Options = {
|
||||
beta: "beta",
|
||||
model: "openai/gpt-5.4",
|
||||
@@ -61,39 +69,38 @@ export function parseArgs(argv: string[]): Options {
|
||||
skipParallels: false,
|
||||
skipTelegram: false,
|
||||
};
|
||||
parseArgv: for (let i = 0; i < args.length; i++) {
|
||||
const arg = args[i];
|
||||
switch (arg) {
|
||||
case "--":
|
||||
break parseArgv;
|
||||
case "--beta":
|
||||
options.beta = requireValue(args, ++i, arg);
|
||||
break;
|
||||
case "--model":
|
||||
options.model = requireValue(args, ++i, arg);
|
||||
break;
|
||||
case "--provider-mode":
|
||||
options.providerMode = requireValue(args, ++i, arg);
|
||||
break;
|
||||
case "--ref":
|
||||
options.ref = requireValue(args, ++i, arg);
|
||||
break;
|
||||
case "--repo":
|
||||
options.repo = requireValue(args, ++i, arg);
|
||||
break;
|
||||
case "--skip-parallels":
|
||||
options.skipParallels = true;
|
||||
break;
|
||||
case "--skip-telegram":
|
||||
options.skipTelegram = true;
|
||||
break;
|
||||
case "-h":
|
||||
case "--help":
|
||||
process.stdout.write(usage());
|
||||
process.exit(0);
|
||||
default:
|
||||
const helpIndex = cliArgs.findIndex((arg) => arg === "-h" || arg === "--help");
|
||||
parseFlagArgs(
|
||||
helpIndex === -1 ? cliArgs : cliArgs.slice(0, helpIndex),
|
||||
options,
|
||||
[
|
||||
...(
|
||||
[
|
||||
["--beta", "beta"],
|
||||
["--model", "model"],
|
||||
["--provider-mode", "providerMode"],
|
||||
["--ref", "ref"],
|
||||
["--repo", "repo"],
|
||||
] as const
|
||||
).map(([flag, key]) =>
|
||||
stringFlag(flag, key, {
|
||||
allowInline: false,
|
||||
rejectShortOptions: true,
|
||||
repeatable: true,
|
||||
}),
|
||||
),
|
||||
booleanFlag("--skip-parallels", "skipParallels", true, { repeatable: true }),
|
||||
booleanFlag("--skip-telegram", "skipTelegram", true, { repeatable: true }),
|
||||
],
|
||||
{
|
||||
onUnhandledArg(arg) {
|
||||
throw new Error(`unknown option: ${arg}`);
|
||||
}
|
||||
},
|
||||
},
|
||||
);
|
||||
if (helpIndex !== -1) {
|
||||
process.stdout.write(usage());
|
||||
process.exit(0);
|
||||
}
|
||||
if (options.skipParallels && options.skipTelegram) {
|
||||
throw new Error("--skip-parallels and --skip-telegram cannot be used together");
|
||||
@@ -101,18 +108,6 @@ export function parseArgs(argv: string[]): Options {
|
||||
return options;
|
||||
}
|
||||
|
||||
function stripLeadingPackageManagerSeparator(argv: string[]): string[] {
|
||||
return argv[0] === "--" ? argv.slice(1) : argv;
|
||||
}
|
||||
|
||||
function requireValue(argv: string[], index: number, flag: string): string {
|
||||
const value = argv[index];
|
||||
if (!value || value.startsWith("-")) {
|
||||
throw new Error(`${flag} requires a value`);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
const CAPTURE_MAX_BUFFER_BYTES = 32 * 1024 * 1024;
|
||||
const DEFAULT_COMMAND_TIMEOUT_MS = readPositiveInt(
|
||||
process.env.OPENCLAW_RELEASE_BETA_SMOKE_COMMAND_MS,
|
||||
|
||||
@@ -17,7 +17,13 @@ import { tmpdir } from "node:os";
|
||||
import { basename, dirname, join, resolve as resolvePath } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { isDeepStrictEqual } from "node:util";
|
||||
import { stripLeadingPackageManagerSeparator } from "./lib/arg-utils.mjs";
|
||||
import {
|
||||
booleanFlag,
|
||||
parseFlagArgs,
|
||||
stringFlag,
|
||||
stringListFlag,
|
||||
stripLeadingPackageManagerSeparator,
|
||||
} from "./lib/arg-utils.mjs";
|
||||
import { readBoundedResponseText } from "./lib/bounded-response.mjs";
|
||||
import {
|
||||
dedicatedSectionVersionForTag,
|
||||
@@ -109,14 +115,6 @@ Options:
|
||||
`;
|
||||
}
|
||||
|
||||
function requireValue(argv, index, flag) {
|
||||
const value = argv[index];
|
||||
if (!value || value.startsWith("-")) {
|
||||
throw new Error(`${flag} requires a value`);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
export function releaseBranchForTag(tag) {
|
||||
if (tag.includes("-alpha.")) {
|
||||
return "";
|
||||
@@ -130,6 +128,8 @@ export function releaseBranchForTag(tag) {
|
||||
*/
|
||||
export function parseArgs(argv) {
|
||||
const args = stripLeadingPackageManagerSeparator(argv);
|
||||
const terminatorIndex = args.indexOf("--");
|
||||
const cliArgs = terminatorIndex === -1 ? args : args.slice(0, terminatorIndex);
|
||||
const options = {
|
||||
repo: DEFAULT_REPO,
|
||||
provider: DEFAULT_PROVIDER,
|
||||
@@ -154,86 +154,49 @@ export function parseArgs(argv) {
|
||||
windowsNodeInstallerDigests: "",
|
||||
outputDir: "",
|
||||
};
|
||||
const seen = new Set();
|
||||
const setOnce = (flag, key, value) => {
|
||||
if (seen.has(flag)) {
|
||||
throw new Error(`${flag} was provided more than once`);
|
||||
}
|
||||
seen.add(flag);
|
||||
options[key] = value;
|
||||
};
|
||||
parseArgv: for (let index = 0; index < args.length; index += 1) {
|
||||
const arg = args[index];
|
||||
switch (arg) {
|
||||
case "--":
|
||||
break parseArgv;
|
||||
case "--tag":
|
||||
setOnce(arg, "tag", requireValue(args, ++index, arg));
|
||||
break;
|
||||
case "--target-sha":
|
||||
setOnce(arg, "targetSha", requireValue(args, ++index, arg));
|
||||
break;
|
||||
case "--workflow-ref":
|
||||
setOnce(arg, "workflowRef", requireValue(args, ++index, arg));
|
||||
break;
|
||||
case "--repo":
|
||||
setOnce(arg, "repo", requireValue(args, ++index, arg));
|
||||
break;
|
||||
case "--full-release-run":
|
||||
setOnce(arg, "fullReleaseRunId", requireValue(args, ++index, arg));
|
||||
break;
|
||||
case "--npm-preflight-run":
|
||||
setOnce(arg, "npmPreflightRunId", requireValue(args, ++index, arg));
|
||||
break;
|
||||
case "--windows-node-tag":
|
||||
setOnce(arg, "windowsNodeTag", requireValue(args, ++index, arg));
|
||||
break;
|
||||
case "--skip-dispatch":
|
||||
setOnce(arg, "skipDispatch", true);
|
||||
break;
|
||||
case "--skip-local-generated-check":
|
||||
setOnce(arg, "skipLocalGeneratedCheck", true);
|
||||
break;
|
||||
case "--skip-parallels":
|
||||
setOnce(arg, "skipParallels", true);
|
||||
break;
|
||||
case "--parallels-registry-package-artifact":
|
||||
options.parallelsRegistryPackageArtifactDirs.push(requireValue(args, ++index, arg));
|
||||
break;
|
||||
case "--skip-telegram":
|
||||
setOnce(arg, "skipTelegram", true);
|
||||
break;
|
||||
case "--telegram-provider-mode":
|
||||
setOnce(arg, "telegramProviderMode", requireValue(args, ++index, arg));
|
||||
break;
|
||||
case "--provider":
|
||||
setOnce(arg, "provider", requireValue(args, ++index, arg));
|
||||
break;
|
||||
case "--mode":
|
||||
setOnce(arg, "mode", requireValue(args, ++index, arg));
|
||||
break;
|
||||
case "--release-profile":
|
||||
setOnce(arg, "releaseProfile", requireValue(args, ++index, arg));
|
||||
break;
|
||||
case "--npm-dist-tag":
|
||||
setOnce(arg, "npmDistTag", requireValue(args, ++index, arg));
|
||||
break;
|
||||
case "--plugin-publish-scope":
|
||||
setOnce(arg, "pluginPublishScope", requireValue(args, ++index, arg));
|
||||
break;
|
||||
case "--plugins":
|
||||
setOnce(arg, "plugins", requireValue(args, ++index, arg));
|
||||
break;
|
||||
case "--output-dir":
|
||||
setOnce(arg, "outputDir", requireValue(args, ++index, arg));
|
||||
break;
|
||||
case "-h":
|
||||
case "--help":
|
||||
process.stdout.write(usage());
|
||||
process.exit(0);
|
||||
default:
|
||||
const helpIndex = cliArgs.findIndex((arg) => arg === "-h" || arg === "--help");
|
||||
parseFlagArgs(
|
||||
helpIndex === -1 ? cliArgs : cliArgs.slice(0, helpIndex),
|
||||
options,
|
||||
[
|
||||
...[
|
||||
["--tag", "tag"],
|
||||
["--target-sha", "targetSha"],
|
||||
["--workflow-ref", "workflowRef"],
|
||||
["--repo", "repo"],
|
||||
["--full-release-run", "fullReleaseRunId"],
|
||||
["--npm-preflight-run", "npmPreflightRunId"],
|
||||
["--windows-node-tag", "windowsNodeTag"],
|
||||
["--telegram-provider-mode", "telegramProviderMode"],
|
||||
["--provider", "provider"],
|
||||
["--mode", "mode"],
|
||||
["--release-profile", "releaseProfile"],
|
||||
["--npm-dist-tag", "npmDistTag"],
|
||||
["--plugin-publish-scope", "pluginPublishScope"],
|
||||
["--plugins", "plugins"],
|
||||
["--output-dir", "outputDir"],
|
||||
].map(([flag, key]) =>
|
||||
stringFlag(flag, key, { allowInline: false, rejectShortOptions: true }),
|
||||
),
|
||||
stringListFlag(
|
||||
"--parallels-registry-package-artifact",
|
||||
"parallelsRegistryPackageArtifactDirs",
|
||||
{ allowInline: false, rejectShortOptions: true },
|
||||
),
|
||||
booleanFlag("--skip-dispatch", "skipDispatch"),
|
||||
booleanFlag("--skip-local-generated-check", "skipLocalGeneratedCheck"),
|
||||
booleanFlag("--skip-parallels", "skipParallels"),
|
||||
booleanFlag("--skip-telegram", "skipTelegram"),
|
||||
],
|
||||
{
|
||||
onUnhandledArg(arg) {
|
||||
throw new Error(`unknown option: ${arg}`);
|
||||
}
|
||||
},
|
||||
},
|
||||
);
|
||||
if (helpIndex !== -1) {
|
||||
process.stdout.write(usage());
|
||||
process.exit(0);
|
||||
}
|
||||
if (!options.tag) {
|
||||
throw new Error("--tag is required");
|
||||
|
||||
@@ -13,11 +13,13 @@ import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { pipeline } from "node:stream/promises";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { booleanFlag, parseFlagArgs, stringFlag } from "./lib/arg-utils.mjs";
|
||||
import { resolveNpmJsonEntries } from "./lib/npm-json-output.mjs";
|
||||
import { resolveRepoRoot } from "./lib/repo-root.mjs";
|
||||
import { resolveWindowsTaskkillPath } from "./lib/windows-taskkill.mjs";
|
||||
import { resolveNpmRunner } from "./npm-runner.mjs";
|
||||
|
||||
const ROOT_DIR = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
||||
const ROOT_DIR = resolveRepoRoot(import.meta.url);
|
||||
const DEFAULT_OUTPUT_NAME = "openclaw-current.tgz";
|
||||
const PACKAGE_URL_DOWNLOAD_TIMEOUT_MS = 60_000;
|
||||
const PACKAGE_URL_MAX_BYTES = 250 * 1024 * 1024;
|
||||
@@ -98,57 +100,49 @@ export function parseArgs(argv) {
|
||||
trustedSourceId: "",
|
||||
trustedSourcePolicy: TRUSTED_PACKAGE_SOURCE_POLICY,
|
||||
};
|
||||
const seen = new Set();
|
||||
const setOnce = (flag, key, value) => {
|
||||
if (seen.has(flag)) {
|
||||
throw new Error(`${flag} was provided more than once`);
|
||||
}
|
||||
seen.add(flag);
|
||||
options[key] = value;
|
||||
};
|
||||
for (let index = 0; index < argv.length; index += 1) {
|
||||
const arg = argv[index];
|
||||
const readValue = (name, readOptions = {}) => {
|
||||
const value = argv[(index += 1)];
|
||||
if (
|
||||
value === undefined ||
|
||||
(!readOptions.allowEmpty && value === "") ||
|
||||
value.startsWith("-")
|
||||
) {
|
||||
throw new Error(`${name} requires a value`);
|
||||
}
|
||||
return value;
|
||||
};
|
||||
if (arg === "--artifact-dir") {
|
||||
setOnce(arg, "artifactDir", readValue(arg));
|
||||
} else if (arg === "--github-output") {
|
||||
setOnce(arg, "githubOutput", readValue(arg));
|
||||
} else if (arg === "--metadata") {
|
||||
setOnce(arg, "metadata", readValue(arg));
|
||||
} else if (arg === "--output-dir") {
|
||||
setOnce(arg, "outputDir", readValue(arg));
|
||||
} else if (arg === "--output-name") {
|
||||
setOnce(arg, "outputName", readValue(arg));
|
||||
} else if (arg === "--package-sha256") {
|
||||
setOnce(arg, "packageSha256", readValue(arg, { allowEmpty: true }).toLowerCase());
|
||||
} else if (arg === "--package-ref") {
|
||||
setOnce(arg, "packageRef", readValue(arg, { allowEmpty: true }));
|
||||
} else if (arg === "--package-spec") {
|
||||
setOnce(arg, "packageSpec", readValue(arg, { allowEmpty: true }));
|
||||
} else if (arg === "--package-url") {
|
||||
setOnce(arg, "packageUrl", readValue(arg, { allowEmpty: true }));
|
||||
} else if (arg === "--source") {
|
||||
setOnce(arg, "source", readValue(arg));
|
||||
} else if (arg === "--trusted-source-id") {
|
||||
setOnce(arg, "trustedSourceId", readValue(arg, { allowEmpty: true }));
|
||||
} else if (arg === "--trusted-source-policy") {
|
||||
setOnce(arg, "trustedSourcePolicy", readValue(arg));
|
||||
} else if (arg === "--help" || arg === "-h") {
|
||||
options.help = true;
|
||||
} else {
|
||||
throw new Error(`unknown argument: ${arg}`);
|
||||
}
|
||||
}
|
||||
parseFlagArgs(
|
||||
argv,
|
||||
options,
|
||||
[
|
||||
...[
|
||||
["--artifact-dir", "artifactDir"],
|
||||
["--github-output", "githubOutput"],
|
||||
["--metadata", "metadata"],
|
||||
["--output-dir", "outputDir"],
|
||||
["--output-name", "outputName"],
|
||||
["--source", "source"],
|
||||
["--trusted-source-policy", "trustedSourcePolicy"],
|
||||
].map(([flag, key]) =>
|
||||
stringFlag(flag, key, { allowInline: false, rejectShortOptions: true }),
|
||||
),
|
||||
...[
|
||||
["--package-ref", "packageRef"],
|
||||
["--package-spec", "packageSpec"],
|
||||
["--package-url", "packageUrl"],
|
||||
["--trusted-source-id", "trustedSourceId"],
|
||||
].map(([flag, key]) =>
|
||||
stringFlag(flag, key, {
|
||||
allowEmpty: true,
|
||||
allowInline: false,
|
||||
rejectShortOptions: true,
|
||||
}),
|
||||
),
|
||||
stringFlag("--package-sha256", "packageSha256", {
|
||||
allowEmpty: true,
|
||||
allowInline: false,
|
||||
rejectShortOptions: true,
|
||||
transform: (value) => value.toLowerCase(),
|
||||
}),
|
||||
booleanFlag("--help", "help", true, { repeatable: true }),
|
||||
booleanFlag("-h", "help", true, { repeatable: true }),
|
||||
],
|
||||
{
|
||||
ignoreDoubleDash: false,
|
||||
onUnhandledArg(arg) {
|
||||
throw new Error(`unknown argument: ${arg}`);
|
||||
},
|
||||
},
|
||||
);
|
||||
validateOutputName(options.outputName);
|
||||
return options;
|
||||
}
|
||||
|
||||
@@ -4,9 +4,9 @@ import fs from "node:fs";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { resolveRepoRoot } from "./lib/repo-root.mjs";
|
||||
|
||||
const scriptDir = path.dirname(fileURLToPath(import.meta.url));
|
||||
const repoRoot = path.resolve(scriptDir, "..");
|
||||
const repoRoot = resolveRepoRoot(import.meta.url);
|
||||
const androidDir = path.join(repoRoot, "apps", "android");
|
||||
const isMain = process.argv[1]
|
||||
? path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)
|
||||
|
||||
@@ -4,13 +4,13 @@ import { spawn } from "node:child_process";
|
||||
import fs from "node:fs";
|
||||
import { createRequire } from "node:module";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { embeddedAgentVitestProjectOwners } from "../test/vitest/vitest.agents-paths.mjs";
|
||||
import { toolingIsolatedTestFiles } from "../test/vitest/vitest.tooling-isolated-paths.mjs";
|
||||
import { isUiTestTarget } from "../test/vitest/vitest.ui-paths.mjs";
|
||||
import { boundaryTestFiles } from "../test/vitest/vitest.unit-paths.mjs";
|
||||
import { runWithFailedTrailer, writeFailedTrailer } from "./lib/failed-trailer.mjs";
|
||||
import { signalExitCode } from "./lib/managed-child-process.mjs";
|
||||
import { resolveRepoRoot } from "./lib/repo-root.mjs";
|
||||
import { resolveLocalVitestEnv } from "./lib/vitest-local-scheduling.mjs";
|
||||
import { spawnPnpmRunner } from "./pnpm-runner.mjs";
|
||||
import {
|
||||
@@ -127,7 +127,7 @@ const VITEST_DOTTED_OPTIONS_WITH_VALUE_PREFIXES = [
|
||||
"--typecheck.",
|
||||
];
|
||||
const require = createRequire(import.meta.url);
|
||||
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
||||
const repoRoot = resolveRepoRoot(import.meta.url);
|
||||
const testProjectsRunnerPath = path.join(repoRoot, "scripts", "test-projects.mjs");
|
||||
|
||||
function isTruthyEnvValue(value) {
|
||||
|
||||
@@ -4,11 +4,12 @@ import fs from "node:fs";
|
||||
import { createRequire } from "node:module";
|
||||
import path from "node:path";
|
||||
import { performance } from "node:perf_hooks";
|
||||
import { fileURLToPath, pathToFileURL } from "node:url";
|
||||
import { pathToFileURL } from "node:url";
|
||||
import { buildSync } from "esbuild";
|
||||
import { copyBundledPluginMetadata } from "./copy-bundled-plugin-metadata.mjs";
|
||||
import { assertRealOutputRoot } from "./lib/output-root-guard.mjs";
|
||||
import { escapeRegExp } from "./lib/regexp.mjs";
|
||||
import { resolveRepoRoot } from "./lib/repo-root.mjs";
|
||||
import {
|
||||
copyStaticExtensionAssets,
|
||||
copyStaticExtensionAssetsToRuntimeOverlay,
|
||||
@@ -21,7 +22,7 @@ import { writeOfficialChannelCatalog } from "./write-official-channel-catalog.mj
|
||||
/** @internal Shared repository-script contract. */
|
||||
export { listStaticExtensionAssetOutputs };
|
||||
|
||||
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
||||
const ROOT = resolveRepoRoot(import.meta.url);
|
||||
const ROOT_RUNTIME_ALIAS_PATTERN = /^(?<base>.+\.(?:runtime|contract))-[A-Za-z0-9_-]+\.js$/u;
|
||||
const ROOT_STABLE_RUNTIME_ALIAS_PATTERN = /^.+\.(?:runtime|contract)\.js$/u;
|
||||
const ROOT_RUNTIME_IMPORT_SPECIFIER_PATTERN =
|
||||
|
||||
@@ -5,9 +5,9 @@ import { spawnSync } from "node:child_process";
|
||||
import fs from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath, pathToFileURL } from "node:url";
|
||||
|
||||
const rootDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
||||
import { pathToFileURL } from "node:url";
|
||||
import { resolveRepoRoot } from "./lib/repo-root.mjs";
|
||||
const rootDir = resolveRepoRoot(import.meta.url);
|
||||
const REQUIRED_RESOURCE_FILES = ["a2ui.bundle.js", "index.html"];
|
||||
|
||||
export function getNativeA2uiResourcePaths(repoRoot = rootDir) {
|
||||
|
||||
@@ -3,13 +3,14 @@ import assert from "node:assert/strict";
|
||||
import fs from "node:fs";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath, pathToFileURL } from "node:url";
|
||||
import { pathToFileURL } from "node:url";
|
||||
import { resolveRepoRoot } from "./lib/repo-root.mjs";
|
||||
import { installProcessWarningFilter } from "./process-warning-filter.mjs";
|
||||
import { stageBundledPluginRuntime } from "./stage-bundled-plugin-runtime.mjs";
|
||||
|
||||
installProcessWarningFilter();
|
||||
|
||||
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
||||
const repoRoot = resolveRepoRoot(import.meta.url);
|
||||
const smokeEntryPath = path.join(repoRoot, "dist", "plugins", "build-smoke-entry.js");
|
||||
assert.ok(fs.existsSync(smokeEntryPath), `missing build output: ${smokeEntryPath}`);
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import { execFileSync, spawnSync } from "node:child_process";
|
||||
import { mkdirSync, writeFileSync } from "node:fs";
|
||||
import path from "node:path";
|
||||
import { booleanFlag, parseFlagArgs, stringFlag } from "./lib/arg-utils.mjs";
|
||||
import { isDirectRunUrl } from "./lib/direct-run.mjs";
|
||||
import { execGhApiRead, plainGhEnv } from "./lib/plain-gh.mjs";
|
||||
|
||||
@@ -31,14 +32,6 @@ const MAX_CI_REUSE_CANDIDATES = 5;
|
||||
const CI_REUSE_RUN_LIST_LIMIT = 50;
|
||||
const GIT_MAX_BUFFER_BYTES = 64 * 1024 * 1024;
|
||||
|
||||
function readOptionValue(argv, index, optionName) {
|
||||
const value = argv[index + 1];
|
||||
if (!value || value.startsWith("-")) {
|
||||
throw new Error(`Expected ${optionName} <value>.`);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
export function parseArgs(argv) {
|
||||
const args = {
|
||||
repo: "",
|
||||
@@ -48,49 +41,44 @@ export function parseArgs(argv) {
|
||||
output: "",
|
||||
changelogOnly: false,
|
||||
};
|
||||
const seen = new Set();
|
||||
const setOnce = (flag, key, value) => {
|
||||
if (seen.has(flag)) {
|
||||
throw new Error(`${flag} was provided more than once.`);
|
||||
}
|
||||
seen.add(flag);
|
||||
args[key] = value;
|
||||
};
|
||||
for (let index = 0; index < argv.length; index += 1) {
|
||||
const arg = argv[index];
|
||||
switch (arg) {
|
||||
case "--repo":
|
||||
setOnce(arg, "repo", readOptionValue(argv, index, arg));
|
||||
index += 1;
|
||||
break;
|
||||
case "--sha":
|
||||
setOnce(arg, "sha", readOptionValue(argv, index, arg));
|
||||
index += 1;
|
||||
break;
|
||||
case "--pr": {
|
||||
const value = Number(readOptionValue(argv, index, arg));
|
||||
if (!Number.isSafeInteger(value) || value <= 0) {
|
||||
throw new Error("Expected --pr <positive-integer>.");
|
||||
}
|
||||
setOnce(arg, "pr", value);
|
||||
index += 1;
|
||||
break;
|
||||
}
|
||||
case "--recent-sha":
|
||||
setOnce(arg, "recentSha", readOptionValue(argv, index, arg));
|
||||
index += 1;
|
||||
break;
|
||||
case "--output":
|
||||
setOnce(arg, "output", readOptionValue(argv, index, arg));
|
||||
index += 1;
|
||||
break;
|
||||
case "--changelog-only":
|
||||
setOnce(arg, "changelogOnly", true);
|
||||
break;
|
||||
default:
|
||||
parseFlagArgs(
|
||||
argv,
|
||||
args,
|
||||
[
|
||||
...[
|
||||
["--repo", "repo"],
|
||||
["--sha", "sha"],
|
||||
["--recent-sha", "recentSha"],
|
||||
["--output", "output"],
|
||||
].map(([flag, key]) =>
|
||||
stringFlag(flag, key, {
|
||||
allowInline: false,
|
||||
missingValueMessage: `Expected ${flag} <value>.`,
|
||||
rejectShortOptions: true,
|
||||
}),
|
||||
),
|
||||
stringFlag("--pr", "pr", {
|
||||
allowInline: false,
|
||||
missingValueMessage: "Expected --pr <value>.",
|
||||
rejectShortOptions: true,
|
||||
transform(value) {
|
||||
const parsed = Number(value);
|
||||
if (!Number.isSafeInteger(parsed) || parsed <= 0) {
|
||||
throw new Error("Expected --pr <positive-integer>.");
|
||||
}
|
||||
return parsed;
|
||||
},
|
||||
}),
|
||||
booleanFlag("--changelog-only", "changelogOnly"),
|
||||
],
|
||||
{
|
||||
duplicateOptionMessage: (flag) => `${flag} was provided more than once.`,
|
||||
ignoreDoubleDash: false,
|
||||
onUnhandledArg(arg) {
|
||||
throw new Error(`Unknown option: ${arg}`);
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
);
|
||||
if (!args.repo || !args.sha || !args.pr || !args.output) {
|
||||
throw new Error(
|
||||
"Usage: node scripts/verify-pr-hosted-gates.mjs --repo <owner/repo> --sha <sha> --pr <number> [--recent-sha <sha>] --output <path>",
|
||||
|
||||
+15
-9
@@ -1,5 +1,6 @@
|
||||
// Runs the broad verification graph used by Crabbox/Testbox: check then test.
|
||||
import { performance } from "node:perf_hooks";
|
||||
import { booleanFlag, parseFlagArgs } from "./lib/arg-utils.mjs";
|
||||
import { formatMs, printTimingSummary } from "./lib/check-timing-summary.mjs";
|
||||
import { runManagedCommand } from "./lib/managed-child-process.mjs";
|
||||
|
||||
@@ -26,15 +27,20 @@ function usage() {
|
||||
* Parses verify wrapper CLI args.
|
||||
*/
|
||||
function parseVerifyArgs(argv) {
|
||||
const args = { help: false };
|
||||
for (const arg of argv) {
|
||||
if (arg === "--help" || arg === "-h") {
|
||||
args.help = true;
|
||||
} else {
|
||||
throw new Error(`unknown argument: ${arg}\n\n${usage()}`);
|
||||
}
|
||||
}
|
||||
return args;
|
||||
return parseFlagArgs(
|
||||
argv,
|
||||
{ help: false },
|
||||
[
|
||||
booleanFlag("--help", "help", true, { repeatable: true }),
|
||||
booleanFlag("-h", "help", true, { repeatable: true }),
|
||||
],
|
||||
{
|
||||
ignoreDoubleDash: false,
|
||||
onUnhandledArg(arg) {
|
||||
throw new Error(`unknown argument: ${arg}\n\n${usage()}`);
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
async function runStage(stage) {
|
||||
|
||||
+16
-15
@@ -1,8 +1,8 @@
|
||||
#!/usr/bin/env node
|
||||
import { execFileSync } from "node:child_process";
|
||||
import { setTimeout as sleep } from "node:timers/promises";
|
||||
import { parseArgs as parseNodeArgs } from "node:util";
|
||||
import { isDirectRunUrl } from "./lib/direct-run.mjs";
|
||||
import { execGhJson } from "./lib/plain-gh.mjs";
|
||||
|
||||
const USAGE =
|
||||
"Usage: node scripts/watch-pr-ci.mjs <pr-number> <head-sha> [--repo owner/repo] [--after run-id] [--attach-timeout 900] [--timeout 3600] [--interval 120]";
|
||||
@@ -15,6 +15,10 @@ const FAILURE_CONCLUSIONS = new Set([
|
||||
"TIMED_OUT",
|
||||
]);
|
||||
const ROLLUP_QUERY = `query($owner:String!,$name:String!,$pr:Int!,$cursor:String){repository(owner:$owner,name:$name){pullRequest(number:$pr){state mergeable headRefOid statusCheckRollup{state contexts(first:100,after:$cursor){totalCount pageInfo{hasNextPage endCursor} nodes{kind:__typename ... on CheckRun{name status conclusion databaseId checkSuite{workflowRun{databaseId workflow{databaseId}}}} ... on StatusContext{context state}}}}}}}`;
|
||||
const GH_READ_OPTIONS = {
|
||||
stdio: ["ignore", "pipe", "pipe"],
|
||||
timeout: 60_000,
|
||||
};
|
||||
// Adapted from Node's MIT-licensed util.stripVTControlCharacters implementation.
|
||||
const ANSI_ESCAPE_SEQUENCE = new RegExp(
|
||||
"[\\u001B\\u009B][[\\]()#;?]*" +
|
||||
@@ -207,18 +211,11 @@ export function classifyRollup(rollup) {
|
||||
return { verdict: "PENDING", pendingCount, failingNames: [], supersededCount };
|
||||
}
|
||||
|
||||
function ghJson(...args) {
|
||||
return JSON.parse(
|
||||
execFileSync("gh", args, {
|
||||
encoding: "utf8",
|
||||
stdio: ["ignore", "pipe", "pipe"],
|
||||
timeout: 60_000,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
const readPr = (pr, repo) =>
|
||||
ghJson(...`pr view ${pr} --repo ${repo} --json state,mergeable,headRefOid`.split(" "));
|
||||
execGhJson(
|
||||
`pr view ${pr} --repo ${repo} --json state,mergeable,headRefOid`.split(" "),
|
||||
GH_READ_OPTIONS,
|
||||
);
|
||||
export const buildFindRunArgs = (repo, sha) => [
|
||||
"run",
|
||||
"list",
|
||||
@@ -237,9 +234,13 @@ export const buildFindRunArgs = (repo, sha) => [
|
||||
];
|
||||
export const selectRunAfter = (runs, after) =>
|
||||
runs.find((run) => after === undefined || run.databaseId > after);
|
||||
const findRun = (repo, sha, after) => selectRunAfter(ghJson(...buildFindRunArgs(repo, sha)), after);
|
||||
const findRun = (repo, sha, after) =>
|
||||
selectRunAfter(execGhJson(buildFindRunArgs(repo, sha), GH_READ_OPTIONS), after);
|
||||
const readRun = (repo, runId) =>
|
||||
ghJson(...`run view ${runId} --repo ${repo} --json status,conclusion`.split(" "));
|
||||
execGhJson(
|
||||
`run view ${runId} --repo ${repo} --json status,conclusion`.split(" "),
|
||||
GH_READ_OPTIONS,
|
||||
);
|
||||
|
||||
export function classifyRunAttachment(runId, run, after) {
|
||||
if (run.conclusion === "skipped") {
|
||||
@@ -317,7 +318,7 @@ function readRollup(pr, repo) {
|
||||
if (cursor !== null) {
|
||||
queryArgs.push("-f", `cursor=${cursor}`);
|
||||
}
|
||||
return ghJson(...queryArgs).data?.repository?.pullRequest;
|
||||
return execGhJson(queryArgs, GH_READ_OPTIONS).data?.repository?.pullRequest;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -38,6 +38,31 @@ describe("scripts/lib/arg-utils parseFlagArgs", () => {
|
||||
expect(parsed.match).toEqual(["alpha", "beta"]);
|
||||
});
|
||||
|
||||
it("supports split-only, empty, transformed, and last-value-wins string contracts", () => {
|
||||
expect(() =>
|
||||
parseFlagArgs(["--value=inline"], { value: "" }, [
|
||||
stringFlag("--value", "value", { allowInline: false }),
|
||||
]),
|
||||
).toThrow("Unknown option: --value=inline");
|
||||
expect(
|
||||
parseFlagArgs(["--value", "", "--value", "SECOND"], { value: "" }, [
|
||||
stringFlag("--value", "value", {
|
||||
allowEmpty: true,
|
||||
repeatable: true,
|
||||
transform: (value) => value.toLowerCase(),
|
||||
}),
|
||||
]).value,
|
||||
).toBe("second");
|
||||
});
|
||||
|
||||
it("supports idempotent boolean flags", () => {
|
||||
expect(
|
||||
parseFlagArgs(["--verbose", "--verbose"], { verbose: false }, [
|
||||
booleanFlag("--verbose", "verbose", true, { repeatable: true }),
|
||||
]).verbose,
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("rejects duplicate single-value flags", () => {
|
||||
expect(() =>
|
||||
parseFlagArgs(["--label", "first", "--label=second"], { label: "" }, [
|
||||
|
||||
@@ -21,13 +21,15 @@ describe("scripts/check", () => {
|
||||
});
|
||||
|
||||
it("rejects unknown args before running check stages", () => {
|
||||
const result = runCheck("--bogus");
|
||||
for (const args of [["--bogus"], ["bogus", "--help"]]) {
|
||||
const result = runCheck(...args);
|
||||
|
||||
expect(result.status).toBe(2);
|
||||
expect(result.stdout).toBe("");
|
||||
expect(result.stderr).toContain("unknown argument: --bogus");
|
||||
expect(result.stderr).toContain("Usage: node scripts/check.mjs");
|
||||
expect(result.stderr).not.toContain("[check]");
|
||||
expect(result.status).toBe(2);
|
||||
expect(result.stdout).toBe("");
|
||||
expect(result.stderr).toContain(`unknown argument: ${args[0]}`);
|
||||
expect(result.stderr).toContain("Usage: node scripts/check.mjs");
|
||||
expect(result.stderr).not.toContain("[check]");
|
||||
}
|
||||
});
|
||||
|
||||
it("runs pnpm commands through the managed child runner", async () => {
|
||||
|
||||
@@ -9,7 +9,6 @@ import {
|
||||
releaseEvidenceVerificationArgs,
|
||||
releaseEvidenceVerifierPath,
|
||||
resolveRemoteTargetRefSha,
|
||||
runGhRead,
|
||||
shouldDeleteTemporaryWorkflowRef,
|
||||
} from "../../scripts/full-release-validation-at-sha.mjs";
|
||||
|
||||
@@ -163,30 +162,9 @@ describe("full-release-validation-at-sha", () => {
|
||||
});
|
||||
|
||||
it("bounds GitHub reads without applying a timeout to workflow dispatch", () => {
|
||||
const calls: unknown[][] = [];
|
||||
expect(
|
||||
runGhRead(["api", "repos/openclaw/openclaw/actions/runs/123"], {
|
||||
execFileSyncImpl: (...args: unknown[]) => {
|
||||
calls.push(args);
|
||||
return " result ";
|
||||
},
|
||||
}),
|
||||
).toBe("result");
|
||||
expect(calls).toEqual([
|
||||
[
|
||||
"gh",
|
||||
["api", "repos/openclaw/openclaw/actions/runs/123"],
|
||||
expect.objectContaining({
|
||||
killSignal: "SIGKILL",
|
||||
timeout: 60_000,
|
||||
}),
|
||||
],
|
||||
]);
|
||||
|
||||
const source = readFileSync("scripts/full-release-validation-at-sha.mjs", "utf8");
|
||||
expect(source).toContain(
|
||||
'runGhRead(["api", `repos/openclaw/openclaw/actions/runs/${parentRunId}`])',
|
||||
);
|
||||
expect(source).toContain("timeout: GH_READ_TIMEOUT_MS");
|
||||
expect(source.match(/GH_READ_OPTIONS/gu)).toHaveLength(3);
|
||||
expect(source).toContain('const dispatchOutput = run("gh", dispatchArgs');
|
||||
});
|
||||
|
||||
|
||||
@@ -206,12 +206,14 @@ describe("generate-dependency-release-evidence", () => {
|
||||
});
|
||||
|
||||
it("reports CLI argument errors without a Node stack trace", () => {
|
||||
const result = runCli("--wat");
|
||||
for (const args of [["--wat"], ["wat", "--help"]]) {
|
||||
const result = runCli(...args);
|
||||
|
||||
expect(result.status).toBe(1);
|
||||
expect(result.stdout).toBe("");
|
||||
expect(result.stderr.trim()).toBe("Unsupported argument: --wat");
|
||||
expectNoNodeStack(result.stderr);
|
||||
expect(result.status).toBe(1);
|
||||
expect(result.stdout).toBe("");
|
||||
expect(result.stderr.trim()).toBe(`Unsupported argument: ${args[0]}`);
|
||||
expectNoNodeStack(result.stderr);
|
||||
}
|
||||
});
|
||||
|
||||
it("falls back to fetching tags when local previous-release resolution misses", () => {
|
||||
|
||||
@@ -6,6 +6,8 @@ import path from "node:path";
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import {
|
||||
execGhApiRead,
|
||||
execGhJson,
|
||||
execGhRead,
|
||||
execPlainGh,
|
||||
plainGhEnv,
|
||||
PLAIN_GH_SYSTEM_CANDIDATES,
|
||||
@@ -114,6 +116,59 @@ describe("plain gh helpers", () => {
|
||||
expect(output).toContain("OPENCLAW_GH_BIN_SET=");
|
||||
});
|
||||
|
||||
it("shares bounded PATH-shim reads and JSON parsing", () => {
|
||||
const calls: unknown[][] = [];
|
||||
const execFileSyncImpl = (...args: unknown[]) => {
|
||||
calls.push(args);
|
||||
return '{"ok":true}';
|
||||
};
|
||||
|
||||
expect(
|
||||
execGhJson(
|
||||
["api", "repos/openclaw/openclaw"],
|
||||
{
|
||||
killSignal: "SIGKILL",
|
||||
stdio: ["ignore", "pipe", "inherit"],
|
||||
timeout: 60_000,
|
||||
},
|
||||
{ execFileSyncImpl },
|
||||
),
|
||||
).toEqual({ ok: true });
|
||||
expect(calls).toEqual([
|
||||
[
|
||||
"gh",
|
||||
["api", "repos/openclaw/openclaw"],
|
||||
expect.objectContaining({
|
||||
encoding: "utf8",
|
||||
killSignal: "SIGKILL",
|
||||
maxBuffer: 32 * 1024 * 1024,
|
||||
stdio: ["ignore", "pipe", "inherit"],
|
||||
timeout: 60_000,
|
||||
}),
|
||||
],
|
||||
]);
|
||||
expect(
|
||||
execGhRead(
|
||||
["api", "rate_limit"],
|
||||
{ encoding: "utf8" },
|
||||
{ execFileSyncImpl: () => " result " },
|
||||
),
|
||||
).toBe(" result ");
|
||||
|
||||
const failure = new Error("gh read failed");
|
||||
expect(() =>
|
||||
execGhRead(
|
||||
["api", "rate_limit"],
|
||||
{},
|
||||
{
|
||||
execFileSyncImpl: () => {
|
||||
throw failure;
|
||||
},
|
||||
},
|
||||
),
|
||||
).toThrow(failure);
|
||||
});
|
||||
|
||||
it("runs the shell helper with color disabled", () => {
|
||||
const ghPath = makeFakeGh();
|
||||
const outputPath = path.join(path.dirname(path.dirname(ghPath)), "output.txt");
|
||||
|
||||
@@ -107,6 +107,11 @@ describe("plugin npm extended-stable workflow", () => {
|
||||
|
||||
it("overlays the complete trusted packaging helper dependency set", () => {
|
||||
const parsed = workflow();
|
||||
const lockGenerator = readFileSync("scripts/generate-npm-package-lock.mjs", "utf8");
|
||||
expect(lockGenerator).toContain(
|
||||
'path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..")',
|
||||
);
|
||||
expect(lockGenerator).not.toContain("./lib/repo-root.mjs");
|
||||
const preflightCheckout = step(
|
||||
parsed.jobs?.preview_plugin_pack,
|
||||
"Checkout trusted packaging helper",
|
||||
|
||||
@@ -59,19 +59,21 @@ describe("plugin SDK surface report", () => {
|
||||
});
|
||||
|
||||
it("rejects unknown CLI options before collecting SDK stats", () => {
|
||||
const result = spawnSync(
|
||||
process.execPath,
|
||||
["scripts/plugin-sdk-surface-report.mjs", "--chekc"],
|
||||
{
|
||||
cwd: process.cwd(),
|
||||
encoding: "utf8",
|
||||
},
|
||||
);
|
||||
for (const args of [["--chekc"], ["chekc", "--help"]]) {
|
||||
const result = spawnSync(
|
||||
process.execPath,
|
||||
["scripts/plugin-sdk-surface-report.mjs", ...args],
|
||||
{
|
||||
cwd: process.cwd(),
|
||||
encoding: "utf8",
|
||||
},
|
||||
);
|
||||
|
||||
expect(result.status).toBe(1);
|
||||
expect(result.stdout).toBe("");
|
||||
expect(result.stderr.trim()).toBe("Unknown plugin SDK surface report option: --chekc");
|
||||
expect(result.stderr).not.toContain("at ");
|
||||
expect(result.status).toBe(1);
|
||||
expect(result.stdout).toBe("");
|
||||
expect(result.stderr.trim()).toBe(`Unknown plugin SDK surface report option: ${args[0]}`);
|
||||
expect(result.stderr).not.toContain("at ");
|
||||
}
|
||||
});
|
||||
|
||||
it("prints help before collecting SDK stats", () => {
|
||||
|
||||
@@ -4,7 +4,7 @@ import { testForceTesting } from "../../scripts/test-force.js";
|
||||
|
||||
describe("scripts/test-force.ts", () => {
|
||||
it("prints help without clearing ports or running tests", () => {
|
||||
const args = testForceTesting.parseArgs(["--help"]);
|
||||
const args = testForceTesting.parseArgs(["--help", "--bogus"]);
|
||||
|
||||
expect(args).toEqual({ help: true });
|
||||
expect(testForceTesting.usage()).toContain("Usage: node --import tsx scripts/test-force.ts");
|
||||
@@ -16,5 +16,11 @@ describe("scripts/test-force.ts", () => {
|
||||
expect(() => testForceTesting.parseArgs(["--bogus"])).toThrow(
|
||||
/unknown argument: --bogus[\s\S]*Usage: node --import tsx scripts\/test-force\.ts/u,
|
||||
);
|
||||
expect(() => testForceTesting.parseArgs(["bogus"])).toThrow(
|
||||
/unknown argument: bogus[\s\S]*Usage: node --import tsx scripts\/test-force\.ts/u,
|
||||
);
|
||||
expect(() => testForceTesting.parseArgs(["bogus", "--help"])).toThrow(
|
||||
/unknown argument: bogus[\s\S]*Usage: node --import tsx scripts\/test-force\.ts/u,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1931,6 +1931,7 @@ describe("scripts/test-projects changed-target routing", () => {
|
||||
"scripts/lib/ts-topology/analyze.ts": ["test/scripts/ts-topology.test.ts"],
|
||||
"scripts/lib/ts-topology/reports.ts": ["test/scripts/ts-topology.test.ts"],
|
||||
"scripts/lib/ts-topology/scope.ts": ["test/scripts/ts-topology.test.ts"],
|
||||
"scripts/lib/repo-root.mjs": ["test/scripts/ts-guard-utils.test.ts"],
|
||||
"scripts/lib/ts-guard-utils.mjs": ["test/scripts/ts-guard-utils.test.ts"],
|
||||
"scripts/lib/tsgo-sparse-guard.mjs": [
|
||||
"test/scripts/run-tsgo.test.ts",
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
// Ts Guard Utils tests cover ts guard utils script behavior.
|
||||
import { existsSync } from "node:fs";
|
||||
import { existsSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import path from "node:path";
|
||||
import { pathToFileURL } from "node:url";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { resolveRepoRoot } from "../../scripts/lib/ts-guard-utils.mjs";
|
||||
import { resolveRepoRoot } from "../../scripts/lib/repo-root.mjs";
|
||||
|
||||
/**
|
||||
* Regression tests for resolveRepoRoot().
|
||||
@@ -49,4 +50,19 @@ describe("resolveRepoRoot", () => {
|
||||
expect(fromLib).toBe(fromScripts);
|
||||
expect(fromScripts).toBe(fromExtension);
|
||||
});
|
||||
|
||||
it("resolves an unpacked workspace without git metadata", () => {
|
||||
const root = mkdtempSync(path.join(tmpdir(), "openclaw-repo-root-"));
|
||||
try {
|
||||
mkdirSync(path.join(root, "scripts", "nested"), { recursive: true });
|
||||
writeFileSync(path.join(root, "package.json"), '{"name":"openclaw"}\n');
|
||||
writeFileSync(path.join(root, "pnpm-workspace.yaml"), "packages: []\n");
|
||||
|
||||
expect(
|
||||
resolveRepoRoot(pathToFileURL(path.join(root, "scripts", "nested", "tool.mjs")).href),
|
||||
).toBe(root);
|
||||
} finally {
|
||||
rmSync(root, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -21,13 +21,15 @@ describe("scripts/verify", () => {
|
||||
});
|
||||
|
||||
it("rejects unknown args before running verify stages", () => {
|
||||
const result = runVerify("--bogus");
|
||||
for (const args of [["--bogus"], ["bogus", "--help"]]) {
|
||||
const result = runVerify(...args);
|
||||
|
||||
expect(result.status).toBe(2);
|
||||
expect(result.stdout).toBe("");
|
||||
expect(result.stderr).toContain("unknown argument: --bogus");
|
||||
expect(result.stderr).toContain("Usage: node scripts/verify.mjs");
|
||||
expect(result.stderr).not.toContain("CRABBOX_PHASE:");
|
||||
expect(result.stderr).not.toContain("[verify]");
|
||||
expect(result.status).toBe(2);
|
||||
expect(result.stdout).toBe("");
|
||||
expect(result.stderr).toContain(`unknown argument: ${args[0]}`);
|
||||
expect(result.stderr).toContain("Usage: node scripts/verify.mjs");
|
||||
expect(result.stderr).not.toContain("CRABBOX_PHASE:");
|
||||
expect(result.stderr).not.toContain("[verify]");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user