mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-06 05:51:15 +08:00
docs: document root build check scripts
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
// Builds browser runtime bundles for the diffs viewer assets.
|
||||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
@@ -26,6 +27,9 @@ function toPosixPath(value) {
|
||||
return String(value ?? "").replaceAll("\\", "/");
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the esbuild plugin that neutralizes Pierre diffs' browser side-effect import.
|
||||
*/
|
||||
export function createPierreDiffsSideEffectImportPlugin() {
|
||||
return {
|
||||
name: "openclaw-diffs-pierre-side-effect-imports",
|
||||
@@ -55,6 +59,9 @@ export function createPierreDiffsSideEffectImportPlugin() {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds one configured diffs viewer runtime target.
|
||||
*/
|
||||
export async function buildDiffsViewerRuntime(targetName) {
|
||||
const target = targets[targetName];
|
||||
if (!target) {
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
// Writes the local build stamp and re-exports build metadata helpers.
|
||||
import process from "node:process";
|
||||
import { pathToFileURL } from "node:url";
|
||||
import { writeBuildStamp } from "./lib/local-build-metadata.mjs";
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
// Runs bundled asset build hooks for the Canvas A2UI runtime.
|
||||
import { pathToFileURL } from "node:url";
|
||||
import { runBundledPluginAssetHooks } from "./bundled-plugin-assets.mjs";
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
// Discovers and runs bundled plugin package asset hooks.
|
||||
import { spawnSync } from "node:child_process";
|
||||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
@@ -57,6 +58,9 @@ function resolveAssetCommand(packageJson, phase) {
|
||||
return typeof command === "string" && command.trim() ? command.trim() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads bundled plugin asset hook commands for a build or copy phase.
|
||||
*/
|
||||
export async function readBundledPluginAssetHooks(options = {}) {
|
||||
const repoRoot = options.rootDir ?? rootDir;
|
||||
const phase = options.phase;
|
||||
@@ -108,6 +112,9 @@ export async function readBundledPluginAssetHooks(options = {}) {
|
||||
return hooks.toSorted((left, right) => left.pluginDir.localeCompare(right.pluginDir));
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs bundled plugin asset hook commands for the selected phase/plugins.
|
||||
*/
|
||||
export async function runBundledPluginAssetHooks(options = {}) {
|
||||
const phase = options.phase;
|
||||
const hooks = await readBundledPluginAssetHooks(options);
|
||||
@@ -131,6 +138,9 @@ export async function runBundledPluginAssetHooks(options = {}) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses `--phase` and repeated `--plugin` flags for asset hook scripts.
|
||||
*/
|
||||
export function parseBundledPluginAssetArgs(argv) {
|
||||
const args = [...argv];
|
||||
const plugins = [];
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// Classifies changed files into CI lanes and release metadata scopes.
|
||||
import { execFileSync } from "node:child_process";
|
||||
import { appendFileSync, existsSync, readFileSync } from "node:fs";
|
||||
import { booleanFlag, parseFlagArgs, stringFlag } from "./lib/arg-utils.mjs";
|
||||
@@ -24,6 +25,9 @@ const TEST_PATH_RE =
|
||||
/(?:^|\/)(?:test|__tests__)\/|(?:\.|\/)(?:test|spec|e2e|browser\.test)\.[cm]?[jt]sx?$/u;
|
||||
const PUBLIC_EXTENSION_CONTRACT_RE =
|
||||
/^(?:src\/plugin-sdk\/|src\/plugins\/contracts\/|src\/channels\/plugins\/|scripts\/lib\/plugin-sdk-entrypoints\.json$|scripts\/sync-plugin-sdk-exports\.mjs$|scripts\/generate-plugin-sdk-api-baseline\.ts$)/u;
|
||||
/**
|
||||
* Files whose changes are treated as release metadata only.
|
||||
*/
|
||||
export const RELEASE_METADATA_PATHS = new Set([
|
||||
"CHANGELOG.md",
|
||||
"apps/android/app/build.gradle.kts",
|
||||
@@ -49,6 +53,9 @@ export const RELEASE_METADATA_PATHS = new Set([
|
||||
* }} ChangedLaneResult
|
||||
*/
|
||||
|
||||
/**
|
||||
* Normalizes a changed file path into repo-relative POSIX form.
|
||||
*/
|
||||
export function normalizeChangedPath(inputPath) {
|
||||
return String(inputPath ?? "")
|
||||
.trim()
|
||||
@@ -56,6 +63,9 @@ export function normalizeChangedPath(inputPath) {
|
||||
.replace(/^\.\/+/u, "");
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the default changed-lanes result object.
|
||||
*/
|
||||
export function createEmptyChangedLanes() {
|
||||
return {
|
||||
core: false,
|
||||
@@ -76,6 +86,9 @@ export function createEmptyChangedLanes() {
|
||||
* @param {{ packageJsonChangeKind?: "liveDockerTooling" | "tooling" | null }} [options]
|
||||
* @returns {ChangedLaneResult}
|
||||
*/
|
||||
/**
|
||||
* Classifies a list of changed paths into docs, app, extension, core, and tooling lanes.
|
||||
*/
|
||||
export function detectChangedLanes(changedPaths, options = {}) {
|
||||
const paths = [...new Set(changedPaths.map(normalizeChangedPath).filter(Boolean))]
|
||||
.toSorted((left, right) => left.localeCompare(right))
|
||||
@@ -217,6 +230,9 @@ export function detectChangedLanes(changedPaths, options = {}) {
|
||||
* @param {{ paths: string[]; base: string; head?: string; staged?: boolean; mergeHeadFirstParent?: boolean }} params
|
||||
* @returns {ChangedLaneResult}
|
||||
*/
|
||||
/**
|
||||
* Classifies changed paths with optional package.json before/after contents.
|
||||
*/
|
||||
export function detectChangedLanesForPaths(params) {
|
||||
const base = params.staged
|
||||
? params.base
|
||||
@@ -240,6 +256,9 @@ export function detectChangedLanesForPaths(params) {
|
||||
* @param {{ base: string; head?: string; includeWorktree?: boolean; cwd?: string; mergeHeadFirstParent?: boolean }} params
|
||||
* @returns {string[]}
|
||||
*/
|
||||
/**
|
||||
* Lists changed paths from git for a base/head comparison.
|
||||
*/
|
||||
export function listChangedPathsFromGit(params) {
|
||||
const head = params.head ?? "HEAD";
|
||||
const cwd = params.cwd ?? process.cwd();
|
||||
@@ -318,6 +337,9 @@ function runGitLsFiles(extraArgs, cwd = process.cwd()) {
|
||||
return output.split("\n").map(normalizeChangedPath).filter(Boolean);
|
||||
}
|
||||
|
||||
/**
|
||||
* Lists staged changed paths for pre-commit checks.
|
||||
*/
|
||||
export function listStagedChangedPaths(cwd = process.cwd()) {
|
||||
const output = execFileSync("git", ["diff", "--cached", "--name-only", "--diff-filter=ACMRD"], {
|
||||
cwd,
|
||||
@@ -328,6 +350,9 @@ export function listStagedChangedPaths(cwd = process.cwd()) {
|
||||
return output.split("\n").map(normalizeChangedPath).filter(Boolean);
|
||||
}
|
||||
|
||||
/**
|
||||
* Classifies package.json script-only changes from git content.
|
||||
*/
|
||||
export function classifyPackageJsonChangeFromGit(params) {
|
||||
try {
|
||||
const { before, after } = readPackageJsonBeforeAfter(params);
|
||||
@@ -340,6 +365,9 @@ export function classifyPackageJsonChangeFromGit(params) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether package scripts changed only live Docker script entries.
|
||||
*/
|
||||
export function isLiveDockerPackageScriptOnlyChange(before, after) {
|
||||
const beforePackage = JSON.parse(before);
|
||||
const afterPackage = JSON.parse(after);
|
||||
@@ -354,6 +382,9 @@ export function isLiveDockerPackageScriptOnlyChange(before, after) {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether package.json changes are limited to scripts.
|
||||
*/
|
||||
export function isPackageScriptOnlyChange(before, after) {
|
||||
const beforePackage = JSON.parse(before);
|
||||
const afterPackage = JSON.parse(after);
|
||||
@@ -444,6 +475,9 @@ function stableJson(value) {
|
||||
return JSON.stringify(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes changed-lane booleans to the GitHub Actions output file.
|
||||
*/
|
||||
export function writeChangedLaneGitHubOutput(result, outputPath = process.env.GITHUB_OUTPUT) {
|
||||
if (!outputPath) {
|
||||
throw new Error("GITHUB_OUTPUT is required");
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
// Finds core/plugin architecture boundary smells in TypeScript sources.
|
||||
import { promises as fs } from "node:fs";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
@@ -10,12 +11,12 @@ import {
|
||||
resolveRepoSpecifier,
|
||||
writeLine,
|
||||
} from "./lib/guard-inventory-utils.mjs";
|
||||
import { mapWithConcurrency } from "./lib/source-file-scan-cache.mjs";
|
||||
import {
|
||||
collectTypeScriptFilesFromRoots,
|
||||
resolveSourceRoots,
|
||||
runAsScript,
|
||||
} from "./lib/ts-guard-utils.mjs";
|
||||
import { mapWithConcurrency } from "./lib/source-file-scan-cache.mjs";
|
||||
|
||||
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
||||
const scanRoots = resolveSourceRoots(repoRoot, ["src/plugin-sdk", "src/plugins/runtime"]);
|
||||
@@ -165,23 +166,22 @@ function scanRuntimeServiceLocatorSmells(source, filePath) {
|
||||
return entries;
|
||||
}
|
||||
|
||||
/**
|
||||
* Collects architecture smell findings from the configured source roots.
|
||||
*/
|
||||
export async function collectArchitectureSmells() {
|
||||
if (!architectureSmellsPromise) {
|
||||
architectureSmellsPromise = (async () => {
|
||||
const files = (await collectTypeScriptFilesFromRoots(scanRoots)).toSorted((left, right) =>
|
||||
normalizeRepoPath(repoRoot, left).localeCompare(normalizeRepoPath(repoRoot, right)),
|
||||
);
|
||||
const entriesByFile = await mapWithConcurrency(
|
||||
files,
|
||||
undefined,
|
||||
async (filePath) => {
|
||||
const source = await fs.readFile(filePath, "utf8");
|
||||
const entries = scanPluginSdkExtensionFacadeSmells(source, filePath);
|
||||
entries.push(...scanRuntimeTypeImplementationSmells(source, filePath));
|
||||
entries.push(...scanRuntimeServiceLocatorSmells(source, filePath));
|
||||
return entries;
|
||||
},
|
||||
);
|
||||
const entriesByFile = await mapWithConcurrency(files, undefined, async (filePath) => {
|
||||
const source = await fs.readFile(filePath, "utf8");
|
||||
const entries = scanPluginSdkExtensionFacadeSmells(source, filePath);
|
||||
entries.push(...scanRuntimeTypeImplementationSmells(source, filePath));
|
||||
entries.push(...scanRuntimeServiceLocatorSmells(source, filePath));
|
||||
return entries;
|
||||
});
|
||||
return entriesByFile.flat().toSorted(compareEntries);
|
||||
})();
|
||||
try {
|
||||
@@ -235,6 +235,9 @@ async function runArchitectureSmellsCheck(argv, io) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the architecture smell check and writes human/JSON output.
|
||||
*/
|
||||
export async function main(argv, io) {
|
||||
return await runArchitectureSmellsCheck(argv, io);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
// Rejects changelog thanks entries that credit bots or internal handles.
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
/**
|
||||
* Exact handles that changelog thanks entries must not credit.
|
||||
*/
|
||||
export const FORBIDDEN_CHANGELOG_THANKS_HANDLES = [
|
||||
"codex",
|
||||
"openclaw",
|
||||
@@ -13,20 +17,38 @@ export const FORBIDDEN_CHANGELOG_THANKS_HANDLES = [
|
||||
"clawsweeper[bot]",
|
||||
"openclaw-clawsweeper[bot]",
|
||||
];
|
||||
/**
|
||||
* Handle prefixes that identify forbidden changelog thanks credits.
|
||||
*/
|
||||
export const FORBIDDEN_CHANGELOG_THANKS_HANDLE_PREFIXES = ["app/"];
|
||||
/**
|
||||
* Handle suffixes that identify forbidden changelog thanks credits.
|
||||
*/
|
||||
export const FORBIDDEN_CHANGELOG_THANKS_HANDLE_SUFFIXES = ["[bot]"];
|
||||
/**
|
||||
* Handles that require an explicit human credit instead.
|
||||
*/
|
||||
export const CHANGELOG_THANKS_REQUIRE_HUMAN_CREDIT_HANDLES = [
|
||||
"clawsweeper",
|
||||
"openclaw-clawsweeper",
|
||||
"clawsweeper[bot]",
|
||||
"openclaw-clawsweeper[bot]",
|
||||
];
|
||||
/**
|
||||
* Handle prefixes that require explicit human credit instead.
|
||||
*/
|
||||
export const CHANGELOG_THANKS_REQUIRE_HUMAN_CREDIT_HANDLE_PREFIXES = ["app/"];
|
||||
/**
|
||||
* Handle suffixes that require explicit human credit instead.
|
||||
*/
|
||||
export const CHANGELOG_THANKS_REQUIRE_HUMAN_CREDIT_HANDLE_SUFFIXES = ["[bot]"];
|
||||
|
||||
const THANKS_PATTERN = /\bThanks\b/iu;
|
||||
const THANKED_HANDLE_PATTERN = /@([-_/A-Za-z0-9]+(?:\[bot\])?)/giu;
|
||||
|
||||
/**
|
||||
* Reports whether a handle is forbidden in changelog thanks text.
|
||||
*/
|
||||
export function isForbiddenChangelogThanksHandle(handle, options = {}) {
|
||||
const { strictBotHandle = false } = options;
|
||||
const normalized = handle.toLowerCase();
|
||||
@@ -48,6 +70,9 @@ export function isForbiddenChangelogThanksHandle(handle, options = {}) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reports whether a handle needs a separate human credit.
|
||||
*/
|
||||
export function requiresExplicitHumanChangelogThanks(handle) {
|
||||
const normalized = handle.toLowerCase();
|
||||
if (normalized === "" || normalized === "null") {
|
||||
@@ -64,6 +89,9 @@ export function requiresExplicitHumanChangelogThanks(handle) {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds changelog lines that thank forbidden handles.
|
||||
*/
|
||||
export function findForbiddenChangelogThanks(content) {
|
||||
return content
|
||||
.split(/\r?\n/u)
|
||||
@@ -82,6 +110,9 @@ export function findForbiddenChangelogThanks(content) {
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the changelog attribution check.
|
||||
*/
|
||||
export async function main(argv = process.argv.slice(2)) {
|
||||
if (argv[0] === "--is-forbidden-handle") {
|
||||
process.exitCode = isForbiddenChangelogThanksHandle(argv[1] ?? "", {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
// Checks channel-agnostic core surfaces for channel-specific coupling.
|
||||
import { promises as fs } from "node:fs";
|
||||
import path from "node:path";
|
||||
import ts from "typescript";
|
||||
@@ -117,6 +118,9 @@ function isModuleSpecifierStringNode(node) {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds channel-specific references inside channel-agnostic protected sources.
|
||||
*/
|
||||
export function findChannelAgnosticBoundaryViolations(
|
||||
content,
|
||||
fileName = "source.ts",
|
||||
@@ -236,6 +240,9 @@ export function findChannelAgnosticBoundaryViolations(
|
||||
return violations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds reverse dependencies from channel core into plugin/runtime surfaces.
|
||||
*/
|
||||
export function findChannelCoreReverseDependencyViolations(content, fileName = "source.ts") {
|
||||
return findChannelAgnosticBoundaryViolations(content, fileName, {
|
||||
checkModuleSpecifiers: true,
|
||||
@@ -246,6 +253,9 @@ export function findChannelCoreReverseDependencyViolations(content, fileName = "
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds user-facing channel names in ACP-owned text sources.
|
||||
*/
|
||||
export function findAcpUserFacingChannelNameViolations(content, fileName = "source.ts") {
|
||||
const sourceFile = ts.createSourceFile(fileName, content, ts.ScriptTarget.Latest, true);
|
||||
const violations = [];
|
||||
@@ -265,6 +275,9 @@ export function findAcpUserFacingChannelNameViolations(content, fileName = "sour
|
||||
return violations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds raw system mark literals where shared constants should be used.
|
||||
*/
|
||||
export function findSystemMarkLiteralViolations(content, fileName = "source.ts") {
|
||||
const sourceFile = ts.createSourceFile(fileName, content, ts.ScriptTarget.Latest, true);
|
||||
const violations = [];
|
||||
@@ -307,6 +320,9 @@ const boundaryRuleSets = [
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
* Runs all channel-agnostic boundary checks.
|
||||
*/
|
||||
export async function main() {
|
||||
const violations = [];
|
||||
for (const ruleSet of boundaryRuleSets) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
// Checks CLI bootstrap chunks for forbidden eager imports and size regressions.
|
||||
import fs from "node:fs";
|
||||
import module from "node:module";
|
||||
import path from "node:path";
|
||||
@@ -57,6 +58,9 @@ function resolveRelativeImport(importer, specifier, fsImpl = fs) {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Lists static import/export specifiers from a JavaScript source string.
|
||||
*/
|
||||
export function listStaticImportSpecifiers(source) {
|
||||
return [...source.matchAll(STATIC_IMPORT_RE)].map((match) => match.groups?.specifier ?? "");
|
||||
}
|
||||
@@ -110,6 +114,9 @@ function walkStaticImportGraph(params) {
|
||||
return errors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Collects forbidden external import errors for CLI bootstrap entrypoints.
|
||||
*/
|
||||
export function collectCliBootstrapExternalImportErrors(params = {}) {
|
||||
const rootDir = params.rootDir ?? process.cwd();
|
||||
const entrypoints = params.entrypoints ?? DEFAULT_ENTRYPOINTS;
|
||||
@@ -152,6 +159,9 @@ function listJsFiles(dirPath, fsImpl = fs) {
|
||||
return files;
|
||||
}
|
||||
|
||||
/**
|
||||
* Collects gateway-run chunk budget errors from built CLI output.
|
||||
*/
|
||||
export function collectGatewayRunChunkBudgetErrors(params = {}) {
|
||||
const rootDir = params.rootDir ?? process.cwd();
|
||||
const fsImpl = params.fs ?? fs;
|
||||
@@ -227,6 +237,9 @@ export function collectGatewayRunChunkBudgetErrors(params = {}) {
|
||||
return errors.toSorted((left, right) => left.localeCompare(right));
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the CLI bootstrap import and chunk-budget checks.
|
||||
*/
|
||||
export function checkCliBootstrapExternalImports(params = {}) {
|
||||
const errors = [
|
||||
...collectCliBootstrapExternalImportErrors(params),
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
// Measures CLI startup memory with an isolated home and RSS hook.
|
||||
import { spawnSync as defaultSpawnSync } from "node:child_process";
|
||||
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
||||
import os from "node:os";
|
||||
@@ -358,6 +359,9 @@ function runStartupMemoryCheck(argv = process.argv.slice(2), params = {}) {
|
||||
return { skipped: false, results };
|
||||
}
|
||||
|
||||
/**
|
||||
* Test-only access to pure startup memory helper functions.
|
||||
*/
|
||||
export const testing = {
|
||||
cases,
|
||||
parseArgs,
|
||||
|
||||
Reference in New Issue
Block a user